Example #1
0
    def add_mod_relay(self, relay_id, do_setup_pin=False):
        """
        Add or modify local dictionary of relay settings form SQL database

        When a relay is added or modified while the relay controller is
        running, these local variables need to also be modified to
        maintain consistency between the SQL database and running controller.

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

        :param relay_id: Unique ID for each relay
        :type relay_id: str
        :param do_setup_pin: If True, initialize GPIO (when adding new relay)
        :type do_setup_pin: bool
        """
        try:
            relay = db_retrieve_table(MYCODO_DB_PATH,
                                      Relay,
                                      device_id=relay_id)
            self.relay_id[relay_id] = relay.id
            self.relay_name[relay_id] = relay.name
            self.relay_pin[relay_id] = relay.pin
            self.relay_amps[relay_id] = relay.amps
            self.relay_trigger[relay_id] = relay.trigger
            self.relay_start_state[relay_id] = relay.start_state
            self.relay_on_until[relay_id] = datetime.datetime.now()
            self.relay_time_turned_on[relay_id] = None
            self.relay_last_duration[relay_id] = 0
            self.relay_on_duration[relay_id] = False
            message = "Relay {id} ({name}) ".format(
                id=self.relay_id[relay_id], name=self.relay_name[relay_id])
            if do_setup_pin and relay.pin:
                self.setup_pin(relay.id, relay.pin, relay.trigger)
                message += "initialized"
            else:
                message += "added"
            self.logger.debug(message)
            return 0, "success"
        except Exception as except_msg:
            return 1, "Add_Mod_Relay Error: ID {id}: {err}".format(
                id=relay_id, err=except_msg)
