Exemplo n.º 1
0
 def stop(self):
     """
     Stop the pump.
     """
     LOGGER.info("Stoping pump (physical pin %s)", self.pin)
     GPIO.output(self.pin, GPIO.LOW)
     self._running.clear()
Exemplo n.º 2
0
Arquivo: h2o.py Projeto: anxuae/pih2o
def create_app(cfgfile="~/.config/pih2o/pih2o.cfg"):
    """Entry point to use with a WSGI server (e.g. gunicorn).
    """
    parser = argparse.ArgumentParser(usage="%(prog)s [options]",
                                     description=pih2o.__doc__)

    parser.add_argument('--version',
                        action='version',
                        version=pih2o.__version__,
                        help=u"show program's version number and exit")

    parser.add_argument("--config",
                        action='store_true',
                        help=u"edit the current configuration")

    parser.add_argument("--reset",
                        action='store_true',
                        help=u"restore the default configuration")

    parser.add_argument("--log",
                        default=None,
                        help=u"save console output to the given file")

    group = parser.add_mutually_exclusive_group()
    group.add_argument("-v",
                       "--verbose",
                       dest='logging',
                       action='store_const',
                       const=logging.DEBUG,
                       help=u"report more information about operations",
                       default=logging.INFO)
    group.add_argument("-q",
                       "--quiet",
                       dest='logging',
                       action='store_const',
                       const=logging.WARNING,
                       help=u"report only errors and warnings",
                       default=logging.INFO)

    options, _args = parser.parse_known_args()

    logging.basicConfig(filename=options.log,
                        filemode='w',
                        format='[ %(levelname)-8s] %(name)-18s: %(message)s',
                        level=options.logging)

    config = PiConfigParser(cfgfile, options.reset)

    if options.config:
        LOGGER.info("Editing the automatic plant watering configuration...")
        config.open_editor()
        sys.exit(0)
    elif not options.reset:
        LOGGER.info("Starting the automatic plant watering application...")
        app = PiApplication(config)
        app.start_daemon()
        return app.flask_app  # Return the WSGI application
    else:
        sys.exit(0)
Exemplo n.º 3
0
Arquivo: h2o.py Projeto: anxuae/pih2o
 def start_daemon(self):
     """Start the watering daemon main loop.
     """
     if self.is_running():
         raise EnvironmentError("Watering daemon is already running")
     self._stop.clear()
     self._thread = threading.Thread(target=self.main_loop)
     self._thread.daemon = True
     self._thread.start()
     LOGGER.debug("Watering daemon started")
Exemplo n.º 4
0
    def start(self):
        """
        Start the pump.
        """
        if self.is_running():
            # Avoid starting several times to prevent concurent access
            raise IOError("Watering is already started")

        LOGGER.info("Starting pump (physical pin %s)", self.pin)
        GPIO.output(self.pin, GPIO.HIGH)
        self._running.set()
Exemplo n.º 5
0
 def _read(self):
     """Return the humidity level (in %) measured by the sensor.
     """
     # Choose gain 1 because the sensor range is +/-4.096V
     value = self.adc.read_adc(self.pin, gain=1)
     if value < min(self.analog_range) or value > max(self.analog_range):
         LOGGER.warning(
             "Sensor %s value '%s' is outside the defined range %s",
             self.pin, value, self.analog_range)
     return 100 - (value - min(self.analog_range)) * 100. / (
         max(self.analog_range) - min(self.analog_range))
Exemplo n.º 6
0
 def save(self, default=False):
     """Save the current or default values into the configuration file.
     """
     LOGGER.info("Generate the configuration file in '%s'", self.filename)
     with open(self.filename, 'w') as fp:
         for section, options in DEFAULT.items():
             fp.write("[{}]\n".format(section))
             for name, value in options.items():
                 if default:
                     val = value[0]
                 else:
                     val = self.get(section, name)
                 fp.write("# {}\n{} = {}\n\n".format(value[1], name, val))
Exemplo n.º 7
0
 def open_editor(self):
     """Open a text editor to edit the configuration file.
     """
     for editor in self.editors:
         try:
             process = subprocess.Popen([editor, self.filename])
             process.communicate()
             self.reload()
             return
         except OSError as e:
             if e.errno != os.errno.ENOENT:
                 # Something else went wrong while trying to run the editor
                 raise
     LOGGER.critical("Can't find installed text editor among %s",
                     self.editors)
Exemplo n.º 8
0
Arquivo: h2o.py Projeto: anxuae/pih2o
    def shutdown_daemon(self):
        """Quit the watering daemon.
        """
        if self._pump_timer:
            self._pump_timer.cancel()

        if self.is_running():
            self._stop.set()
            self._thread.join()
            LOGGER.debug("Watering daemon stopped")

        # To be sure and avoid floor flooding :)
        self.pump.stop()

        GPIO.cleanup()
