Exemple #1
0
    def initialize_values(self):
        """Set PID parameters"""
        pid = db_retrieve_table_daemon(PID, unique_id=self.pid_id)
        self.is_activated = pid.is_activated
        self.is_held = pid.is_held
        self.is_paused = pid.is_paused
        self.method_id = pid.method_id
        self.direction = pid.direction
        self.raise_output_id = pid.raise_output_id
        self.raise_min_duration = pid.raise_min_duration
        self.raise_max_duration = pid.raise_max_duration
        self.raise_min_off_duration = pid.raise_min_off_duration
        self.lower_output_id = pid.lower_output_id
        self.lower_min_duration = pid.lower_min_duration
        self.lower_max_duration = pid.lower_max_duration
        self.lower_min_off_duration = pid.lower_min_off_duration
        self.Kp = pid.p
        self.Ki = pid.i
        self.Kd = pid.d
        self.integrator_min = pid.integrator_min
        self.integrator_max = pid.integrator_max
        self.period = pid.period
        self.start_offset = pid.start_offset
        self.max_measure_age = pid.max_measure_age
        self.default_setpoint = pid.setpoint
        self.setpoint = pid.setpoint
        self.band = pid.band
        self.store_lower_as_negative = pid.store_lower_as_negative

        # Autotune
        self.autotune_activated = pid.autotune_activated
        self.autotune_noiseband = pid.autotune_noiseband
        self.autotune_outstep = pid.autotune_outstep

        self.device_id = pid.measurement.split(',')[0]
        self.measurement_id = pid.measurement.split(',')[1]

        input_dev = db_retrieve_table_daemon(Input, unique_id=self.device_id)
        math = db_retrieve_table_daemon(Math, unique_id=self.device_id)
        if input_dev:
            self.input_duration = input_dev.period
        elif math:
            self.input_duration = math.period

        try:
            self.raise_output_type = db_retrieve_table_daemon(
                Output, unique_id=self.raise_output_id).output_type
        except AttributeError:
            self.raise_output_type = None
        try:
            self.lower_output_type = db_retrieve_table_daemon(
                Output, unique_id=self.lower_output_id).output_type
        except AttributeError:
            self.lower_output_type = None

        self.logger.info("PID Settings: {}".format(self.pid_parameters_str()))

        return "success"
Exemple #2
0
    def setup_lcd_line(self, display_id, line, device_id, measurement_id):
        if measurement_id == 'output':
            device_measurement = db_retrieve_table_daemon(
                Output, unique_id=device_id)
        elif measurement_id in ['BLANK', 'IP']:
            device_measurement = None
        else:
            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)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)
        else:
            channel = None
            unit = None
            measurement = None

        self.lcd_line[display_id][line]['setup'] = False
        self.lcd_line[display_id][line]['id'] = device_id
        self.lcd_line[display_id][line]['name'] = None
        self.lcd_line[display_id][line]['unit'] = unit
        self.lcd_line[display_id][line]['measure'] = measurement
        self.lcd_line[display_id][line]['channel'] = channel

        if 'time' in measurement_id:
            self.lcd_line[display_id][line]['measure'] = 'time'
        elif measurement_id in ['BLANK', 'IP']:
            self.lcd_line[display_id][line]['measure'] = measurement_id
            self.lcd_line[display_id][line]['name'] = ''

        if not device_id:
            return

        if unit in self.dict_units:
            self.lcd_line[display_id][line]['unit'] = unit
        else:
            self.lcd_line[display_id][line]['unit'] = ''

        # Determine the name
        controllers = [
            Output,
            PID,
            Input,
            Math
        ]
        for each_controller in controllers:
            controller_found = db_retrieve_table_daemon(each_controller, unique_id=device_id)
            if controller_found:
                self.lcd_line[display_id][line]['name'] = controller_found.name

        if (self.lcd_line[display_id][line]['measure'] in ['BLANK', 'IP', 'time'] or
                None not in [self.lcd_line[display_id][line]['name'],
                             self.lcd_line[display_id][line]['unit']]):
            self.lcd_line[display_id][line]['setup'] = True