Example #2
0
    def run(self):
        self.start_all_controllers()
        self.startup_stats()

        try:
            # loop until daemon is instructed to shut down
            while self.daemon_run:
                now = time.time()

                # If time-lapse active, take photo at predefined periods
                if (os.path.isfile(FILE_TIMELAPSE_PARAM) and
                        os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    # Read user-defined time-lapse parameters
                    with open(FILE_TIMELAPSE_PARAM, mode='r') as infile:
                        reader = csv.reader(infile)
                        dict_timelapse_param = OrderedDict((row[0], row[1]) for row in reader)
                    if now > float(dict_timelapse_param['end_time']):
                        try:
                            os.remove(FILE_TIMELAPSE_PARAM)
                            os.remove(LOCK_FILE_TIMELAPSE)
                        except Exception as e:
                            self.logger.error("{cls} raised an exception: "
                                              "{err}".format(cls=type(self).__name__, err=e))
                    elif now > float(dict_timelapse_param['next_capture']):
                        # Ensure next capture is greater than now (in case of power failure/reboot)
                        next_capture = float(dict_timelapse_param['next_capture'])
                        capture_number = int(dict_timelapse_param['capture_number'])
                        while now > next_capture:
                            # Update last capture and image number to latest before capture
                            next_capture += float(dict_timelapse_param['interval'])
                            capture_number += 1
                        add_update_csv(FILE_TIMELAPSE_PARAM,
                                       'next_capture',
                                       next_capture)
                        add_update_csv(FILE_TIMELAPSE_PARAM,
                                       'capture_number',
                                       capture_number)
                        # Capture image
                        camera = db_retrieve_table(
                            MYCODO_DB_PATH, CameraStill, entry='first')
                        camera_record(
                            'timelapse',
                            camera,
                            start_time=dict_timelapse_param['start_time'],
                            capture_number=capture_number)

                elif (os.path.isfile(FILE_TIMELAPSE_PARAM) or
                        os.path.isfile(LOCK_FILE_TIMELAPSE)):
                    try:
                        os.remove(FILE_TIMELAPSE_PARAM)
                        os.remove(LOCK_FILE_TIMELAPSE)
                    except Exception as e:
                        self.logger.error("{cls} raised an exception: "
                                          "{err}".format(cls=type(self).__name__, err=e))

                # Log ram usage every 24 hours
                if now > self.timer_ram_use:
                    self.timer_ram_use = now+86400
                    ram = resource.getrusage(
                        resource.RUSAGE_SELF).ru_maxrss / float(1000)
                    self.logger.info("{} MB ram in use".format(ram))

                # collect and send anonymous statistics
                if (not self.opt_out_statistics and
                        now > self.timer_stats):
                    self.send_stats()

                time.sleep(0.25)
            GPIO.cleanup()
        except Exception as except_msg:
            self.logger.exception("Unexpected error: {}: {}".format(
                sys.exc_info()[0], except_msg))
            raise

        # If the daemon errors or finishes, shut it down
        finally:
            self.logger.debug("Stopping all running controllers")
            self.stop_all_controllers()

        self.logger.info("Mycodo terminated in {:.3f} seconds".format(
            timeit.default_timer()-self.thread_shutdown_timer))
        self.terminated = True

        # Wait for the client to receive the response before it disconnects
        time.sleep(0.25)
Example #3
0
    def calculate_method_setpoint(self, method_id):
        method = db_retrieve_table(MYCODO_DB_PATH, Method)

        method_key = method.filter(Method.method_id == method_id)
        method_key = method_key.filter(Method.method_order == 0).first()

        method = method.filter(Method.method_id == method_id)
        method = method.filter(Method.relay_id == None)
        method = method.filter(Method.method_order > 0)
        method = method.order_by(Method.method_order.asc()).all()

        now = datetime.datetime.now()

        # Calculate where the current time/date is within the time/date method
        if method_key.method_type == 'Date':
            for each_method in method:
                start_time = datetime.datetime.strptime(
                    each_method.start_time, '%Y-%m-%d %H:%M:%S')
                end_time = datetime.datetime.strptime(each_method.end_time,
                                                      '%Y-%m-%d %H:%M:%S')
                if start_time < now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    total_seconds = (end_time - start_time).total_seconds()
                    part_seconds = (now - start_time).total_seconds()
                    percent_total = part_seconds / total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_total)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time, end_time))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug(
                        "[Method] Total: {} Part total: {} ({}%)".format(
                            total_seconds, part_seconds, percent_total))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate where the current Hour:Minute:Seconds is within the Daily method
        elif method_key.method_type == 'Daily':
            daily_now = datetime.datetime.now().strftime('%H:%M:%S')
            daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S')
            for each_method in method:
                start_time = datetime.datetime.strptime(
                    each_method.start_time, '%H:%M:%S')
                end_time = datetime.datetime.strptime(each_method.end_time,
                                                      '%H:%M:%S')
                if start_time < daily_now < end_time:
                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint

                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    total_seconds = (end_time - start_time).total_seconds()
                    part_seconds = (daily_now - start_time).total_seconds()
                    percent_total = part_seconds / total_seconds

                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_total)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_total)

                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_time.strftime('%H:%M:%S'),
                        end_time.strftime('%H:%M:%S')))
                    self.logger.debug("[Method] Start: {} End: {}".format(
                        start_setpoint, end_setpoint))
                    self.logger.debug(
                        "[Method] Total: {} Part total: {} ({}%)".format(
                            total_seconds, part_seconds, percent_total))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0

        # Calculate sine y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailySine':
            new_setpoint = sine_wave_y_out(method_key.amplitude,
                                           method_key.frequency,
                                           method_key.shift_angle,
                                           method_key.shift_y)
            self.set_point = new_setpoint
            return 0

        # Calculate Bezier curve y-axis value from the x-axis (seconds of the day)
        elif method_key.method_type == 'DailyBezier':
            new_setpoint = bezier_curve_y_out(method_key.shift_angle,
                                              (method_key.x0, method_key.y0),
                                              (method_key.x1, method_key.y1),
                                              (method_key.x2, method_key.y2),
                                              (method_key.x3, method_key.y3))
            self.set_point = new_setpoint
            return 0

        # Calculate the duration in the method based on self.method_start_time
        elif method_key.method_type == 'Duration' and self.method_start_time != 'Ended':
            seconds_from_start = (now - self.method_start_time).total_seconds()
            total_sec = 0
            previous_total_sec = 0
            for each_method in method:
                total_sec += each_method.duration_sec
                if previous_total_sec <= seconds_from_start < total_sec:
                    row_start_time = float(
                        self.method_start_time.strftime(
                            '%s')) + previous_total_sec
                    row_since_start_sec = (
                        now - (self.method_start_time + datetime.timedelta(
                            0, previous_total_sec))).total_seconds()
                    percent_row = row_since_start_sec / each_method.duration_sec

                    start_setpoint = each_method.start_setpoint
                    if each_method.end_setpoint:
                        end_setpoint = each_method.end_setpoint
                    else:
                        end_setpoint = each_method.start_setpoint
                    setpoint_diff = abs(end_setpoint - start_setpoint)
                    if start_setpoint < end_setpoint:
                        new_setpoint = start_setpoint + (setpoint_diff *
                                                         percent_row)
                    else:
                        new_setpoint = start_setpoint - (setpoint_diff *
                                                         percent_row)

                    self.logger.debug(
                        "[Method] Start: {} Seconds Since: {}".format(
                            self.method_start_time, seconds_from_start))
                    self.logger.debug("[Method] Start time of row: {}".format(
                        datetime.datetime.fromtimestamp(row_start_time)))
                    self.logger.debug(
                        "[Method] Sec since start of row: {}".format(
                            row_since_start_sec))
                    self.logger.debug(
                        "[Method] Percent of row: {}".format(percent_row))
                    self.logger.debug(
                        "[Method] New Setpoint: {}".format(new_setpoint))
                    self.set_point = new_setpoint
                    return 0
                previous_total_sec = total_sec

            # Duration method has ended, reset start_time locally and in DB
            if self.method_start_time:
                with session_scope(MYCODO_DB_PATH) as db_session:
                    mod_method = db_session.query(Method).filter(
                        Method.method_id == self.method_id)
                    mod_method = mod_method.filter(
                        Method.method_order == 0).first()
                    mod_method.start_time = 'Ended'
                    db_session.commit()
                self.method_start_time = 'Ended'

        # Setpoint not needing to be calculated, use default setpoint
        self.set_point = self.default_set_point
