Esempio n. 1
0
def gpio_state_unique_id(unique_id):
    """Return the GPIO state, for dashboard output """
    output = Output.query.filter(
                Output.unique_id == unique_id).first()
    daemon_control = DaemonControl()
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)

    if output.output_type == 'wired' and output.pin and -1 < output.pin < 40:
        GPIO.setup(output.pin, GPIO.OUT)
        if GPIO.input(output.pin) == output.trigger:
            state = 'on'
        else:
            state = 'off'
    elif (output.output_type in ['command',
                                 'command_pwm',
                                 'python',
                                 'python_pwm',
                                 'atlas_ezo_pmp'] or
            (output.output_type in ['pwm', 'wireless_rpi_rf'] and
             output.pin and -1 < output.pin < 40)):
        state = daemon_control.output_state(output.unique_id)
    else:
        state = None

    return jsonify(state)
Esempio n. 2
0
def gpio_state():
    """Return the GPIO state, for output page status"""
    output = Output.query.all()
    daemon_control = DaemonControl()
    state = {}
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    for each_output in output:
        if each_output.output_type == 'wired' and each_output.pin and -1 < each_output.pin < 40:
            GPIO.setup(each_output.pin, GPIO.OUT)
            if GPIO.input(each_output.pin) == each_output.trigger:
                state[each_output.unique_id] = 'on'
            else:
                state[each_output.unique_id] = 'off'
        elif (each_output.output_type in ['command',
                                          'command_pwm',
                                          'python',
                                          'python_pwm',
                                          'atlas_ezo_pmp'] or
                (each_output.output_type in ['pwm', 'wireless_rpi_rf'] and
                 each_output.pin and
                 -1 < each_output.pin < 40)):
            state[each_output.unique_id] = daemon_control.output_state(each_output.unique_id)
        else:
            state[each_output.unique_id] = None

    return jsonify(state)
Esempio n. 3
0
def gpio_state_unique_id(unique_id, channel_id):
    """Return the GPIO state, for dashboard output """
    output = Output.query.filter(Output.unique_id == unique_id).first()
    channel = OutputChannel.query.filter(
        OutputChannel.unique_id == channel_id).first()
    daemon_control = DaemonControl()
    state = daemon_control.output_state(unique_id, channel.channel)
    return jsonify(state)
Esempio n. 4
0
def get_condition_measurement(sql_condition):
    """
    Returns condition measurements for Conditional controllers
    :param sql_condition: str containing comma-separated device ID and measurement ID
    :return: measurement: float measurement, gpio_state: int 0 or 1, output_state: 'on', 'off', or int duty cycle
    """
    # Check Measurement Conditions
    if sql_condition.condition_type == 'measurement':
        device_id = sql_condition.measurement.split(',')[0]
        measurement_id = sql_condition.measurement.split(',')[1]

        device_measurement = db_retrieve_table_daemon(DeviceMeasurements,
                                                      unique_id=measurement_id)
        if device_measurement:
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
        else:
            conversion = None
        channel, unit, measurement = return_measurement_info(
            device_measurement, conversion)

        if None in [channel, unit]:
            logger.error(
                "Could not determine channel or unit from measurement ID: "
                "{}".format(measurement_id))
            return

        max_age = sql_condition.max_age
        # Check if there hasn't been a measurement in the last set number
        # of seconds. If not, trigger conditional
        last_measurement = get_last_measurement(device_id, unit, measurement,
                                                channel, max_age)
        return last_measurement

    # Return GPIO state
    elif sql_condition.condition_type == 'gpio_state':
        try:
            GPIO.setmode(GPIO.BCM)
            GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN)
            gpio_state = GPIO.input(int(sql_condition.gpio_pin))
        except:
            gpio_state = None
            logger.error("Exception reading the GPIO pin")
        return gpio_state

    # Return output state
    elif sql_condition.condition_type == 'output_state':
        output = Output.query.filter(
            Output.unique_id == sql_condition.output_id).first()
        if output:
            control = DaemonControl()
            return control.output_state(output.unique_id)
Esempio n. 5
0
def gpio_state_unique_id(unique_id):
    """Return the GPIO state, for dashboard output """
    output = Output.query.filter(Output.unique_id == unique_id).first()
    daemon_control = DaemonControl()
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)

    if output.output_type == 'wired' and output.pin and -1 < output.pin < 40:
        GPIO.setup(output.pin, GPIO.OUT)
        if GPIO.input(output.pin) == output.trigger:
            state = 'on'
        else:
            state = 'off'
    elif (output.output_type in ['command', 'command_pwm']
          or (output.output_type in ['pwm', 'wireless_433MHz_pi_switch']
              and output.pin and -1 < output.pin < 40)):
        state = daemon_control.output_state(output.id)
    else:
        state = None

    return jsonify(state)
Esempio n. 6
0
def output_sec_on(output_id, past_seconds, output_channel=0):
    """ Return the number of seconds a output has been ON in the past number of seconds """
    # Get the number of seconds ON stored in the database
    output = db_retrieve_table_daemon(Output, unique_id=output_id)
    client = InfluxDBClient(INFLUXDB_HOST,
                            INFLUXDB_PORT,
                            INFLUXDB_USER,
                            INFLUXDB_PASSWORD,
                            INFLUXDB_DATABASE,
                            timeout=5)
    if not output_id:
        return None

    # Get the number of seconds not stored in the database (if currently on)
    output_time_on = 0
    try:
        control = DaemonControl()
        if control.output_state(output_id,
                                output_channel=output_channel) == 'on':
            output_time_on = control.output_sec_currently_on(
                output_id, output_channel=output_channel)
    except Exception:
        logger.exception("output_sec_on()")

    query = query_string('s',
                         output.unique_id,
                         measure='duration_time',
                         channel=output_channel,
                         value='SUM',
                         past_sec=past_seconds)
    query_output = client.query(query)
    sec_recorded_on = 0
    if query_output:
        sec_recorded_on = query_output.raw['series'][0]['values'][0][1]

    sec_currently_on = 0
    if output_time_on:
        sec_currently_on = min(output_time_on, past_seconds)

    return sec_recorded_on + sec_currently_on
Esempio n. 7
0
    def get(self, unique_id):
        """Show the settings and status for an output"""
        if not utils_general.user_has_permission('edit_controllers'):
            abort(403)

        try:
            dict_data = get_from_db(OutputSchema, Output, unique_id=unique_id)

            measure_schema = DeviceMeasurementsSchema()
            list_data = return_list_of_dictionaries(
                measure_schema.dump(
                    DeviceMeasurements.query.filter_by(
                        device_id=unique_id).all(), many=True))

            control = DaemonControl()
            output_state = control.output_state(unique_id)
            return {'output settings': dict_data,
                    'output device measurements': list_data,
                    'output state': output_state}, 200
        except Exception:
            abort(500,
                  message='An exception occurred',
                  error=traceback.format_exc())
Esempio n. 8
0
def gpio_state():
    """Return the GPIO state, for output page status"""
    output = Output.query.all()
    daemon_control = DaemonControl()
    state = {}
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    for each_output in output:
        if each_output.output_type == 'wired' and each_output.pin and -1 < each_output.pin < 40:
            GPIO.setup(each_output.pin, GPIO.OUT)
            if GPIO.input(each_output.pin) == each_output.trigger:
                state[each_output.unique_id] = 'on'
            else:
                state[each_output.unique_id] = 'off'
        elif (each_output.output_type in ['command', 'command_pwm'] or
              (each_output.output_type in ['pwm', 'wireless_433MHz_pi_switch']
               and each_output.pin and -1 < each_output.pin < 40)):
            state[each_output.unique_id] = daemon_control.output_state(
                each_output.unique_id)
        else:
            state[each_output.unique_id] = None

    return jsonify(state)