Exemplo n.º 9
0
Arquivo: h2o.py Projeto: anxuae/pih2o
    def main_loop(self):
        """Watering daemon loop.
        """
        cron_pattern = self.config.get('GENERAL', 'record_interval')
        if not croniter.is_valid(cron_pattern):
            raise ValueError("Invalid cron pattern '{}'".format(cron_pattern))

        cron = croniter(cron_pattern)
        next_record = cron.get_next()

        while not self._stop.is_set():
            if self._stop.wait(next_record - time.time()):
                break  # Stop requested

            # Calculate next wakeup time
            next_record = cron.get_next()

            # Take a new measurement
            triggered_sensors = []
            untriggered_sensors = []
            with self.flask_app.app_context():

                for measure in self.read_sensors():
                    models.db.session.add(measure)
                    if measure.triggered:
                        LOGGER.info("Sensor on physical pin '%s' is triggered",
                                    measure.sensor)
                        triggered_sensors.append(measure)
                    else:
                        untriggered_sensors.append(measure)

                models.db.session.commit()

            # Start the watering if necessary (do nothing if already running)
            strategy = self.config.get('GENERAL', 'watering_strategy')
            if (strategy == 'majority' and float(len(triggered_sensors)) >= float(len(untriggered_sensors))) or \
                    (strategy == 'first' and triggered_sensors) or \
                    (strategy == 'last' and not untriggered_sensors):
                if self.pump.is_running():
                    LOGGER.warning(
                        "Skipping watering because pump is already running")
                else:
                    self.pump.start()
                    self._stop.wait(self.config.getint("PUMP", "duration"))
                    self.pump.stop()
Exemplo n.º 10
0
    def enable_autostart(self, enable=True):
        """Auto-start pih2o at the Raspberry Pi startup.
        """
        filename = osp.expanduser('~/.config/autostart/pih2o.desktop')
        dirname = osp.dirname(filename)
        if enable and not osp.isfile(filename):

            if not osp.isdir(dirname):
                os.makedirs(dirname)

            LOGGER.info("Generate the auto-startup file in '%s'", dirname)
            with open(filename, 'w') as fp:
                fp.write("[Desktop Entry]\n")
                fp.write("Name=pih2o\n")
                fp.write("Exec=pih2o\n")
                fp.write("Type=application\n")

        elif not enable and osp.isfile(filename):
            LOGGER.info("Remove the auto-startup file in '%s'", dirname)
            os.remove(filename)
Exemplo n.º 11
0
Arquivo: h2o.py Projeto: anxuae/pih2o
    def read_sensors(self, sensor_pin=None):
        """Read values from one or all sensors.

        :param sensor_id: pin of the sensor
        :type sensor_id: int
        """
        if not self.sensors:
            raise EnvironmentError("The sensors are not initialized")

        data = []
        for sensor in self.sensors:
            if sensor_pin is not None and sensor.pin != sensor_pin:
                continue

            if sensor.stype == 'analog':
                humidity = sensor.get_value()
                triggered = humidity <= self.config.getfloat(
                    "GENERAL", "humidity_threshold")
            else:
                humidity = 0
                triggered = sensor.get_value()

            measure = models.Measurement(
                **{
                    'sensor': sensor.pin,
                    'humidity': humidity,
                    'triggered': triggered,
                    'record_time': datetime.now()
                })

            LOGGER.debug(
                "New measurement: sensor=%s, humidity=%s, triggered=%s",
                sensor.pin, measure.humidity, measure.triggered)

            data.append(measure)

        for sensor in self.sensors:
            sensor.power_off()

        return data
Exemplo n.º 12
0
Arquivo: h2o.py Projeto: anxuae/pih2o
    def __init__(self, config):
        self._thread = None
        self._stop = threading.Event()
        self.config = config

        LOGGER.debug("Initializing flask instance")
        self.flask_app = flask.Flask(pih2o.__name__)
        self.flask_app.config.from_object('pih2o.config')
        got_request_exception.connect(self.log_exception, self.flask_app)

        @self.flask_app.route('/pih2o')
        def say_hello():
            return flask.jsonify({
                "name": pih2o.__name__,
                "version": pih2o.__version__,
                "running": self.is_running()
            })

        LOGGER.debug("Initializing the database for measurements")
        models.db.init_app(self.flask_app)
        with self.flask_app.app_context():
            models.db.create_all()

        LOGGER.debug("Initializing the RESTful API")
        self.api = Api(self.flask_app, catch_all_404s=True)

        root = '/pih2o/api/v1'
        self.api.add_resource(ApiConfig,
                              root + '/config',
                              root + '/config/<string:section>',
                              root + '/config/<string:section>/<string:key>',
                              endpoint='config',
                              resource_class_args=(config, ))
        self.api.add_resource(ApiPump,
                              root + '/pump',
                              root + '/pump/<int:duration>',
                              endpoint='pump',
                              resource_class_args=(self, ))
        self.api.add_resource(ApiSensors,
                              root + '/sensors',
                              root + '/sensors/<int:pin>',
                              endpoint='sensors',
                              resource_class_args=(self, ))
        self.api.add_resource(ApiMeasurements,
                              root + '/measurements',
                              endpoint='measurements',
                              resource_class_args=(models.db, ))

        # The HW connection of the controls
        GPIO.setmode(GPIO.BOARD)  # GPIO in physical pins mode
        self.pump = None
        self.sensors = []
        self._pump_timer = None

        self.init_controls()

        atexit.register(self.shutdown_daemon)
Exemplo n.º 13
0
    def __init__(self, filename, clear=False):
        ConfigParser.__init__(self)
        self.filename = osp.abspath(osp.expanduser(filename))
        self.db_filename = osp.join(osp.dirname(self.filename), 'pih2o.db')

        # Update Flask configuration
        global SQLALCHEMY_DATABASE_URI
        SQLALCHEMY_DATABASE_URI = 'sqlite:///' + self.db_filename

        if not osp.isfile(self.filename) or clear:
            dirname = osp.dirname(self.filename)
            if not osp.isdir(dirname):
                os.makedirs(dirname)
            self.save(True)

            if osp.isfile(self.db_filename):
                resp = input(
                    "You really want to erase the current database? (y/N)")
                if resp in ('y', 'yes', 'Y', 'YES'):
                    LOGGER.info("Dropping all measurements from database '%s'",
                                self.db_filename)
                    os.remove(self.db_filename)

        self.reload()