Example #4
0
    def manipulate_relays(self):
        """
        Activate a relay based on PID output (control variable) and whether
        the manipulation directive is to raise, lower, or both.

        :rtype: None
        """
        # If there was a measurement able to be retrieved from
        # influxdb database that was entered within the past minute
        if self.last_measurement_success:
            #
            # PID control variable positive to raise environmental condition
            #
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                if self.control_variable > 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.raise_max_duration and
                            self.control_variable > self.raise_max_duration):
                        self.raise_seconds_on = self.raise_max_duration
                    else:
                        self.raise_seconds_on = float("{0:.2f}".format(
                            self.control_variable))

                    # Turn off lower_relay if active, because we're now raising
                    if self.lower_relay_id:
                        relay = db_retrieve_table(
                            MYCODO_DB_PATH,
                            Relay,
                            device_id=self.lower_relay_id)
                        if relay.is_on():
                            self.control.relay_off(self.lower_relay_id)

                    if self.raise_seconds_on > self.raise_min_duration:
                        # Activate raise_relay for a duration
                        self.logger.debug(
                            "Setpoint: {sp} Output: {op} to relay "
                            "{relay}".format(sp=self.set_point,
                                             op=self.control_variable,
                                             relay=self.raise_relay_id))
                        self.control.relay_on(self.raise_relay_id,
                                              self.raise_seconds_on)
                else:
                    self.control.relay_off(self.raise_relay_id)

            #
            # PID control variable negative to lower environmental condition
            #
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                if self.control_variable < 0:
                    # Ensure the relay on duration doesn't exceed the set maximum
                    if (self.lower_max_duration and abs(self.control_variable)
                            > self.lower_max_duration):
                        self.lower_seconds_on = self.lower_max_duration
                    else:
                        self.lower_seconds_on = abs(
                            float("{0:.2f}".format(self.control_variable)))

                    # Turn off raise_relay if active, because we're now lowering
                    if self.raise_relay_id:
                        relay = db_retrieve_table(
                            MYCODO_DB_PATH,
                            Relay,
                            device_id=self.raise_relay_id)
                        if relay.is_on():
                            self.control.relay_off(self.raise_relay_id)

                    if self.lower_seconds_on > self.lower_min_duration:
                        # Activate lower_relay for a duration
                        self.logger.debug("Setpoint: {sp} Output: {op} to "
                                          "relay {relay}".format(
                                              sp=self.set_point,
                                              op=self.control_variable,
                                              relay=self.lower_relay_id))
                        self.control.relay_on(self.lower_relay_id,
                                              self.lower_seconds_on)
                else:
                    self.control.relay_off(self.lower_relay_id)

        else:
            if self.direction in ['raise', 'both'] and self.raise_relay_id:
                self.control.relay_off(self.raise_relay_id)
            if self.direction in ['lower', 'both'] and self.lower_relay_id:
                self.control.relay_off(self.lower_relay_id)