Exemple #3
0
    def setup_method(self, method_id):
        """ Initialize method variables to start running a method """
        self.method_id = ''

        method = db_retrieve_table_daemon(Method, unique_id=method_id)
        method_data = db_retrieve_table_daemon(MethodData)
        method_data = method_data.filter(MethodData.method_id == method_id)
        method_data_repeat = method_data.filter(MethodData.duration_sec == 0).first()
        pid = db_retrieve_table_daemon(PID, unique_id=self.pid_id)
        self.method_type = method.method_type
        self.method_start_act = pid.method_start_time
        self.method_start_time = None
        self.method_end_time = None

        if self.method_type == 'Duration':
            if self.method_start_act == 'Ended':
                # Method has ended and hasn't been instructed to begin again
                pass
            elif (self.method_start_act == 'Ready' or
                    self.method_start_act is None):
                # Method has been instructed to begin
                now = datetime.datetime.now()
                self.method_start_time = now
                if method_data_repeat and method_data_repeat.duration_end:
                    self.method_end_time = now + datetime.timedelta(
                        seconds=float(method_data_repeat.duration_end))

                with session_scope(MYCODO_DB_PATH) as db_session:
                    mod_pid = db_session.query(PID).filter(
                        PID.unique_id == self.pid_id).first()
                    mod_pid.method_start_time = self.method_start_time
                    mod_pid.method_end_time = self.method_end_time
                    db_session.commit()
            else:
                # Method neither instructed to begin or not to
                # Likely there was a daemon restart ot power failure
                # Resume method with saved start_time
                self.method_start_time = datetime.datetime.strptime(
                    str(pid.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
                if method_data_repeat and method_data_repeat.duration_end:
                    self.method_end_time = datetime.datetime.strptime(
                        str(pid.method_end_time), '%Y-%m-%d %H:%M:%S.%f')
                    if self.method_end_time > datetime.datetime.now():
                        self.logger.warning(
                            "Resuming method {id}: started {start}, "
                            "ends {end}".format(
                                id=method_id,
                                start=self.method_start_time,
                                end=self.method_end_time))
                    else:
                        self.method_start_act = 'Ended'
                else:
                    self.method_start_act = 'Ended'

        self.method_id = method_id
Exemple #4
0
    def write_pid_values(self):
        """ Write PID values to the measurement database """
        if self.band:
            setpoint_band_lower = self.setpoint - self.band
            setpoint_band_upper = self.setpoint + self.band
        else:
            setpoint_band_lower = None
            setpoint_band_upper = None

        list_measurements = [
            self.setpoint,
            setpoint_band_lower,
            setpoint_band_upper,
            self.P_value,
            self.I_value,
            self.D_value
        ]

        measurement_dict = {}
        measurements = self.device_measurements.filter(
            DeviceMeasurements.device_id == self.pid_id).all()
        for each_channel, each_measurement in enumerate(measurements):
            if (each_measurement.channel not in measurement_dict and
                    each_measurement.channel < len(list_measurements)):

                # If setpoint, get unit from PID measurement
                if each_measurement.measurement_type == 'setpoint':
                    setpoint_pid = db_retrieve_table_daemon(
                        PID, unique_id=each_measurement.device_id)
                    if setpoint_pid and ',' in setpoint_pid.measurement:
                        pid_measurement = setpoint_pid.measurement.split(',')[1]
                        setpoint_measurement = db_retrieve_table_daemon(
                            DeviceMeasurements, unique_id=pid_measurement)
                        if setpoint_measurement:
                            conversion = db_retrieve_table_daemon(
                                Conversion, unique_id=setpoint_measurement.conversion_id)
                            _, unit, _ = return_measurement_info(
                                setpoint_measurement, conversion)
                            measurement_dict[each_channel] = {
                                'measurement': each_measurement.measurement,
                                'unit': unit,
                                'value': list_measurements[each_channel]
                            }
                else:
                    measurement_dict[each_channel] = {
                        'measurement': each_measurement.measurement,
                        'unit': each_measurement.unit,
                        'value': list_measurements[each_channel]
                    }

        add_measurements_influxdb(self.pid_id, measurement_dict)
Exemple #5
0
 def refresh_daemon_misc_settings(self):
     old_time = self.output_usage_report_next_gen
     self.output_usage_report_next_gen = next_schedule(
         self.output_usage_report_span,
         self.output_usage_report_day,
         self.output_usage_report_hour)
     try:
         self.logger.debug("Refreshing misc settings")
         misc = db_retrieve_table_daemon(Misc, entry='first')
         self.opt_out_statistics = misc.stats_opt_out
         self.enable_upgrade_check = misc.enable_upgrade_check
         self.output_usage_report_gen = misc.output_usage_report_gen
         self.output_usage_report_span = misc.output_usage_report_span
         self.output_usage_report_day = misc.output_usage_report_day
         self.output_usage_report_hour = misc.output_usage_report_hour
         if (self.output_usage_report_gen and
                 old_time != self.output_usage_report_next_gen):
             str_next_report = time.strftime(
                 '%c', time.localtime(self.output_usage_report_next_gen))
             self.logger.debug(
                 "Generating next output usage report {time_date}".format(
                     time_date=str_next_report))
     except Exception as except_msg:
         message = "Could not refresh misc settings:" \
                   " {err}".format(err=except_msg)
         self.logger.exception(message)
Exemple #6
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger('mycodo.mcp3008')
        self.acquiring_measurement = False
        self.adc = None

        if not testing:
            import Adafruit_MCP3008
            self.logger = logging.getLogger(
                'mycodo.mcp3008_{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.pin_clock = input_dev.pin_clock
            self.pin_cs = input_dev.pin_cs
            self.pin_miso = input_dev.pin_miso
            self.pin_mosi = input_dev.pin_mosi
            self.scale_from_max = input_dev.scale_from_max

            self.adc = Adafruit_MCP3008.MCP3008(clk=self.pin_clock,
                                                cs=self.pin_cs,
                                                miso=self.pin_miso,
                                                mosi=self.pin_mosi)
    def get_method_output(self, method_id):
        """ Get output variable from method """
        this_controller = db_retrieve_table_daemon(
            Trigger, unique_id=self.function_id)
        setpoint, ended = calculate_method_setpoint(
            method_id,
            Trigger,
            this_controller,
            Method,
            MethodData,
            self.logger)

        if setpoint is not None:
            if setpoint > 100:
                setpoint = 100
            elif setpoint < 0:
                setpoint = 0

        if ended:
            with session_scope(MYCODO_DB_PATH) as db_session:
                mod_conditional = db_session.query(Trigger)
                mod_conditional = mod_conditional.filter(
                    Trigger.unique_id == self.function_id).first()
                mod_conditional.is_activated = False
                db_session.commit()
            self.is_activated = False
            self.stop_controller()

        return setpoint, ended
Exemple #8
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.amg8833")
        self.save_image = False
        self.temp_max = None
        self.temp_min = None
        self.report = False
        self.scale = 2
        self.nx = 8
        self.ny = 8

        if not testing:
            from Adafruit_AMG88xx import Adafruit_AMG88xx
            self.logger = logging.getLogger(
                "mycodo.ds18b20_{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.Adafruit_AMG88xx = Adafruit_AMG88xx
            self.i2c_address = int(str(input_dev.i2c_location), 16)
            self.i2c_bus = input_dev.i2c_bus
            self.input_dev = input_dev
            self.sensor = self.Adafruit_AMG88xx(address=self.i2c_address,
                                                busnum=self.i2c_bus)
            time.sleep(.1)  # wait for it to boot
Exemple #9
0
    def __init__(self, input_dev, mode=BMP280_STANDARD, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.bmp280")

        if not testing:
            import Adafruit_GPIO.I2C as I2C
            self.logger = logging.getLogger(
                "mycodo.bmp280_{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_address = int(str(input_dev.i2c_location), 16)
            self.i2c_bus = input_dev.i2c_bus
            if mode not in [BMP280_ULTRALOWPOWER,
                            BMP280_STANDARD,
                            BMP280_HIGHRES,
                            BMP280_ULTRAHIGHRES]:
                raise ValueError(
                    'Unexpected mode value {0}.  Set mode to one of '
                    'BMP280_ULTRALOWPOWER, BMP280_STANDARD, BMP280_HIGHRES, '
                    'or BMP280_ULTRAHIGHRES'.format(mode))
            self._mode = mode
            # Create I2C device.
            i2c = I2C
            self._device = i2c.get_i2c_device(self.i2c_address,
                                              busnum=self.i2c_bus)
            # Load calibration values.
            self._load_calibration()
            self._tfine = 0
Exemple #10
0
    def get_last_measurement(self):
        """
        Retrieve the latest input measurement from InfluxDB

        :rtype: None
        """
        self.last_measurement_success = False

        # Get latest measurement from influxdb
        try:
            device_measurement = get_measurement(self.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)

            self.last_measurement = read_last_influxdb(
                self.device_id,
                unit,
                measurement,
                channel,
                int(self.max_measure_age))

            if self.last_measurement:
                self.last_time = self.last_measurement[0]
                self.last_measurement = self.last_measurement[1]

                utc_dt = datetime.datetime.strptime(
                    self.last_time.split(".")[0],
                    '%Y-%m-%dT%H:%M:%S')
                utc_timestamp = calendar.timegm(utc_dt.timetuple())
                local_timestamp = str(datetime.datetime.fromtimestamp(utc_timestamp))
                self.logger.debug("Latest (CH{ch}, Unit: {unit}): {last} @ {ts}".format(
                    ch=channel,
                    unit=unit,
                    last=self.last_measurement,
                    ts=local_timestamp))
                if calendar.timegm(time.gmtime()) - utc_timestamp > self.max_measure_age:
                    self.logger.error(
                        "Last measurement was {last_sec} seconds ago, however"
                        " the maximum measurement age is set to {max_sec}"
                        " seconds.".format(
                            last_sec=calendar.timegm(time.gmtime()) - utc_timestamp,
                            max_sec=self.max_measure_age
                        ))
                self.last_measurement_success = True
            else:
                self.logger.warning("No data returned from influxdb")
        except requests.ConnectionError:
            self.logger.error("Failed to read measurement from the "
                              "influxdb database: Could not connect.")
        except Exception as except_msg:
            self.logger.exception(
                "Exception while reading measurement from the influxdb "
                "database: {err}".format(err=except_msg))
Exemple #11
0
 def output_state(output_id):
     output = db_retrieve_table_daemon(Output, unique_id=output_id)
     GPIO.setmode(GPIO.BCM)
     if GPIO.input(output.pin) == output.trigger:
         gpio_state = 'On'
     else:
         gpio_state = 'Off'
     return gpio_state
Exemple #12
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.winsen_zh03b")
        self.fan_is_on = False

        if not testing:
            import serial
            import binascii
            self.logger = logging.getLogger(
                "mycodo.winsen_zh03b_{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.binascii = binascii
            self.uart_location = input_dev.uart_location
            self.baud_rate = input_dev.baud_rate
            # Check if device is valid
            self.serial_device = is_device(self.uart_location)

            self.fan_modulate = True
            self.fan_seconds = 50.0

            if input_dev.custom_options:
                for each_option in input_dev.custom_options.split(';'):
                    option = each_option.split(',')[0]
                    value = each_option.split(',')[1]
                    if option == 'fan_modulate':
                        self.fan_modulate = bool(value)
                    elif option == 'fan_seconds':
                        self.fan_seconds = float(value)

            if self.serial_device:
                try:
                    self.ser = serial.Serial(
                        port=self.serial_device,
                        baudrate=self.baud_rate,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        bytesize=serial.EIGHTBITS,
                        timeout=10
                    )
                    self.ser.flushInput()

                    if not self.fan_modulate:
                        self.dormant_mode('run')

                    time.sleep(0.1)

                except serial.SerialException:
                    self.logger.exception('Opening serial')
            else:
                self.logger.error(
                    'Could not open "{dev}". '
                    'Check the device location is correct.'.format(
                        dev=self.uart_location))
Exemple #13
0
def get_measurement(measurement_id):
    """ Find measurement """
    device_measurement = db_retrieve_table_daemon(
        DeviceMeasurements).filter(
        DeviceMeasurements.unique_id == measurement_id).first()
    if device_measurement:
        return device_measurement
    else:
        return None
Exemple #14
0
 def refresh_daemon_camera_settings(self):
     try:
         self.logger.debug("Refreshing camera settings")
         self.camera = db_retrieve_table_daemon(
             Camera, entry='all')
     except Exception as except_msg:
         self.camera = []
         message = "Could not read camera table:" \
                   " {err}".format(err=except_msg)
         self.logger.exception(message)
Exemple #15
0
def convert_from_x_to_y_unit(unit_from, unit_to, in_value):
    conversion = db_retrieve_table_daemon(Conversion)
    conversion = conversion.filter(Conversion.convert_unit_from == unit_from)
    conversion = conversion.filter(Conversion.convert_unit_to == unit_to).first()
    if conversion:
        replaced_str = conversion.equation.replace('x', str(in_value))
        return float('{0:.5f}'.format(eval(replaced_str)))
    else:
        logger.error("Conversion not found for {uf} to {ut}".format(
            uf=unit_to, ut=unit_from))
def get_condition_measurement(sql_condition):
    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 Measurement Conditions
    if sql_condition.condition_type == 'measurement':
        # 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

    # If the edge detection variable is set, calling this function will
    # trigger an edge detection event. This will merely produce the correct
    # message based on the edge detection settings.
    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
Exemple #17
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.raspi")

        if not testing:
            self.logger = logging.getLogger(
                "mycodo.raspi_{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)
Exemple #18
0
    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()
Exemple #19
0
def convert_units(conversion_id, measure_value):
    """
    Convert from one unit to another, such as ppm to ppb.
    See UNIT_CONVERSIONS in config_devices_units.py for available conversions.

    :param conversion_id: conversion ID
    :param measure_value: The value to convert
    :return: converted value
    """
    conversion = db_retrieve_table_daemon(Conversion, unique_id=conversion_id)
    replaced_str = conversion.equation.replace('x', str(measure_value))
    return float('{0:.5f}'.format(eval(replaced_str)))
    def edge_detected(self, bcm_pin):
        """
        Callback function from GPIO.add_event_detect() for when an edge is detected

        Write rising (1) or falling (-1) edge to influxdb database
        Trigger any conditionals that match the rising/falling/both edge

        :param bcm_pin: BMC pin of rising/falling edge (required parameter)
        :return: None
        """
        gpio_state = GPIO.input(int(self.gpio_location))
        if time.time() > self.edge_reset_timer:
            self.edge_reset_timer = time.time()+self.switch_reset_period

            if (self.switch_edge == 'rising' or
                    (self.switch_edge == 'both' and gpio_state)):
                rising_or_falling = 1  # Rising edge detected
                state_str = 'Rising'
            else:
                rising_or_falling = -1  # Falling edge detected
                state_str = 'Falling'

            write_db = threading.Thread(
                target=write_influxdb_value,
                args=(self.unique_id, 'edge', rising_or_falling,))
            write_db.start()

            trigger = db_retrieve_table_daemon(Trigger)
            trigger = trigger.filter(
                Trigger.trigger_type == 'trigger_edge')
            trigger = trigger.filter(
                Trigger.measurement == self.unique_id)
            trigger = trigger.filter(
                Trigger.is_activated == True)

            for each_trigger in trigger.all():
                if each_trigger.edge_detected in ['both', state_str.lower()]:
                    now = time.time()
                    timestamp = datetime.datetime.fromtimestamp(
                        now).strftime('%Y-%m-%d %H-%M-%S')
                    message = "{ts}\n[Trigger {cid} ({cname})] " \
                              "Input {oid} ({name}) {state} edge detected " \
                              "on pin {pin} (BCM)".format(
                                    ts=timestamp,
                                    cid=each_trigger.id,
                                    cname=each_trigger.name,
                                    oid=self.input_id,
                                    name=self.input_name,
                                    state=state_str,
                                    pin=bcm_pin)

                    self.control.trigger_all_actions(
                        each_trigger.unique_id, message=message)
    def start_method(self, method_id):
        """ Instruct a method to start running """
        if method_id:
            method = db_retrieve_table_daemon(Method, unique_id=method_id)
            method_data = db_retrieve_table_daemon(MethodData)
            method_data = method_data.filter(MethodData.method_id == method_id)
            method_data_repeat = method_data.filter(MethodData.duration_sec == 0).first()
            self.method_start_act = self.method_start_time
            self.method_start_time = None
            self.method_end_time = None

            if method.method_type == 'Duration':
                if self.method_start_act == 'Ended':
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_conditional = db_session.query(Trigger)
                        mod_conditional = mod_conditional.filter(
                            Trigger.unique_id == self.function_id).first()
                        mod_conditional.is_activated = False
                        db_session.commit()
                    self.stop_controller()
                    self.logger.warning(
                        "Method has ended. "
                        "Activate the Trigger controller to start it again.")
                elif (self.method_start_act == 'Ready' or
                        self.method_start_act is None):
                    # Method has been instructed to begin
                    now = datetime.datetime.now()
                    self.method_start_time = now
                    if method_data_repeat and method_data_repeat.duration_end:
                        self.method_end_time = now + datetime.timedelta(
                            seconds=float(method_data_repeat.duration_end))

                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_conditional = db_session.query(Trigger)
                        mod_conditional = mod_conditional.filter(
                            Trigger.unique_id == self.function_id).first()
                        mod_conditional.method_start_time = self.method_start_time
                        mod_conditional.method_end_time = self.method_end_time
                        db_session.commit()
    def setup_settings(self):
        """ Define all settings """
        cond = db_retrieve_table_daemon(
            Conditional, unique_id=self.function_id)

        self.is_activated = cond.is_activated

        self.smtp_max_count = db_retrieve_table_daemon(
            SMTP, entry='first').hourly_max
        self.email_count = 0
        self.allowed_to_send_notice = True

        now = time.time()

        self.smtp_wait_timer = now + 3600
        self.timer_period = None

        self.conditional_statement = cond.conditional_statement
        self.period = cond.period
        self.start_offset = cond.start_offset
        self.refractory_period = cond.refractory_period
        self.timer_refractory_period = 0
        self.smtp_wait_timer = now + 3600
        self.timer_period = now + self.start_offset
Exemple #23
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.bmp180")

        if not testing:
            from Adafruit_BMP import BMP085
            self.logger = logging.getLogger(
                "mycodo.bmp180_{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.bmp = BMP085.BMP085(busnum=self.i2c_bus)
Exemple #24
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.cozir_co2")

        if not testing:
            from cozir import Cozir
            self.logger = logging.getLogger(
                "mycodo.cozir_co2_{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.uart_location = input_dev.uart_location
            self.sensor = Cozir(self.uart_location)
Exemple #25
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.htu21d")

        if not testing:
            import pigpio
            self.logger = logging.getLogger(
                "mycodo.htu21d_{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.i2c_address = 0x40  # HTU21D-F Address
            self.pi = pigpio.pi()
Exemple #26
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.sht2x")

        if not testing:
            from smbus2 import SMBus
            self.logger = logging.getLogger(
                "mycodo.sht2x_{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_address = int(str(input_dev.i2c_location), 16)
            self.i2c_bus = input_dev.i2c_bus
            self.sht2x = SMBus(self.i2c_bus)
Exemple #27
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.signal_pwm")

        if not testing:
            import pigpio
            self.logger = logging.getLogger(
                "mycodo.signal_pwm_{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.gpio = int(input_dev.gpio_location)
            self.weighting = input_dev.weighting
            self.sample_time = input_dev.sample_time
            self.pigpio = pigpio
Exemple #28
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.hdc1000")

        if not testing:
            self.logger = logging.getLogger(
                "mycodo.hdc1000_{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.resolution_temperature = input_dev.resolution
            self.resolution_humidity = input_dev.resolution_2
            self.i2c_bus = input_dev.i2c_bus
            self.i2c_address = 0x40  # HDC1000-F Address

            self.HDC1000_fr = io.open("/dev/i2c-" + str(self.i2c_bus), "rb", buffering=0)
            self.HDC1000_fw = io.open("/dev/i2c-" + str(self.i2c_bus), "wb", buffering=0)

            # set device address
            fcntl.ioctl(self.HDC1000_fr, I2C_SLAVE, self.i2c_address)
            fcntl.ioctl(self.HDC1000_fw, I2C_SLAVE, self.i2c_address)
            time.sleep(0.015)  # 15ms startup time

            config = HDC1000_CONFIG_ACQUISITION_MODE

            s = [HDC1000_CONFIGURATION_REGISTER, config >> 8, 0x00]
            s2 = bytearray(s)
            self.HDC1000_fw.write(s2)  # sending config register bytes
            time.sleep(0.015)  # From the data sheet

            # Set resolutions
            if self.resolution_temperature == 11:
                self.set_temperature_resolution(HDC1000_CONFIG_TEMPERATURE_RESOLUTION_11BIT)
            elif self.resolution_temperature == 14:
                self.set_temperature_resolution(HDC1000_CONFIG_TEMPERATURE_RESOLUTION_14BIT)

            if self.resolution_humidity == 8:
                self.set_humidity_resolution(HDC1000_CONFIG_HUMIDITY_RESOLUTION_8BIT)
            elif self.resolution_humidity == 11:
                self.set_humidity_resolution(HDC1000_CONFIG_HUMIDITY_RESOLUTION_11BIT)
            elif self.resolution_humidity == 14:
                self.set_humidity_resolution(HDC1000_CONFIG_HUMIDITY_RESOLUTION_14BIT)
Exemple #29
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.bh1750")

        if not testing:
            from smbus2 import SMBus
            self.logger = logging.getLogger(
                "mycodo.bh1750_{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_address = int(str(input_dev.i2c_location), 16)
            self.resolution = input_dev.resolution
            self.sensitivity = input_dev.sensitivity
            self.i2c_bus = SMBus(input_dev.i2c_bus)
            self.power_down()
            self.set_sensitivity(sensitivity=self.sensitivity)
Exemple #30
0
    def __init__(self, input_dev, testing=False):
        super(InputModule, self).__init__()
        self.logger = logging.getLogger("mycodo.inputs.th16")
        self.ip_address = None

        if not testing:
            self.logger = logging.getLogger(
                "mycodo.th16_{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)

            if input_dev.custom_options:
                for each_option in input_dev.custom_options.split(';'):
                    option = each_option.split(',')[0]
                    value = each_option.split(',')[1]
                    if option == 'ip_address':
                        self.ip_address = value.replace(" ", "")  # Remove spaces from string
    def edge_detected(self, bcm_pin):
        """
        Callback function from GPIO.add_event_detect() for when an edge is detected

        Write rising (1) or falling (-1) edge to influxdb database
        Trigger any conditionals that match the rising/falling/both edge

        :param bcm_pin: BMC pin of rising/falling edge (required parameter)
        :return: None
        """
        try:
            import RPi.GPIO as GPIO
            gpio_state = GPIO.input(int(self.gpio_location))
        except:
            self.logger.error(
                "RPi.GPIO and Raspberry Pi required for this action")
            gpio_state = None

        if gpio_state is not None and time.time() > self.edge_reset_timer:
            self.edge_reset_timer = time.time() + self.switch_reset_period

            if (self.switch_edge == 'rising'
                    or (self.switch_edge == 'both' and gpio_state)):
                rising_or_falling = 1  # Rising edge detected
                state_str = 'Rising'
            else:
                rising_or_falling = -1  # Falling edge detected
                state_str = 'Falling'

            write_db = threading.Thread(target=write_influxdb_value,
                                        args=(
                                            self.unique_id,
                                            'edge',
                                            rising_or_falling,
                                        ))
            write_db.start()

            trigger = db_retrieve_table_daemon(Trigger)
            trigger = trigger.filter(Trigger.trigger_type == 'trigger_edge')
            trigger = trigger.filter(Trigger.measurement == self.unique_id)
            trigger = trigger.filter(Trigger.is_activated == True)

            for each_trigger in trigger.all():
                if each_trigger.edge_detected in ['both', state_str.lower()]:
                    now = time.time()
                    timestamp = datetime.datetime.fromtimestamp(now).strftime(
                        '%Y-%m-%d %H-%M-%S')
                    message = "{ts}\n[Trigger {cid} ({cname})] " \
                              "Input {oid} ({name}) {state} edge detected " \
                              "on pin {pin} (BCM)".format(
                                    ts=timestamp,
                                    cid=each_trigger.id,
                                    cname=each_trigger.name,
                                    oid=self.unique_id,
                                    name=self.input_name,
                                    state=state_str,
                                    pin=bcm_pin)
                    self.logger.debug("Edge: {}".format(message))

                    self.control.trigger_all_actions(each_trigger.unique_id,
                                                     message=message)
Exemple #32
0
    def run_function(self):
        temp_c = None
        hum_percent = None
        vpd_pa = None

        last_measurement_temp = self.get_last_measurement(
            self.select_measurement_temperature_c_device_id,
            self.select_measurement_temperature_c_measurement_id,
            max_age=self.max_measure_age_temperature_c)

        if last_measurement_temp:
            device_measurement = get_measurement(
                self.select_measurement_temperature_c_measurement_id)
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)
            temp_c = convert_from_x_to_y_unit(unit, 'C', last_measurement_temp[1])

        last_measurement_hum = self.get_last_measurement(
            self.select_measurement_humidity_device_id,
            self.select_measurement_humidity_measurement_id,
            max_age=self.max_measure_age_humidity)

        if last_measurement_hum:
            device_measurement = get_measurement(
                self.select_measurement_humidity_measurement_id)
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)
            hum_percent = convert_from_x_to_y_unit(unit, 'percent', last_measurement_hum[1])

        if temp_c and hum_percent:
            measurement_dict = copy.deepcopy(measurements_dict)

            try:
                vpd_pa = calculate_vapor_pressure_deficit(temp_c, hum_percent)
            except TypeError as err:
                self.logger.error("Error: {msg}".format(msg=err))

            if vpd_pa:
                dev_measurement = self.channels_measurement[0]
                channel, unit, measurement = return_measurement_info(
                    dev_measurement, self.channels_conversion[0])

                vpd_store = convert_from_x_to_y_unit('Pa', unit, vpd_pa)

                measurement_dict[0] = {
                    'measurement': measurement,
                    'unit': unit,
                    'value': vpd_store
                }

            # Add measurement(s) to influxdb
            if measurement_dict:
                self.logger.debug(
                    "Adding measurements to InfluxDB with ID {}: {}".format(
                        self.unique_id, measurement_dict))
                add_measurements_influxdb(self.unique_id, measurement_dict)
            else:
                self.logger.debug(
                    "No measurements to add to InfluxDB with ID {}".format(
                        self.unique_id))
        else:
            self.logger.debug(
                "Could not acquire both temperature and humidity measurements.")
    def initialize_variables(self):
        self.dict_inputs = parse_input_information()

        self.sample_rate = db_retrieve_table_daemon(
            Misc, entry='first').sample_rate_controller_input

        input_dev = db_retrieve_table_daemon(Input, unique_id=self.unique_id)

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

        self.conversions = db_retrieve_table_daemon(Conversion)

        self.input_dev = input_dev
        self.input_name = input_dev.name
        self.unique_id = input_dev.unique_id
        self.log_level_debug = input_dev.log_level_debug
        self.gpio_location = input_dev.gpio_location
        self.device = input_dev.device
        self.interface = input_dev.interface
        self.period = input_dev.period
        self.start_offset = input_dev.start_offset

        # Edge detection
        self.switch_edge = input_dev.switch_edge
        self.switch_bouncetime = input_dev.switch_bouncetime
        self.switch_reset_period = input_dev.switch_reset_period

        # Pre-Output: Activates prior to input measurement
        self.pre_output_id = input_dev.pre_output_id
        self.pre_output_duration = input_dev.pre_output_duration
        self.pre_output_during_measure = input_dev.pre_output_during_measure
        self.pre_output_setup = False
        self.last_measurement = 0
        self.next_measurement = time.time() + self.start_offset
        self.get_new_measurement = False
        self.trigger_cond = False
        self.measurement_acquired = False
        self.pre_output_activated = False
        self.pre_output_locked = False
        self.pre_output_timer = time.time()

        self.set_log_level_debug(self.log_level_debug)

        # Check if Pre-Output ID actually exists
        output = db_retrieve_table_daemon(Output, entry='all')
        for each_output in output:
            if (each_output.unique_id == self.pre_output_id
                    and self.pre_output_duration):
                self.pre_output_setup = True

        smtp = db_retrieve_table_daemon(SMTP, entry='first')
        self.smtp_max_count = smtp.hourly_max
        self.email_count = 0
        self.allowed_to_send_notice = True

        # Set up input lock
        self.lock_file = '/var/lock/input_pre_output_{id}'.format(
            id=self.pre_output_id)

        # Convert string I2C address to base-16 int
        if self.interface == 'I2C':
            self.i2c_address = int(str(self.input_dev.i2c_location), 16)

        # Set up edge detection of a GPIO pin
        if self.device == 'EDGE':
            try:
                import RPi.GPIO as GPIO
                if self.switch_edge == 'rising':
                    self.switch_edge_gpio = GPIO.RISING
                elif self.switch_edge == 'falling':
                    self.switch_edge_gpio = GPIO.FALLING
                else:
                    self.switch_edge_gpio = GPIO.BOTH
            except:
                self.logger.error(
                    "RPi.GPIO and Raspberry Pi required for this action")

        self.device_recognized = True

        if self.device in self.dict_inputs:
            input_loaded = load_module_from_file(
                self.dict_inputs[self.device]['file_path'], 'inputs')

            if self.device == 'EDGE':
                # Edge detection handled internally, no module to load
                self.measure_input = None
            else:
                self.measure_input = input_loaded.InputModule(self.input_dev)

        else:
            self.device_recognized = False
            self.logger.debug(
                "Device '{device}' not recognized".format(device=self.device))
            raise Exception("'{device}' is not a valid device type.".format(
                device=self.device))

        self.edge_reset_timer = time.time()
        self.input_timer = time.time()
        self.lastUpdate = None

        # Set up edge detection
        if self.device == 'EDGE':
            try:
                import RPi.GPIO as GPIO
                GPIO.setmode(GPIO.BCM)
                GPIO.setup(int(self.gpio_location), GPIO.IN)
                GPIO.add_event_detect(int(self.gpio_location),
                                      self.switch_edge_gpio,
                                      callback=self.edge_detected,
                                      bouncetime=self.switch_bouncetime)
            except:
                self.logger.error(
                    "RPi.GPIO and Raspberry Pi required for this action")

        # Set up MQTT listener
        elif ('listener' in self.dict_inputs[self.device]
              and self.dict_inputs[self.device]['listener']):
            input_listener = threading.Thread(
                target=self.measure_input.listener())
            input_listener.daemon = True
            input_listener.start()
def trigger_function_actions(function_id, message=''):
    """
    Execute the Actions belonging to a particular Function

    :param function_id:
    :param message: The message generated from the conditional check
    :return:
    """
    logger_actions = logging.getLogger(
        "mycodo.trigger_function_actions_{id}".format(
            id=function_id.split('-')[0]))

    # List of all email notification recipients
    # List is appended with TO email addresses when an email Action is
    # encountered. An email is sent to all recipients after all actions
    # have been executed.
    email_recipients = []

    # List of tags to add to a note
    note_tags = []

    attachment_file = None
    attachment_type = None

    actions = db_retrieve_table_daemon(Actions)
    actions = actions.filter(Actions.function_id == function_id).all()

    for cond_action in actions:
        (message, note_tags, email_recipients, attachment_file,
         attachment_type) = trigger_action(cond_action.unique_id,
                                           message=message,
                                           single_action=False,
                                           note_tags=note_tags,
                                           email_recipients=email_recipients,
                                           attachment_file=attachment_file,
                                           attachment_type=attachment_type)

    # Send email after all conditional actions have been checked
    # In order to append all action messages to send in the email
    # send_email_at_end will be None or the TO email address
    if email_recipients:
        smtp = db_retrieve_table_daemon(SMTP, entry='first')
        send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw,
                   smtp.email_from, email_recipients, message, attachment_file,
                   attachment_type)

    # Create a note with the tags from the unique_ids in the list note_tags
    if note_tags:
        list_tags = []
        for each_note_tag_id in note_tags:
            check_tag = db_retrieve_table_daemon(NoteTags,
                                                 unique_id=each_note_tag_id)
            if check_tag:
                list_tags.append(each_note_tag_id)
        if list_tags:
            with session_scope(MYCODO_DB_PATH) as db_session:
                new_note = Notes()
                new_note.name = 'Action'
                new_note.tags = ','.join(list_tags)
                new_note.note = message
                db_session.add(new_note)

    logger_actions.debug(message)
def which_controller(unique_id):
    """Determine which type of controller the unique_id is for"""
    controller_type = None
    controller_object = None
    controller_entry = None
    if db_retrieve_table_daemon(Conditional, unique_id=unique_id):
        controller_type = 'Conditional'
        controller_object = Conditional
        controller_entry = db_retrieve_table_daemon(Conditional,
                                                    unique_id=unique_id)
    elif db_retrieve_table_daemon(Input, unique_id=unique_id):
        controller_type = 'Input'
        controller_object = Input
        controller_entry = db_retrieve_table_daemon(Input, unique_id=unique_id)
    elif db_retrieve_table_daemon(LCD, unique_id=unique_id):
        controller_type = 'LCD'
        controller_object = LCD
        controller_entry = db_retrieve_table_daemon(LCD, unique_id=unique_id)
    elif db_retrieve_table_daemon(Math, unique_id=unique_id):
        controller_type = 'Math'
        controller_object = Math
        controller_entry = db_retrieve_table_daemon(Math, unique_id=unique_id)
    elif db_retrieve_table_daemon(PID, unique_id=unique_id):
        controller_type = 'PID'
        controller_object = PID
        controller_entry = db_retrieve_table_daemon(PID, unique_id=unique_id)
    elif db_retrieve_table_daemon(Trigger, unique_id=unique_id):
        controller_type = 'Trigger'
        controller_object = Trigger
        controller_entry = db_retrieve_table_daemon(Trigger,
                                                    unique_id=unique_id)
    return controller_type, controller_object, controller_entry
Exemple #36
0
def send_anonymous_stats(start_time, debug=False):
    """
    Send anonymous usage statistics

    Example use:
        current_stat = return_stat_file_dict(csv_file)
        add_update_csv(csv_file, 'stat', current_stat['stat'] + 5)
    """
    if debug:
        logger.setLevel(logging.DEBUG)
    else:
        logger.setLevel(logging.INFO)

    try:
        client = InfluxDBClient(STATS_HOST, STATS_PORT, STATS_USER,
                                STATS_PASSWORD, STATS_DATABASE)
        # Prepare stats before sending
        uptime = (time.time() - start_time) / 86400.0  # Days
        add_update_csv(STATS_CSV, 'uptime', uptime)

        version_num = db_retrieve_table_daemon(AlembicVersion, entry='first')
        version_send = version_num.version_num if version_num else 'None'
        add_update_csv(STATS_CSV, 'alembic_version', version_send)

        outputs = db_retrieve_table_daemon(Output)
        add_update_csv(STATS_CSV, 'num_relays', outputs.count())

        inputs = db_retrieve_table_daemon(Input)
        add_update_csv(STATS_CSV, 'num_sensors', inputs.count())
        add_update_csv(STATS_CSV, 'num_sensors_active',
                       inputs.filter(Input.is_activated.is_(True)).count())

        conditionals = db_retrieve_table_daemon(Conditional)
        add_update_csv(STATS_CSV, 'num_conditionals', conditionals.count())
        add_update_csv(
            STATS_CSV, 'num_conditionals_active',
            conditionals.filter(Conditional.is_activated.is_(True)).count())

        pids = db_retrieve_table_daemon(PID)
        add_update_csv(STATS_CSV, 'num_pids', pids.count())
        add_update_csv(STATS_CSV, 'num_pids_active',
                       pids.filter(PID.is_activated.is_(True)).count())

        triggers = db_retrieve_table_daemon(Trigger)
        add_update_csv(STATS_CSV, 'num_triggers', triggers.count())
        add_update_csv(STATS_CSV, 'num_triggers_active',
                       triggers.filter(Trigger.is_activated.is_(True)).count())

        functions = db_retrieve_table_daemon(CustomController)
        add_update_csv(STATS_CSV, 'num_functions', functions.count())
        add_update_csv(
            STATS_CSV, 'num_functions_active',
            functions.filter(CustomController.is_activated.is_(True)).count())

        actions = db_retrieve_table_daemon(Actions)
        add_update_csv(STATS_CSV, 'num_actions', actions.count())

        methods = db_retrieve_table_daemon(Method)
        add_update_csv(STATS_CSV, 'num_methods', methods.count())
        add_update_csv(
            STATS_CSV, 'num_methods_in_pid',
            pids.filter(PID.setpoint_tracking_type == 'method').count())
        add_update_csv(
            STATS_CSV, 'num_setpoint_meas_in_pid',
            pids.filter(PID.setpoint_tracking_type == 'input-math').count())

        country = geocoder.ip('me').country
        if not country:
            country = 'None'
        add_update_csv(STATS_CSV, 'country', country)
        add_update_csv(
            STATS_CSV, 'ram_use_mb',
            resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / float(1000))

        add_update_csv(STATS_CSV, 'Mycodo_revision', MYCODO_VERSION)
        add_update_csv(
            STATS_CSV, 'master_branch',
            int(os.path.exists(os.path.join(INSTALL_DIRECTORY, '.master'))))

        # Combine stats into list of dictionaries
        new_stats_dict = return_stat_file_dict(STATS_CSV)
        formatted_stat_dict = []
        for each_key, each_value in new_stats_dict.items():
            if each_key != 'stat':  # Do not send header row
                formatted_stat_dict = add_stat_dict(formatted_stat_dict,
                                                    new_stats_dict['id'],
                                                    each_key, each_value)

        # Send stats to secure, remote influxdb server (only write permission)
        client.write_points(formatted_stat_dict)
        logger.debug("Sent anonymous usage statistics")
        return 0
    except requests.ConnectionError:
        logger.debug("Could not send anonymous usage statistics: Connection "
                     "timed out (expected if there's no internet or the "
                     "server is down)")
    except InfluxDBServerError as except_msg:
        logger.error("Statistics: InfluxDB server error: {}".format(
            except_msg['error']))
    except Exception as except_msg:
        logger.exception(
            "Could not send anonymous usage statistics: {err}".format(
                err=except_msg))
    return 1
Exemple #37
0
 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)
Exemple #38
0
    def run(self):
        self.running = True
        self.logger.info("Activated in {:.1f} ms".format(
            (timeit.default_timer() - self.thread_startup_timer) * 1000))
        self.ready.set()

        while self.running:

            # Timer is set to react at a specific hour and minute of the day
            if self.timer_type == 'time':
                if (int(self.start_hour) == datetime.datetime.now().hour and
                        int(self.start_minute) == datetime.datetime.now().minute):
                    # Ensure this is triggered only once at this specific time
                    if self.date_timer_not_executed:
                        self.date_timer_not_executed = False
                        message = "At {st}, turn Output {id} {state}".format(
                            st=self.time_start,
                            id=self.output_id,
                            state=self.state)
                        if self.state == 'on' and self.duration_on:
                            message += " for {sec} seconds".format(
                                sec=self.duration_on)
                        else:
                            self.duration_on = 0
                        self.logger.debug(message)

                        modulate_output = threading.Thread(
                            target=self.control.output_on_off,
                            args=(self.output_id,
                                  self.state,),
                            kwargs={'duration': self.duration_on})
                        modulate_output.start()
                elif not self.date_timer_not_executed:
                    self.date_timer_not_executed = True

            # Timer is set to react at a specific time duration of the day
            elif self.timer_type == 'timespan':
                if time_between_range(self.time_start, self.time_end):
                    current_output_state = self.control.relay_state(self.output_id)
                    if self.state != current_output_state:
                        message = "Output {output} should be {state}, but is " \
                                  "{cstate}. Turning {state}.".format(
                                    output=self.output_id,
                                    state=self.state,
                                    cstate=current_output_state)
                        modulate_output = threading.Thread(
                            target=self.control.output_on_off,
                            args=(self.output_id,
                                  self.state,))
                        modulate_output.start()
                        self.logger.debug(message)

            # Timer is a simple on/off duration timer
            elif self.timer_type == 'duration':
                if time.time() > self.duration_timer:
                    self.duration_timer = (time.time() +
                                           self.duration_on +
                                           self.duration_off)
                    self.logger.debug("Turn Output {output} on for {onsec} "
                                      "seconds, then off for {offsec} "
                                      "seconds".format(
                                        output=self.output_id,
                                        onsec=self.duration_on,
                                        offsec=self.duration_off))
                    output_on = threading.Thread(target=self.control.relay_on,
                                                args=(self.output_id,
                                                      self.duration_on,))
                    output_on.start()

            # Timer is a PWM Method timer
            elif self.timer_type == 'pwm_method':
                try:
                    if time.time() > self.pwm_method_timer:
                        if self.method_start_act == 'Ended':
                            self.stop_controller(ended_normally=False, deactivate_timer=True)
                            self.logger.info(
                                "Method has ended. "
                                "Activate the Timer controller to start it again.")
                        else:
                            this_controller = db_retrieve_table_daemon(
                                Timer, device_id=self.timer_id)
                            setpoint, ended = calculate_method_setpoint(
                                self.method_id,
                                Timer,
                                this_controller,
                                Method,
                                MethodData,
                                self.logger)
                            if ended:
                                self.method_start_act = 'Ended'
                            if setpoint > 100:
                                setpoint = 100
                            elif setpoint < 0:
                                setpoint = 0
                            self.logger.debug(
                                "Turn Output {output} to a PWM duty cycle of "
                                "{dc:.1f} %".format(
                                    output=self.output_id,
                                    dc=setpoint))
                            # Activate pwm with calculated duty cycle
                            self.control.relay_on(
                                self.output_id,
                                duty_cycle=setpoint)
                        self.pwm_method_timer = time.time() + self.method_period
                except Exception:
                    self.logger.exception(1)

            time.sleep(0.1)

        self.control.relay_off(self.output_id)
        self.running = False
        self.logger.info("Deactivated in {:.1f} ms".format(
            (timeit.default_timer() - self.thread_shutdown_timer) * 1000))
Exemple #39
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
    if settings.output_id:
        daemon_control = DaemonControl()
        daemon_control.output_on(settings.output_id)

    # 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':
        # 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))

    # Turn off output, if configured
    if settings.output_id and daemon_control:
        daemon_control.output_off(settings.output_id)

    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))
Exemple #40
0
    def download_data(self):
        # Clear data previously stored in dictionary
        self.gadget.loggedDataReadout = {'Temp': {}, 'Humi': {}}

        # Download stored data starting from self.gadget.newestTimeStampMs
        self.gadget.readLoggedDataInterval(
            startMs=self.gadget.newestTimeStampMs)

        while self.running:
            if (not self.gadget.waitForNotifications(5) or
                    not self.gadget.isLogReadoutInProgress()):
                break  # Done reading data

        list_timestamps_temp = []
        list_timestamps_humi = []

        # Store logged temperature
        measurement = self.device_measurements.filter(
            DeviceMeasurements.channel == 0).first()
        conversion = db_retrieve_table_daemon(
            Conversion, unique_id=measurement.conversion_id)
        for each_ts, each_measure in self.gadget.loggedDataReadout['Temp'].items():
            list_timestamps_temp.append(each_ts)
            datetime_ts = datetime.datetime.utcfromtimestamp(each_ts / 1000)
            if self.is_enabled(0):
                measurement_single = {
                    0: {
                        'measurement': 'temperature',
                        'unit': 'C',
                        'value': each_measure
                    }
                }
                measurement_single = parse_measurement(
                    conversion,
                    measurement,
                    measurement_single,
                    measurement.channel,
                    measurement_single[0])
                write_influxdb_value(
                    self.unique_id,
                    measurement_single[0]['unit'],
                    value=measurement_single[0]['value'],
                    measure=measurement_single[0]['measurement'],
                    channel=0,
                    timestamp=datetime_ts)

        # Store logged humidity
        measurement = self.device_measurements.filter(
            DeviceMeasurements.channel == 1).first()
        conversion = db_retrieve_table_daemon(
            Conversion, unique_id=measurement.conversion_id)
        for each_ts, each_measure in self.gadget.loggedDataReadout['Humi'].items():
            list_timestamps_humi.append(each_ts)
            datetime_ts = datetime.datetime.utcfromtimestamp(each_ts / 1000)
            if self.is_enabled(1):
                measurement_single = {
                    1: {
                        'measurement': 'humidity',
                        'unit': 'percent',
                        'value': each_measure
                    }
                }
                measurement_single = parse_measurement(
                    conversion,
                    measurement,
                    measurement_single,
                    measurement.channel,
                    measurement_single[1])
                write_influxdb_value(
                    self.unique_id,
                    measurement_single[1]['unit'],
                    value=measurement_single[1]['value'],
                    measure=measurement_single[1]['measurement'],
                    channel=1,
                    timestamp=datetime_ts)

        # Find common timestamps from both temperature and humidity lists
        list_timestamps_both = list(
            set(list_timestamps_temp).intersection(list_timestamps_humi))

        for each_ts in list_timestamps_both:
            datetime_ts = datetime.datetime.utcfromtimestamp(each_ts / 1000)
            # Calculate and store dew point
            if (self.is_enabled(3) and
                    self.is_enabled(0) and
                    self.is_enabled(1)):
                measurement = self.device_measurements.filter(
                    DeviceMeasurements.channel == 3).first()
                conversion = db_retrieve_table_daemon(
                    Conversion, unique_id=measurement.conversion_id)
                dewpoint = calculate_dewpoint(
                    self.gadget.loggedDataReadout['Temp'][each_ts],
                    self.gadget.loggedDataReadout['Humi'][each_ts])
                measurement_single = {
                    3: {
                        'measurement': 'dewpoint',
                        'unit': 'C',
                        'value': dewpoint
                    }
                }
                measurement_single = parse_measurement(
                    conversion,
                    measurement,
                    measurement_single,
                    measurement.channel,
                    measurement_single[3])
                write_influxdb_value(
                    self.unique_id,
                    measurement_single[3]['unit'],
                    value=measurement_single[3]['value'],
                    measure=measurement_single[3]['measurement'],
                    channel=3,
                    timestamp=datetime_ts)

            # Calculate and store vapor pressure deficit
            if (self.is_enabled(4) and
                    self.is_enabled(0) and
                    self.is_enabled(1)):
                measurement = self.device_measurements.filter(
                    DeviceMeasurements.channel == 4).first()
                conversion = db_retrieve_table_daemon(
                    Conversion, unique_id=measurement.conversion_id)
                vpd = calculate_vapor_pressure_deficit(
                    self.gadget.loggedDataReadout['Temp'][each_ts],
                    self.gadget.loggedDataReadout['Humi'][each_ts])
                measurement_single = {
                    4: {
                        'measurement': 'vapor_pressure_deficit',
                        'unit': 'Pa',
                        'value': vpd
                    }
                }
                measurement_single = parse_measurement(
                    conversion,
                    measurement,
                    measurement_single,
                    measurement.channel,
                    measurement_single[4])
                write_influxdb_value(
                    self.unique_id,
                    measurement_single[4]['unit'],
                    value=measurement_single[4]['value'],
                    measure=measurement_single[4]['measurement'],
                    channel=4,
                    timestamp=datetime_ts)

        # Download successfully finished, set newest timestamp
        self.gadget.newestTimeStampMs = self.gadget.tmp_newestTimeStampMs
Exemple #41
0
    def controller_activate(self, cont_type, cont_id):
        """
        Activate currently-inactive controller

        :return: 0 for success, 1 for fail, with success or error message
        :rtype: int, str

        :param cont_type: Which controller type is to be activated?
        :type cont_type: str
        :param cont_id: Unique ID for controller
        :type cont_id: str
        """
        try:
            if cont_id in self.controller[cont_type]:
                if self.controller[cont_type][cont_id].is_running():
                    message = "Cannot activate {type} controller with ID {id}: " \
                              "It's already active.".format(type=cont_type,
                                                            id=cont_id)
                    self.logger.warning(message)
                    return 1, message

            controller_manage = {}
            ready = threading.Event()
            if cont_type == 'LCD':
                controller_manage['type'] = LCD
                controller_manage['function'] = LCDController
            elif cont_type == 'Math':
                controller_manage['type'] = Math
                controller_manage['function'] = MathController
            elif cont_type == 'PID':
                controller_manage['type'] = PID
                controller_manage['function'] = PIDController
            elif cont_type == 'Input':
                controller_manage['type'] = Input
                controller_manage['function'] = InputController
            elif cont_type == 'Timer':
                controller_manage['type'] = Timer
                controller_manage['function'] = TimerController
            else:
                return 1, "'{type}' not a valid controller type.".format(
                    type=cont_type)

            # Check if the controller ID actually exists and start it
            controller = db_retrieve_table_daemon(controller_manage['type'],
                                                  device_id=cont_id)

            if controller:
                self.controller[cont_type][cont_id] = controller_manage[
                    'function'](ready, cont_id)
                self.controller[cont_type][cont_id].daemon = True
                self.controller[cont_type][cont_id].start()
                ready.wait()  # wait for thread to return ready
                return 0, "{type} controller with ID {id} " \
                    "activated.".format(type=cont_type, id=cont_id)
            else:
                return 1, "{type} controller with ID {id} not found.".format(
                    type=cont_type, id=cont_id)
        except Exception as except_msg:
            message = "Could not activate {type} controller with ID {id}:" \
                      " {err}".format(type=cont_type, id=cont_id,
                                      err=except_msg)
            self.logger.exception(message)
            return 1, message
Exemple #42
0
    def check_pid(self):
        """ Get measurement and apply to PID controller """
        # Ensure the timer ends in the future
        while time.time() > self.timer:
            self.timer = self.timer + self.period

        # If PID is active, retrieve measurement and update
        # the control variable.
        # A PID on hold will sustain the current output and
        # not update the control variable.
        if self.is_activated and (not self.is_paused or not self.is_held):
            self.get_last_measurement()

            if self.last_measurement_success:
                if self.method_id != '':
                    # Update setpoint using a method
                    this_pid = db_retrieve_table_daemon(PID,
                                                        unique_id=self.pid_id)
                    setpoint, ended = calculate_method_setpoint(
                        self.method_id, PID, this_pid, Method, MethodData,
                        self.logger)
                    if ended:
                        self.method_start_act = 'Ended'
                    if setpoint is not None:
                        self.setpoint = setpoint
                    else:
                        self.setpoint = self.default_setpoint

                # If autotune activated, determine control variable (output) from autotune
                if self.autotune_activated:
                    if not self.autotune.run(self.last_measurement):
                        self.control_variable = self.autotune.output

                        if self.autotune_debug:
                            self.logger.info('')
                            self.logger.info("state: {}".format(
                                self.autotune.state))
                            self.logger.info("output: {}".format(
                                self.autotune.output))
                    else:
                        # Autotune has finished
                        timestamp = time.time() - self.autotune_timestamp
                        self.logger.info('')
                        self.logger.info('time:  {0} min'.format(
                            round(timestamp / 60)))
                        self.logger.info('state: {0}'.format(
                            self.autotune.state))

                        if self.autotune.state == PIDAutotune.STATE_SUCCEEDED:
                            for rule in self.autotune.tuning_rules:
                                params = self.autotune.get_pid_parameters(rule)
                                self.logger.info('')
                                self.logger.info('rule: {0}'.format(rule))
                                self.logger.info('Kp: {0}'.format(params.Kp))
                                self.logger.info('Ki: {0}'.format(params.Ki))
                                self.logger.info('Kd: {0}'.format(params.Kd))

                        self.stop_controller(deactivate_pid=True)
                else:
                    # Calculate new control variable (output) from PID Controller

                    # Original PID method
                    self.control_variable = self.update_pid_output(
                        self.last_measurement)

                    # New PID method (untested)
                    # self.control_variable = self.PID_Controller.calc(
                    #     self.last_measurement, self.setpoint)

                self.write_pid_values()  # Write variables to database

        # Is PID in a state that allows manipulation of outputs
        if self.is_activated and (not self.is_paused or self.is_held):
            self.manipulate_output()
Exemple #43
0
    def __init__(self, ready, math_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger(
            "mycodo.math_{id}".format(id=math_id.split('-')[0]))

        try:
            self.measurements = None
            self.running = False
            self.thread_startup_timer = timeit.default_timer()
            self.thread_shutdown_timer = 0
            self.ready = ready
            self.pause_loop = False
            self.verify_pause_loop = True
            self.control = DaemonControl()

            self.sample_rate = db_retrieve_table_daemon(
                Misc, entry='first').sample_rate_controller_math

            smtp = db_retrieve_table_daemon(SMTP, entry='first')
            self.smtp_max_count = smtp.hourly_max
            self.email_count = 0
            self.allowed_to_send_notice = True

            self.math_id = math_id
            math = db_retrieve_table_daemon(Math, unique_id=self.math_id)

            self.device_measurements = db_retrieve_table_daemon(
                DeviceMeasurements).filter(
                    DeviceMeasurements.device_id == self.math_id)

            # General variables
            self.unique_id = math.unique_id
            self.name = math.name
            self.math_type = math.math_type
            self.is_activated = math.is_activated
            self.period = math.period
            self.start_offset = math.start_offset
            self.max_measure_age = math.max_measure_age

            # Inputs to calculate with
            self.inputs = math.inputs

            # Difference variables
            self.difference_reverse_order = math.difference_reverse_order
            self.difference_absolute = math.difference_absolute

            # Equation variables
            self.equation_input = math.equation_input
            self.equation = math.equation

            # Redundancy variables
            self.order_of_use = math.order_of_use

            # Verification variables
            self.max_difference = math.max_difference

            # Humidity variables
            self.dry_bulb_t_id = math.dry_bulb_t_id
            self.dry_bulb_t_measure_id = math.dry_bulb_t_measure_id
            self.wet_bulb_t_id = math.wet_bulb_t_id
            self.wet_bulb_t_measure_id = math.wet_bulb_t_measure_id
            self.pressure_pa_id = math.pressure_pa_id
            self.pressure_pa_measure_id = math.pressure_pa_measure_id

            # Misc ids
            self.unique_id_1 = math.unique_id_1
            self.unique_measurement_id_1 = math.unique_measurement_id_1
            self.unique_id_2 = math.unique_id_2
            self.unique_measurement_id_2 = math.unique_measurement_id_2

            self.timer = time.time() + self.start_offset
        except Exception as except_msg:
            self.logger.exception("Init Error: {err}".format(err=except_msg))
    def initialize_variables(self):
        lcd_dev = db_retrieve_table_daemon(LCD, unique_id=self.unique_id)
        self.lcd_type = lcd_dev.lcd_type
        self.lcd_name = lcd_dev.name
        self.lcd_period = lcd_dev.period
        self.lcd_x_characters = lcd_dev.x_characters
        self.lcd_y_lines = lcd_dev.y_lines
        self.timer = time.time() + self.lcd_period
        self.backlight_timer = time.time()
        self.log_level_debug = lcd_dev.log_level_debug

        self.set_log_level_debug(self.log_level_debug)

        # Add custom measurement and units to list
        self.list_inputs = add_custom_measurements(
            db_retrieve_table_daemon(Measurement, entry='all'))

        self.list_inputs.update(
            {'input_time': {'unit': None, 'name': 'Time'}})
        self.list_inputs.update(
            {'pid_time': {'unit': None, 'name': 'Time'}})

        self.dict_units = add_custom_units(
            db_retrieve_table_daemon(Unit, entry='all'))

        lcd_data = db_retrieve_table_daemon(
            LCDData).filter(LCDData.lcd_id == lcd_dev.unique_id).all()

        for each_lcd_display in lcd_data:
            self.display_sets.append(each_lcd_display.unique_id)
            self.lcd_string_line[each_lcd_display.unique_id] = {}
            self.lcd_line[each_lcd_display.unique_id] = {}
            self.lcd_text[each_lcd_display.unique_id] = {}
            self.lcd_max_age[each_lcd_display.unique_id] = {}
            self.lcd_decimal_places[each_lcd_display.unique_id] = {}

            for i in range(1, self.lcd_y_lines + 1):
                self.lcd_string_line[each_lcd_display.unique_id][i] = ''
                self.lcd_line[each_lcd_display.unique_id][i] = {}
                if i == 1:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_1_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_1_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_1_decimal_places
                elif i == 2:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_2_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_2_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_2_decimal_places
                elif i == 3:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_3_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_3_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_3_decimal_places
                elif i == 4:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_4_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_4_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_4_decimal_places
                elif i == 5:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_5_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_5_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_5_decimal_places
                elif i == 6:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_6_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_6_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_6_decimal_places
                elif i == 7:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_7_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_7_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_7_decimal_places
                elif i == 8:
                    self.lcd_text[each_lcd_display.unique_id][i] = each_lcd_display.line_8_text
                    self.lcd_max_age[each_lcd_display.unique_id][i] = each_lcd_display.line_8_max_age
                    self.lcd_decimal_places[each_lcd_display.unique_id][i] = each_lcd_display.line_8_decimal_places

            if self.lcd_y_lines in [2, 4, 8]:
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 1,
                    each_lcd_display.line_1_id,
                    each_lcd_display.line_1_measurement)
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 2,
                    each_lcd_display.line_2_id,
                    each_lcd_display.line_2_measurement)

            if self.lcd_y_lines in [4, 8]:
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 3,
                    each_lcd_display.line_3_id,
                    each_lcd_display.line_3_measurement)
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 4,
                    each_lcd_display.line_4_id,
                    each_lcd_display.line_4_measurement)

            if self.lcd_y_lines == 8:
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 5,
                    each_lcd_display.line_5_id,
                    each_lcd_display.line_5_measurement)
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 6,
                    each_lcd_display.line_6_id,
                    each_lcd_display.line_6_measurement)
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 7,
                    each_lcd_display.line_7_id,
                    each_lcd_display.line_7_measurement)
                self.setup_lcd_line(
                    each_lcd_display.unique_id, 8,
                    each_lcd_display.line_8_id,
                    each_lcd_display.line_8_measurement)

        if self.lcd_type in ['16x2_generic',
                             '20x4_generic']:
            from mycodo.devices.lcd_generic import LCD_Generic
            self.lcd_out = LCD_Generic(lcd_dev)
            self.lcd_init()
        elif self.lcd_type in ['16x2_grove_lcd_rgb']:
            from mycodo.devices.lcd_grove_lcd_rgb import LCD_Grove_LCD_RGB
            self.lcd_out = LCD_Grove_LCD_RGB(lcd_dev)
            self.lcd_init()
        elif self.lcd_type in ['128x32_pioled',
                               '128x64_pioled']:
            from mycodo.devices.lcd_pioled import LCD_Pioled
            self.lcd_out = LCD_Pioled(lcd_dev)
            self.lcd_init()
        elif self.lcd_type in ['128x32_pioled_circuit_python',
                               '128x64_pioled_circuit_python']:
            from mycodo.devices.lcd_pioled_circuitpython import LCD_Pioled_Circuitpython
            self.lcd_out = LCD_Pioled_Circuitpython(lcd_dev)
            self.lcd_init()
        else:
            self.logger.error("Unknown LCD type: {}".format(self.lcd_type))

        if self.lcd_initialized:
            line_1 = 'Mycodo {}'.format(MYCODO_VERSION)
            line_2 = 'Start {}'.format(self.lcd_name)
            self.lcd_out.lcd_write_lines(line_1, line_2, '', '')
Exemple #45
0
    def calculate_math(self):
        measurement_dict = {}

        #
        # Average (multiple channels)
        #
        if self.math_type == 'average':
            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == 0).first()

            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)

            success, measure = self.get_measurements_from_str(self.inputs)
            if success:
                average = float(sum(measure) / float(len(measure)))

                measurement_dict = {
                    channel: {
                        'measurement': measurement,
                        'unit': unit,
                        'value': average
                    }
                }
            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Average (single channel)
        #
        elif self.math_type == 'average_single':
            device_id = self.inputs.split(',')[0]
            measurement_id = self.inputs.split(',')[1]

            if measurement_id == 'output':
                output = db_retrieve_table_daemon(Output, unique_id=device_id)
                channel = output.channel
                unit = output.unit
                measurement = output.measurement
            else:
                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)

            try:
                average_measurements = average_past_seconds(
                    device_id,
                    unit,
                    channel,
                    self.max_measure_age,
                    measure=measurement)
                if average_measurements:
                    measurement_dict = {
                        channel: {
                            'measurement': measurement,
                            'unit': unit,
                            'value': average_measurements
                        }
                    }
                else:
                    self.error_not_within_max_age()
            except Exception as msg:
                self.logger.exception(
                    "average_single Error: {err}".format(err=msg))

        #
        # Sum (multiple channels)
        #
        if self.math_type == 'sum':
            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == 0).first()

            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)

            success, measure = self.get_measurements_from_str(self.inputs)
            if success:
                sum_value = float(sum(measure))

                measurement_dict = {
                    channel: {
                        'measurement': measurement,
                        'unit': unit,
                        'value': sum_value
                    }
                }
            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Sum (single channel)
        #
        elif self.math_type == 'sum_single':
            device_id = self.inputs.split(',')[0]
            measurement_id = self.inputs.split(',')[1]

            if measurement_id == 'output':
                output = db_retrieve_table_daemon(Output, unique_id=device_id)
                channel = output.channel
                unit = output.unit
                measurement = output.measurement
            else:
                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)

            try:
                sum_measurements = sum_past_seconds(device_id,
                                                    unit,
                                                    channel,
                                                    self.max_measure_age,
                                                    measure=measurement)
                if sum_measurements:
                    measurement_dict = {
                        channel: {
                            'measurement': measurement,
                            'unit': unit,
                            'value': sum_measurements
                        }
                    }
                else:
                    self.error_not_within_max_age()
            except Exception as msg:
                self.logger.exception(
                    "sum_single Error: {err}".format(err=msg))

        #
        # Difference between two channels
        #
        elif self.math_type == 'difference':
            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == 0).first()
            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)

            success, measure = self.get_measurements_from_str(self.inputs)
            if success:
                if self.difference_reverse_order:
                    difference = measure[1] - measure[0]
                else:
                    difference = measure[0] - measure[1]
                if self.difference_absolute:
                    difference = abs(difference)

                measurement_dict = {
                    channel: {
                        'measurement': measurement,
                        'unit': unit,
                        'value': difference
                    }
                }
            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Equation (math performed on measurement)
        #
        elif self.math_type == 'equation':
            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == 0).first()
            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)

            success, measure = self.get_measurements_from_str(
                self.equation_input)
            if success:
                replaced_str = self.equation.replace('x', str(measure[0]))
                equation_output = eval(replaced_str)

                measurement_dict = {
                    channel: {
                        'measurement': measurement,
                        'unit': unit,
                        'value': float(equation_output)
                    }
                }
            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Redundancy (Use next measurement if one isn't currently available)
        #
        elif self.math_type == 'redundancy':
            list_order = self.order_of_use.split(';')
            measurement_success = False

            for each_id_measurement_id in list_order:
                device_id = each_id_measurement_id.split(',')[0]
                measurement_id = each_id_measurement_id.split(',')[1]

                device_measurement = self.device_measurements.filter(
                    DeviceMeasurements.channel == 0).first()
                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)

                try:
                    success_measure, measure = self.get_measurements_from_id(
                        device_id, measurement_id)

                    if success_measure:
                        measurement_dict = {
                            channel: {
                                'measurement': measurement,
                                'unit': unit,
                                'value': float(measure[1]),
                                'timestamp': measure[0],
                            }
                        }
                        measurement_success = True
                        break

                except Exception as msg:
                    self.logger.exception(
                        "redundancy Error: {err}".format(err=msg))

            if not measurement_success:
                self.error_not_within_max_age()

        #
        # Statistical analysis on all measurements from a period of time
        #
        elif self.math_type == 'statistics':
            success, measure = self.get_measurements_from_str(self.inputs)
            if success:
                # Perform some math
                stat_mean = float(sum(measure) / float(len(measure)))
                stat_median = median(measure)
                stat_minimum = min(measure)
                stat_maximum = max(measure)
                stdev_ = stdev(measure)
                stdev_mean_upper = stat_mean + stdev_
                stdev_mean_lower = stat_mean - stdev_

                list_measurement = [
                    stat_mean, stat_median, stat_minimum, stat_maximum, stdev_,
                    stdev_mean_upper, stdev_mean_lower
                ]

                for each_measurement in self.device_measurements.all():
                    conversion = db_retrieve_table_daemon(
                        Conversion, unique_id=each_measurement.conversion_id)
                    channel, unit, measurement = return_measurement_info(
                        each_measurement, conversion)

                    measurement_dict[channel] = {
                        'measurement': measurement,
                        'unit': unit,
                        'value': list_measurement[channel]
                    }

            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Verification (only use measurement if it's close to another measurement)
        #
        elif self.math_type == 'verification':
            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.channel == 0).first()
            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)

            success, measure = self.get_measurements_from_str(self.inputs)
            if (success and max(measure) - min(measure) < self.max_difference):
                difference = max(measure) - min(measure)

                measurement_dict = {
                    channel: {
                        'measurement': measurement,
                        'unit': unit,
                        'value': difference
                    }
                }
            elif measure:
                self.logger.error(measure)
            else:
                self.error_not_within_max_age()

        #
        # Calculate humidity from wet- and dry-bulb temperatures
        #
        elif self.math_type == 'humidity':
            pressure_pa = 101325
            critical_error = False

            if self.pressure_pa_id and self.pressure_pa_measure_id:
                success_pa, pressure = self.get_measurements_from_id(
                    self.pressure_pa_id, self.pressure_pa_measure_id)
                if success_pa:
                    pressure_pa = int(pressure[1])
                    # Pressure must be in Pa, convert if not

                    if db_retrieve_table_daemon(
                            DeviceMeasurements,
                            unique_id=self.pressure_pa_measure_id):
                        measurement = db_retrieve_table_daemon(
                            DeviceMeasurements,
                            unique_id=self.pressure_pa_measure_id)
                    else:
                        self.logger.error(
                            "Could not find pressure measurement")
                        measurement = None
                        critical_error = True

                    if measurement and measurement.unit != 'Pa':
                        for each_conv in db_retrieve_table_daemon(Conversion,
                                                                  entry='all'):
                            if (each_conv.convert_unit_from == measurement.unit
                                    and each_conv.convert_unit_to == 'Pa'):
                                pressure_pa = convert_units(
                                    each_conv.unique_id, pressure_pa)
                            else:
                                self.logger.error(
                                    "Could not find conversion for unit "
                                    "{unit} to Pa (Pascals)".format(
                                        unit=measurement.unit))
                                critical_error = True

            success_dbt, dry_bulb_t = self.get_measurements_from_id(
                self.dry_bulb_t_id, self.dry_bulb_t_measure_id)
            success_wbt, wet_bulb_t = self.get_measurements_from_id(
                self.wet_bulb_t_id, self.wet_bulb_t_measure_id)

            if success_dbt and success_wbt:
                dbt_kelvin = float(dry_bulb_t[1])
                wbt_kelvin = float(wet_bulb_t[1])

                if db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.dry_bulb_t_measure_id):
                    measurement = db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.dry_bulb_t_measure_id)
                else:
                    self.logger.error("Could not find pressure measurement")
                    measurement = None
                    critical_error = True

                if measurement and measurement.unit != 'K':
                    conversion_found = False
                    for each_conv in db_retrieve_table_daemon(Conversion,
                                                              entry='all'):
                        if (each_conv.convert_unit_from == measurement.unit
                                and each_conv.convert_unit_to == 'K'):
                            dbt_kelvin = convert_units(each_conv.unique_id,
                                                       dbt_kelvin)
                            conversion_found = True
                    if not conversion_found:
                        self.logger.error("Could not find conversion for unit "
                                          "{unit} to K (Kelvin)".format(
                                              unit=measurement.unit))
                        critical_error = True

                if db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.dry_bulb_t_measure_id):
                    measurement = db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.dry_bulb_t_measure_id)
                else:
                    self.logger.error("Could not find pressure measurement")
                    measurement = None
                    critical_error = True

                if measurement and measurement.unit != 'K':
                    conversion_found = False
                    for each_conv in db_retrieve_table_daemon(Conversion,
                                                              entry='all'):
                        if (each_conv.convert_unit_from == measurement.unit
                                and each_conv.convert_unit_to == 'K'):
                            wbt_kelvin = convert_units(each_conv.unique_id,
                                                       wbt_kelvin)
                            conversion_found = True
                    if not conversion_found:
                        self.logger.error("Could not find conversion for unit "
                                          "{unit} to K (Kelvin)".format(
                                              unit=measurement.unit))
                        critical_error = True

                # Convert temperatures to Kelvin (already done above)
                # dbt_kelvin = celsius_to_kelvin(dry_bulb_t_c)
                # wbt_kelvin = celsius_to_kelvin(wet_bulb_t_c)
                psypi = None

                try:
                    if not critical_error:
                        psypi = SI.state("DBT", dbt_kelvin, "WBT", wbt_kelvin,
                                         pressure_pa)
                    else:
                        self.logger.error(
                            "One or more critical errors prevented the "
                            "humidity from being calculated")
                except TypeError as err:
                    self.logger.error("TypeError: {msg}".format(msg=err))

                if psypi:
                    percent_relative_humidity = psypi[2] * 100

                    # Ensure percent humidity stays within 0 - 100 % range
                    if percent_relative_humidity > 100:
                        percent_relative_humidity = 100
                    elif percent_relative_humidity < 0:
                        percent_relative_humidity = 0

                    # Dry bulb temperature: psypi[0])
                    # Wet bulb temperature: psypi[5])

                    specific_enthalpy = float(psypi[1])
                    humidity = float(percent_relative_humidity)
                    specific_volume = float(psypi[3])
                    humidity_ratio = float(psypi[4])

                    list_measurement = [
                        specific_enthalpy, humidity, specific_volume,
                        humidity_ratio
                    ]

                    for each_measurement in self.device_measurements.all():
                        conversion = db_retrieve_table_daemon(
                            Conversion,
                            unique_id=each_measurement.conversion_id)
                        channel, unit, measurement = return_measurement_info(
                            each_measurement, conversion)

                        measurement_dict[channel] = {
                            'measurement': measurement,
                            'unit': unit,
                            'value': list_measurement[channel]
                        }
            else:
                self.error_not_within_max_age()

        #
        # Calculate vapor pressure deficit from temperature and humidity
        #
        elif self.math_type == 'vapor_pressure_deficit':
            vpd_pa = None
            critical_error = False

            success_dbt, temperature = self.get_measurements_from_id(
                self.unique_id_1, self.unique_measurement_id_1)
            success_wbt, humidity = self.get_measurements_from_id(
                self.unique_id_2, self.unique_measurement_id_2)

            if success_dbt and success_wbt:
                vpd_temperature_celsius = float(temperature[1])
                vpd_humidity_percent = float(humidity[1])

                if db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.unique_measurement_id_1):
                    measurement = db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.unique_measurement_id_1)
                else:
                    self.logger.error("Could not find temperature measurement")
                    measurement = None
                    critical_error = True

                if measurement and measurement.unit != 'C':
                    conversion_found = False
                    for each_conv in db_retrieve_table_daemon(Conversion,
                                                              entry='all'):
                        if (each_conv.convert_unit_from == measurement.unit
                                and each_conv.convert_unit_to == 'C'):
                            vpd_temperature_celsius = convert_units(
                                each_conv.unique_id, vpd_temperature_celsius)
                            conversion_found = True
                    if not conversion_found:
                        self.logger.error("Could not find conversion for unit "
                                          "{unit} to C (Celsius)".format(
                                              unit=measurement.unit))
                        critical_error = True

                if db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.unique_measurement_id_2):
                    measurement = db_retrieve_table_daemon(
                        DeviceMeasurements,
                        unique_id=self.unique_measurement_id_2)
                else:
                    self.logger.error("Could not find humidity measurement")
                    measurement = None
                    critical_error = True

                if measurement and measurement.unit != 'percent':
                    conversion_found = False
                    for each_conv in db_retrieve_table_daemon(Conversion,
                                                              entry='all'):
                        if (each_conv.convert_unit_from == measurement.unit
                                and each_conv.convert_unit_to == 'percent'):
                            vpd_humidity_percent = convert_units(
                                each_conv.unique_id, vpd_humidity_percent)
                            conversion_found = True
                    if not conversion_found:
                        self.logger.error("Could not find conversion for unit "
                                          "{unit} to percent (%)".format(
                                              unit=measurement.unit))
                        critical_error = True

                try:
                    if not critical_error:
                        vpd_pa = calculate_vapor_pressure_deficit(
                            vpd_temperature_celsius, vpd_humidity_percent)
                    else:
                        self.logger.error(
                            "One or more critical errors prevented the "
                            "vapor pressure deficit from being calculated")
                except TypeError as err:
                    self.logger.error("TypeError: {msg}".format(msg=err))

                if vpd_pa:
                    measure = self.device_measurements.first()
                    conversion = db_retrieve_table_daemon(
                        Conversion, unique_id=measure.conversion_id)
                    channel, unit, measurement = return_measurement_info(
                        measure, conversion)

                    measurement_dict[channel] = {
                        'measurement': measurement,
                        'unit': unit,
                        'value': vpd_pa
                    }
            else:
                self.error_not_within_max_age()

        else:
            self.logger.error(
                "Unknown math type: {type}".format(type=self.math_type))

        # Finally, add measurements to influxdb
        add_measurements_influxdb(self.unique_id, measurement_dict)