Esempio n. 9
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the DHT22's humidity and temperature
    and calculates the dew point

    An adaptation of DHT22 code from https://github.com/joan2937/pigpio

    The sensor is also known as the AM2302.
    The sensor can be powered from the Pi 3.3-volt or 5-volt rail.
    Powering from the 3.3-volt rail is simpler and safer.  You may need
    to power from 5 if the sensor is connected via a long cable.
    For 3.3-volt operation connect pin 1 to 3.3 volts and pin 4 to ground.
    Connect pin 2 to a gpio.
    For 5-volt operation connect pin 1 to the 5 volts and pin 4 to ground.
    The following pin 2 connection works for me.  Use at YOUR OWN RISK.

    5V--5K_resistor--+--10K_resistor--Ground
                     |
    DHT22 pin 2 -----+
                     |
    gpio ------------+

    """
    def __init__(self, input_dev, testing=False):
        """
        Instantiate with the Pi and gpio to which the DHT22 output
        pin is connected.

        Optionally a gpio used to power the sensor may be specified.
        This gpio will be set high to power the sensor.  If the sensor
        locks it will be power cycled to restart the readings.

        Taking readings more often than about once every two seconds will
        eventually cause the DHT22 to hang.  A 3 second interval seems OK.
        """
        super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)
        self.temp_temperature = None
        self.temp_humidity = None
        self.temp_dew_point = None
        self.temp_vpd = None
        self.power_output_id = None
        self.powered = False
        self.pi = None

        if not testing:
            import pigpio
            from mycodo.mycodo_client import DaemonControl

            self.power_output_id = input_dev.power_output_id

            self.control = DaemonControl()
            self.pigpio = pigpio
            self.pi = self.pigpio.pi()

            self.gpio = int(input_dev.gpio_location)
            self.bad_CS = 0  # Bad checksum count
            self.bad_SM = 0  # Short message count
            self.bad_MM = 0  # Missing message count
            self.bad_SR = 0  # Sensor reset count

            # Power cycle if timeout > MAX_NO_RESPONSE
            self.MAX_NO_RESPONSE = 3
            self.no_response = None
            self.tov = None
            self.high_tick = None
            self.bit = None
            self.either_edge_cb = None

        self.start_input()

    def get_measurement(self):
        """ Gets the humidity and temperature """
        self.return_dict = measurements_dict.copy()

        if not self.pi.connected:  # Check if pigpiod is running
            self.logger.error('Could not connect to pigpiod. '
                              'Ensure it is running and try again.')
            return None, None, None

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and
                db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and
                self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_input()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(4):
            self.measure_sensor()
            if self.temp_dew_point is not None:
                if self.is_enabled(0):
                    self.value_set(0, self.temp_temperature)
                if self.is_enabled(1):
                    self.value_set(1, self.temp_humidity)
                if (self.is_enabled(2) and
                        self.is_enabled(0) and
                        self.is_enabled(1)):
                    self.value_set(2, self.temp_dew_point)
                if (self.is_enabled(3) and
                        self.is_enabled(0) and
                        self.is_enabled(1)):
                    self.value_set(3, self.temp_vpd)
                return self.return_dict  # success - no errors
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id is not None and self.running:
            self.stop_input()
            time.sleep(3)
            self.start_input()
            for _ in range(2):
                self.measure_sensor()
                if self.temp_dew_point is not None:
                    if self.is_enabled(0):
                        self.value_set(0, self.temp_temperature)
                    if self.is_enabled(1):
                        self.value_set(1, self.temp_humidity)
                    if (self.is_enabled(2) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        self.value_set(2, self.temp_dew_point)
                    if (self.is_enabled(3) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        self.value_set(3, self.temp_vpd)
                    return self.return_dict  # success - no errors
                time.sleep(2)

        self.logger.debug("Could not acquire a measurement")
        return None

    def measure_sensor(self):
        self.temp_temperature = None
        self.temp_humidity = None
        self.temp_dew_point = None
        self.temp_vpd = None

        initialized = False

        try:
            self.close()
            time.sleep(0.2)
            self.setup()
            time.sleep(0.2)
            initialized = True
        except Exception as except_msg:
            self.logger.error(
                "Could not initialize sensor. Check if it's connected "
                "properly and pigpiod is running. Error: {msg}".format(
                    msg=except_msg))

        if initialized:
            try:
                self.pi.write(self.gpio, self.pigpio.LOW)
                time.sleep(0.017)  # 17 ms
                self.pi.set_mode(self.gpio, self.pigpio.INPUT)
                self.pi.set_watchdog(self.gpio, 200)
                time.sleep(0.2)
                if (self.temp_humidity is not None and
                        self.temp_temperature is not None):
                    self.temp_dew_point = calculate_dewpoint(
                        self.temp_temperature, self.temp_humidity)
                    self.temp_vpd = calculate_vapor_pressure_deficit(
                        self.temp_temperature, self.temp_humidity)
            except Exception as e:
                self.logger.exception(
                    "Exception when taking a reading: {err}".format(
                        err=e))
            finally:
                self.close()

    def setup(self):
        """
        Clears the internal gpio pull-up/down resistor.
        Kills any watchdogs.
        Setup callbacks
        """
        self.no_response = 0
        self.tov = None
        self.high_tick = 0
        self.bit = 40
        self.either_edge_cb = None
        self.pi.set_pull_up_down(self.gpio, self.pigpio.PUD_OFF)
        self.pi.set_watchdog(self.gpio, 0)  # Kill any watchdogs
        self.register_callbacks()

    def register_callbacks(self):
        """ Monitors RISING_EDGE changes using callback """
        self.either_edge_cb = self.pi.callback(self.gpio,
                                               self.pigpio.EITHER_EDGE,
                                               self.either_edge_callback)

    def either_edge_callback(self, gpio, level, tick):
        """
        Either Edge callbacks, called each time the gpio edge changes.
        Accumulate the 40 data bits from the DHT22 sensor.

        Format into 5 bytes, humidity high,
        humidity low, temperature high, temperature low, checksum.
        """
        level_handlers = {
            self.pigpio.FALLING_EDGE: self._edge_fall,
            self.pigpio.RISING_EDGE: self._edge_rise,
            self.pigpio.EITHER_EDGE: self._edge_either
        }
        handler = level_handlers[level]
        diff = self.pigpio.tickDiff(self.high_tick, tick)
        handler(tick, diff)

    def _edge_rise(self, tick, diff):
        """ Handle Rise signal """
        # Edge length determines if bit is 1 or 0.
        if diff >= 50:
            val = 1
            if diff >= 200:  # Bad bit?
                self.CS = 256  # Force bad checksum.
        else:
            val = 0

        if self.bit >= 40:  # Message complete.
            self.bit = 40
        elif self.bit >= 32:  # In checksum byte.
            self.CS = (self.CS << 1) + val
            if self.bit == 39:
                # 40th bit received.
                self.pi.set_watchdog(self.gpio, 0)
                self.no_response = 0
                total = self.hH + self.hL + self.tH + self.tL
                if (total & 255) == self.CS:  # Is checksum ok?
                    self.temp_humidity = ((self.hH << 8) + self.hL) * 0.1
                    if self.tH & 128:  # Negative temperature.
                        mult = -0.1
                        self.tH &= 127
                    else:
                        mult = 0.1
                    self.temp_temperature = ((self.tH << 8) + self.tL) * mult
                    self.tov = time.time()
                else:
                    self.bad_CS += 1
        elif self.bit >= 24:  # in temp low byte
            self.tL = (self.tL << 1) + val
        elif self.bit >= 16:  # in temp high byte
            self.tH = (self.tH << 1) + val
        elif self.bit >= 8:  # in humidity low byte
            self.hL = (self.hL << 1) + val
        elif self.bit >= 0:  # in humidity high byte
            self.hH = (self.hH << 1) + val
        self.bit += 1

    def _edge_fall(self, tick, diff):
        """ Handle Fall signal """
        # Edge length determines if bit is 1 or 0.
        self.high_tick = tick
        if diff <= 250000:
            return
        self.bit = -2
        self.hH = 0
        self.hL = 0
        self.tH = 0
        self.tL = 0
        self.CS = 0

    def _edge_either(self, tick, diff):
        """ Handle Either signal or Timeout """
        self.pi.set_watchdog(self.gpio, 0)
        if self.bit < 8:  # Too few data bits received.
            self.bad_MM += 1  # Bump missing message count.
            self.no_response += 1
            if self.no_response > self.MAX_NO_RESPONSE:
                self.no_response = 0
                self.bad_SR += 1  # Bump sensor reset count.
                if self.power_output_id is not None:
                    self.logger.error(
                        "Invalid data, power cycling sensor.")
                    self.stop_input()
                    time.sleep(2)
                    self.start_input()
        elif self.bit < 39:  # Short message received.
            self.bad_SM += 1  # Bump short message count.
            self.no_response = 0
        else:  # Full message received.
            self.no_response = 0

    def staleness(self):
        """ Return time since measurement made """
        if self.tov is not None:
            return time.time() - self.tov
        else:
            return -999

    def bad_checksum(self):
        """ Return count of messages received with bad checksums """
        return self.bad_CS

    def short_message(self):
        """ Return count of short messages """
        return self.bad_SM

    def missing_message(self):
        """ Return count of missing messages """
        return self.bad_MM

    def sensor_resets(self):
        """ Return count of power cycles because of sensor hangs """
        return self.bad_SR

    def close(self):
        """ Stop reading sensor, remove callbacks """
        self.pi.set_watchdog(self.gpio, 0)
        if self.either_edge_cb:
            self.either_edge_cb.cancel()
            self.either_edge_cb = None

    def start_input(self):
        """ Turn the sensor on """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_input(self):
        """ Turn the sensor off """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 10
0
class CustomModule(AbstractController, threading.Thread):
    """
    Class to operate custom controller
    """
    def __init__(self, ready, unique_id, testing=False):
        threading.Thread.__init__(self)
        super(CustomModule, self).__init__(ready,
                                           unique_id=unique_id,
                                           name=__name__)

        self.unique_id = unique_id
        self.log_level_debug = None
        self.control_variable = None
        self.timestamp = None
        self.timer = None
        self.control = DaemonControl()
        self.outputIsOn = False

        # Initialize custom options
        self.measurement_device_id = None
        self.measurement_measurement_id = None
        self.output_device_id = None
        self.output_measurement_id = None
        self.output_channel_id = None
        self.setpoint = None
        self.hysteresis = None
        self.direction = None
        self.output_channel = None
        self.update_period = None

        # Set custom options
        custom_function = db_retrieve_table_daemon(CustomController,
                                                   unique_id=unique_id)
        self.setup_custom_options(FUNCTION_INFORMATION['custom_options'],
                                  custom_function)

        self.output_channel = self.get_output_channel_from_channel_id(
            self.output_channel_id)

        self.initialize_variables()

    def initialize_variables(self):
        controller = db_retrieve_table_daemon(CustomController,
                                              unique_id=self.unique_id)
        self.log_level_debug = controller.log_level_debug
        self.set_log_level_debug(self.log_level_debug)

        self.timestamp = time.time()

    def run(self):
        try:
            if self.output_channel is None:
                self.logger.error(
                    "Cannot start bang-bang controller: Could not find output channel."
                )
                self.deactivate_self()
                return

            self.logger.info("Activated in {:.1f} ms".format(
                (timeit.default_timer() - self.thread_startup_timer) * 1000))

            self.ready.set()
            self.running = True
            self.timer = time.time()

            self.logger.info(
                "Bang-Bang controller started with options: "
                "Measurement Device: {}, Measurement: {}, Output: {}, "
                "Output_Channel: {}, Setpoint: {}, Hysteresis: {}, "
                "Direction: {}, Period: {}".format(
                    self.measurement_device_id,
                    self.measurement_measurement_id, self.output_device_id,
                    self.output_channel, self.setpoint, self.hysteresis,
                    self.direction, self.update_period))

            # Start a loop
            while self.running:
                self.loop()
                time.sleep(self.update_period)
        except:
            self.logger.exception("Run Error")
        finally:
            self.run_finally()
            self.running = False
            if self.thread_shutdown_timer:
                self.logger.info("Deactivated in {:.1f} ms".format(
                    (timeit.default_timer() - self.thread_shutdown_timer) *
                    1000))
            else:
                self.logger.error("Deactivated unexpectedly")

    def loop(self):
        last_measurement = self.get_last_measurement(
            self.measurement_device_id, self.measurement_measurement_id)[1]
        outputState = self.control.output_state(self.output_device_id,
                                                self.output_channel)

        self.logger.info("Input: {}, output: {}, target: {}, hyst: {}".format(
            last_measurement, outputState, self.setpoint, self.hysteresis))

        if self.direction == 'raise':
            if outputState == 'on':
                # looking to turn output off
                if last_measurement > (self.setpoint + self.hysteresis):
                    self.control.output_off(
                        self.output_device_id,
                        output_channel=self.output_channel,
                    )
            else:
                # looking to turn output on
                if last_measurement < (self.setpoint - self.hysteresis):
                    self.control.output_on(self.output_device_id,
                                           output_channel=self.output_channel)
        elif self.direction == 'lower':
            if outputState == 'on':
                # looking to turn output off
                if last_measurement < (self.setpoint - self.hysteresis):
                    self.control.output_off(
                        self.output_device_id,
                        output_channel=self.output_channel,
                    )
            else:
                # looking to turn output on
                if last_measurement > (self.setpoint + self.hysteresis):
                    self.control.output_on(self.output_device_id,
                                           output_channel=self.output_channel)
        else:
            self.logger.info("Unknown controller direction: {}".format(
                self.direction))

    def deactivate_self(self):
        self.logger.info("Deactivating bang-bang controller")

        with session_scope(MYCODO_DB_PATH) as new_session:
            mod_cont = new_session.query(CustomController).filter(
                CustomController.unique_id == self.unique_id).first()
            mod_cont.is_activated = False
            new_session.commit()

        deactivate_controller = threading.Thread(
            target=self.control.controller_deactivate, args=(self.unique_id, ))
        deactivate_controller.start()

    def pre_stop(self):
        self.control.output_off(self.output_device_id, self.output_channel)
Esempio n. 11
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the AM2315's humidity and temperature
    and calculates the dew point

    """
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.setup_logger(testing=testing, name=__name__, input_dev=input_dev)
        self.powered = False
        self.am = None

        if not testing:
            from mycodo.mycodo_client import DaemonControl

            self.device_measurements = db_retrieve_table_daemon(
                DeviceMeasurements).filter(
                    DeviceMeasurements.device_id == input_dev.unique_id)

            self.i2c_bus = input_dev.i2c_bus
            self.power_output_id = input_dev.power_output_id
            self.control = DaemonControl()
            self.start_sensor()
            self.am = AM2315(self.i2c_bus)

    def get_measurement(self):
        """ Gets the humidity and temperature """
        self.return_dict = measurements_dict.copy()

        temperature = None
        humidity = None
        dew_point = None
        measurements_success = False

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and db_retrieve_table_daemon(
                Output, unique_id=self.power_output_id)
                and self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_sensor()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(2):
            dew_point, humidity, temperature = self.return_measurements()
            if dew_point is not None:
                measurements_success = True
                break
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id and not measurements_success:
            self.stop_sensor()
            time.sleep(2)
            self.start_sensor()
            for _ in range(2):
                dew_point, humidity, temperature = self.return_measurements()
                if dew_point is not None:
                    measurements_success = True
                    break
                time.sleep(2)

        if measurements_success:
            if self.is_enabled(0):
                self.set_value(0, temperature)

            if self.is_enabled(1):
                self.set_value(1, humidity)

            if (self.is_enabled(2) and self.is_enabled(0)
                    and self.is_enabled(1)):
                self.set_value(
                    2, calculate_dewpoint(self.get_value(0),
                                          self.get_value(1)))

            if (self.is_enabled(3) and self.is_enabled(0)
                    and self.is_enabled(1)):
                self.set_value(
                    3,
                    calculate_vapor_pressure_deficit(self.get_value(0),
                                                     self.get_value(1)))

            return self.return_dict
        else:
            self.logger.debug("Could not acquire a measurement")

    def return_measurements(self):
        # Retry measurement if CRC fails
        for num_measure in range(3):
            humidity, temperature = self.am.data()
            if humidity is None:
                self.logger.debug(
                    "Measurement {num} returned failed CRC".format(
                        num=num_measure))
                pass
            else:
                dew_pt = calculate_dewpoint(temperature, humidity)
                return dew_pt, humidity, temperature
            time.sleep(2)

        self.logger.error("All measurements returned failed CRC")
        return None, None, None

    def start_sensor(self):
        """ Turn the sensor on """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_sensor(self):
        """ Turn the sensor off """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 12
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the DHT11's humidity and temperature
    and calculates the dew point

    The DHT11 class is a stripped version of the DHT22 sensor code by joan2937.
    You can find the initial implementation here:
    - https://github.com/srounet/pigpio/tree/master/EXAMPLES/Python/DHT22_AM2302_SENSOR

    """
    def __init__(self, input_dev, testing=False):
        """
        :param gpio: gpio pin number
        :type gpio: int
        :param power: Power pin number
        :type power: int

        Instantiate with the Pi and gpio to which the DHT11 output
        pin is connected.

        Optionally a gpio used to power the sensor may be specified.
        This gpio will be set high to power the sensor.

        """
        super(InputModule, self).__init__(input_dev, testing=testing, name=__name__)

        self.pi = None
        self.pigpio = None
        self.control = None

        self.temp_temperature = 0
        self.temp_humidity = 0
        self.temp_dew_point = None
        self.temp_vpd = None
        self.power_output_id = None
        self.powered = False

        if not testing:
            self.initialize_input()

    def initialize_input(self):
        import pigpio
        from mycodo.mycodo_client import DaemonControl

        self.gpio = int(self.input_dev.gpio_location)
        self.power_output_id = self.input_dev.power_output_id

        self.control = DaemonControl()
        self.pigpio = pigpio
        self.pi = self.pigpio.pi()

        self.high_tick = None
        self.bit = None
        self.either_edge_cb = None

        self.start_input()

    def get_measurement(self):
        """ Gets the humidity and temperature """
        if not self.pi.connected:  # Check if pigpiod is running
            self.logger.error("Could not connect to pigpiod. Ensure it is running and try again.")
            return None

        self.return_dict = copy.deepcopy(measurements_dict)

        import pigpio
        self.pigpio = pigpio

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and
                db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and
                self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. Turning on.'.format(rel=self.power_output_id))
            self.start_input()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(2):
            self.measure_sensor()
            if self.temp_dew_point is not None:
                self.value_set(0, self.temp_temperature)
                self.value_set(1, self.temp_humidity)
                self.value_set(2, self.temp_dew_point)
                self.value_set(3, self.temp_vpd)
                return self.return_dict  # success - no errors
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id is not None and self.running:
            self.stop_input()
            time.sleep(2)
            self.start_input()
            for _ in range(2):
                self.measure_sensor()
                if self.temp_dew_point is not None:
                    self.value_set(0, self.temp_temperature)
                    self.value_set(1, self.temp_humidity)
                    self.value_set(2, self.temp_dew_point)
                    self.value_set(3, self.temp_vpd)
                    return self.return_dict  # success - no errors
                time.sleep(2)

        self.logger.error("Could not acquire a measurement")
        return None

    def measure_sensor(self):
        self.temp_temperature = 0
        self.temp_humidity = 0
        self.temp_dew_point = None
        self.temp_vpd = None

        try:
            try:
                self.setup()
            except Exception as except_msg:
                self.logger.error(
                    'Could not initialize sensor. Check if gpiod is running. Error: {msg}'.format(msg=except_msg))
            self.pi.write(self.gpio, self.pigpio.LOW)
            time.sleep(0.017)  # 17 ms
            self.pi.set_mode(self.gpio, self.pigpio.INPUT)
            self.pi.set_watchdog(self.gpio, 200)
            time.sleep(0.2)
            if self.temp_humidity != 0:
                self.temp_dew_point = calculate_dewpoint(self.temp_temperature, self.temp_humidity)
                self.temp_vpd = calculate_vapor_pressure_deficit(self.temp_temperature, self.temp_humidity)
        except Exception as e:
            self.logger.error("Exception raised when taking a reading: {err}".format(err=e))
        finally:
            self.close()
            return (self.temp_dew_point,
                    self.temp_humidity,
                    self.temp_temperature)

    def setup(self):
        """
        Clears the internal gpio pull-up/down resistor.
        Kills any watchdogs.
        Setup callbacks
        """
        self.high_tick = 0
        self.bit = 40
        self.either_edge_cb = None
        self.pi.set_pull_up_down(self.gpio, self.pigpio.PUD_OFF)
        self.pi.set_watchdog(self.gpio, 0)
        self.register_callbacks()

    def register_callbacks(self):
        """ Monitors RISING_EDGE changes using callback """
        self.either_edge_cb = self.pi.callback(
            self.gpio,
            self.pigpio.EITHER_EDGE,
            self.either_edge_callback)

    def either_edge_callback(self, gpio, level, tick):
        """
        Either Edge callbacks, called each time the gpio edge changes.
        Accumulate the 40 data bits from the DHT11 sensor.
        """
        level_handlers = {
            self.pigpio.FALLING_EDGE: self._edge_fall,
            self.pigpio.RISING_EDGE: self._edge_rise,
            self.pigpio.EITHER_EDGE: self._edge_either
        }
        handler = level_handlers[level]
        diff = self.pigpio.tickDiff(self.high_tick, tick)
        handler(tick, diff)

    def _edge_rise(self, tick, diff):
        """ Handle Rise signal """
        val = 0
        if diff >= 50:
            val = 1
        if diff >= 200:  # Bad bit?
            self.checksum = 256  # Force bad checksum
        if self.bit >= 40:  # Message complete
            self.bit = 40
        elif self.bit >= 32:  # In checksum byte
            self.checksum = (self.checksum << 1) + val
            if self.bit == 39:
                # 40th bit received
                self.pi.set_watchdog(self.gpio, 0)
                total = self.temp_humidity + self.temp_temperature
                # is checksum ok ?
                if not (total & 255) == self.checksum:
                    # For some reason the port from python 2 to python 3 causes
                    # this bad checksum error to happen during every read
                    # TODO: Investigate how to properly check the checksum in python 3
                    self.logger.debug("Exception raised when taking a reading: Bad Checksum.")
        elif 16 <= self.bit < 24:  # in temperature byte
            self.temp_temperature = (self.temp_temperature << 1) + val
        elif 0 <= self.bit < 8:  # in humidity byte
            self.temp_humidity = (self.temp_humidity << 1) + val
        self.bit += 1

    def _edge_fall(self, tick, diff):
        """ Handle Fall signal """
        self.high_tick = tick
        if diff <= 250000:
            return
        self.bit = -2
        self.checksum = 0
        self.temp_temperature = 0
        self.temp_humidity = 0

    def _edge_either(self, tick, diff):
        """ Handle Either signal """
        self.pi.set_watchdog(self.gpio, 0)

    def close(self):
        """ Stop reading sensor, remove callbacks """
        self.pi.set_watchdog(self.gpio, 0)
        if self.either_edge_cb:
            self.either_edge_cb.cancel()
            self.either_edge_cb = None

    def start_input(self):
        """ Power the sensor """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(
                self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_input(self):
        """ Depower the sensor """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 13
0
def get_condition_value(condition_id):
    """
    Returns condition measurements for Conditional controllers
    :param condition_id: Conditional condition ID
    :return: measurement: multiple types
    """
    sql_condition = db_retrieve_table_daemon(ConditionalConditions).filter(
        ConditionalConditions.unique_id == condition_id).first()

    # Check Measurement Conditions
    if sql_condition.condition_type in ['measurement',
                                        'measurement_past_average',
                                        'measurement_past_sum']:
        device_id = sql_condition.measurement.split(',')[0]
        measurement_id = sql_condition.measurement.split(',')[1]

        device_measurement = db_retrieve_table_daemon(
            DeviceMeasurements, unique_id=measurement_id)
        if device_measurement:
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
        else:
            conversion = None
        channel, unit, measurement = return_measurement_info(
            device_measurement, conversion)

        if None in [channel, unit]:
            logger.error(
                "Could not determine channel or unit from measurement ID: "
                "{}".format(measurement_id))
            return

        max_age = sql_condition.max_age

        if sql_condition.condition_type == 'measurement':
            return_measurement = get_last_measurement(
                device_id, unit, measurement, channel, max_age)
        elif sql_condition.condition_type == 'measurement_past_average':
            measurement_list = []
            measurements_str = get_past_measurements(
                device_id, unit, measurement, channel, max_age)
            for each_set in measurements_str.split(';'):
                measurement_list.append(float(each_set.split(',')[1]))
            return_measurement = sum(measurement_list) / len(measurement_list)
        elif sql_condition.condition_type == 'measurement_past_sum':
            measurement_list = []
            measurements_str = get_past_measurements(
                device_id, unit, measurement, channel, max_age)
            for each_set in measurements_str.split(';'):
                measurement_list.append(float(each_set.split(',')[1]))
            return_measurement = sum(measurement_list)
        else:
            return

        return return_measurement

    # Return GPIO state
    elif sql_condition.condition_type == 'gpio_state':
        try:
            import RPi.GPIO as GPIO
            GPIO.setmode(GPIO.BCM)
            GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN)
            gpio_state = GPIO.input(int(sql_condition.gpio_pin))
        except Exception as e:
            gpio_state = None
            logger.error("Exception reading the GPIO pin: {}".format(e))
        return gpio_state

    # Return output state
    elif sql_condition.condition_type == 'output_state':
        control = DaemonControl()
        return control.output_state(sql_condition.output_id)

    # Return the duration the output is currently on for
    elif sql_condition.condition_type == 'output_duration_on':
        control = DaemonControl()
        return control.output_sec_currently_on(sql_condition.output_id)

    # Return controller active state
    elif sql_condition.condition_type == 'controller_status':
        controller_type, _, _ = which_controller(sql_condition.controller_id)
        control = DaemonControl()
        return control.controller_is_active(sql_condition.controller_id)
Esempio n. 14
0
class AM2315Sensor(AbstractInput):
    """
    A sensor support class that measures the AM2315's humidity and temperature
    and calculates the dew point

    """
    def __init__(self, input_dev, testing=False):
        super(AM2315Sensor, self).__init__()
        self.logger = logging.getLogger('mycodo.inputs.am2315')
        self._dew_point = None
        self._humidity = None
        self._temperature = None
        self.powered = False
        self.am = None

        if not testing:
            from mycodo.mycodo_client import DaemonControl
            self.logger = logging.getLogger(
                'mycodo.inputs.am2315_{id}'.format(id=input_dev.id))
            self.i2c_bus = input_dev.i2c_bus
            self.power_output_id = input_dev.power_output_id
            self.convert_to_unit = input_dev.convert_to_unit
            self.control = DaemonControl()
            self.start_sensor()
            self.am = AM2315(self.i2c_bus)

    def __repr__(self):
        """  Representation of object """
        return "<{cls}(dewpoint={dpt})(humidity={hum})(temperature={temp})>".format(
            cls=type(self).__name__,
            dpt="{0:.2f}".format(self._dew_point),
            hum="{0:.2f}".format(self._humidity),
            temp="{0:.2f}".format(self._temperature))

    def __str__(self):
        """ Return measurement information """
        return "Dew Point: {dpt}, Humidity: {hum}, Temperature: {temp}".format(
            dpt="{0:.2f}".format(self._dew_point),
            hum="{0:.2f}".format(self._humidity),
            temp="{0:.2f}".format(self._temperature))

    def __iter__(self):  # must return an iterator
        """ AM2315Sensor iterates through live measurement readings """
        return self

    def next(self):
        """ Get next measurement reading """
        if self.read():  # raised an error
            raise StopIteration  # required
        return dict(dewpoint=float('{0:.2f}'.format(self._dew_point)),
                    humidity=float('{0:.2f}'.format(self._humidity)),
                    temperature=float('{0:.2f}'.format(self._temperature)))

    @property
    def dew_point(self):
        """ AM2315 dew point in Celsius """
        if self._dew_point is None:  # update if needed
            self.read()
        return self._dew_point

    @property
    def humidity(self):
        """ AM2315 relative humidity in percent """
        if self._humidity is None:  # update if needed
            self.read()
        return self._humidity

    @property
    def temperature(self):
        """ AM2315 temperature in Celsius """
        if self._temperature is None:  # update if needed
            self.read()
        return self._temperature

    def get_measurement(self):
        """ Gets the humidity and temperature """
        self._dew_point = None
        self._humidity = None
        self._temperature = None

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and db_retrieve_table_daemon(
                Output, unique_id=self.power_output_id)
                and self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_sensor()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(2):
            dew_point, humidity, temperature = self.return_measurements()
            if dew_point is not None:
                dew_point = convert_units('dewpoint', 'celsius',
                                          self.convert_to_unit, dew_point)
                temperature = convert_units('temperature', 'celsius',
                                            self.convert_to_unit, temperature)
                return dew_point, humidity, temperature  # success - no errors
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id:
            self.stop_sensor()
            time.sleep(2)
            self.start_sensor()
            for _ in range(2):
                dew_point, humidity, temperature = self.return_measurements()
                if dew_point is not None:
                    dew_point = convert_units('dewpoint', 'celsius',
                                              self.convert_to_unit, dew_point)
                    temperature = convert_units('temperature', 'celsius',
                                                self.convert_to_unit,
                                                temperature)
                    return dew_point, humidity, temperature  # success
                time.sleep(2)

        self.logger.debug("Could not acquire a measurement")
        return None, None, None

    def return_measurements(self):
        # Retry measurement if CRC fails
        for num_measure in range(3):
            humidity, temperature = self.am.data()
            if humidity is None:
                self.logger.debug(
                    "Measurement {num} returned failed CRC".format(
                        num=num_measure))
                pass
            else:
                dew_pt = dewpoint(temperature, humidity)
                return dew_pt, humidity, temperature
            time.sleep(2)

        self.logger.error("All measurements returned failed CRC")
        return None, None, None

    def read(self):
        """
        Takes a reading from the AM2315 and updates the self.dew_point,
        self._humidity, and self._temperature values

        :returns: None on success or 1 on error
        """
        try:
            (self._dew_point, self._humidity,
             self._temperature) = self.get_measurement()
            if self._dew_point is not None:
                return  # success - no errors
        except Exception as e:
            self.logger.exception(
                "{cls} raised an exception when taking a reading: "
                "{err}".format(cls=type(self).__name__, err=e))
        return 1

    def start_sensor(self):
        """ Turn the sensor on """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_sensor(self):
        """ Turn the sensor off """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 15
0
def camera_record(record_type,
                  unique_id,
                  duration_sec=None,
                  tmp_filename=None):
    """
    Record still image from cameras
    :param record_type:
    :param unique_id:
    :param duration_sec:
    :param tmp_filename:
    :return:
    """
    daemon_control = None
    settings = db_retrieve_table_daemon(Camera, unique_id=unique_id)
    timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
    assure_path_exists(PATH_CAMERAS)
    camera_path = assure_path_exists(
        os.path.join(PATH_CAMERAS, '{uid}'.format(uid=settings.unique_id)))
    if record_type == 'photo':
        if settings.path_still:
            save_path = settings.path_still
        else:
            save_path = assure_path_exists(os.path.join(camera_path, 'still'))
        filename = 'Still-{cam_id}-{cam}-{ts}.jpg'.format(
            cam_id=settings.id, cam=settings.name,
            ts=timestamp).replace(" ", "_")
    elif record_type == 'timelapse':
        if settings.path_timelapse:
            save_path = settings.path_timelapse
        else:
            save_path = assure_path_exists(
                os.path.join(camera_path, 'timelapse'))
        start = datetime.datetime.fromtimestamp(
            settings.timelapse_start_time).strftime("%Y-%m-%d_%H-%M-%S")
        filename = 'Timelapse-{cam_id}-{cam}-{st}-img-{cn:05d}.jpg'.format(
            cam_id=settings.id,
            cam=settings.name,
            st=start,
            cn=settings.timelapse_capture_number).replace(" ", "_")
    elif record_type == 'video':
        if settings.path_video:
            save_path = settings.path_video
        else:
            save_path = assure_path_exists(os.path.join(camera_path, 'video'))
        filename = 'Video-{cam}-{ts}.h264'.format(cam=settings.name,
                                                  ts=timestamp).replace(
                                                      " ", "_")
    else:
        return

    assure_path_exists(save_path)

    if tmp_filename:
        filename = tmp_filename

    path_file = os.path.join(save_path, filename)

    # Turn on output, if configured
    output_already_on = False
    output_id = None
    output_channel_id = None
    output_channel = None
    if settings.output_id and ',' in settings.output_id:
        output_id = settings.output_id.split(",")[0]
        output_channel_id = settings.output_id.split(",")[1]
        output_channel = db_retrieve_table_daemon(OutputChannel,
                                                  unique_id=output_channel_id)

    if output_id and output_channel:
        daemon_control = DaemonControl()
        if daemon_control.output_state(
                output_id, output_channel=output_channel.channel) == "on":
            output_already_on = True
        else:
            daemon_control.output_on(output_id,
                                     output_channel=output_channel.channel)

    # Pause while the output remains on for the specified duration.
    # Used for instance to allow fluorescent lights to fully turn on before
    # capturing an image.
    if settings.output_duration:
        time.sleep(settings.output_duration)

    if settings.library == 'picamera':
        import picamera

        # Try 5 times to access the pi camera (in case another process is accessing it)
        for _ in range(5):
            try:
                with picamera.PiCamera() as camera:
                    camera.resolution = (settings.width, settings.height)
                    camera.hflip = settings.hflip
                    camera.vflip = settings.vflip
                    camera.rotation = settings.rotation
                    camera.brightness = int(settings.brightness)
                    camera.contrast = int(settings.contrast)
                    camera.exposure_compensation = int(settings.exposure)
                    camera.saturation = int(settings.saturation)
                    camera.shutter_speed = settings.picamera_shutter_speed
                    camera.sharpness = settings.picamera_sharpness
                    camera.iso = settings.picamera_iso
                    camera.awb_mode = settings.picamera_awb
                    if settings.picamera_awb == 'off':
                        camera.awb_gains = (settings.picamera_awb_gain_red,
                                            settings.picamera_awb_gain_blue)
                    camera.exposure_mode = settings.picamera_exposure_mode
                    camera.meter_mode = settings.picamera_meter_mode
                    camera.image_effect = settings.picamera_image_effect

                    camera.start_preview()
                    time.sleep(2)  # Camera warm-up time

                    if record_type in ['photo', 'timelapse']:
                        camera.capture(path_file, use_video_port=False)
                    elif record_type == 'video':
                        camera.start_recording(path_file,
                                               format='h264',
                                               quality=20)
                        camera.wait_recording(duration_sec)
                        camera.stop_recording()
                    else:
                        return
                    break
            except picamera.exc.PiCameraMMALError:
                logger.error(
                    "The camera is already open by picamera. Retrying 4 times."
                )
            time.sleep(1)

    elif settings.library == 'fswebcam':
        cmd = "/usr/bin/fswebcam --device {dev} --resolution {w}x{h} --set brightness={bt}% " \
              "--no-banner --save {file}".format(dev=settings.device,
                                                 w=settings.width,
                                                 h=settings.height,
                                                 bt=settings.brightness,
                                                 file=path_file)
        if settings.hflip:
            cmd += " --flip h"
        if settings.vflip:
            cmd += " --flip h"
        if settings.rotation:
            cmd += " --rotate {angle}".format(angle=settings.rotation)
        if settings.custom_options:
            cmd += " {}".format(settings.custom_options)

        out, err, status = cmd_output(cmd, stdout_pipe=False, user='******')
        logger.debug("Camera debug message: "
                     "cmd: {}; out: {}; error: {}; status: {}".format(
                         cmd, out, err, status))

    elif settings.library == 'opencv':
        import cv2
        import imutils

        cap = cv2.VideoCapture(settings.opencv_device)
        cap.set(cv2.CAP_PROP_FRAME_WIDTH, settings.width)
        cap.set(cv2.CAP_PROP_FRAME_HEIGHT, settings.height)
        cap.set(cv2.CAP_PROP_EXPOSURE, settings.exposure)
        cap.set(cv2.CAP_PROP_GAIN, settings.gain)
        cap.set(cv2.CAP_PROP_BRIGHTNESS, settings.brightness)
        cap.set(cv2.CAP_PROP_CONTRAST, settings.contrast)
        cap.set(cv2.CAP_PROP_HUE, settings.hue)
        cap.set(cv2.CAP_PROP_SATURATION, settings.saturation)

        # Check if image can be read
        status, _ = cap.read()
        if not status:
            logger.error("Cannot detect USB camera with device '{dev}'".format(
                dev=settings.opencv_device))
            return

        # Discard a few frames to allow camera to adjust to settings
        for _ in range(2):
            cap.read()

        if record_type in ['photo', 'timelapse']:
            edited = False
            status, img_orig = cap.read()
            cap.release()

            if not status:
                logger.error("Could not acquire image")
                return

            img_edited = img_orig.copy()

            if any((settings.hflip, settings.vflip, settings.rotation)):
                edited = True

            if settings.hflip and settings.vflip:
                img_edited = cv2.flip(img_orig, -1)
            elif settings.hflip:
                img_edited = cv2.flip(img_orig, 1)
            elif settings.vflip:
                img_edited = cv2.flip(img_orig, 0)

            if settings.rotation:
                img_edited = imutils.rotate_bound(img_orig, settings.rotation)

            if edited:
                cv2.imwrite(path_file, img_edited)
            else:
                cv2.imwrite(path_file, img_orig)

        elif record_type == 'video':
            # TODO: opencv video recording is currently not working. No idea why. Try to fix later.
            try:
                cap = cv2.VideoCapture(settings.opencv_device)
                fourcc = cv2.CV_FOURCC('X', 'V', 'I', 'D')
                resolution = (settings.width, settings.height)
                out = cv2.VideoWriter(path_file, fourcc, 20.0, resolution)

                time_end = time.time() + duration_sec
                while cap.isOpened() and time.time() < time_end:
                    ret, frame = cap.read()
                    if ret:
                        # write the frame
                        out.write(frame)
                        if cv2.waitKey(1) & 0xFF == ord('q'):
                            break
                    else:
                        break
                cap.release()
                out.release()
                cv2.destroyAllWindows()
            except Exception as e:
                logger.exception("Exception raised while recording video: "
                                 "{err}".format(err=e))
        else:
            return

    elif settings.library == 'http_address':
        import cv2
        import imutils
        from urllib.error import HTTPError
        from urllib.parse import urlparse
        from urllib.request import urlretrieve

        if record_type in ['photo', 'timelapse']:
            path_tmp = "/tmp/tmpimg.jpg"

            # Get filename and extension, if available
            a = urlparse(settings.url_still)
            filename = os.path.basename(a.path)
            if filename:
                path_tmp = "/tmp/{}".format(filename)

            try:
                os.remove(path_tmp)
            except FileNotFoundError:
                pass

            try:
                urlretrieve(settings.url_still, path_tmp)
            except HTTPError as err:
                logger.error(err)
            except Exception as err:
                logger.exception(err)

            try:
                img_orig = cv2.imread(path_tmp)

                if img_orig is not None and img_orig.shape is not None:
                    if any(
                        (settings.hflip, settings.vflip, settings.rotation)):
                        img_edited = None
                        if settings.hflip and settings.vflip:
                            img_edited = cv2.flip(img_orig, -1)
                        elif settings.hflip:
                            img_edited = cv2.flip(img_orig, 1)
                        elif settings.vflip:
                            img_edited = cv2.flip(img_orig, 0)

                        if settings.rotation:
                            img_edited = imutils.rotate_bound(
                                img_orig, settings.rotation)

                        if img_edited:
                            cv2.imwrite(path_file, img_edited)
                    else:
                        cv2.imwrite(path_file, img_orig)
                else:
                    os.rename(path_tmp, path_file)
            except Exception as err:
                logger.error(
                    "Could not convert, rotate, or invert image: {}".format(
                        err))
                try:
                    os.rename(path_tmp, path_file)
                except FileNotFoundError:
                    logger.error("Camera image not found")

        elif record_type == 'video':
            pass  # No video (yet)

    elif settings.library == 'http_address_requests':
        import cv2
        import imutils
        import requests

        if record_type in ['photo', 'timelapse']:
            path_tmp = "/tmp/tmpimg.jpg"
            try:
                os.remove(path_tmp)
            except FileNotFoundError:
                pass

            try:
                r = requests.get(settings.url_still)
                if r.status_code == 200:
                    open(path_tmp, 'wb').write(r.content)
                else:
                    logger.error(
                        "Could not download image. Status code: {}".format(
                            r.status_code))
            except requests.HTTPError as err:
                logger.error("HTTPError: {}".format(err))
            except Exception as err:
                logger.exception(err)

            try:
                img_orig = cv2.imread(path_tmp)

                if img_orig is not None and img_orig.shape is not None:
                    if any(
                        (settings.hflip, settings.vflip, settings.rotation)):
                        if settings.hflip and settings.vflip:
                            img_edited = cv2.flip(img_orig, -1)
                        elif settings.hflip:
                            img_edited = cv2.flip(img_orig, 1)
                        elif settings.vflip:
                            img_edited = cv2.flip(img_orig, 0)

                        if settings.rotation:
                            img_edited = imutils.rotate_bound(
                                img_orig, settings.rotation)

                        cv2.imwrite(path_file, img_edited)
                    else:
                        cv2.imwrite(path_file, img_orig)
                else:
                    os.rename(path_tmp, path_file)
            except Exception as err:
                logger.error(
                    "Could not convert, rotate, or invert image: {}".format(
                        err))
                try:
                    os.rename(path_tmp, path_file)
                except FileNotFoundError:
                    logger.error("Camera image not found")

        elif record_type == 'video':
            pass  # No video (yet)

    try:
        set_user_grp(path_file, 'mycodo', 'mycodo')
    except Exception as e:
        logger.exception(
            "Exception raised in 'camera_record' when setting user grp: "
            "{err}".format(err=e))

    # Turn off output, if configured
    if output_id and output_channel and daemon_control and not output_already_on:
        daemon_control.output_off(output_id,
                                  output_channel=output_channel.channel)

    try:
        set_user_grp(path_file, 'mycodo', 'mycodo')
        return save_path, filename
    except Exception as e:
        logger.exception(
            "Exception raised in 'camera_record' when setting user grp: "
            "{err}".format(err=e))
Esempio n. 16
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the AM2315's humidity and temperature
    and calculates the dew point

    """
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger('mycodo.inputs.am2315')
        self.powered = False
        self.am = None

        if not testing:
            from mycodo.mycodo_client import DaemonControl
            self.logger = logging.getLogger(
                'mycodo.am2315_{id}'.format(id=input_dev.unique_id.split('-')[0]))

            self.device_measurements = db_retrieve_table_daemon(
                DeviceMeasurements).filter(
                    DeviceMeasurements.device_id == input_dev.unique_id)

            self.i2c_bus = input_dev.i2c_bus
            self.power_output_id = input_dev.power_output_id
            self.control = DaemonControl()
            self.start_sensor()
            self.am = AM2315(self.i2c_bus)

    def get_measurement(self):
        """ Gets the humidity and temperature """
        return_dict = measurements_dict.copy()

        temperature = None
        humidity = None
        dew_point = None
        measurements_success = False

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and
                db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and
                self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_sensor()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(2):
            dew_point, humidity, temperature = self.return_measurements()
            if dew_point is not None:
                measurements_success = True
                break
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id and not measurements_success:
            self.stop_sensor()
            time.sleep(2)
            self.start_sensor()
            for _ in range(2):
                dew_point, humidity, temperature = self.return_measurements()
                if dew_point is not None:
                    measurements_success = True
                    break
                time.sleep(2)

        if measurements_success:
            if self.is_enabled(0):
                return_dict[0]['value'] = temperature

            if self.is_enabled(1):
                return_dict[1]['value'] = humidity

            if (self.is_enabled(2) and
                    self.is_enabled(0) and
                    self.is_enabled(1)):
                return_dict[2]['value'] = calculate_dewpoint(
                    return_dict[0]['value'], return_dict[1]['value'])

            if (self.is_enabled(3) and
                    self.is_enabled(0) and
                    self.is_enabled(1)):
                return_dict[3]['value'] = calculate_vapor_pressure_deficit(
                    return_dict[0]['value'], return_dict[1]['value'])

            return return_dict
        else:
            self.logger.debug("Could not acquire a measurement")

    def return_measurements(self):
        # Retry measurement if CRC fails
        for num_measure in range(3):
            humidity, temperature = self.am.data()
            if humidity is None:
                self.logger.debug(
                    "Measurement {num} returned failed CRC".format(
                        num=num_measure))
                pass
            else:
                dew_pt = calculate_dewpoint(temperature, humidity)
                return dew_pt, humidity, temperature
            time.sleep(2)

        self.logger.error("All measurements returned failed CRC")
        return None, None, None

    def start_sensor(self):
        """ Turn the sensor on """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_sensor(self):
        """ Turn the sensor off """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 17