Example #5
0
    def __init__(self, ready, sensor_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger(
            "mycodo.sensor_{id}".format(id=sensor_id))

        list_devices_i2c = [
            'ADS1x15', 'AM2315', 'ATLAS_PT1000', 'BME280', 'BMP', 'CHIRP',
            'HTU21D', 'MCP342x', 'SHT2x', 'TMP006', 'TSL2561'
        ]

        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.lock = {}
        self.measurement = None
        self.updateSuccess = False
        self.sensor_id = sensor_id
        self.control = DaemonControl()
        self.pause_loop = False
        self.verify_pause_loop = True

        self.cond_id = {}
        self.cond_name = {}
        self.cond_activated = {}
        self.cond_period = {}
        self.cond_measurement_type = {}
        self.cond_edge_select = {}
        self.cond_edge_detected = {}
        self.cond_gpio_state = {}
        self.cond_direction = {}
        self.cond_setpoint = {}
        self.cond_relay_id = {}
        self.cond_relay_state = {}
        self.cond_relay_on_duration = {}
        self.cond_execute_command = {}
        self.cond_email_notify = {}
        self.cond_flash_lcd = {}
        self.cond_camera_record = {}
        self.cond_timer = {}
        self.smtp_wait_timer = {}

        self.setup_sensor_conditionals()

        sensor = db_retrieve_table(MYCODO_DB_PATH,
                                   Sensor,
                                   device_id=self.sensor_id)
        self.i2c_bus = sensor.i2c_bus
        self.location = sensor.location
        self.device = sensor.device
        self.sensor_type = sensor.device_type
        self.period = sensor.period
        self.multiplexer_address_raw = sensor.multiplexer_address
        self.multiplexer_bus = sensor.multiplexer_bus
        self.multiplexer_channel = sensor.multiplexer_channel
        self.adc_channel = sensor.adc_channel
        self.adc_gain = sensor.adc_gain
        self.adc_resolution = sensor.adc_resolution
        self.adc_measure = sensor.adc_measure
        self.adc_measure_units = sensor.adc_measure_units
        self.adc_volts_min = sensor.adc_volts_min
        self.adc_volts_max = sensor.adc_volts_max
        self.adc_units_min = sensor.adc_units_min
        self.adc_units_max = sensor.adc_units_max
        self.sht_clock_pin = sensor.sht_clock_pin
        self.sht_voltage = sensor.sht_voltage

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

        # Relay that will activate prior to sensor read
        self.pre_relay_id = sensor.pre_relay_id
        self.pre_relay_duration = sensor.pre_relay_duration
        self.pre_relay_setup = False
        self.next_measurement = time.time()
        self.get_new_measurement = False
        self.measurement_acquired = False
        self.pre_relay_activated = False
        self.pre_relay_timer = time.time()

        relay = db_retrieve_table(MYCODO_DB_PATH, Relay, entry='all')
        for each_relay in relay:  # Check if relay ID actually exists
            if each_relay.id == self.pre_relay_id and self.pre_relay_duration:
                self.pre_relay_setup = True

        smtp = db_retrieve_table(MYCODO_DB_PATH, 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.device in list_devices_i2c:
            self.i2c_address = int(str(self.location), 16)

        # Set up multiplexer if enabled
        if self.device in list_devices_i2c and self.multiplexer_address_raw:
            self.multiplexer_address_string = self.multiplexer_address_raw
            self.multiplexer_address = int(str(self.multiplexer_address_raw),
                                           16)
            self.multiplexer_lock_file = "/var/lock/mycodo_multiplexer_0x{:02X}.pid".format(
                self.multiplexer_address)
            self.multiplexer = TCA9548A(self.multiplexer_bus,
                                        self.multiplexer_address)
        else:
            self.multiplexer = None

        if self.device in ['ADS1x15', 'MCP342x'] and self.location:
            self.adc_lock_file = "/var/lock/mycodo_adc_bus{}_0x{:02X}.pid".format(
                self.i2c_bus, self.i2c_address)

        # Set up edge detection of a GPIO pin
        if self.device == 'EDGE':
            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

        # Set up analog-to-digital converter
        elif self.device == 'ADS1x15':
            self.adc = ADS1x15Read(self.i2c_address, self.i2c_bus,
                                   self.adc_channel, self.adc_gain)
        elif self.device == 'MCP342x':
            self.adc = MCP342xRead(self.i2c_address, self.i2c_bus,
                                   self.adc_channel, self.adc_gain,
                                   self.adc_resolution)
        else:
            self.adc = None

        self.device_recognized = True

        # Set up sensor
        if self.device in ['EDGE', 'ADS1x15', 'MCP342x']:
            self.measure_sensor = None
        elif self.device == 'RPiCPULoad':
            self.measure_sensor = RaspberryPiCPULoad()
        elif self.device == 'RPi':
            self.measure_sensor = RaspberryPiCPUTemp()
        elif self.device == 'CHIRP':
            self.measure_sensor = ChirpSensor(self.i2c_address, self.i2c_bus)
        elif self.device == 'DS18B20':
            self.measure_sensor = DS18B20Sensor(self.location)
        elif self.device == 'DHT11':
            self.measure_sensor = DHT11Sensor(self.sensor_id,
                                              int(self.location))
        elif self.device in ['DHT22', 'AM2302']:
            self.measure_sensor = DHT22Sensor(self.sensor_id,
                                              int(self.location))
        elif self.device == 'HTU21D':
            self.measure_sensor = HTU21DSensor(self.i2c_bus)
        elif self.device == 'AM2315':
            self.measure_sensor = AM2315Sensor(self.i2c_bus)
        elif self.device == 'ATLAS_PT1000':
            self.measure_sensor = AtlasPT1000Sensor(self.i2c_address,
                                                    self.i2c_bus)
        elif self.device == 'K30':
            self.measure_sensor = K30Sensor()
        elif self.device == 'BME280':
            self.measure_sensor = BME280Sensor(self.i2c_address, self.i2c_bus)
        elif self.device == 'BMP':
            self.measure_sensor = BMPSensor(self.i2c_bus)
        elif self.device == 'SHT1x_7x':
            self.measure_sensor = SHT1x7xSensor(self.location,
                                                self.sht_clock_pin,
                                                self.sht_voltage)
        elif self.device == 'SHT2x':
            self.measure_sensor = SHT2xSensor(self.i2c_address, self.i2c_bus)
        elif self.device == 'TMP006':
            self.measure_sensor = TMP006Sensor(self.i2c_address, self.i2c_bus)
        elif self.device == 'TSL2561':
            self.measure_sensor = TSL2561Sensor(self.i2c_address, self.i2c_bus)
        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.sensor_timer = time.time()
        self.running = False
        self.lastUpdate = None
Example #6
0
    def setup_sensor_conditionals(self, cond_mod='setup', cond_id=None):
        logger_cond = logging.getLogger(
            "mycodo.SensorCond-{id}".format(id=cond_id))
        # Signal to pause the main loop and wait for verification
        self.pause_loop = True
        while not self.verify_pause_loop:
            time.sleep(0.1)

        if cond_mod == 'del':
            self.cond_id.pop(cond_id, None)
            self.cond_activated.pop(cond_id, None)
            self.cond_period.pop(cond_id, None)
            self.cond_name.pop(cond_id, None)
            self.cond_measurement_type.pop(cond_id, None)
            self.cond_edge_select.pop(cond_id, None)
            self.cond_edge_detected.pop(cond_id, None)
            self.cond_gpio_state.pop(cond_id, None)
            self.cond_direction.pop(cond_id, None)
            self.cond_setpoint.pop(cond_id, None)
            self.cond_relay_id.pop(cond_id, None)
            self.cond_relay_state.pop(cond_id, None)
            self.cond_relay_on_duration.pop(cond_id, None)
            self.cond_execute_command.pop(cond_id, None)
            self.cond_email_notify.pop(cond_id, None)
            self.cond_flash_lcd.pop(cond_id, None)
            self.cond_camera_record.pop(cond_id, None)
            self.cond_timer.pop(cond_id, None)
            self.smtp_wait_timer.pop(cond_id, None)
            logger_cond.debug("Deleted Conditional from Sensor {sen}".format(
                sen=self.sensor_id))
        else:
            if cond_mod == 'setup':
                self.cond_id = {}
                self.cond_name = {}
                self.cond_activated = {}
                self.cond_period = {}
                self.cond_measurement_type = {}
                self.cond_edge_select = {}
                self.cond_edge_detected = {}
                self.cond_gpio_state = {}
                self.cond_direction = {}
                self.cond_setpoint = {}
                self.cond_relay_id = {}
                self.cond_relay_state = {}
                self.cond_relay_on_duration = {}
                self.cond_execute_command = {}
                self.cond_email_notify = {}
                self.cond_flash_lcd = {}
                self.cond_camera_record = {}
                self.cond_timer = {}
                self.smtp_wait_timer = {}

                self.sensor_conditional = db_retrieve_table(
                    MYCODO_DB_PATH, SensorConditional)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.activated == 1)
            elif cond_mod == 'add':
                self.sensor_conditional = db_retrieve_table(
                    MYCODO_DB_PATH, SensorConditional)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.sensor_id == self.sensor_id)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.activated == 1)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.id == cond_id)
                logger_cond.debug("Added Conditional to Sensor {sen}".format(
                    sen=self.sensor_id))
            elif cond_mod == 'mod':
                self.sensor_conditional = db_retrieve_table(
                    MYCODO_DB_PATH, SensorConditional)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.sensor_id == self.sensor_id)
                self.sensor_conditional = self.sensor_conditional.filter(
                    SensorConditional.id == cond_id)
                logger_cond.debug(
                    "Modified Conditional from Sensor {sen}".format(
                        sen=self.sensor_id))
            else:
                return 1

            for each_cond in self.sensor_conditional.all():
                if cond_mod == 'setup':
                    logger_cond.debug("Activated Conditional from Sensor "
                                      "{sen}".format(sen=self.sensor_id))
                self.cond_id[each_cond.id] = each_cond.id
                self.cond_name[each_cond.id] = each_cond.name
                self.cond_activated[each_cond.id] = each_cond.activated
                self.cond_period[each_cond.id] = each_cond.period
                self.cond_measurement_type[
                    each_cond.id] = each_cond.measurement_type
                self.cond_edge_select[each_cond.id] = each_cond.edge_select
                self.cond_edge_detected[each_cond.id] = each_cond.edge_detected
                self.cond_gpio_state[each_cond.id] = each_cond.gpio_state
                self.cond_direction[each_cond.id] = each_cond.direction
                self.cond_setpoint[each_cond.id] = each_cond.setpoint
                self.cond_relay_id[each_cond.id] = each_cond.relay_id
                self.cond_relay_state[each_cond.id] = each_cond.relay_state
                self.cond_relay_on_duration[
                    each_cond.id] = each_cond.relay_on_duration
                self.cond_execute_command[
                    each_cond.id] = each_cond.execute_command
                self.cond_email_notify[each_cond.id] = each_cond.email_notify
                self.cond_flash_lcd[each_cond.id] = each_cond.email_notify
                self.cond_camera_record[each_cond.id] = each_cond.camera_record
                self.cond_timer[each_cond.id] = time.time() + self.cond_period[
                    each_cond.id]
                self.smtp_wait_timer[each_cond.id] = time.time() + 3600

        self.pause_loop = False
        self.verify_pause_loop = False