Exemple #46
0
    def setup_output(self):
        import Adafruit_PCA9685

        self.setup_output_variables(OUTPUT_INFORMATION)

        error = []
        if self.pwm_hertz < 40:
            error.append("PWM Hertz must be a value between 40 and 1600")
        if error:
            for each_error in error:
                self.logger.error(each_error)
            return

        try:
            self.pwm_output = Adafruit_PCA9685.PCA9685(
                address=int(str(self.output.i2c_location), 16),
                busnum=self.output.i2c_bus)

            self.pwm_output.set_pwm_freq(self.pwm_hertz)

            self.output_setup = True
            self.logger.debug("Output setup on bus {} at {}".format(
                self.output.i2c_bus, self.output.i2c_location))

            for i in range(16):
                if self.options_channels['state_startup'][i] == 0:
                    self.logger.debug(
                        "Startup state channel {ch}: off".format(ch=i))
                    self.output_switch('off', output_channel=i)
                elif self.options_channels['state_startup'][
                        i] == 'set_duty_cycle':
                    self.logger.debug(
                        "Startup state channel {ch}: on ({dc:.2f} %)".format(
                            ch=i,
                            dc=self.options_channels['startup_value'][i]))
                    self.output_switch(
                        'on',
                        output_channel=i,
                        amount=self.options_channels['startup_value'][i])
                elif self.options_channels['state_startup'][
                        i] == 'last_duty_cycle':
                    self.logger.debug(
                        "Startup state channel {ch}: last".format(ch=i))
                    device_measurement = db_retrieve_table_daemon(
                        DeviceMeasurements).filter(
                            and_(
                                DeviceMeasurements.device_id == self.unique_id,
                                DeviceMeasurements.channel == i)).first()

                    last_measurement = None
                    if device_measurement:
                        channel, unit, measurement = return_measurement_info(
                            device_measurement, None)
                        last_measurement = read_last_influxdb(
                            self.unique_id,
                            unit,
                            channel,
                            measure=measurement,
                            duration_sec=None)

                    if last_measurement:
                        self.logger.debug(
                            "Setting channel {ch} startup duty cycle to last known value of {dc} %"
                            .format(ch=i, dc=last_measurement[1]))
                        self.output_switch('on', amount=last_measurement[1])
                    else:
                        self.logger.error(
                            "Output channel {} instructed at startup to be set to "
                            "the last known duty cycle, but a last known "
                            "duty cycle could not be found in the measurement "
                            "database".format(i))
                else:
                    self.logger.debug(
                        "Startup state channel {ch}: no change".format(ch=i))
        except Exception as except_msg:
            self.logger.exception(
                "Output was unable to be setup: {err}".format(err=except_msg))