0
class CustomModule(AbstractFunction):
    """
    Class to operate custom controller
    """
    def __init__(self, function, testing=False):
        super(CustomModule, self).__init__(function,
                                           testing=testing,
                                           name=__name__)

        self.control_variable = None
        self.timestamp = None
        self.control = DaemonControl()
        self.timer_loop = time.time()

        # Initialize custom options
        self.measurement_device_id = None
        self.measurement_measurement_id = None
        self.output_raise_device_id = None
        self.output_raise_measurement_id = None
        self.output_raise_channel_id = None
        self.output_lower_device_id = None
        self.output_lower_measurement_id = None
        self.output_lower_channel_id = None
        self.setpoint = None
        self.hysteresis = None
        self.direction = None
        self.output_raise_channel = None
        self.output_lower_channel = None
        self.update_period = None

        # Set custom options
        custom_function = db_retrieve_table_daemon(CustomController,
                                                   unique_id=self.unique_id)
        self.setup_custom_options(FUNCTION_INFORMATION['custom_options'],
                                  custom_function)

        if not testing:
            self.initialize_variables()

    def initialize_variables(self):
        self.timestamp = time.time()

        self.output_raise_channel = self.get_output_channel_from_channel_id(
            self.output_raise_channel_id)
        self.output_lower_channel = self.get_output_channel_from_channel_id(
            self.output_lower_channel_id)

        self.logger.info(
            "Bang-Bang controller started with options: "
            "Measurement Device: {}, Measurement: {},"
            "Output Raise: {}, Output_Raise_Channel: {},"
            "Output Lower: {}, Output_Lower_Channel: {},"
            "Setpoint: {}, Hysteresis: {}, "
            "Direction: {}, Period: {}".format(
                self.measurement_device_id, self.measurement_measurement_id,
                self.output_raise_device_id, self.output_raise_channel,
                self.output_lower_device_id, self.output_lower_channel,
                self.setpoint, self.hysteresis, self.direction,
                self.update_period))

    def loop(self):
        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.update_period

        if ((self.direction == 'raise' and self.output_raise_channel is None)
                or
            (self.direction == 'lower' and self.output_lower_channel is None)
                or self.direction == 'both' and None
                in [self.output_raise_channel, self.output_lower_channel]):
            self.logger.error(
                "Cannot start bang-bang controller: Check output channel(s).")
            return

        last_measurement = self.get_last_measurement(
            self.measurement_device_id, self.measurement_measurement_id)[1]

        output_raise_state = self.control.output_state(
            self.output_raise_device_id, self.output_raise_channel)
        output_lower_state = self.control.output_state(
            self.output_lower_device_id, self.output_raise_channel)

        self.logger.info(
            "Input: {}, output_raise: {}, output_lower: {}, target: {}, hyst: {}"
            .format(last_measurement, output_raise_state, output_lower_state,
                    self.setpoint, self.hysteresis))

        if self.direction == 'raise':
            if last_measurement > (self.setpoint + self.hysteresis):
                if output_raise_state == 'on':
                    self.control.output_off(
                        self.output_raise_device_id,
                        output_channel=self.output_raise_channel)
            elif last_measurement < (self.setpoint - self.hysteresis):
                self.control.output_on(
                    self.output_raise_device_id,
                    output_channel=self.output_raise_channel)
        elif self.direction == 'lower':
            if last_measurement < (self.setpoint - self.hysteresis):
                if output_lower_state == 'on':
                    self.control.output_off(
                        self.output_lower_device_id,
                        output_channel=self.output_lower_channel)
            elif last_measurement > (self.setpoint + self.hysteresis):
                self.control.output_on(
                    self.output_lower_device_id,
                    output_channel=self.output_lower_channel)
        elif self.direction == 'both':
            if (last_measurement > (self.setpoint - self.hysteresis)
                    or last_measurement < (self.setpoint + self.hysteresis)):
                if output_raise_state == 'on':
                    self.control.output_off(
                        self.output_raise_device_id,
                        output_channel=self.output_raise_channel)
                if output_lower_state == 'on':
                    self.control.output_off(
                        self.output_lower_device_id,
                        output_channel=self.output_lower_channel)
            elif last_measurement > (self.setpoint + self.hysteresis):
                self.control.output_on(
                    self.output_lower_device_id,
                    output_channel=self.output_lower_channel)
            elif last_measurement < (self.setpoint - self.hysteresis):
                self.control.output_on(
                    self.output_raise_device_id,
                    output_channel=self.output_raise_channel)
        else:
            self.logger.info("Unknown controller direction: '{}'".format(
                self.direction))

    def stop_function(self):
        self.control.output_off(self.output_raise_device_id,
                                self.output_raise_channel)
        self.control.output_off(self.output_lower_device_id,
                                self.output_lower_channel)