Example #7
0
    def check_conditionals(self, cond_id):
        """
        Check if any sensor conditional statements are activated and
        execute their actions if the conditional is true.

        For example, if measured temperature is above 30C, notify [email protected]

        :rtype: None

        :param cond_id: ID of conditional to check
        :type cond_id: str
        """
        logger_cond = logging.getLogger(
            "mycodo.SensorCond-{id}".format(id=cond_id))
        attachment_file = False
        attachment_type = False
        message = ""

        conditional = False
        if self.cond_edge_detected[cond_id]:
            conditional = 'edge'
        elif self.cond_direction[cond_id]:
            conditional = 'measurement'

        now = time.time()
        timestamp = datetime.datetime.fromtimestamp(now).strftime(
            '%Y-%m-%d %H-%M-%S')

        if conditional == 'measurement':
            last_measurement = self.get_last_measurement(
                self.cond_measurement_type[cond_id])
            if (last_measurement and
                ((self.cond_direction[cond_id] == 'above'
                  and last_measurement > self.cond_setpoint[cond_id]) or
                 (self.cond_direction[cond_id] == 'below'
                  and last_measurement < self.cond_setpoint[cond_id]))):

                message = "{}\n[Sensor Conditional {}] {}\n{} {} ".format(
                    timestamp, cond_id, self.cond_name[cond_id],
                    self.cond_measurement_type[cond_id], last_measurement)
                if self.cond_direction[cond_id] == 'above':
                    message += ">"
                elif self.cond_direction[cond_id] == 'below':
                    message += "<"
                message += " {} setpoint.".format(self.cond_setpoint[cond_id])
            else:
                logger_cond.debug("Last measurement not found")
                return 1

        elif conditional == 'edge':
            if self.cond_edge_select[cond_id] == 'edge':
                message = "{}\n[Sensor Conditional {}] {}. {} Edge Detected.".format(
                    timestamp, cond_id, self.cond_name[cond_id],
                    self.cond_edge_detected)
            elif self.cond_edge_select[cond_id] == 'state':
                if GPIO.input(int(
                        self.location)) == self.cond_gpio_state[cond_id]:
                    message = "{}\n[Sensor Conditional {}] {}. {} GPIO State Detected.".format(
                        timestamp, cond_id, self.cond_name[cond_id],
                        self.cond_gpio_state[cond_id])
                else:
                    return 0

        if (self.cond_relay_id[cond_id]
                and self.cond_relay_state[cond_id] in ['on', 'off']):
            message += "\nTurning relay {} {}".format(
                self.cond_relay_id[cond_id], self.cond_relay_state[cond_id])
            if (self.cond_relay_state[cond_id] == 'on'
                    and self.cond_relay_on_duration[cond_id]):
                message += " for {} seconds".format(
                    self.cond_relay_on_duration[cond_id])
            message += ". "
            relay_on_off = threading.Thread(
                target=self.control.relay_on_off,
                args=(
                    self.cond_relay_id[cond_id],
                    self.cond_relay_state[cond_id],
                    self.cond_relay_on_duration[cond_id],
                ))
            relay_on_off.start()

        # Execute command in shell
        if self.cond_execute_command[cond_id]:
            message += "\nExecute '{}'. ".format(
                self.cond_execute_command[cond_id])
            _, _, cmd_status = cmd_output(self.cond_execute_command[cond_id])
            message += "Status: {}. ".format(cmd_status)

        if self.cond_camera_record[cond_id] in ['photo', 'photoemail']:
            camera_still = db_retrieve_table(MYCODO_DB_PATH,
                                             CameraStill,
                                             entry='first')
            attachment_file = camera_record('photo', camera_still)
        elif self.cond_camera_record[cond_id] in ['video', 'videoemail']:
            camera_stream = db_retrieve_table(MYCODO_DB_PATH,
                                              CameraStream,
                                              entry='first')
            attachment_file = camera_record('video',
                                            camera_stream,
                                            duration_sec=5)

        if self.cond_email_notify[cond_id]:
            if (self.email_count >= self.smtp_max_count
                    and time.time() < self.smtp_wait_timer[cond_id]):
                self.allowed_to_send_notice = False
            else:
                if time.time() > self.smtp_wait_timer[cond_id]:
                    self.email_count = 0
                    self.smtp_wait_timer[cond_id] = time.time() + 3600
                self.allowed_to_send_notice = True
            self.email_count += 1

            # If the emails per hour limit has not been exceeded
            if self.allowed_to_send_notice:
                message += "\nNotify {}.".format(
                    self.cond_email_notify[cond_id])
                # attachment_type != False indicates to
                # attach a photo or video
                if self.cond_camera_record[cond_id] == 'photoemail':
                    message += "\nPhoto attached."
                    attachment_type = 'still'
                elif self.cond_camera_record[cond_id] == 'videoemail':
                    message += "\nVideo attached."
                    attachment_type = 'video'

                smtp = db_retrieve_table(MYCODO_DB_PATH, SMTP, entry='first')
                send_email(smtp.host, smtp.ssl, smtp.port, smtp.user,
                           smtp.passw, smtp.email_from,
                           self.cond_email_notify[cond_id], message,
                           attachment_file, attachment_type)
            else:
                logger_cond.debug(
                    "{:.0f} seconds left to be allowed to email "
                    "again.".format(self.smtp_wait_timer[cond_id] -
                                    time.time()))

        if self.cond_flash_lcd[cond_id]:
            start_flashing = threading.Thread(target=self.control.flash_lcd,
                                              args=(
                                                  self.cond_flash_lcd[cond_id],
                                                  1,
                                              ))
            start_flashing.start()

        logger_cond.debug(message)