Exemple #47
0
    def initialize_values(self):
        """Set PID parameters"""
        pid = db_retrieve_table_daemon(PID, unique_id=self.pid_id)
        self.is_activated = pid.is_activated
        self.is_held = pid.is_held
        self.is_paused = pid.is_paused
        self.log_level_debug = pid.log_level_debug
        self.method_id = pid.method_id
        self.direction = pid.direction
        self.raise_output_id = pid.raise_output_id
        self.raise_min_duration = pid.raise_min_duration
        self.raise_max_duration = pid.raise_max_duration
        self.raise_min_off_duration = pid.raise_min_off_duration
        self.lower_output_id = pid.lower_output_id
        self.lower_min_duration = pid.lower_min_duration
        self.lower_max_duration = pid.lower_max_duration
        self.lower_min_off_duration = pid.lower_min_off_duration
        self.Kp = pid.p
        self.Ki = pid.i
        self.Kd = pid.d
        self.integrator_min = pid.integrator_min
        self.integrator_max = pid.integrator_max
        self.period = pid.period
        self.start_offset = pid.start_offset
        self.max_measure_age = pid.max_measure_age
        self.default_setpoint = pid.setpoint
        self.setpoint = pid.setpoint
        self.band = pid.band
        self.store_lower_as_negative = pid.store_lower_as_negative

        # Autotune
        self.autotune_activated = pid.autotune_activated
        self.autotune_noiseband = pid.autotune_noiseband
        self.autotune_outstep = pid.autotune_outstep

        self.device_id = pid.measurement.split(',')[0]
        self.measurement_id = pid.measurement.split(',')[1]

        if self.log_level_debug:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.INFO)

        input_dev = db_retrieve_table_daemon(Input, unique_id=self.device_id)
        math = db_retrieve_table_daemon(Math, unique_id=self.device_id)
        if input_dev:
            self.input_duration = input_dev.period
        elif math:
            self.input_duration = math.period

        try:
            self.raise_output_type = db_retrieve_table_daemon(
                Output, unique_id=self.raise_output_id).output_type
        except AttributeError:
            self.raise_output_type = None
        try:
            self.lower_output_type = db_retrieve_table_daemon(
                Output, unique_id=self.lower_output_id).output_type
        except AttributeError:
            self.lower_output_type = None

        self.logger.info("PID Settings: {}".format(self.pid_parameters_str()))

        return "success"