Esempio n. 18
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the DHT22's humidity and temperature
    and calculates the dew point

    An adaptation of DHT22 code from https://github.com/joan2937/pigpio

    The sensor is also known as the AM2302.
    The sensor can be powered from the Pi 3.3-volt or 5-volt rail.
    Powering from the 3.3-volt rail is simpler and safer.  You may need
    to power from 5 if the sensor is connected via a long cable.
    For 3.3-volt operation connect pin 1 to 3.3 volts and pin 4 to ground.
    Connect pin 2 to a gpio.
    For 5-volt operation connect pin 1 to the 5 volts and pin 4 to ground.
    The following pin 2 connection works for me.  Use at YOUR OWN RISK.

    5V--5K_resistor--+--10K_resistor--Ground
                     |
    DHT22 pin 2 -----+
                     |
    gpio ------------+

    """
    def __init__(self, input_dev, testing=False):
        """
        Instantiate with the Pi and gpio to which the DHT22 output
        pin is connected.

        Optionally a gpio used to power the sensor may be specified.
        This gpio will be set high to power the sensor.  If the sensor
        locks it will be power cycled to restart the readings.

        Taking readings more often than about once every two seconds will
        eventually cause the DHT22 to hang.  A 3 second interval seems OK.
        """
        super(InputModule, self).__init__()
        self.logger = logging.getLogger('mycodo.inputs.dht22')
        self.temp_temperature = None
        self.temp_humidity = None
        self.temp_dew_point = None
        self.temp_vpd = None
        self.power_output_id = None
        self.powered = False
        self.pi = None

        if not testing:
            import pigpio
            from mycodo.mycodo_client import DaemonControl
            self.logger = logging.getLogger(
                'mycodo.dht22_{id}'.format(id=input_dev.unique_id.split('-')[0]))

            self.device_measurements = db_retrieve_table_daemon(
                DeviceMeasurements).filter(
                    DeviceMeasurements.device_id == input_dev.unique_id)

            self.power_output_id = input_dev.power_output_id

            self.control = DaemonControl()
            self.pigpio = pigpio
            self.pi = self.pigpio.pi()

            self.gpio = int(input_dev.gpio_location)
            self.bad_CS = 0  # Bad checksum count
            self.bad_SM = 0  # Short message count
            self.bad_MM = 0  # Missing message count
            self.bad_SR = 0  # Sensor reset count

            # Power cycle if timeout > MAX_NO_RESPONSE
            self.MAX_NO_RESPONSE = 3
            self.no_response = None
            self.tov = None
            self.high_tick = None
            self.bit = None
            self.either_edge_cb = None

        self.start_sensor()

    def get_measurement(self):
        """ Gets the humidity and temperature """
        return_dict = measurements_dict.copy()

        if not self.pi.connected:  # Check if pigpiod is running
            self.logger.error('Could not connect to pigpiod. '
                              'Ensure it is running and try again.')
            return None, None, None

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and
                db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and
                self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_sensor()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(4):
            self.measure_sensor()
            if self.temp_dew_point is not None:
                if self.is_enabled(0):
                    return_dict[0]['value'] = self.temp_temperature
                if self.is_enabled(1):
                    return_dict[1]['value'] = self.temp_humidity
                if (self.is_enabled(2) and
                        self.is_enabled(0) and
                        self.is_enabled(1)):
                    return_dict[2]['value'] = self.temp_dew_point
                if (self.is_enabled(3) and
                        self.is_enabled(0) and
                        self.is_enabled(1)):
                    return_dict[3]['value'] = self.temp_vpd
                return return_dict  # success - no errors
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id is not None and self.running:
            self.stop_sensor()
            time.sleep(3)
            self.start_sensor()
            for _ in range(2):
                self.measure_sensor()
                if self.temp_dew_point is not None:
                    if self.is_enabled(0):
                        return_dict[0]['value'] = self.temp_temperature
                    if self.is_enabled(1):
                        return_dict[1]['value'] = self.temp_humidity
                    if (self.is_enabled(2) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        return_dict[2]['value'] = self.temp_dew_point
                    if (self.is_enabled(3) and
                            self.is_enabled(0) and
                            self.is_enabled(1)):
                        return_dict[3]['value'] = self.temp_vpd
                    return return_dict  # success - no errors
                time.sleep(2)

        self.logger.debug("Could not acquire a measurement")
        return None

    def measure_sensor(self):
        self.temp_temperature = None
        self.temp_humidity = None
        self.temp_dew_point = None
        self.temp_vpd = None

        initialized = False

        try:
            self.close()
            time.sleep(0.2)
            self.setup()
            time.sleep(0.2)
            initialized = True
        except Exception as except_msg:
            self.logger.error(
                "Could not initialize sensor. Check if it's connected "
                "properly and pigpiod is running. Error: {msg}".format(
                    msg=except_msg))

        if initialized:
            try:
                self.pi.write(self.gpio, self.pigpio.LOW)
                time.sleep(0.017)  # 17 ms
                self.pi.set_mode(self.gpio, self.pigpio.INPUT)
                self.pi.set_watchdog(self.gpio, 200)
                time.sleep(0.2)
                if (self.temp_humidity is not None and
                        self.temp_temperature is not None):
                    self.temp_dew_point = calculate_dewpoint(
                        self.temp_temperature, self.temp_humidity)
                    self.temp_vpd = calculate_vapor_pressure_deficit(
                        self.temp_temperature, self.temp_humidity)
            except Exception as e:
                self.logger.exception(
                    "Exception when taking a reading: {err}".format(
                        err=e))
            finally:
                self.close()

    def setup(self):
        """
        Clears the internal gpio pull-up/down resistor.
        Kills any watchdogs.
        Setup callbacks
        """
        self.no_response = 0
        self.tov = None
        self.high_tick = 0
        self.bit = 40
        self.either_edge_cb = None
        self.pi.set_pull_up_down(self.gpio, self.pigpio.PUD_OFF)
        self.pi.set_watchdog(self.gpio, 0)  # Kill any watchdogs
        self.register_callbacks()

    def register_callbacks(self):
        """ Monitors RISING_EDGE changes using callback """
        self.either_edge_cb = self.pi.callback(self.gpio,
                                               self.pigpio.EITHER_EDGE,
                                               self.either_edge_callback)

    def either_edge_callback(self, gpio, level, tick):
        """
        Either Edge callbacks, called each time the gpio edge changes.
        Accumulate the 40 data bits from the DHT22 sensor.

        Format into 5 bytes, humidity high,
        humidity low, temperature high, temperature low, checksum.
        """
        level_handlers = {
            self.pigpio.FALLING_EDGE: self._edge_fall,
            self.pigpio.RISING_EDGE: self._edge_rise,
            self.pigpio.EITHER_EDGE: self._edge_either
        }
        handler = level_handlers[level]
        diff = self.pigpio.tickDiff(self.high_tick, tick)
        handler(tick, diff)

    def _edge_rise(self, tick, diff):
        """ Handle Rise signal """
        # Edge length determines if bit is 1 or 0.
        if diff >= 50:
            val = 1
            if diff >= 200:  # Bad bit?
                self.CS = 256  # Force bad checksum.
        else:
            val = 0

        if self.bit >= 40:  # Message complete.
            self.bit = 40
        elif self.bit >= 32:  # In checksum byte.
            self.CS = (self.CS << 1) + val
            if self.bit == 39:
                # 40th bit received.
                self.pi.set_watchdog(self.gpio, 0)
                self.no_response = 0
                total = self.hH + self.hL + self.tH + self.tL
                if (total & 255) == self.CS:  # Is checksum ok?
                    self.temp_humidity = ((self.hH << 8) + self.hL) * 0.1
                    if self.tH & 128:  # Negative temperature.
                        mult = -0.1
                        self.tH &= 127
                    else:
                        mult = 0.1
                    self.temp_temperature = ((self.tH << 8) + self.tL) * mult
                    self.tov = time.time()
                else:
                    self.bad_CS += 1
        elif self.bit >= 24:  # in temp low byte
            self.tL = (self.tL << 1) + val
        elif self.bit >= 16:  # in temp high byte
            self.tH = (self.tH << 1) + val
        elif self.bit >= 8:  # in humidity low byte
            self.hL = (self.hL << 1) + val
        elif self.bit >= 0:  # in humidity high byte
            self.hH = (self.hH << 1) + val
        self.bit += 1

    def _edge_fall(self, tick, diff):
        """ Handle Fall signal """
        # Edge length determines if bit is 1 or 0.
        self.high_tick = tick
        if diff <= 250000:
            return
        self.bit = -2
        self.hH = 0
        self.hL = 0
        self.tH = 0
        self.tL = 0
        self.CS = 0

    def _edge_either(self, tick, diff):
        """ Handle Either signal or Timeout """
        self.pi.set_watchdog(self.gpio, 0)
        if self.bit < 8:  # Too few data bits received.
            self.bad_MM += 1  # Bump missing message count.
            self.no_response += 1
            if self.no_response > self.MAX_NO_RESPONSE:
                self.no_response = 0
                self.bad_SR += 1  # Bump sensor reset count.
                if self.power_output_id is not None:
                    self.logger.error(
                        "Invalid data, power cycling sensor.")
                    self.stop_sensor()
                    time.sleep(2)
                    self.start_sensor()
        elif self.bit < 39:  # Short message received.
            self.bad_SM += 1  # Bump short message count.
            self.no_response = 0
        else:  # Full message received.
            self.no_response = 0

    def staleness(self):
        """ Return time since measurement made """
        if self.tov is not None:
            return time.time() - self.tov
        else:
            return -999

    def bad_checksum(self):
        """ Return count of messages received with bad checksums """
        return self.bad_CS

    def short_message(self):
        """ Return count of short messages """
        return self.bad_SM

    def missing_message(self):
        """ Return count of missing messages """
        return self.bad_MM

    def sensor_resets(self):
        """ Return count of power cycles because of sensor hangs """
        return self.bad_SR

    def close(self):
        """ Stop reading sensor, remove callbacks """
        self.pi.set_watchdog(self.gpio, 0)
        if self.either_edge_cb:
            self.either_edge_cb.cancel()
            self.either_edge_cb = None

    def start_sensor(self):
        """ Turn the sensor on """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_sensor(self):
        """ Turn the sensor off """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False
Esempio n. 19
0
def get_condition_value(condition_id):
    """
    Returns condition measurements for Conditional controllers
    :param condition_id: Conditional condition ID
    :return: measurement: multiple types
    """
    sql_condition = db_retrieve_table_daemon(ConditionalConditions).filter(
        ConditionalConditions.unique_id == condition_id).first()

    # Check Measurement Conditions
    if sql_condition.condition_type == 'measurement':
        device_id = sql_condition.measurement.split(',')[0]
        measurement_id = sql_condition.measurement.split(',')[1]

        device_measurement = db_retrieve_table_daemon(DeviceMeasurements,
                                                      unique_id=measurement_id)
        if device_measurement:
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
        else:
            conversion = None
        channel, unit, measurement = return_measurement_info(
            device_measurement, conversion)

        if None in [channel, unit]:
            logger.error(
                "Could not determine channel or unit from measurement ID: "
                "{}".format(measurement_id))
            return

        max_age = sql_condition.max_age
        # Check if there hasn't been a measurement in the last set number
        # of seconds. If not, trigger conditional
        last_measurement = get_last_measurement(device_id, unit, measurement,
                                                channel, max_age)
        return last_measurement

    # Return GPIO state
    elif sql_condition.condition_type == 'gpio_state':
        try:
            GPIO.setmode(GPIO.BCM)
            GPIO.setup(int(sql_condition.gpio_pin), GPIO.IN)
            gpio_state = GPIO.input(int(sql_condition.gpio_pin))
        except Exception as e:
            gpio_state = None
            logger.error("Exception reading the GPIO pin: {}".format(e))
        return gpio_state

    # Return output state
    elif sql_condition.condition_type == 'output_state':
        control = DaemonControl()
        return control.output_state(sql_condition.output_id)

    # Return the duration the output is currently on for
    elif sql_condition.condition_type == 'output_duration_on':
        control = DaemonControl()
        return control.output_sec_currently_on(sql_condition.output_id)

    # Return controller active state
    elif sql_condition.condition_type == 'controller_status':
        controller_type, _, _ = which_controller(sql_condition.controller_id)
        control = DaemonControl()
        return control.controller_is_active(controller_type,
                                            sql_condition.controller_id)
Esempio n. 20
0
class CustomModule(AbstractFunction):
    """
    Class to operate custom controller
    """
    def __init__(self, function, testing=False):
        super().__init__(function, testing=testing, name=__name__)

        self.control_variable = None
        self.timestamp = None
        self.control = DaemonControl()
        self.outputIsOn = False
        self.timer_loop = time.time()

        # Initialize custom options
        self.measurement_device_id = None
        self.measurement_measurement_id = None
        self.output_device_id = None
        self.output_measurement_id = None
        self.output_channel_id = None
        self.setpoint = None
        self.hysteresis = None
        self.direction = None
        self.output_channel = None
        self.update_period = None
        self.duty_cycle_increase = None
        self.duty_cycle_maintain = None
        self.duty_cycle_decrease = None
        self.duty_cycle_shutdown = None

        # Set custom options
        custom_function = db_retrieve_table_daemon(CustomController,
                                                   unique_id=self.unique_id)
        self.setup_custom_options(FUNCTION_INFORMATION['custom_options'],
                                  custom_function)

        if not testing:
            self.try_initialize()

    def initialize(self):
        self.timestamp = time.time()

        self.output_channel = self.get_output_channel_from_channel_id(
            self.output_channel_id)

        self.logger.info(
            "Bang-Bang controller started with options: "
            "Measurement Device: {}, Measurement: {}, Output: {}, "
            "Output_Channel: {}, Setpoint: {}, Hysteresis: {}, "
            "Direction: {}, Increase: {}%, Maintain: {}%, Decrease: {}%, "
            "Shutdown: {}%, Period: {}".format(
                self.measurement_device_id, self.measurement_measurement_id,
                self.output_device_id, self.output_channel, self.setpoint,
                self.hysteresis, self.direction, self.duty_cycle_increase,
                self.duty_cycle_maintain, self.duty_cycle_decrease,
                self.duty_cycle_shutdown, self.update_period))

    def loop(self):
        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.update_period

        if self.output_channel is None:
            self.logger.error(
                "Cannot run bang-bang controller: Check output channel.")
            return

        last_measurement = self.get_last_measurement(
            self.measurement_device_id, self.measurement_measurement_id)[1]

        outputState = self.control.output_state(self.output_device_id,
                                                self.output_channel)

        self.logger.info("Input: {}, output: {}, target: {}, hyst: {}".format(
            last_measurement, outputState, self.setpoint, self.hysteresis))

        if self.direction == 'raise':
            if last_measurement < (self.setpoint - self.hysteresis):
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_increase,
                                       output_channel=self.output_channel)
            else:
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_maintain,
                                       output_channel=self.output_channel)
        elif self.direction == 'lower':
            if last_measurement > (self.setpoint + self.hysteresis):
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_decrease,
                                       output_channel=self.output_channel)
            else:
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_maintain,
                                       output_channel=self.output_channel)
        elif self.direction == 'both':
            if last_measurement < (self.setpoint - self.hysteresis):
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_increase,
                                       output_channel=self.output_channel)
            elif last_measurement > (self.setpoint + self.hysteresis):
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_decrease,
                                       output_channel=self.output_channel)
            else:
                self.control.output_on(self.output_device_id,
                                       output_type='pwm',
                                       amount=self.duty_cycle_maintain,
                                       output_channel=self.output_channel)
        else:
            self.logger.info("Unknown controller direction: '{}'".format(
                self.direction))

    def stop_function(self):
        self.control.output_on(self.output_device_id,
                               output_type='pwm',
                               amount=self.duty_cycle_shutdown,
                               output_channel=self.output_channel)
Esempio n. 21
0
class CustomModule(AbstractFunction):
    """
    Class to operate custom controller
    """
    def __init__(self, function, testing=False):
        super(CustomModule, self).__init__(function,
                                           testing=testing,
                                           name=__name__)

        self.control_variable = None
        self.timestamp = None
        self.control = DaemonControl()
        self.outputIsOn = False
        self.timer_loop = time.time()

        # Initialize custom options
        self.measurement_device_id = None
        self.measurement_measurement_id = None
        self.output_device_id = None
        self.output_measurement_id = None
        self.output_channel_id = None
        self.setpoint = None
        self.hysteresis = None
        self.direction = None
        self.output_channel = None
        self.update_period = None

        # Set custom options
        custom_function = db_retrieve_table_daemon(CustomController,
                                                   unique_id=self.unique_id)
        self.setup_custom_options(FUNCTION_INFORMATION['custom_options'],
                                  custom_function)

        self.output_channel = self.get_output_channel_from_channel_id(
            self.output_channel_id)

        if not testing:
            self.initialize_variables()

    def initialize_variables(self):
        self.timestamp = time.time()

        self.logger.info(
            "Bang-Bang controller started with options: "
            "Measurement Device: {}, Measurement: {}, Output: {}, "
            "Output_Channel: {}, Setpoint: {}, Hysteresis: {}, "
            "Direction: {}, Period: {}".format(
                self.measurement_device_id, self.measurement_measurement_id,
                self.output_device_id, self.output_channel, self.setpoint,
                self.hysteresis, self.direction, self.update_period))

    def loop(self):
        if self.output_channel is None:
            self.logger.error(
                "Cannot start bang-bang controller: Could not find output channel."
            )
            self.deactivate_self()
            return

        if self.timer_loop > time.time():
            return

        while self.timer_loop < time.time():
            self.timer_loop += self.update_period

        last_measurement = self.get_last_measurement(
            self.measurement_device_id, self.measurement_measurement_id)[1]
        outputState = self.control.output_state(self.output_device_id,
                                                self.output_channel)

        self.logger.info("Input: {}, output: {}, target: {}, hyst: {}".format(
            last_measurement, outputState, self.setpoint, self.hysteresis))

        if self.direction == 'raise':
            if outputState == 'on':
                # looking to turn output off
                if last_measurement > (self.setpoint + self.hysteresis):
                    self.control.output_off(self.output_device_id,
                                            output_channel=self.output_channel)
            else:
                # looking to turn output on
                if last_measurement < (self.setpoint - self.hysteresis):
                    self.control.output_on(self.output_device_id,
                                           output_channel=self.output_channel)
        elif self.direction == 'lower':
            if outputState == 'on':
                # looking to turn output off
                if last_measurement < (self.setpoint - self.hysteresis):
                    self.control.output_off(self.output_device_id,
                                            output_channel=self.output_channel)
            else:
                # looking to turn output on
                if last_measurement > (self.setpoint + self.hysteresis):
                    self.control.output_on(self.output_device_id,
                                           output_channel=self.output_channel)
        else:
            self.logger.info("Unknown controller direction: {}".format(
                self.direction))

    def deactivate_self(self):
        self.logger.info("Deactivating bang-bang controller")

        with session_scope(MYCODO_DB_PATH) as new_session:
            mod_cont = new_session.query(CustomController).filter(
                CustomController.unique_id == self.unique_id).first()
            mod_cont.is_activated = False
            new_session.commit()

        deactivate_controller = threading.Thread(
            target=self.control.controller_deactivate, args=(self.unique_id, ))
        deactivate_controller.start()

    def stop_function(self):
        self.control.output_off(self.output_device_id, self.output_channel)
Esempio n. 22
0
class InputModule(AbstractInput):
    """
    A sensor support class that measures the DHT11's humidity and temperature
    and calculates the dew point

    The DHT11 class is a stripped version of the DHT22 sensor code by joan2937.
    You can find the initial implementation here:
    - https://github.com/srounet/pigpio/tree/master/EXAMPLES/Python/DHT22_AM2302_SENSOR

    """
    def __init__(self, input_dev, testing=False):
        """
        :param gpio: gpio pin number
        :type gpio: int
        :param power: Power pin number
        :type power: int

        Instantiate with the Pi and gpio to which the DHT11 output
        pin is connected.

        Optionally a gpio used to power the sensor may be specified.
        This gpio will be set high to power the sensor.

        """
        super(InputModule, self).__init__()
        self.logger = logging.getLogger('mycodo.inputs.dht11')
        self._dew_point = None
        self._humidity = None
        self._temperature = None
        self.temp_temperature = 0
        self.temp_humidity = 0
        self.temp_dew_point = None
        self.power_output_id = None
        self.powered = False

        if not testing:
            import pigpio
            from mycodo.mycodo_client import DaemonControl
            self.logger = logging.getLogger('mycodo.dht11_{id}'.format(
                id=input_dev.unique_id.split('-')[0]))

            self.convert_to_unit = input_dev.convert_to_unit
            self.gpio = int(input_dev.gpio_location)
            self.power_output_id = input_dev.power_output_id

            self.control = DaemonControl()

            self.pigpio = pigpio
            self.pi = self.pigpio.pi()

            self.high_tick = None
            self.bit = None
            self.either_edge_cb = None

        self.start_sensor()

    def __repr__(self):
        """  Representation of object """
        return "<{cls}(dewpoint={dpt})(humidity={hum})(temperature={temp})>".format(
            cls=type(self).__name__,
            dpt="{0:.2f}".format(self._dew_point),
            hum="{0:.2f}".format(float(self._humidity)),
            temp="{0:.2f}".format(float(self._temperature)))

    def __str__(self):
        """ Return measurement information """
        return "Dew Point: {dpt}, Humidity: {hum}, Temperature: {temp}".format(
            dpt="{0:.2f}".format(self._dew_point),
            hum="{0:.2f}".format(float(self._humidity)),
            temp="{0:.2f}".format(float(self._temperature)))

    def __iter__(self):  # must return an iterator
        """ DHT11Sensor iterates through live measurement readings """
        return self

    def next(self):
        """ Get next measurement reading """
        if self.read():  # raised an error
            raise StopIteration  # required
        return dict(dewpoint=float('{0:.2f}'.format(self._dew_point)),
                    humidity=float("{0:.2f}".format(float(self._humidity))),
                    temperature=float("{0:.2f}".format(float(
                        self._temperature))))

    @property
    def dew_point(self):
        """ DHT11 dew point in Celsius """
        if self._dew_point is None:  # update if needed
            self.read()
        return self._dew_point

    @property
    def humidity(self):
        """ DHT11 relative humidity in percent """
        if self._humidity is None:  # update if needed
            self.read()
        return self._humidity

    @property
    def temperature(self):
        """ DHT11 temperature in Celsius """
        if self._temperature is None:  # update if needed
            self.read()
        return self._temperature

    def get_measurement(self):
        """ Gets the humidity and temperature """
        self._dew_point = None
        self._humidity = None
        self._temperature = None

        if not self.pi.connected:  # Check if pigpiod is running
            self.logger.error("Could not connect to pigpiod."
                              "Ensure it is running and try again.")
            return None, None, None

        import pigpio
        self.pigpio = pigpio

        # Ensure if the power pin turns off, it is turned back on
        if (self.power_output_id and db_retrieve_table_daemon(
                Output, unique_id=self.power_output_id)
                and self.control.output_state(self.power_output_id) == 'off'):
            self.logger.error(
                'Sensor power output {rel} detected as being off. '
                'Turning on.'.format(rel=self.power_output_id))
            self.start_sensor()
            time.sleep(2)

        # Try twice to get measurement. This prevents an anomaly where
        # the first measurement fails if the sensor has just been powered
        # for the first time.
        for _ in range(2):
            self.measure_sensor()
            if self.temp_dew_point is not None:
                self.temp_dew_point = convert_units('dewpoint', 'C',
                                                    self.convert_to_unit,
                                                    self.temp_dew_point)
                self.temp_temperature = convert_units('temperature', 'C',
                                                      self.convert_to_unit,
                                                      self.temp_temperature)
                self.temp_humidity = convert_units('humidity', 'percent',
                                                   self.convert_to_unit,
                                                   self.temp_humidity)
                return (self.temp_dew_point, self.temp_humidity,
                        self.temp_temperature)  # success - no errors
            time.sleep(2)

        # Measurement failure, power cycle the sensor (if enabled)
        # Then try two more times to get a measurement
        if self.power_output_id is not None and self.running:
            self.stop_sensor()
            time.sleep(2)
            self.start_sensor()
            for _ in range(2):
                self.measure_sensor()
                if self.temp_dew_point is not None:
                    self.temp_dew_point = convert_units(
                        'dewpoint', 'C', self.convert_to_unit,
                        self.temp_dew_point)
                    self.temp_temperature = convert_units(
                        'temperature', 'C', self.convert_to_unit,
                        self.temp_temperature)
                    self.temp_humidity = convert_units('humidity', 'percent',
                                                       self.convert_to_unit,
                                                       self.temp_humidity)
                    return (self.temp_dew_point, self.temp_humidity,
                            self.temp_temperature)  # success - no errors
                time.sleep(2)

        self.logger.error("Could not acquire a measurement")
        return None, None, None

    def read(self):
        """
        Takes a reading from the DHT11 and updates the self.dew_point,
        self._humidity, and self._temperature values

        :returns: None on success or 1 on error
        """
        try:
            (self._dew_point, self._humidity,
             self._temperature) = self.get_measurement()
            if self._dew_point is not None:
                return  # success - no errors
        except Exception as e:
            self.logger.exception(
                "{cls} raised an exception when taking a reading: "
                "{err}".format(cls=type(self).__name__, err=e))
        return 1

    def measure_sensor(self):
        self.temp_temperature = 0
        self.temp_humidity = 0
        self.temp_dew_point = None

        try:
            try:
                self.setup()
            except Exception as except_msg:
                self.logger.error(
                    'Could not initialize sensor. Check if gpiod is running. '
                    'Error: {msg}'.format(msg=except_msg))
            self.pi.write(self.gpio, self.pigpio.LOW)
            time.sleep(0.017)  # 17 ms
            self.pi.set_mode(self.gpio, self.pigpio.INPUT)
            self.pi.set_watchdog(self.gpio, 200)
            time.sleep(0.2)
            if self.temp_humidity != 0:
                self.temp_dew_point = calculate_dewpoint(
                    self.temp_temperature, self.temp_humidity)
        except Exception as e:
            self.logger.error(
                "Exception raised when taking a reading: {err}".format(err=e))
        finally:
            self.close()
            return (self.temp_dew_point, self.temp_humidity,
                    self.temp_temperature)

    def setup(self):
        """
        Clears the internal gpio pull-up/down resistor.
        Kills any watchdogs.
        Setup callbacks
        """
        self._humidity = 0
        self._temperature = 0
        self.high_tick = 0
        self.bit = 40
        self.either_edge_cb = None
        self.pi.set_pull_up_down(self.gpio, self.pigpio.PUD_OFF)
        self.pi.set_watchdog(self.gpio, 0)
        self.register_callbacks()

    def register_callbacks(self):
        """ Monitors RISING_EDGE changes using callback """
        self.either_edge_cb = self.pi.callback(self.gpio,
                                               self.pigpio.EITHER_EDGE,
                                               self.either_edge_callback)

    def either_edge_callback(self, gpio, level, tick):
        """
        Either Edge callbacks, called each time the gpio edge changes.
        Accumulate the 40 data bits from the DHT11 sensor.
        """
        level_handlers = {
            self.pigpio.FALLING_EDGE: self._edge_fall,
            self.pigpio.RISING_EDGE: self._edge_rise,
            self.pigpio.EITHER_EDGE: self._edge_either
        }
        handler = level_handlers[level]
        diff = self.pigpio.tickDiff(self.high_tick, tick)
        handler(tick, diff)

    def _edge_rise(self, tick, diff):
        """ Handle Rise signal """
        val = 0
        if diff >= 50:
            val = 1
        if diff >= 200:  # Bad bit?
            self.checksum = 256  # Force bad checksum
        if self.bit >= 40:  # Message complete
            self.bit = 40
        elif self.bit >= 32:  # In checksum byte
            self.checksum = (self.checksum << 1) + val
            if self.bit == 39:
                # 40th bit received
                self.pi.set_watchdog(self.gpio, 0)
                total = self.temp_humidity + self.temp_temperature
                # is checksum ok ?
                if not (total & 255) == self.checksum:
                    # For some reason the port from python 2 to python 3 causes
                    # this bad checksum error to happen during every read
                    # TODO: Investigate how to properly check the checksum in python 3
                    self.logger.debug(
                        "Exception raised when taking a reading: "
                        "Bad Checksum.")
        elif 16 <= self.bit < 24:  # in temperature byte
            self.temp_temperature = (self.temp_temperature << 1) + val
        elif 0 <= self.bit < 8:  # in humidity byte
            self.temp_humidity = (self.temp_humidity << 1) + val
        self.bit += 1

    def _edge_fall(self, tick, diff):
        """ Handle Fall signal """
        self.high_tick = tick
        if diff <= 250000:
            return
        self.bit = -2
        self.checksum = 0
        self.temp_temperature = 0
        self.temp_humidity = 0

    def _edge_either(self, tick, diff):
        """ Handle Either signal """
        self.pi.set_watchdog(self.gpio, 0)

    def close(self):
        """ Stop reading sensor, remove callbacks """
        self.pi.set_watchdog(self.gpio, 0)
        if self.either_edge_cb:
            self.either_edge_cb.cancel()
            self.either_edge_cb = None

    def start_sensor(self):
        """ Power the sensor """
        if self.power_output_id:
            self.logger.info("Turning on sensor")
            self.control.output_on(self.power_output_id, 0)
            time.sleep(2)
            self.powered = True

    def stop_sensor(self):
        """ Depower the sensor """
        if self.power_output_id:
            self.logger.info("Turning off sensor")
            self.control.output_off(self.power_output_id)
            self.powered = False