Example #8
0
    def check_conditionals(self, relay_id, on_duration):
        conditionals = db_retrieve_table(MYCODO_DB_PATH, RelayConditional)

        conditionals = conditionals.filter(
            RelayConditional.if_relay_id == relay_id)
        conditionals = conditionals.filter(RelayConditional.activated == True)

        if self.is_on(relay_id):
            conditionals = conditionals.filter(
                RelayConditional.if_action == 'on')
            conditionals = conditionals.filter(
                RelayConditional.if_duration == on_duration)
        else:
            conditionals = conditionals.filter(
                RelayConditional.if_action == 'off')

        for each_conditional in conditionals.all():
            message = None
            if (each_conditional.do_relay_id
                    or each_conditional.execute_command
                    or each_conditional.email_notify):
                now = time.time()
                timestamp = datetime.datetime.fromtimestamp(now).strftime(
                    '%Y-%m-%d %H-%M-%S')
                message = "{}\n[Relay Conditional {}] {}\n".format(
                    timestamp, each_conditional.id, each_conditional.name)
                message += "If relay {} ({}) turns {}, Then:\n".format(
                    each_conditional.if_relay_id,
                    self.relay_name[each_conditional.if_relay_id],
                    each_conditional.if_action)

            if each_conditional.do_relay_id:
                message += "Turn relay {} ({}) {}".format(
                    each_conditional.do_relay_id,
                    self.relay_name[each_conditional.do_relay_id],
                    each_conditional.do_action)

                if each_conditional.do_duration == 0:
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action)
                else:
                    message += " for {} seconds".format(
                        each_conditional.do_duration)
                    self.relay_on_off(each_conditional.do_relay_id,
                                      each_conditional.do_action,
                                      each_conditional.do_duration)
                message += ".\n"

            if each_conditional.execute_command:
                # Execute command as user mycodo
                message += "Execute: '{}'. ".format(
                    each_conditional.execute_command)
                _, _, cmd_status = cmd_output(each_conditional.execute_command)
                message += "Status: {}. ".format(cmd_status)

            if each_conditional.email_notify:
                if (self.email_count >= self.smtp_max_count
                        and time.time() < self.smtp_wait_time):
                    self.allowed_to_send_notice = False
                else:
                    if time.time() > self.smtp_wait_time:
                        self.email_count = 0
                        self.smtp_wait_time = time.time() + 3600
                    self.allowed_to_send_notice = True
                self.email_count += 1

                if self.allowed_to_send_notice:
                    message += "Notify {}.".format(
                        each_conditional.email_notify)

                    smtp = db_retrieve_table(MYCODO_DB_PATH,
                                             SMTP,
                                             entry='first')
                    send_email(smtp.host, smtp.ssl, smtp.port, smtp.user,
                               smtp.passw, smtp.email_from,
                               each_conditional.email_notify, message)
                else:
                    self.logger.debug("[Relay Conditional {}] True: "
                                      "{:.0f} seconds left to be "
                                      "allowed to email again.".format(
                                          each_conditional.id,
                                          (self.smtp_wait_time - time.time())))

            if each_conditional.flash_lcd:
                start_flashing = threading.Thread(
                    target=self.control.flash_lcd,
                    args=(
                        each_conditional.flash_lcd,
                        1,
                    ))
                start_flashing.start()

            if (each_conditional.do_relay_id
                    or each_conditional.execute_command
                    or each_conditional.email_notify):
                self.logger.debug("{}".format(message))