Exemple #48
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
    if settings.output_id and ',' in settings.output_id:
        output_id = settings.output_id.split(",")[0]
        output_channel_id = settings.output_id.split(",")[1]

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

    # 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_id and daemon_control and not output_already_on:
        daemon_control.output_off(output_id, output_channel=output_channel_id)

    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))
Exemple #49
0
    def __init__(self, ready, pid_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger("{}_{}".format(__name__,
                                                       pid_id.split('-')[0]))

        self.running = False
        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.pid_id = pid_id
        self.control = DaemonControl()

        self.sample_rate = db_retrieve_table_daemon(
            Misc, entry='first').sample_rate_controller_pid

        self.device_measurements = db_retrieve_table_daemon(DeviceMeasurements)

        self.log_level_debug = None
        self.PID_Controller = None
        self.control_variable = 0.0
        self.derivator = 0.0
        self.integrator = 0.0
        self.error = 0.0
        self.P_value = None
        self.I_value = None
        self.D_value = None
        self.lower_seconds_on = 0.0
        self.raise_seconds_on = 0.0
        self.lower_duty_cycle = 0.0
        self.raise_duty_cycle = 0.0
        self.last_time = None
        self.last_measurement = None
        self.last_measurement_success = False

        self.is_activated = None
        self.is_held = None
        self.is_paused = None
        self.measurement = None
        self.method_id = None
        self.direction = None
        self.raise_output_id = None
        self.raise_min_duration = None
        self.raise_max_duration = None
        self.raise_min_off_duration = None
        self.lower_output_id = None
        self.lower_min_duration = None
        self.lower_max_duration = None
        self.lower_min_off_duration = None
        self.Kp = None
        self.Ki = None
        self.Kd = None
        self.integrator_min = None
        self.integrator_max = None
        self.period = None
        self.start_offset = None
        self.max_measure_age = None
        self.default_setpoint = None
        self.setpoint = None
        self.store_lower_as_negative = None

        # Hysteresis options
        self.band = None
        self.allow_raising = False
        self.allow_lowering = False

        # PID Autotune
        self.autotune = None
        self.autotune_activated = False
        self.autotune_debug = False
        self.autotune_noiseband = None
        self.autotune_outstep = None
        self.autotune_timestamp = None

        self.device_id = None
        self.measurement_id = None

        self.input_duration = None

        self.raise_output_type = None
        self.lower_output_type = None

        self.first_start = True

        self.initialize_values()

        self.timer = time.time() + self.start_offset

        # Check if a method is set for this PID
        self.method_type = None
        self.method_start_act = None
        self.method_start_time = None
        self.method_end_time = None
        if self.method_id != '':
            self.setup_method(self.method_id)
Exemple #50
0
    def run_function(self):
        temp_wet_k = None
        temp_dry_k = None
        pressure_pa = 101325

        if (self.select_measurement_pressure_pa_device_id
                and self.select_measurement_pressure_pa_measurement_id
                and self.max_measure_age_pressure_pa):

            device_measurement = get_measurement(
                self.select_measurement_pressure_pa_measurement_id)
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)

            last_measurement_pa = self.get_last_measurement(
                self.select_measurement_pressure_pa_device_id,
                self.select_measurement_pressure_pa_measurement_id,
                max_age=self.max_measure_age_pressure_pa)

            if last_measurement_pa:
                pressure_pa = convert_from_x_to_y_unit(unit, 'Pa',
                                                       last_measurement_pa[1])

        last_measurement_wet = self.get_last_measurement(
            self.select_measurement_temp_wet_c_device_id,
            self.select_measurement_temp_wet_c_measurement_id,
            max_age=self.max_measure_age_temp_wet_c)

        if last_measurement_wet:
            device_measurement = get_measurement(
                self.select_measurement_temp_wet_c_measurement_id)
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)
            temp_wet_k = convert_from_x_to_y_unit(unit, 'K',
                                                  last_measurement_wet[1])

        last_measurement_dry = self.get_last_measurement(
            self.select_measurement_temp_dry_c_device_id,
            self.select_measurement_temp_dry_c_measurement_id,
            max_age=self.max_measure_age_temp_dry_c)

        if last_measurement_dry:
            device_measurement = get_measurement(
                self.select_measurement_temp_dry_c_measurement_id)
            conversion = db_retrieve_table_daemon(
                Conversion, unique_id=device_measurement.conversion_id)
            channel, unit, measurement = return_measurement_info(
                device_measurement, conversion)
            temp_dry_k = convert_from_x_to_y_unit(unit, 'K',
                                                  last_measurement_dry[1])

        if temp_wet_k and temp_dry_k:
            measurements = copy.deepcopy(measurements_dict)
            psypi = None

            try:
                psypi = SI.state("DBT", temp_dry_k, "WBT", temp_wet_k,
                                 pressure_pa)
            except TypeError as err:
                self.logger.error("TypeError: {msg}".format(msg=err))

            if not psypi:
                self.logger.error(
                    "Could not calculate humidity from wet/dry bulbs")
                return

            percent_relative_humidity = psypi[2] * 100

            # Ensure percent humidity stays within 0 - 100 % range
            if percent_relative_humidity > 100:
                percent_relative_humidity = 100
            elif percent_relative_humidity < 0:
                percent_relative_humidity = 0

            # Dry bulb temperature: psypi[0])
            # Wet bulb temperature: psypi[5])

            specific_enthalpy = float(psypi[1])
            humidity = float(percent_relative_humidity)
            specific_volume = float(psypi[3])
            humidity_ratio = float(psypi[4])

            self.logger.debug("Dry Temp: {dtk}, "
                              "Wet Temp: {wtk}, "
                              "Pressure: {pres},"
                              "Humidity: {rh}".format(dtk=temp_dry_k,
                                                      wtk=temp_wet_k,
                                                      pres=pressure_pa,
                                                      rh=humidity))

            list_measurement = [
                humidity, humidity_ratio, specific_enthalpy, specific_volume
            ]

            for each_measurement in self.device_measurements.all():
                if each_measurement.is_enabled:
                    conversion = db_retrieve_table_daemon(
                        Conversion, unique_id=each_measurement.conversion_id)
                    channel, unit, measurement = return_measurement_info(
                        each_measurement, conversion)

                    measurements[channel] = {
                        'measurement': measurement,
                        'unit': unit,
                        'value': list_measurement[channel]
                    }

            # Add measurement(s) to influxdb
            if measurements:
                self.logger.debug(
                    "Adding measurements to InfluxDB with ID {}: {}".format(
                        self.unique_id, measurements))
                add_measurements_influxdb(self.unique_id, measurements)
            else:
                self.logger.debug(
                    "No measurements to add to InfluxDB with ID {}".format(
                        self.unique_id))
    def get_new_data(self, past_seconds):
        # Basic implementation. Future development may use more complex library to access API
        endpoint = "https://{app}.data.thethingsnetwork.org/api/v2/query/{dev}?last={time}".format(
            app=self.application_id,
            dev=self.device_id,
            time="{}s".format(int(past_seconds)))
        headers = {"Authorization": "key {k}".format(k=self.app_api_key)}
        timestamp_format = '%Y-%m-%dT%H:%M:%S.%f'

        response = requests.get(endpoint, headers=headers)
        try:
            response.json()
        except ValueError:  # No data returned
            self.logger.debug(
                "Response Error. Response: {}. Likely there is no data to be retrieved on TTN"
                .format(response.content))
            return

        for each_resp in response.json():
            if not self.running:
                break

            try:
                datetime_utc = datetime.datetime.strptime(
                    each_resp['time'][:-7], timestamp_format)
            except Exception:
                # Sometimes the original timestamp is in milliseconds
                # instead of nanoseconds. Therefore, remove 3 less digits
                # past the decimal and try again to parse.
                try:
                    datetime_utc = datetime.datetime.strptime(
                        each_resp['time'][:-4], timestamp_format)
                except Exception as e:
                    self.logger.error(
                        "Could not parse timestamp '{}': {}".format(
                            each_resp['time'], e))
                    continue  # Malformed timestamp encountered. Discard measurement.

            if (not self.latest_datetime
                    or self.latest_datetime < datetime_utc):
                self.latest_datetime = datetime_utc

            measurements = {}
            for channel in self.channels_measurement:
                if (self.is_enabled(channel)
                        and self.options_channels['variable_name'][channel]
                        in each_resp
                        and each_resp[self.options_channels['variable_name']
                                      [channel]] is not None):

                    # Original value/unit
                    measurements[channel] = {}
                    measurements[channel][
                        'measurement'] = self.channels_measurement[
                            channel].measurement
                    measurements[channel]['unit'] = self.channels_measurement[
                        channel].unit
                    measurements[channel]['value'] = each_resp[
                        self.options_channels['variable_name'][channel]]
                    measurements[channel]['timestamp_utc'] = datetime_utc

                    # Convert value/unit is conversion_id present and valid
                    if self.channels_conversion[channel]:
                        conversion = db_retrieve_table_daemon(
                            Conversion,
                            unique_id=self.channels_measurement[channel].
                            conversion_id)
                        if conversion:
                            meas = parse_measurement(
                                self.channels_conversion[channel],
                                self.channels_measurement[channel],
                                measurements,
                                channel,
                                measurements[channel],
                                timestamp=datetime_utc)

                            measurements[channel]['measurement'] = meas[
                                channel]['measurement']
                            measurements[channel]['unit'] = meas[channel][
                                'unit']
                            measurements[channel]['value'] = meas[channel][
                                'value']

            if measurements:
                self.logger.debug(
                    "Adding measurements to influxdb: {}".format(measurements))
                add_measurements_influxdb(
                    self.unique_id,
                    measurements,
                    use_same_timestamp=INPUT_INFORMATION[
                        'measurements_use_same_timestamp'])
            else:
                self.logger.debug("No measurements to add to influxdb.")

        # set datetime to latest timestamp
        if self.running:
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_input = new_session.query(Input).filter(
                    Input.unique_id == self.unique_id).first()
                if not mod_input.datetime or mod_input.datetime < self.latest_datetime:
                    mod_input.datetime = self.latest_datetime
                    new_session.commit()
