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)
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)
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
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)
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
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
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)
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))
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))
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] = ''