Example #9
0
    def __init__(self, ready, lcd_id):
        threading.Thread.__init__(self)

        self.logger = logging.getLogger("mycodo.lcd_{id}".format(id=lcd_id))

        self.running = False
        self.thread_startup_timer = timeit.default_timer()
        self.thread_shutdown_timer = 0
        self.ready = ready
        self.flash_lcd_on = False
        self.lcd_is_on = False
        self.lcd_id = lcd_id

        try:
            lcd = db_retrieve_table(MYCODO_DB_PATH,
                                    LCD,
                                    device_id=self.lcd_id)

            self.lcd_name = lcd.name
            self.lcd_pin = lcd.pin
            self.lcd_period = lcd.period
            self.lcd_x_characters = lcd.x_characters
            self.lcd_y_lines = lcd.y_lines

            if lcd.multiplexer_address:
                self.multiplexer_address_string = lcd.multiplexer_address
                self.multiplexer_address = int(str(lcd.multiplexer_address),
                                               16)
                self.multiplexer_channel = lcd.multiplexer_channel
                self.multiplexer = TCA9548A(self.multiplexer_address)
            else:
                self.multiplexer = None

            self.lcd_line = {}
            for i in range(1, 5):
                self.lcd_line[i] = {}

            list_sensors = [
                'sensor_time', 'temperature', 'humidity', 'co2', 'pressure',
                'altitude', 'temperature_die', 'temperature_object', 'lux'
            ]

            list_pids = ['setpoint', 'pid_time']

            list_relays = ['duration_sec', 'relay_time', 'relay_state']

            if self.lcd_y_lines in [2, 4]:
                self.lcd_line[1]['id'] = lcd.line_1_sensor_id
                self.lcd_line[1]['measurement'] = lcd.line_1_measurement
                if lcd.line_1_sensor_id:
                    table = None
                    if lcd.line_1_measurement in list_sensors:
                        table = Sensor
                    elif lcd.line_1_measurement in list_pids:
                        table = PID
                    elif lcd.line_1_measurement in list_relays:
                        table = Relay
                    sensor_line_1 = db_retrieve_table(
                        MYCODO_DB_PATH,
                        table,
                        device_id=lcd.line_1_sensor_id)
                    self.lcd_line[1]['name'] = sensor_line_1.name
                    if 'time' in lcd.line_1_measurement:
                        self.lcd_line[1]['measurement'] = 'time'

                self.lcd_line[2]['id'] = lcd.line_2_sensor_id
                self.lcd_line[2]['measurement'] = lcd.line_2_measurement
                if lcd.line_2_sensor_id:
                    table = None
                    if lcd.line_2_measurement in list_sensors:
                        table = Sensor
                    elif lcd.line_2_measurement in list_pids:
                        table = PID
                    elif lcd.line_2_measurement in list_relays:
                        table = Relay
                    sensor_line_2 = db_retrieve_table(
                        MYCODO_DB_PATH,
                        table,
                        device_id=lcd.line_2_sensor_id)
                    self.lcd_line[2]['name'] = sensor_line_2.name
                    if 'time' in lcd.line_2_measurement:
                        self.lcd_line[2]['measurement'] = 'time'

            if self.lcd_y_lines == 4:
                self.lcd_line[3]['id'] = lcd.line_3_sensor_id
                self.lcd_line[3]['measurement'] = lcd.line_3_measurement
                if lcd.line_3_sensor_id:
                    table = None
                    if lcd.line_3_measurement in list_sensors:
                        table = Sensor
                    elif lcd.line_3_measurement in list_pids:
                        table = PID
                    elif lcd.line_3_measurement in list_relays:
                        table = Relay
                    sensor_line_3 = db_retrieve_table(
                        MYCODO_DB_PATH,
                        table,
                        device_id=lcd.line_3_sensor_id)
                    self.lcd_line[3]['name'] = sensor_line_3.name
                    if 'time' in lcd.line_3_measurement:
                        self.lcd_line[3]['measurement'] = 'time'

                self.lcd_line[4]['id'] = lcd.line_4_sensor_id
                self.lcd_line[4]['measurement'] = lcd.line_4_measurement
                if lcd.line_4_sensor_id:
                    table = None
                    if lcd.line_4_measurement in list_sensors:
                        table = Sensor
                    elif lcd.line_4_measurement in list_pids:
                        table = PID
                    elif lcd.line_4_measurement in list_relays:
                        table = Relay
                    sensor_line_4 = db_retrieve_table(
                        MYCODO_DB_PATH,
                        table,
                        device_id=lcd.line_4_sensor_id)
                    self.lcd_line[4]['name'] = sensor_line_4.name
                    if 'time' in lcd.line_4_measurement:
                        self.lcd_line[4]['measurement'] = 'time'

            self.timer = time.time() + self.lcd_period
            self.backlight_timer = time.time()

            self.lcd_string_line = {}
            for i in range(1, self.lcd_y_lines + 1):
                self.lcd_string_line[i] = ''

            self.LCD_WIDTH = self.lcd_x_characters  # Max characters per line

            self.LCD_LINE = {
                1: 0x80,
                2: 0xC0,
                3: 0x94,
                4: 0xD4
            }

            self.LCD_CHR = 1  # Mode - Sending data
            self.LCD_CMD = 0  # Mode - SenLCDding command

            self.LCD_BACKLIGHT = 0x08  # On
            self.LCD_BACKLIGHT_OFF = 0x00  # Off

            self.ENABLE = 0b00000100  # Enable bit

            # Timing constants
            self.E_PULSE = 0.0005
            self.E_DELAY = 0.0005

            # Setup I2C bus
            try:
                if GPIO.RPI_REVISION == 2 or GPIO.RPI_REVISION == 3:
                    i2c_bus_number = 1
                else:
                    i2c_bus_number = 0
                self.bus = smbus.SMBus(i2c_bus_number)
            except Exception as except_msg:
                self.logger.exception(
                    "Could not initialize I2C bus: {err}".format(
                        err=except_msg))

            self.I2C_ADDR = int(self.lcd_pin, 16)
            self.lcd_init()
            self.lcd_string_write('Mycodo {}'.format(MYCODO_VERSION),
                                  self.LCD_LINE[1])
            self.lcd_string_write('Start {}'.format(
                self.lcd_name), self.LCD_LINE[2])
        except Exception as except_msg:
            self.logger.exception("Error: {err}".format(err=except_msg))