Exemple #52
0
def generate_output_usage_report():
    """
    Generate output usage report in a csv file

    """
    logger.debug("Generating output usage report...")
    try:
        assure_path_exists(USAGE_REPORTS_PATH)

        misc = db_retrieve_table_daemon(Misc, entry='first')
        output = db_retrieve_table_daemon(Output)
        output_usage = return_output_usage(misc, output.all())

        timestamp = time.strftime("%Y-%m-%d_%H-%M")
        file_name = 'output_usage_report_{ts}.csv'.format(ts=timestamp)
        report_path_file = os.path.join(USAGE_REPORTS_PATH, file_name)

        with open(report_path_file, 'wb') as f:
            w = csv.writer(f)
            # Header row
            w.writerow([
                'Relay ID', 'Relay Unique ID', 'Relay Name', 'Type',
                'Past Day', 'Past Week', 'Past Month',
                'Past Month (from {})'.format(misc.output_usage_dayofmonth),
                'Past Year'
            ])
            for key, value in output_usage.items():
                if key in ['total_duration', 'total_cost', 'total_kwh']:
                    # Totals rows
                    w.writerow([
                        '', '', '', key, value['1d'], value['1w'], value['1m'],
                        value['1m_date'], value['1y']
                    ])
                else:
                    # Each output rows
                    each_output = output.filter(
                        Output.unique_id == key).first()
                    w.writerow([
                        each_output.unique_id, each_output.unique_id,
                        str(each_output.name).encode("utf-8"), 'hours_on',
                        value['1d']['hours_on'], value['1w']['hours_on'],
                        value['1m']['hours_on'], value['1m_date']['hours_on'],
                        value['1y']['hours_on']
                    ])
                    w.writerow([
                        each_output.unique_id, each_output.unique_id,
                        str(each_output.name).encode("utf-8"), 'kwh',
                        value['1d']['kwh'], value['1w']['kwh'],
                        value['1m']['kwh'], value['1m_date']['kwh'],
                        value['1y']['kwh']
                    ])
                    w.writerow([
                        each_output.unique_id, each_output.unique_id,
                        str(each_output.name).encode("utf-8"), 'cost',
                        value['1d']['cost'], value['1w']['cost'],
                        value['1m']['cost'], value['1m_date']['cost'],
                        value['1y']['cost']
                    ])

        set_user_grp(report_path_file, 'mycodo', 'mycodo')
    except Exception:
        logger.exception("Energy Usage Report Generation ERROR")
Exemple #53
0
    def get_measurement(self):
        """ Gets the sensor's pH measurement via UART/I2C """
        ph = None

        return_dict = measurements_dict.copy()

        # Calibrate the pH measurement based on a temperature measurement
        if (self.calibrate_sensor_measure and
                ',' in self.calibrate_sensor_measure):
            self.logger.debug("pH sensor set to calibrate temperature")

            device_id = self.calibrate_sensor_measure.split(',')[0]
            measurement_id = self.calibrate_sensor_measure.split(',')[1]

            device_measurement = self.device_measurements.filter(
                DeviceMeasurements.unique_id == measurement_id).first()
            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)

            last_measurement = read_last_influxdb(
                device_id,
                measurement.unit,
                measurement.measurement,
                measurement.channel,
                self.max_age)

            if last_measurement:
                self.logger.debug(
                    "Latest temperature used to calibrate: {temp}".format(
                        temp=last_measurement[1]))

                atlas_command = AtlasScientificCommand(self.input_dev)
                ret_value, ret_msg = atlas_command.calibrate(
                    'temperature', temperature=last_measurement[1])
                time.sleep(0.5)

                self.logger.debug(
                    "Calibration returned: {val}, {msg}".format(
                        val=ret_value, msg=ret_msg))
            else:
                self.logger.error(
                    "Calibration measurement not found within the past "
                    "{} seconds".format(self.max_age))

        # Read sensor via FTDI
        if self.interface == 'FTDI':
            if self.atlas_sensor_ftdi.setup:
                lines = self.atlas_sensor_ftdi.query('R')
                if lines:
                    self.logger.debug(
                        "All Lines: {lines}".format(lines=lines))

                    # 'check probe' indicates an error reading the sensor
                    if 'check probe' in lines:
                        self.logger.error(
                            '"check probe" returned from sensor')
                    # if a string resembling a float value is returned, this
                    # is out measurement value
                    elif str_is_float(lines[0]):
                        ph = float(lines[0])
                        self.logger.debug(
                            'Value[0] is float: {val}'.format(val=ph))
                    else:
                        # During calibration, the sensor is put into
                        # continuous mode, which causes a return of several
                        # values in one string. If the return value does
                        # not represent a float value, it is likely to be a
                        # string of several values. This parses and returns
                        # the first value.
                        if str_is_float(lines[0].split(b'\r')[0]):
                            ph = lines[0].split(b'\r')[0]
                        # Lastly, this is called if the return value cannot
                        # be determined. Watchthe output in the GUI to see
                        # what it is.
                        else:
                            ph = lines[0]
                            self.logger.error(
                                'Value[0] is not float or "check probe": '
                                '{val}'.format(val=ph))
            else:
                self.logger.error('FTDI device is not set up.'
                                  'Check the log for errors.')

        # Read sensor via UART
        elif self.interface == 'UART':
            if self.atlas_sensor_uart.setup:
                lines = self.atlas_sensor_uart.query('R')
                if lines:
                    self.logger.debug(
                        "All Lines: {lines}".format(lines=lines))

                    # 'check probe' indicates an error reading the sensor
                    if 'check probe' in lines:
                        self.logger.error(
                            '"check probe" returned from sensor')
                    # if a string resembling a float value is returned, this
                    # is out measurement value
                    elif str_is_float(lines[0]):
                        ph = float(lines[0])
                        self.logger.debug(
                            'Value[0] is float: {val}'.format(val=ph))
                    else:
                        # During calibration, the sensor is put into
                        # continuous mode, which causes a return of several
                        # values in one string. If the return value does
                        # not represent a float value, it is likely to be a
                        # string of several values. This parses and returns
                        # the first value.
                        if str_is_float(lines[0].split(b'\r')[0]):
                            ph = lines[0].split(b'\r')[0]
                        # Lastly, this is called if the return value cannot
                        # be determined. Watchthe output in the GUI to see
                        # what it is.
                        else:
                            ph = lines[0]
                            self.logger.error(
                                'Value[0] is not float or "check probe": '
                                '{val}'.format(val=ph))
            else:
                self.logger.error('UART device is not set up.'
                                  'Check the log for errors.')

        # Read sensor via I2C
        elif self.interface == 'I2C':
            if self.atlas_sensor_i2c.setup:
                ph_status, ph_str = self.atlas_sensor_i2c.query('R')
                if ph_status == 'error':
                    self.logger.error(
                        "Sensor read unsuccessful: {err}".format(
                            err=ph_str))
                elif ph_status == 'success':
                    ph = float(ph_str)
            else:
                self.logger.error(
                    'I2C device is not set up. Check the log for errors.')

        return_dict[0]['value'] = ph

        return return_dict
Exemple #54
0
    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")