Example #10
0
    def get_lcd_strings(self):
        """
        Retrieve measurements and/or timestamps and create strings for LCDs
        If no data is retrieveable, create string "NO DATA RETURNED".
        """
        # loop to acquire all measurements required to be displayed on the LCD
        for i in range(1, self.lcd_y_lines + 1):
            if self.lcd_line[i]['id']:
                # Get latest measurement (within past minute) from influxdb
                # FROM '/.*/' returns any measurement (for grabbing time of last measurement)
                last_measurement_success = False
                try:
                    if self.lcd_line[i]['measurement'] == 'relay_state':
                        self.lcd_line[i]['measurement_value'] = self.relay_state(self.lcd_line[i]['id'])
                        last_measurement_success = True
                    else:
                        if self.lcd_line[i]['measurement'] == 'time':
                            last_measurement = read_last_influxdb(
                                self.lcd_line[i]['id'],
                                '/.*/').raw
                        else:
                            last_measurement = read_last_influxdb(
                                self.lcd_line[i]['id'],
                                self.lcd_line[i]['measurement']).raw
                        if last_measurement:
                            number = len(last_measurement['series'][0]['values'])
                            self.lcd_line[i]['time'] = last_measurement['series'][0]['values'][number - 1][0]
                            self.lcd_line[i]['measurement_value'] = last_measurement['series'][0]['values'][number - 1][1]
                            utc_dt = datetime.datetime.strptime(
                                self.lcd_line[i]['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 {}: {} @ {}".format(
                                self.lcd_line[i]['measurement'],
                                self.lcd_line[i]['measurement_value'], local_timestamp))
                            last_measurement_success = True
                        else:
                            self.lcd_line[i]['time'] = None
                            self.lcd_line[i]['measurement_value'] = None
                            self.logger.debug("No data returned from "
                                              "influxdb")
                except Exception as except_msg:
                    self.logger.debug("Failed to read measurement from the "
                                      "influxdb database: {err}".format(
                                        err=except_msg))

                try:
                    if last_measurement_success:
                        # Determine if the LCD output will have a value unit
                        measurement = ''
                        if self.lcd_line[i]['measurement'] == 'setpoint':
                            pid = db_retrieve_table(
                                MYCODO_DB_PATH,
                                PID,
                                device_id=self.lcd_line[i]['id'])
                            measurement = pid.measure_type
                        elif self.lcd_line[i]['measurement'] in [
                                'temperature',
                                'temperature_die',
                                'temperature_object',
                                'humidity',
                                'co2',
                                'lux',
                                'pressure',
                                'altitude']:
                            measurement = self.lcd_line[i]['measurement']
                        elif self.lcd_line[i]['measurement'] == 'duration_sec':
                            measurement = 'duration_sec'

                        # Produce the line that will be displayed on the LCD
                        number_characters = self.lcd_x_characters
                        if self.lcd_line[i]['measurement'] == 'time':
                            # Convert UTC timestamp to local timezone
                            utc_dt = datetime.datetime.strptime(
                                self.lcd_line[i]['time'].split(".")[0],
                                '%Y-%m-%dT%H:%M:%S')
                            utc_timestamp = calendar.timegm(utc_dt.timetuple())
                            self.lcd_string_line[i] = str(
                                datetime.datetime.fromtimestamp(utc_timestamp))
                        elif measurement:
                            value_length = len(str(
                                self.lcd_line[i]['measurement_value']))
                            unit_length = len(MEASUREMENT_UNITS[measurement])
                            name_length = number_characters - value_length - unit_length - 2
                            name_cropped = self.lcd_line[i]['name'].ljust(name_length)[:name_length]
                            self.lcd_string_line[i] = '{} {} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'],
                                MEASUREMENT_UNITS[measurement])
                        else:
                            value_length = len(str(
                                self.lcd_line[i]['measurement_value']))
                            name_length = number_characters - value_length - 1
                            name_cropped = self.lcd_line[i]['name'][:name_length]
                            self.lcd_string_line[i] = '{} {}'.format(
                                name_cropped,
                                self.lcd_line[i]['measurement_value'])
                    else:
                        self.lcd_string_line[i] = 'NO DATA < 5 MIN'
                except Exception as except_msg:
                    self.logger.exception("Error: {err}".format(
                        err=except_msg))
            else:
                self.lcd_string_line[i] = ''