Exemple #55
0
    def __init__(self, ready, timer_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger(
            "mycodo.timer_{id}".format(id=timer_id))

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.timer_id = timer_id
        self.control = DaemonControl()

        timer = db_retrieve_table_daemon(Timer, device_id=self.timer_id)
        self.timer_type = timer.timer_type
        self.output_unique_id = timer.relay_id
        self.method_id = timer.method_id
        self.method_period = timer.method_period
        self.state = timer.state
        self.time_start = timer.time_start
        self.time_end = timer.time_end
        self.duration_on = timer.duration_on
        self.duration_off = timer.duration_off

        self.output_id = db_retrieve_table_daemon(
            Output, unique_id=self.output_unique_id).id

        # Time of day split into hour and minute
        if self.time_start:
            time_split = self.time_start.split(":")
            self.start_hour = time_split[0]
            self.start_minute = time_split[1]
        else:
            self.start_hour = None
            self.start_minute = None

        if self.time_end:
            time_split = self.time_end.split(":")
            self.end_hour = time_split[0]
            self.end_minute = time_split[1]
        else:
            self.end_hour = None
            self.end_minute = None

        self.duration_timer = time.time()
        self.pwm_method_timer = time.time()
        self.date_timer_not_executed = True
        self.running = False

        if self.method_id:
            method = db_retrieve_table_daemon(Method, device_id=self.method_id)
            method_data = db_retrieve_table_daemon(MethodData)
            method_data = method_data.filter(MethodData.method_id == self.method_id)
            method_data_repeat = method_data.filter(MethodData.duration_sec == 0).first()
            self.method_type = method.method_type
            self.method_start_act = timer.method_start_time
            self.method_start_time = None
            self.method_end_time = None

            if self.method_type == 'Duration':
                if self.method_start_act == 'Ended':
                    self.stop_controller(ended_normally=False,
                                         deactivate_timer=True)
                    self.logger.warning(
                        "Method has ended. "
                        "Activate the Timer controller to start it again.")
                elif self.method_start_act == 'Ready' or self.method_start_act is None:
                    # Method has been instructed to begin
                    now = datetime.datetime.now()
                    self.method_start_time = now
                    if method_data_repeat and method_data_repeat.duration_end:
                        self.method_end_time = now + datetime.timedelta(
                            seconds=float(method_data_repeat.duration_end))

                    with session_scope(MYCODO_DB_PATH) as db_session:
                        mod_timer = db_session.query(Timer)
                        mod_timer = mod_timer.filter(Timer.id == self.timer_id).first()
                        mod_timer.method_start_time = self.method_start_time
                        mod_timer.method_end_time = self.method_end_time
                        db_session.commit()
            else:
                # Method neither instructed to begin or not to
                # Likely there was a daemon restart ot power failure
                # Resume method with saved start_time
                self.method_start_time = datetime.datetime.strptime(
                    str(timer.method_start_time), '%Y-%m-%d %H:%M:%S.%f')
                if method_data_repeat and method_data_repeat.duration_end:
                    self.method_end_time = datetime.datetime.strptime(
                        str(timer.method_end_time), '%Y-%m-%d %H:%M:%S.%f')
                    if self.method_end_time > datetime.datetime.now():
                        self.logger.warning(
                            "Resuming method {id}: started {start}, "
                            "ends {end}".format(
                                id=self.method_id,
                                start=self.method_start_time,
                                end=self.method_end_time))
                    else:
                        self.method_start_act = 'Ended'
                else:
                    self.method_start_act = 'Ended'
    def get_new_data(self, past_seconds):
        # Basic implementation. Future development may use more complex library to access API
        endpoint = "https://{app}.data.thethingsnetwork.org/api/v2/query/{dev}?last={time}".format(
            app=self.application_id, dev=self.device_id, time="{}s".format(int(past_seconds)))
        headers = {"Authorization": "key {k}".format(k=self.app_api_key)}
        timestamp_format = '%Y-%m-%dT%H:%M:%S.%f'

        response = requests.get(endpoint, headers=headers)
        try:
            responses = response.json()
        except ValueError:  # No data returned
            self.logger.debug("Response Error. Response: {}".format(
                response.content))
            return

        for each_resp in response.json():
            if not self.running:
                break

            ts_formatted_correctly = False
            try:
                datetime_utc = datetime.datetime.strptime(
                    each_resp['time'][:-7], timestamp_format)
                ts_formatted_correctly = True
            except:
                # Sometimes the original timestamp is in milliseconds
                # instead of nanoseconds. Therefore, remove 3 less digits
                # past the decimal and try again to parse.
                try:
                    datetime_utc = datetime.datetime.strptime(
                        each_resp['time'][:-4], timestamp_format)
                    ts_formatted_correctly = True
                except:
                    self.logger.error("Could not parse timestamp: {}".format(
                        each_resp['time']))

            if not ts_formatted_correctly:
                # Malformed timestamp encountered. Discard measurement.
                continue

            if (not self.latest_datetime or
                    self.latest_datetime < datetime_utc):
                self.latest_datetime = datetime_utc

            measurements = {}
            for each_meas in self.device_measurements.all():
                if (self.is_enabled(each_meas.channel) and
                        each_meas.name in each_resp and
                        each_resp[each_meas.name] is not None):

                    # Original value/unit
                    measurements[each_meas.channel] = {}
                    measurements[each_meas.channel]['measurement'] = each_meas.measurement
                    measurements[each_meas.channel]['unit'] = each_meas.unit
                    measurements[each_meas.channel]['value'] = each_resp[each_meas.name]
                    measurements[each_meas.channel]['timestamp'] = datetime_utc

                    # Convert value/unit is conversion_id present and valid
                    if each_meas.conversion_id:
                        conversion = db_retrieve_table_daemon(
                            Conversion, unique_id=each_meas.conversion_id)
                        if conversion:
                            meas = parse_measurement(
                                conversion,
                                each_meas,
                                measurements,
                                each_meas.channel,
                                measurements[each_meas.channel])

                            measurements[each_meas.channel]['measurement'] = meas[each_meas.channel]['measurement']
                            measurements[each_meas.channel]['unit'] = meas[each_meas.channel]['unit']
                            measurements[each_meas.channel]['value'] = meas[each_meas.channel]['value']
                            measurements[each_meas.channel]['timestamp'] = datetime_utc

            add_measurements_influxdb(self.unique_id, measurements)

        # set datetime to latest timestamp
        if self.running:
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_input = new_session.query(Input).filter(
                    Input.unique_id == self.unique_id).first()
                if not mod_input.datetime or mod_input.datetime < self.latest_datetime:
                    mod_input.datetime = self.latest_datetime
                    new_session.commit()
    def initialize_variables(self):
        input_dev = db_retrieve_table_daemon(Input, unique_id=self.unique_id)

        self.log_level_debug = input_dev.log_level_debug
        self.set_log_level_debug(self.log_level_debug)

        self.dict_inputs = parse_input_information()

        self.sample_rate = db_retrieve_table_daemon(
            Misc, entry='first').sample_rate_controller_input

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

        self.conversions = db_retrieve_table_daemon(Conversion)

        self.input_dev = input_dev
        self.input_name = input_dev.name
        self.unique_id = input_dev.unique_id
        self.gpio_location = input_dev.gpio_location
        self.device = input_dev.device
        self.interface = input_dev.interface
        self.period = input_dev.period
        self.start_offset = input_dev.start_offset

        # Pre-Output (activates output prior to and/or during input measurement)
        self.pre_output_setup = False

        if self.input_dev.pre_output_id and "," in self.input_dev.pre_output_id:
            try:
                self.pre_output_id = input_dev.pre_output_id.split(",")[0]
                self.pre_output_channel_id = input_dev.pre_output_id.split(
                    ",")[1]

                self.pre_output_duration = input_dev.pre_output_duration
                self.pre_output_during_measure = input_dev.pre_output_during_measure
                self.pre_output_activated = False
                self.pre_output_timer = time.time()
                self.pre_output_lock_file = '/var/lock/input_pre_output_{id}_{ch}'.format(
                    id=self.pre_output_id, ch=self.pre_output_channel_id)

                # Check if Pre Output and channel IDs exists
                output = db_retrieve_table_daemon(Output,
                                                  unique_id=self.pre_output_id)
                output_channel = db_retrieve_table_daemon(
                    OutputChannel, unique_id=self.pre_output_channel_id)
                if output and output_channel and self.pre_output_duration:
                    self.pre_output_channel = output_channel.channel
                    self.logger.debug("Pre output successfully set up")
                    self.pre_output_setup = True
            except:
                self.logger.exception("Could not set up pre-output")

        self.last_measurement = 0
        self.next_measurement = time.time() + self.start_offset
        self.get_new_measurement = False
        self.trigger_cond = False
        self.measurement_acquired = False

        smtp = db_retrieve_table_daemon(SMTP, entry='first')
        self.smtp_max_count = smtp.hourly_max
        self.email_count = 0
        self.allowed_to_send_notice = True

        # Convert string I2C address to base-16 int
        if self.interface == 'I2C' and self.input_dev.i2c_location:
            self.i2c_address = int(str(self.input_dev.i2c_location), 16)

        self.device_recognized = True

        if self.device in self.dict_inputs:
            input_loaded = load_module_from_file(
                self.dict_inputs[self.device]['file_path'], 'inputs')

            self.measure_input = input_loaded.InputModule(self.input_dev)

        else:
            self.device_recognized = False
            self.logger.debug(
                "Device '{device}' not recognized".format(device=self.device))
            raise Exception("'{device}' is not a valid device type.".format(
                device=self.device))

        self.input_timer = time.time()
        self.lastUpdate = None

        # Set up listener (e.g. MQTT, EDGE)
        if ('listener' in self.dict_inputs[self.device]
                and self.dict_inputs[self.device]['listener']):
            self.logger.debug(
                "Detected as listener. Starting listener thread.")
            input_listener = threading.Thread(
                target=self.measure_input.listener())
            input_listener.daemon = True
            input_listener.start()
Exemple #58
0
    def setup_output(self):
        import pigpio

        self.pigpio = pigpio

        error = []
        if self.options_channels['pin'][0] is None:
            error.append("Pin must be set")
        if self.options_channels['pwm_hertz'][0] <= 0:
            error.append("PWM Hertz must be a positive value")
        if error:
            for each_error in error:
                self.logger.error(each_error)
            return

        try:
            self.pwm_output = self.pigpio.pi()
            if not self.pwm_output.connected:
                self.logger.error("Could not connect to pigpiod")
                self.pwm_output = None
                return
            if self.options_channels['pwm_library'][0] == 'pigpio_hardware':
                self.pwm_output.hardware_PWM(
                    self.options_channels['pin'][0],
                    self.options_channels['pwm_hertz'][0], 0)
            elif self.options_channels['pwm_library'][0] == 'pigpio_any':
                self.pwm_output.set_PWM_frequency(
                    self.options_channels['pin'][0],
                    self.options_channels['pwm_hertz'][0])
                self.pwm_output.set_PWM_dutycycle(
                    self.options_channels['pin'][0], 0)

            self.output_setup = True
            self.logger.info("Output setup on pin {}".format(
                self.options_channels['pin'][0]))

            if self.options_channels['state_startup'][0] == 0:
                self.output_switch('off')
            elif self.options_channels['state_startup'][0] == 'set_duty_cycle':
                self.output_switch(
                    'on', amount=self.options_channels['startup_value'][0])
            elif self.options_channels['state_startup'][
                    0] == 'last_duty_cycle':
                device_measurement = db_retrieve_table_daemon(
                    DeviceMeasurements).filter(
                        and_(DeviceMeasurements.device_id == self.unique_id,
                             DeviceMeasurements.channel == 0)).first()

                last_measurement = None
                if device_measurement:
                    channel, unit, measurement = return_measurement_info(
                        device_measurement, None)
                    last_measurement = read_last_influxdb(self.unique_id,
                                                          unit,
                                                          channel,
                                                          measure=measurement,
                                                          duration_sec=None)

                if last_measurement:
                    self.logger.info(
                        "Setting startup duty cycle to last known value of {dc} %"
                        .format(dc=last_measurement[1]))
                    self.output_switch('on', amount=last_measurement[1])
                else:
                    self.logger.error(
                        "Output instructed at startup to be set to "
                        "the last known duty cycle, but a last known "
                        "duty cycle could not be found in the measurement "
                        "database")
        except Exception as except_msg:
            self.logger.exception(
                "Output was unable to be setup on pin {pin}: {err}".format(
                    pin=self.options_channels['pin'][0], err=except_msg))
Exemple #59
0
    def output_on_off(self,
                      output_id,
                      state,
                      duration=0.0,
                      min_off=0.0,
                      duty_cycle=0.0,
                      trigger_conditionals=True):
        """
        Turn a output on or off
        The GPIO may be either HIGH or LOW to activate a output. This trigger
        state will be referenced to determine if the GPIO needs to be high or
        low to turn the output on or off.

        Conditionals will be checked for each action requested of a output, and
        if true, those conditional actions will be executed. For example:
            'If output 1 turns on, turn output 3 off'

        :param output_id: ID for output
        :type output_id: int
        :param state: What state is desired? 'on' or 'off'
        :type state: str
        :param duration: If state is 'on', a duration can be set to turn the output off after
        :type duration: float
        :param min_off: Don't turn on if not off for at least this duration (0 = disabled)
        :type min_off: float
        :param duty_cycle: Duty cycle of PWM output
        :type duty_cycle: float
        :param trigger_conditionals: Whether to trigger conditionals to act or not
        :type trigger_conditionals: bool
        """
        # Check if output exists
        output_id = int(output_id)
        if output_id not in self.output_id:
            self.logger.warning("Cannot turn {state} Output with ID {id}. "
                                "It doesn't exist".format(state=state,
                                                          id=output_id))
            return 1

        # Signaled to turn output on
        if state == 'on':

            # Check if pin is valid
            if (self.output_type[output_id]
                    in ['pwm', 'wired', 'wireless_433MHz_pi_switch']
                    and self.output_pin[output_id] is None):
                self.logger.warning(
                    "Invalid pin for output {id} ({name}): {pin}.".format(
                        id=self.output_id[output_id],
                        name=self.output_name[output_id],
                        pin=self.output_pin[output_id]))
                return 1

            # Check if max amperage will be exceeded
            if self.output_type[output_id] in [
                    'command', 'wired', 'wireless_433MHz_pi_switch'
            ]:
                current_amps = self.current_amp_load()
                max_amps = db_retrieve_table_daemon(Misc,
                                                    entry='first').max_amps
                if current_amps + self.output_amps[output_id] > max_amps:
                    self.logger.warning(
                        "Cannot turn output {} ({}) On. If this output turns on, "
                        "there will be {} amps being drawn, which exceeds the "
                        "maximum set draw of {} amps.".format(
                            self.output_id[output_id],
                            self.output_name[output_id], current_amps,
                            max_amps))
                    return 1

                # If the output is used in a PID, a minimum off duration is set,
                # and if the off duration has surpassed that amount of time (i.e.
                # has it been off for longer then the minimum off duration?).
                current_time = datetime.datetime.now()
                if (min_off and not self.is_on(output_id)
                        and current_time > self.output_on_until[output_id]):
                    off_seconds = (
                        current_time -
                        self.output_on_until[output_id]).total_seconds()
                    if off_seconds < min_off:
                        self.logger.debug(
                            "Output {id} ({name}) instructed to turn on by PID, "
                            "however the minimum off period of {min_off_sec} "
                            "seconds has not been reached yet (it has only been "
                            "off for {off_sec} seconds).".format(
                                id=self.output_id[output_id],
                                name=self.output_name[output_id],
                                min_off_sec=min_off,
                                off_sec=off_seconds))
                        return 1

            # Turn output on for a duration
            if (self.output_type[output_id]
                    in ['command', 'wired', 'wireless_433MHz_pi_switch']
                    and duration != 0):
                time_now = datetime.datetime.now()

                # Output is already on for a duration
                if self.is_on(
                        output_id) and self.output_on_duration[output_id]:

                    if self.output_on_until[output_id] > time_now:
                        remaining_time = (self.output_on_until[output_id] -
                                          time_now).total_seconds()
                    else:
                        remaining_time = 0

                    time_on = abs(
                        self.output_last_duration[output_id]) - remaining_time
                    self.logger.debug(
                        "Output {rid} ({rname}) is already on for a duration "
                        "of {ron:.2f} seconds (with {rremain:.2f} seconds "
                        "remaining). Recording the amount of time the output "
                        "has been on ({rbeenon:.2f} sec) and updating the on "
                        "duration to {rnewon:.2f} seconds.".format(
                            rid=self.output_id[output_id],
                            rname=self.output_name[output_id],
                            ron=abs(self.output_last_duration[output_id]),
                            rremain=remaining_time,
                            rbeenon=time_on,
                            rnewon=abs(duration)))
                    self.output_on_until[output_id] = (
                        time_now + datetime.timedelta(seconds=abs(duration)))
                    self.output_last_duration[output_id] = duration

                    # Write the duration the output was ON to the
                    # database at the timestamp it turned ON
                    if time_on > 0:
                        # Make sure the recorded value is recorded negative
                        # if instructed to do so
                        if self.output_last_duration[output_id] < 0:
                            duration_on = float(-time_on)
                        else:
                            duration_on = float(time_on)
                        timestamp = (
                            datetime.datetime.utcnow() -
                            datetime.timedelta(seconds=abs(duration_on)))
                        write_db = threading.Thread(
                            target=write_influxdb_value,
                            args=(
                                self.output_unique_id[output_id],
                                'duration_sec',
                                duration_on,
                                timestamp,
                            ))
                        write_db.start()

                    return 0

                # Output is on, but not for a duration
                elif self.is_on(output_id) and not self.output_on_duration:
                    self.output_on_duration[output_id] = True
                    self.output_on_until[output_id] = (
                        time_now + datetime.timedelta(seconds=abs(duration)))
                    self.output_last_duration[output_id] = duration
                    self.logger.debug(
                        "Output {id} ({name}) is currently on without a "
                        "duration. Turning into a duration of {dur:.1f} "
                        "seconds.".format(id=self.output_id[output_id],
                                          name=self.output_name[output_id],
                                          dur=abs(duration)))
                    return 0

                # Output is not already on
                else:
                    self.output_on_duration[output_id] = True
                    self.output_last_duration[output_id] = duration
                    self.logger.debug("Output {id} ({name}) on for {dur:.1f} "
                                      "seconds.".format(
                                          id=self.output_id[output_id],
                                          name=self.output_name[output_id],
                                          dur=abs(duration)))
                    self.output_switch(output_id, 'on')
                    self.output_on_until[output_id] = (
                        datetime.datetime.now() +
                        datetime.timedelta(seconds=abs(duration)))

            # Just turn output on
            elif self.output_type[output_id] in [
                    'command', 'wired', 'wireless_433MHz_pi_switch'
            ]:
                if self.is_on(output_id):
                    self.logger.debug(
                        "Output {id} ({name}) is already on.".format(
                            id=self.output_id[output_id],
                            name=self.output_name[output_id]))
                    return 1
                else:
                    # Record the time the output was turned on in order to
                    # calculate and log the total duration is was on, when
                    # it eventually turns off.
                    self.output_time_turned_on[
                        output_id] = datetime.datetime.now()
                    self.logger.debug(
                        "Output {id} ({name}) ON at {timeon}.".format(
                            id=self.output_id[output_id],
                            name=self.output_name[output_id],
                            timeon=self.output_time_turned_on[output_id]))
                    self.output_switch(output_id, 'on')

            # PWM command output
            elif self.output_type[output_id] == 'command_pwm':
                if duty_cycle:
                    self.pwm_time_turned_on[output_id] = datetime.datetime.now(
                    )
                else:
                    self.pwm_time_turned_on[output_id] = None
                self.logger.debug(
                    "PWM command {id} ({name}) ON with a duty cycle of {dc:.2f}%"
                    .format(id=self.output_id[output_id],
                            name=self.output_name[output_id],
                            dc=abs(duty_cycle)))
                self.output_switch(output_id, 'on', duty_cycle=duty_cycle)

                # Write the duty cycle of the PWM to the database
                write_db = threading.Thread(
                    target=write_influxdb_value,
                    args=(
                        self.output_unique_id[output_id],
                        'duty_cycle',
                        duty_cycle,
                    ))
                write_db.start()

            # PWM output
            elif self.output_type[output_id] == 'pwm':
                # Record the time the PWM was turned on
                if self.pwm_hertz[output_id] <= 0:
                    self.logger.warning("PWM Hertz must be a positive value")
                    return 1
                self.pwm_time_turned_on[output_id] = datetime.datetime.now()
                self.logger.debug(
                    "PWM {id} ({name}) ON with a duty cycle of {dc:.2f}% at {hertz} Hz"
                    .format(id=self.output_id[output_id],
                            name=self.output_name[output_id],
                            dc=abs(duty_cycle),
                            hertz=self.pwm_hertz[output_id]))
                self.output_switch(output_id, 'on', duty_cycle=duty_cycle)

                # Write the duty cycle of the PWM to the database
                write_db = threading.Thread(
                    target=write_influxdb_value,
                    args=(
                        self.output_unique_id[output_id],
                        'duty_cycle',
                        duty_cycle,
                    ))
                write_db.start()

        # Signaled to turn output off
        elif state == 'off':

            if not self._is_setup(output_id):
                return
            if (self.output_type[output_id]
                    in ['pwm', 'wired', 'wireless_433MHz_pi_switch']
                    and self.output_pin[output_id] is None):
                return

            self.output_switch(output_id, 'off')

            self.logger.debug("Output {id} ({name}) turned off.".format(
                id=self.output_id[output_id],
                name=self.output_name[output_id]))

            # Write PWM duty cycle to database
            if (self.output_type[output_id] in ['pwm', 'command_pwm']
                    and self.pwm_time_turned_on[output_id] is not None):
                # Write the duration the PWM was ON to the database
                # at the timestamp it turned ON
                duration_on = (
                    datetime.datetime.now() -
                    self.pwm_time_turned_on[output_id]).total_seconds()
                self.pwm_time_turned_on[output_id] = None
                timestamp = (datetime.datetime.utcnow() -
                             datetime.timedelta(seconds=duration_on))
                write_db = threading.Thread(
                    target=write_influxdb_value,
                    args=(
                        self.output_unique_id[output_id],
                        'duty_cycle',
                        duty_cycle,
                        timestamp,
                    ))
                write_db.start()

            # Write output duration on to database
            elif (self.output_time_turned_on[output_id] is not None
                  or self.output_on_duration[output_id]):
                duration_sec = None
                timestamp = None
                if self.output_on_duration[output_id]:
                    remaining_time = 0
                    time_now = datetime.datetime.now()

                    if self.output_on_until[output_id] > time_now:
                        remaining_time = (self.output_on_until[output_id] -
                                          time_now).total_seconds()
                    duration_sec = (abs(self.output_last_duration[output_id]) -
                                    remaining_time)
                    timestamp = (datetime.datetime.utcnow() -
                                 datetime.timedelta(seconds=duration_sec))

                    # Store negative duration if a negative duration is received
                    if self.output_last_duration[output_id] < 0:
                        duration_sec = -duration_sec

                    self.output_on_duration[output_id] = False
                    self.output_on_until[output_id] = datetime.datetime.now()

                if self.output_time_turned_on[output_id] is not None:
                    # Write the duration the output was ON to the database
                    # at the timestamp it turned ON
                    duration_sec = (
                        datetime.datetime.now() -
                        self.output_time_turned_on[output_id]).total_seconds()
                    timestamp = (datetime.datetime.utcnow() -
                                 datetime.timedelta(seconds=duration_sec))
                    self.output_time_turned_on[output_id] = None

                write_db = threading.Thread(
                    target=write_influxdb_value,
                    args=(
                        self.output_unique_id[output_id],
                        'duration_sec',
                        duration_sec,
                        timestamp,
                    ))
                write_db.start()

        if trigger_conditionals:
            self.check_conditionals(output_id,
                                    state=state,
                                    on_duration=duration,
                                    duty_cycle=duty_cycle)
def trigger_action(cond_action_id,
                   message='',
                   note_tags=None,
                   email_recipients=None,
                   attachment_file=None,
                   attachment_type=None,
                   single_action=False):
    """
    Trigger individual action

    If single_action == False, message, note_tags, email_recipients,
    attachment_file, and attachment_type are returned and may be
    passed back to this function in order to append to those lists.

    :param cond_action_id: unique_id of action
    :param message: message string to append to that will be sent back
    :param note_tags: list of note tags to use if creating a note
    :param email_recipients: list of email addresses to notify if sending an email
    :param attachment_file: string location of a file to attach to an email
    :param attachment_type: string type of email attachment
    :param single_action: True if only one action is being triggered, False if only one of multiple actions
    :return: message or (message, note_tags, email_recipients, attachment_file, attachment_type)
    """
    cond_action = db_retrieve_table_daemon(Actions, unique_id=cond_action_id)
    if not cond_action:
        message += 'Error: Action with ID {} not found!'.format(cond_action_id)
        if single_action:
            return message
        else:
            return message, note_tags, email_recipients, attachment_file, attachment_type

    logger_actions = logging.getLogger("mycodo.trigger_action_{id}".format(
        id=cond_action.unique_id.split('-')[0]))

    message += "\n[Action {id}]:".format(
        id=cond_action.unique_id.split('-')[0],
        action_type=cond_action.action_type)

    try:
        control = DaemonControl()

        smtp_table = db_retrieve_table_daemon(SMTP, entry='first')
        smtp_max_count = smtp_table.hourly_max
        smtp_wait_timer = smtp_table.smtp_wait_timer
        email_count = smtp_table.email_count

        # Pause
        if cond_action.action_type == 'pause_actions':
            message += " [{id}] Pause actions for {sec} seconds.".format(
                id=cond_action.id, sec=cond_action.pause_duration)

            time.sleep(cond_action.pause_duration)

        # Actuate output (duration)
        if (cond_action.action_type == 'output' and cond_action.do_unique_id
                and cond_action.do_output_state in ['on', 'off']):
            this_output = db_retrieve_table_daemon(
                Output, unique_id=cond_action.do_unique_id, entry='first')
            message += " Turn output {unique_id} ({id}, {name}) {state}".format(
                unique_id=cond_action.do_unique_id,
                id=this_output.id,
                name=this_output.name,
                state=cond_action.do_output_state)
            if (cond_action.do_output_state == 'on'
                    and cond_action.do_output_duration):
                message += " for {sec} seconds".format(
                    sec=cond_action.do_output_duration)
            message += "."

            output_on_off = threading.Thread(
                target=control.output_on_off,
                args=(
                    cond_action.do_unique_id,
                    cond_action.do_output_state,
                ),
                kwargs={'duration': cond_action.do_output_duration})
            output_on_off.start()

        # Actuate output (PWM)
        if (cond_action.action_type == 'output_pwm'
                and cond_action.do_unique_id
                and 0 <= cond_action.do_output_pwm <= 100):
            this_output = db_retrieve_table_daemon(
                Output, unique_id=cond_action.do_unique_id, entry='first')
            message += " Turn output {unique_id} ({id}, {name}) duty cycle to {duty_cycle}%.".format(
                unique_id=cond_action.do_unique_id,
                id=this_output.id,
                name=this_output.name,
                duty_cycle=cond_action.do_output_pwm)

            output_on = threading.Thread(
                target=control.output_on,
                args=(cond_action.do_unique_id, ),
                kwargs={'duty_cycle': cond_action.do_output_pwm})
            output_on.start()

        # Execute command in shell
        if cond_action.action_type == 'command':

            # Replace string variables with actual values
            command_str = cond_action.do_action_string

            # TODO: Maybe get this working again with the new measurement system
            # # Replace measurement variables
            # if last_measurement:
            #     command_str = command_str.replace(
            #         "((measure_{var}))".format(
            #             var=device_measurement), str(last_measurement))
            # if device and device.period:
            #     command_str = command_str.replace(
            #         "((measure_period))", str(device.period))
            # if input_dev:
            #     command_str = command_str.replace(
            #         "((measure_location))", str(input_dev.location))
            # if input_dev and device_measurement == input_dev.measurements:
            #     command_str = command_str.replace(
            #         "((measure_linux_command))", str(last_measurement))
            #
            # # Replace output variables
            # if output:
            #     if output.pin:
            #         command_str = command_str.replace(
            #             "((output_pin))", str(output.pin))
            #     if output_state:
            #         command_str = command_str.replace(
            #             "((output_action))", str(output_state))
            #     if on_duration:
            #         command_str = command_str.replace(
            #             "((output_duration))", str(on_duration))
            #     if duty_cycle:
            #         command_str = command_str.replace(
            #             "((output_pwm))", str(duty_cycle))
            #
            # # Replace edge variables
            # if edge:
            #     command_str = command_str.replace(
            #         "((edge_state))", str(edge))

            message += " Execute '{com}' ".format(com=command_str)

            _, _, cmd_status = cmd_output(command_str)

            message += "(return status: {stat}).".format(stat=cmd_status)

        # Create Note
        if cond_action.action_type == 'create_note':
            tag_name = db_retrieve_table_daemon(
                NoteTags, unique_id=cond_action.do_action_string).name

            message += " Create note with tag '{}'.".format(tag_name)
            if single_action and cond_action.do_action_string:
                list_tags = []
                check_tag = db_retrieve_table_daemon(
                    NoteTags, unique_id=cond_action.do_action_string)
                if check_tag:
                    list_tags.append(cond_action.do_action_string)

                if list_tags:
                    with session_scope(MYCODO_DB_PATH) as db_session:
                        new_note = Notes()
                        new_note.name = 'Action'
                        new_note.tags = ','.join(list_tags)
                        new_note.note = message
                        db_session.add(new_note)
            else:
                note_tags.append(cond_action.do_action_string)

        # Capture photo
        # If emailing later, store location of photo as attachment_file
        if cond_action.action_type in ['photo', 'photo_email']:
            this_camera = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id, entry='first')
            message += "  Capturing photo with camera {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=this_camera.id,
                name=this_camera.name)
            camera_still = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id)
            attachment_path_file = camera_record('photo',
                                                 camera_still.unique_id)
            attachment_file = os.path.join(attachment_path_file[0],
                                           attachment_path_file[1])

        # Capture video
        if cond_action.action_type in ['video', 'video_email']:
            this_camera = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id, entry='first')
            message += "  Capturing video with camera {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=this_camera.id,
                name=this_camera.name)
            camera_stream = db_retrieve_table_daemon(
                Camera, unique_id=cond_action.do_unique_id)
            attachment_path_file = camera_record(
                'video',
                camera_stream.unique_id,
                duration_sec=cond_action.do_camera_duration)
            attachment_file = os.path.join(attachment_path_file[0],
                                           attachment_path_file[1])

        # Activate Controller
        if cond_action.action_type == 'activate_controller':
            (controller_type, controller_object,
             controller_entry) = which_controller(cond_action.do_unique_id)
            message += " Activate Controller {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=controller_entry.id,
                name=controller_entry.name)
            if controller_entry.is_activated:
                message += " Notice: Controller is already active!"
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_cont = new_session.query(controller_object).filter(
                        controller_object.unique_id ==
                        cond_action.do_unique_id).first()
                    mod_cont.is_activated = True
                    new_session.commit()
                activate_controller = threading.Thread(
                    target=control.controller_activate,
                    args=(
                        controller_type,
                        cond_action.do_unique_id,
                    ))
                activate_controller.start()

        # Deactivate Controller
        if cond_action.action_type == 'deactivate_controller':
            (controller_type, controller_object,
             controller_entry) = which_controller(cond_action.do_unique_id)
            message += " Deactivate Controller {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id,
                id=controller_entry.id,
                name=controller_entry.name)
            if not controller_entry.is_activated:
                message += " Notice: Controller is already inactive!"
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_cont = new_session.query(controller_object).filter(
                        controller_object.unique_id ==
                        cond_action.do_unique_id).first()
                    mod_cont.is_activated = False
                    new_session.commit()
                deactivate_controller = threading.Thread(
                    target=control.controller_deactivate,
                    args=(
                        controller_type,
                        cond_action.do_unique_id,
                    ))
                deactivate_controller.start()

        # Resume PID controller
        if cond_action.action_type == 'resume_pid':
            pid = db_retrieve_table_daemon(PID,
                                           unique_id=cond_action.do_unique_id,
                                           entry='first')
            message += " Resume PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name)
            if not pid.is_paused:
                message += " Notice: PID is not paused!"
            elif pid.is_activated:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.is_paused = False
                    new_session.commit()
                resume_pid = threading.Thread(
                    target=control.pid_resume,
                    args=(cond_action.do_unique_id, ))
                resume_pid.start()

        # Pause PID controller
        if cond_action.action_type == 'pause_pid':
            pid = db_retrieve_table_daemon(PID,
                                           unique_id=cond_action.do_unique_id,
                                           entry='first')
            message += " Pause PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name)
            if pid.is_paused:
                message += " Notice: PID is already paused!"
            elif pid.is_activated:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.is_paused = True
                    new_session.commit()
                pause_pid = threading.Thread(target=control.pid_pause,
                                             args=(cond_action.do_unique_id, ))
                pause_pid.start()

        # Set PID Setpoint
        if cond_action.action_type == 'setpoint_pid':
            pid = db_retrieve_table_daemon(PID,
                                           unique_id=cond_action.do_unique_id,
                                           entry='first')
            message += " Set Setpoint of PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name)
            if pid.is_activated:
                setpoint_pid = threading.Thread(
                    target=control.pid_set,
                    args=(
                        pid.unique_id,
                        'setpoint',
                        float(cond_action.do_action_string),
                    ))
                setpoint_pid.start()
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.setpoint = cond_action.do_action_string
                    new_session.commit()

        # Set PID Method and start method from beginning
        if cond_action.action_type == 'method_pid':
            pid = db_retrieve_table_daemon(PID,
                                           unique_id=cond_action.do_unique_id,
                                           entry='first')
            message += " Set Method of PID {unique_id} ({id}, {name}).".format(
                unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name)

            # Instruct method to start
            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_pid = new_session.query(PID).filter(
                    PID.unique_id == cond_action.do_unique_id).first()
                mod_pid.method_start_time = 'Ready'
                new_session.commit()

            pid = db_retrieve_table_daemon(PID,
                                           unique_id=cond_action.do_unique_id,
                                           entry='first')
            if pid.is_activated:
                method_pid = threading.Thread(target=control.pid_set,
                                              args=(
                                                  pid.unique_id,
                                                  'method',
                                                  cond_action.do_action_string,
                                              ))
                method_pid.start()
            else:
                with session_scope(MYCODO_DB_PATH) as new_session:
                    mod_pid = new_session.query(PID).filter(
                        PID.unique_id == cond_action.do_unique_id).first()
                    mod_pid.method_id = cond_action.do_action_string
                    new_session.commit()

        # Email the Conditional message. Optionally capture a photo or
        # video and attach to the email.
        if cond_action.action_type in ['email', 'photo_email', 'video_email']:

            if (email_count >= smtp_max_count
                    and time.time() < smtp_wait_timer):
                allowed_to_send_notice = False
            else:
                if time.time() > smtp_wait_timer:
                    with session_scope(MYCODO_DB_PATH) as new_session:
                        mod_smtp = new_session.query(SMTP).first()
                        mod_smtp.email_count = 0
                        mod_smtp.smtp_wait_timer = time.time() + 3600
                        new_session.commit()
                allowed_to_send_notice = True

            with session_scope(MYCODO_DB_PATH) as new_session:
                mod_smtp = new_session.query(SMTP).first()
                mod_smtp.email_count += 1
                new_session.commit()

            # If the emails per hour limit has not been exceeded
            if allowed_to_send_notice:
                message += " Notify {email}.".format(
                    email=cond_action.do_action_string)
                # attachment_type != False indicates to
                # attach a photo or video
                if cond_action.action_type == 'photo_email':
                    message += " Photo attached to email."
                    attachment_type = 'still'
                elif cond_action.action_type == 'video_email':
                    message += " Video attached to email."
                    attachment_type = 'video'

                if single_action and cond_action.do_action_string:
                    smtp = db_retrieve_table_daemon(SMTP, entry='first')
                    send_email(smtp.host, smtp.ssl, smtp.port, smtp.user,
                               smtp.passw, smtp.email_from,
                               [cond_action.do_action_string], message,
                               attachment_file, attachment_type)
                else:
                    email_recipients.append(cond_action.do_action_string)
            else:
                logger_actions.error(
                    "Wait {sec:.0f} seconds to email again.".format(
                        sec=smtp_wait_timer - time.time()))

        if cond_action.action_type == 'flash_lcd_on':
            lcd = db_retrieve_table_daemon(LCD,
                                           unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Flash On.".format(
                unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name)

            start_flashing = threading.Thread(target=control.lcd_flash,
                                              args=(
                                                  cond_action.do_unique_id,
                                                  True,
                                              ))
            start_flashing.start()

        if cond_action.action_type == 'flash_lcd_off':
            lcd = db_retrieve_table_daemon(LCD,
                                           unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Flash Off.".format(
                unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name)

            start_flashing = threading.Thread(target=control.lcd_flash,
                                              args=(
                                                  cond_action.do_unique_id,
                                                  False,
                                              ))
            start_flashing.start()

        if cond_action.action_type == 'lcd_backlight_off':
            lcd = db_retrieve_table_daemon(LCD,
                                           unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Backlight Off.".format(
                unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name)

            start_flashing = threading.Thread(target=control.lcd_backlight,
                                              args=(
                                                  cond_action.do_unique_id,
                                                  False,
                                              ))
            start_flashing.start()

        if cond_action.action_type == 'lcd_backlight_on':
            lcd = db_retrieve_table_daemon(LCD,
                                           unique_id=cond_action.do_unique_id)
            message += " LCD {unique_id} ({id}, {name}) Backlight On.".format(
                unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name)

            start_flashing = threading.Thread(target=control.lcd_backlight,
                                              args=(
                                                  cond_action.do_unique_id,
                                                  True,
                                              ))
            start_flashing.start()
    except Exception:
        logger_actions.exception("Error triggering action:")
        message += " Error while executing action! See Daemon log for details."

    if single_action:
        return message
    else:
        return message, note_tags, email_recipients, attachment_file, attachment_type