def __init__(self, ready, logger, timer_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.timer_id = timer_id self.control = DaemonControl() with session_scope(MYCODO_DB_PATH) as new_session: timer = new_session.query(Timer).filter( Timer.id == self.timer_id).first() self.name = timer.name self.relay_id = timer.relay_id self.state = timer.state self.time = timer.time_on self.duration_on = timer.duration_on self.duration_off = timer.duration_off # Time of day split into hour and minute if self.time: time_split = self.time.split(":") self.hour = time_split[0] self.minute = time_split[1] else: self.hour = None self.minute = None self.duration_timer = time.time() self.date_timer_not_executed = True self.running = False
def startup_stats(self): """Initial statistics collection and transmssion at startup""" with session_scope(MYCODO_DB_PATH) as new_session: misc = new_session.query(Misc).first() self.opt_out_statistics = misc.stats_opt_out if not self.opt_out_statistics: self.logger.info("[Daemon] Anonymous statistics enabled") else: self.logger.info("[Daemon] Anonymous statistics disabled") try: # if statistics file doesn't exist, create it if not os.path.isfile(STATS_CSV): self.logger.debug("[Daemon] Statistics file doesn't " "exist, creating {}".format(STATS_CSV)) daemonutils.recreate_stat_file() daemon_startup_time = timeit.default_timer()-self.startup_timer self.logger.info("[Daemon] Mycodo v{} started in {} seconds".format( MYCODO_VERSION, daemon_startup_time)) daemonutils.add_update_stat( self.logger, 'daemon_startup_seconds', daemon_startup_time) except Exception as msg: self.logger.warning( "[Daemon] Statistics initilization Error: {}".format(msg))
def __init__(self, ready, pid_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.pid_{id}".format(id=pid_id)) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.pid_id = pid_id self.control = DaemonControl() self.initialize_values() self.control_variable = 0 self.Derivator = 0 self.Integrator = 0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.set_point = 0 self.lower_seconds_on = 0 self.raise_seconds_on = 0 self.last_measurement_success = False self.timer = t.time() + self.measure_interval # Check if a method is set for this PID if self.method_id: method = db_retrieve_table(MYCODO_DB_PATH, Method) method = method.filter(Method.method_id == self.method_id) method = method.filter(Method.method_order == 0).first() self.method_type = method.method_type self.method_start_time = method.start_time if self.method_type == 'Duration': if self.method_start_time == 'Ended': # Method has ended and hasn't been instructed to begin again pass elif self.method_start_time == 'Ready' or self.method_start_time is None: # Method has been instructed to begin with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(Method) mod_method = mod_method.filter( Method.method_id == self.method_id) mod_method = mod_method.filter( Method.method_order == 0).first() mod_method.start_time = datetime.datetime.now() self.method_start_time = mod_method.start_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( str(self.method_start_time), '%Y-%m-%d %H:%M:%S.%f') self.logger.warning( "Resuming method {id} started at {time}".format( id=self.method_id, time=self.method_start_time))
def __init__(self, ready, logger, pid_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.pid_id = pid_id self.control = DaemonControl() with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter(PID.id == self.pid_id).first() self.sensor_id = pid.sensor_id self.measure_type = pid.measure_type self.direction = pid.direction self.raise_relay_id = pid.raise_relay_id self.raise_min_duration = pid.raise_min_duration self.raise_max_duration = pid.raise_max_duration self.lower_relay_id = pid.lower_relay_id self.lower_min_duration = pid.lower_min_duration self.lower_max_duration = pid.lower_max_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.measure_interval = pid.period self.default_set_point = pid.setpoint self.set_point = pid.setpoint with session_scope(MYCODO_DB_PATH) as new_session: self.pidsetpoints = new_session.query(PIDSetpoints) self.pidsetpoints = self.pidsetpoints.filter(PIDSetpoints.pid_id == self.pid_id) self.pidsetpoints = self.pidsetpoints.order_by(PIDSetpoints.start_time.asc()) new_session.expunge_all() new_session.close() self.Derivator = 0 self.Integrator = 0 self.Integrator_max = 500 self.Integrator_min = -500 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.raise_seconds_on = 0 self.timer = t.time() + self.measure_interval
def stop_controller(self, ended_normally=True, deactivate_pid=False): self.thread_shutdown_timer = timeit.default_timer() self.running = False # Unset method start time if self.method_id and ended_normally: with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.id == self.pid_id).first() mod_pid.method_start_time = 'Ended' mod_pid.method_end_time = None db_session.commit() if deactivate_pid: with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.id == self.pid_id).first() mod_pid.is_activated = False db_session.commit()
def stop_controller(self): self.thread_shutdown_timer = timeit.default_timer() self.running = False # Unset method start time if self.method_id: with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(Method).filter( Method.id == self.method_id).first() mod_method.method_start_time = 'Ended' db_session.commit()
def relay_state(self, relay_id): with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter(Relay.id == relay_id).first() gpio_state = '' GPIO.setmode(GPIO.BCM) if GPIO.input(relay.pin) == relay.trigger: gpio_state = 'On' else: gpio_state = 'Off' return gpio_state
def relay_state(self, relay_id): with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == relay_id).first() gpio_state = '' GPIO.setmode(GPIO.BCM) if GPIO.input(relay.pin) == relay.trigger: gpio_state = 'On' else: gpio_state = 'Off' return gpio_state
def stopController(self): self.thread_shutdown_timer = timeit.default_timer() self.running = False # Unset method start time if self.method_id: with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(Method) mod_method = mod_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()
def start_all_controllers(self): """Start all activated controllers""" # Obtain database configuration options with session_scope(MYCODO_DB_PATH) as new_session: lcd = new_session.query(LCD).all() log = new_session.query(Log).all() pid = new_session.query(PID).all() sensor = new_session.query(Sensor).all() timer = new_session.query(Timer).all() new_session.expunge_all() new_session.close() # Start thread to control relays turning on and off self.logger.debug("[Daemon] Starting relay controller") self.controller['Relay'] = RelayController(self.logger) self.controller['Relay'].start() # Start threads to modulate relays at predefined periods self.logger.debug("[Daemon] Starting all activated timer controllers") for each_timer in timer: if each_timer.activated: self.activateController('Timer', each_timer.id) self.logger.info("[Daemon] All activated timer controllers started") # Start threads to read sensors and create influxdb entries self.logger.debug("[Daemon] Starting all activated sensor controllers") for each_sensor in sensor: if each_sensor.activated: self.activateController('Sensor', each_sensor.id) self.logger.info("[Daemon] All activated sensor controllers started") # Start threads to read influxdb entries and write to a log file self.logger.debug("[Daemon] Starting all activated log controllers") for each_log in log: if each_log.activated: self.activateController('Log', each_log.id) self.logger.info("[Daemon] All activated log controllers started") # start threads to manipulate relays with discrete PID control self.logger.debug("[Daemon] Starting all activated PID controllers") for each_pid in pid: if each_pid.activated: self.activateController('PID', each_pid.id) self.logger.info("[Daemon] All activated PID controllers started") # start threads to output to LCDs self.logger.debug("[Daemon] Starting all activated LCD controllers") for each_lcd in lcd: if each_lcd.activated: self.activateController('LCD', each_lcd.id) self.logger.info("[Daemon] All activated LCD controllers started")
def start_all_controllers(self): """ Start all activated controllers See the files named controller_[name].py for details of what each controller does. """ # Obtain database configuration options with session_scope(MYCODO_DB_PATH) as new_session: lcd = new_session.query(LCD).all() log = new_session.query(Log).all() pid = new_session.query(PID).all() sensor = new_session.query(Sensor).all() timer = new_session.query(Timer).all() new_session.expunge_all() new_session.close() self.logger.debug("[Daemon] Starting relay controller") self.controller['Relay'] = RelayController(self.logger) self.controller['Relay'].start() self.logger.debug("[Daemon] Starting all activated timer controllers") for each_timer in timer: if each_timer.activated: self.activateController('Timer', each_timer.id) self.logger.info("[Daemon] All activated timer controllers started") self.logger.debug("[Daemon] Starting all activated sensor controllers") for each_sensor in sensor: if each_sensor.activated: self.activateController('Sensor', each_sensor.id) self.logger.info("[Daemon] All activated sensor controllers started") self.logger.debug("[Daemon] Starting all activated log controllers") for each_log in log: if each_log.activated: self.activateController('Log', each_log.id) self.logger.info("[Daemon] All activated log controllers started") self.logger.debug("[Daemon] Starting all activated PID controllers") for each_pid in pid: if each_pid.activated: self.activateController('PID', each_pid.id) self.logger.info("[Daemon] All activated PID controllers started") self.logger.debug("[Daemon] Starting all activated LCD controllers") for each_lcd in lcd: if each_lcd.activated: self.activateController('LCD', each_lcd.id) self.logger.info("[Daemon] All activated LCD controllers started")
def __init__(self, logger): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.logger = logger self.control = DaemonControl() self.relay_id = {} self.relay_name = {} self.relay_pin = {} self.relay_amps = {} self.relay_trigger = {} self.relay_start_state = {} self.relay_on_until = {} self.relay_last_duration = {} self.relay_on_duration = {} self.logger.debug("[Relay] Initializing relays") try: # Setup GPIO (BCM numbering) and initialize all relays in database GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() self.smtp_max_count = smtp.hourly_max self.smtp_wait_time = time.time() + 3600 self.smtp_timer = time.time() self.email_count = 0 self.allowed_to_send_notice = True relays = new_session.query(Relay).all() self.all_relays_initialize(relays) # Turn all relays off self.all_relays_off() # Turn relays on that are set to be on at start self.all_relays_on() self.logger.info("[Relay] Finished initializing relays") except Exception as except_msg: self.logger.exception("[Relay] Problem initializing " "relays: {}", except_msg) self.running = False
def __init__(self, logger): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.logger = logger self.control = DaemonControl() self.relay_id = {} self.relay_name = {} self.relay_pin = {} self.relay_amps = {} self.relay_trigger = {} self.relay_start_state = {} self.relay_on_until = {} self.relay_last_duration = {} self.relay_on_duration = {} self.logger.debug("[Relay] Initializing Relays") try: # Setup GPIO (BCM numbering) and initialize all relays in database GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() self.smtp_max_count = smtp.hourly_max self.smtp_wait_time = time.time() + 3600 self.smtp_timer = time.time() self.email_count = 0 self.allowed_to_send_notice = True relays = new_session.query(Relay).all() self.all_relays_initialize(relays) # Turn all relays off self.all_relays_off() # Turn relays on that are set to be on at start self.all_relays_on() self.logger.info("[Relay] Relays Initialized") except Exception as except_msg: self.logger.exception("[Relay] Problem initializing " "relays: {}", except_msg) self.running = False
def __init__(self, ready, logger, log_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.log_id = log_id with session_scope(MYCODO_DB_PATH) as new_session: log = new_session.query(Log).filter(Log.id == self.log_id).first() self.name = log.name self.sensor_id = log.sensor_id self.measure_type = log.measure_type self.period = log.period self.last_timestamp = 0 self.timer = time.time() + self.period self.running = False self.lastUpdate = None
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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == relay_id).first() 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] Relay {} ({}) ".format( self.relay_id[relay_id], self.relay_name[relay_id]) if do_setup_pin: self.setup_pin(relay.pin) message += "initiliazed" else: message += "added" self.logger.debug(message) return 0, "success" except Exception as msg: return 1, "Add_Mod_Relay Error: ID {}: {}".format(relay_id, msg)
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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == relay_id).first() 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_last_duration[relay_id] = 0 self.relay_on_duration[relay_id] = False message = "[Relay] Relay {} ({}) ".format( self.relay_id[relay_id], self.relay_name[relay_id]) if not do_setup_pin: message += "added" else: self.setup_pin(relay.pin) message += "initiliazed" self.logger.debug(message) return 0, "success" except Exception as msg: return 1, "Error: {}".format(msg)
def __init__(self, new_logger): threading.Thread.__init__(self) self.startup_timer = timeit.default_timer() self.thread_shutdown_timer = None self.logger = new_logger self.daemon_run = True self.terminated = False self.controller = {} self.controller['LCD'] = {} self.controller['Log'] = {} self.controller['PID'] = {} self.controller['Relay'] = None self.controller['Sensor'] = {} self.controller['Timer'] = {} self.timer_ram_use = time.time() self.timer_stats = time.time()+120 with session_scope(MYCODO_DB_PATH) as new_session: misc = new_session.query(Misc).first() self.opt_out_statistics = misc.stats_opt_out if self.opt_out_statistics: self.logger.info("[Daemon] Anonymous statistics disabled") else: self.logger.info("[Daemon] Anonymous statistics enabled")
def __init__(self, new_logger): threading.Thread.__init__(self) self.startup_timer = timeit.default_timer() self.thread_shutdown_timer = None self.logger = new_logger self.daemon_run = True self.terminated = False self.controller = {} self.controller['LCD'] = {} self.controller['Log'] = {} self.controller['PID'] = {} self.controller['Relay'] = None self.controller['Sensor'] = {} self.controller['Timer'] = {} self.timer_ram_use = time.time() self.timer_stats = time.time() + 120 with session_scope(MYCODO_DB_PATH) as new_session: misc = new_session.query(Misc).first() self.opt_out_statistics = misc.stats_opt_out if self.opt_out_statistics: self.logger.info("[Daemon] Anonymous statistics disabled") else: self.logger.info("[Daemon] Anonymous statistics enabled")
def db_retrieve_table(database, table, entry=None, device_id=None): """ Return SQL database query object with optional filtering If entry='first', only the first table entry is returned. If entry='all', all table entries are returned. If device_id is set, the first entry with that device ID is returned. Otherwise, the table object is returned. """ with session_scope(database) as new_session: if device_id: return_table = new_session.query(table).filter( table.id == device_id) else: return_table = new_session.query(table) if entry == 'first' or device_id: return_table = return_table.first() elif entry == 'all': return_table = return_table.all() new_session.expunge_all() new_session.close() return return_table
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 (from within the 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( INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, self.lcd_line[i]['id'], '/.*/', duration_min=5).raw else: last_measurement = read_last_influxdb( INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, self.lcd_line[i]['id'], self.lcd_line[i]['measurement'], duration_min=5).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("[LCD {}] Latest {}: {} @ {}".format( self.lcd_id, 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("[LCD {}] No data returned " "from influxdb".format(self.lcd_id)) except Exception as except_msg: self.logger.debug("[LCD {}] Failed to read " "measurement from the influxdb database: " "{}".format(self.lcd_id, 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': with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter( PID.id == self.lcd_line[i]['id']).first() new_session.expunge_all() new_session.close() 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(self.measurement_unit['metric'][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'], self.measurement_unit['metric'][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("[LCD {}] Error ({}): {}".format( self.lcd_id, except_msg)) else: self.lcd_string_line[i] = ''
def checkConditionals(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 each_cond: Object of SQL table entries for a specific column :type each_cond: sqlalchemy object """ last_measurement = self.getLastMeasurement(self.cond_measurement_type[cond_id]) if last_measurement: message = "[Sensor Conditional {}] Last measurement {} is ".format( cond_id, last_measurement) if ((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])): if (self.cond_relay_id[cond_id] and self.cond_relay_state[cond_id] in ['on', 'off']): message += " {} {}, turning relay {} {}".format( self.cond_direction[cond_id], self.cond_setpoint[cond_id], 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() if self.cond_execute_command[cond_id]: message += "Execute '{}'. ".format( self.cond_execute_command[cond_id]) # execute command here 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 self.allowed_to_send_notice: message += "Notify {}.".format( self.cond_email_notify[cond_id]) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() email(self.logger, smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, self.cond_email_notify[cond_id], message) else: self.logger.debug("[Sensor Conditional {}] " "{:.0f} seconds left to be " "allowed to email again.".format( cond_id, (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() self.logger.debug(message) else: self.logger.debug("[Sensor Conditional {}] Last measurement " "not found".format(cond_id))
def send_stats(logger, host, port, user, password, dbname): """ Send anonymous usage statistics Example use: current_stat = return_stat_file_dict() add_update_stat(logger, 'stat', current_stat['stat'] + 5) """ try: client = InfluxDBClient(host, port, user, password, dbname) # Prepare stats before sending with session_scope(MYCODO_DB_PATH) as new_session: relays = new_session.query(Relay) add_update_stat(logger, 'num_relays', get_count(relays)) sensors = new_session.query(Sensor) add_update_stat(logger, 'num_sensors', get_count(sensors)) add_update_stat(logger, 'num_sensors_active', get_count(sensors.filter( Sensor.activated == True))) pids = new_session.query(PID) add_update_stat(logger, 'num_pids', get_count(pids)) add_update_stat(logger, 'num_pids_active', get_count(pids.filter( PID.activated == True))) lcds = new_session.query(LCD) add_update_stat(logger, 'num_lcds', get_count(lcds)) add_update_stat(logger, 'num_lcds_active', get_count(lcds.filter( LCD.activated == True))) logs = new_session.query(Log) add_update_stat(logger, 'num_logs', get_count(logs)) add_update_stat(logger, 'num_logs_active', get_count(logs.filter( Log.activated == True))) timers = new_session.query(Timer) add_update_stat(logger, 'num_timers', get_count(timers)) add_update_stat(logger, 'num_timers_active', get_count(timers.filter( Timer.activated == True))) add_update_stat(logger, 'country', geocoder.ip('me').country) add_update_stat(logger, 'ram_use_mb', resource.getrusage( resource.RUSAGE_SELF).ru_maxrss / float(1000)) user_count = 0 admin_count = 0 with session_scope(USER_DB_PATH) as db_session: users = db_session.query(Users).all() for each_user in users: user_count += 1 if each_user.user_restriction == 'admin': admin_count += 1 add_update_stat(logger, 'num_users_admin', admin_count) add_update_stat(logger, 'num_users_guest', user_count-admin_count) add_update_stat(logger, 'Mycodo_revision', MYCODO_VERSION) # Combine stats into list of dictionaries to be pushed to influxdb new_stats_dict = return_stat_file_dict() formatted_stat_dict = [] for each_key, each_value in new_stats_dict.iteritems(): if each_key != 'stat': # Do not send header row formatted_stat_dict = add_stat_dict(formatted_stat_dict, new_stats_dict['id'], each_key, each_value) # Send stats to influxdb client.write_points(formatted_stat_dict) logger.debug("[Daemon] Sent anonymous usage statistics") return 0 except Exception as except_msg: logger.warning('[Daemon] Could not send anonymous usage statictics: ' '{}'.format(except_msg)) return 1
def checkConditionals(self, relay_id, on_duration): with session_scope(MYCODO_DB_PATH) as new_session: conditionals = new_session.query(RelayConditional) new_session.expunge_all() new_session.close() 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_out, cmd_err, cmd_status = cmd_output( self.cond_execute_command[cond_id]) 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) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() send_email(self.logger, 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_cond.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, logger, lcd_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.flash_lcd_on = False self.lcd_is_on = False try: with session_scope(MYCODO_DB_PATH) as new_session: lcd = new_session.query(LCD).filter(LCD.id == lcd_id).first() self.lcd_id = 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: 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 = new_session.query(table).filter( table.id == lcd.line_1_sensor_id).first() 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: 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 = new_session.query(table).filter( table.id == lcd.line_2_sensor_id).first() 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: 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 = new_session.query(table).filter( table.id == lcd.line_3_sensor_id).first() 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: 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 = new_session.query(table).filter( table.id == lcd.line_4_sensor_id).first() self.lcd_line[4]['name'] = sensor_line_4.name if 'time' in lcd.line_4_measurement: self.lcd_line[4]['measurement'] = 'time' self.measurement_unit = {} self.measurement_unit['metric'] = { "temperature": "C", "humidity": "%", "co2": "ppmv", "pressure": "Pa", "altitude": "m", "duration_sec": "s", "temperature_die": "C", "temperature_object": "C", "lux": "lux", } self.measurement_unit['standard'] = { "temperature": "F", "humidity": "%", "co2": "ppmv", "pressure": "atm", "altitude": "ft", "duration_sec": "s", "temperature_die": "F", "temperature_object": "F", "lux": "lux", } 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 # Maximum characters per line self.LCD_LINE = {} self.LCD_LINE[1] = 0x80 # LCD RAM address for the 1st line self.LCD_LINE[2] = 0xC0 # LCD RAM address for the 2nd line self.LCD_LINE[3] = 0x94 # LCD RAM address for the 3rd line self.LCD_LINE[4] = 0xD4 # LCD RAM address for the 4th line 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: {}".format(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("[LCD {}] Error: {}".format( self.lcd_id, except_msg))
def __init__(self, ready, logger, sensor_id): threading.Thread.__init__(self) list_devices_i2c = [ 'ADS1x15', 'AM2315', 'ATLAS_PT1000', 'BMP', 'HTU21D', 'MCP342x', 'SHT2x', 'TMP006', 'TSL2561' ] self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.lock = {} self.sensor_id = sensor_id self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.setup_sensor_conditionals() with session_scope(MYCODO_DB_PATH) as new_session: sensor = new_session.query(Sensor) sensor = sensor.filter(Sensor.id == self.sensor_id).first() self.i2c_bus = sensor.i2c_bus self.location = sensor.location self.device_type = 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 = new_session.query(Relay).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 = new_session.query(SMTP).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_type in list_devices_i2c: self.i2c_address = int(str(self.location), 16) # Set up multiplexer if enabled if self.device_type 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_type 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_type == '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_type == 'ADS1x15': self.adc = ADS1x15_read(self.i2c_address, self.i2c_bus, self.adc_channel, self.adc_gain) elif self.device_type == 'MCP342x': self.adc = MCP342x_read(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_type in ['EDGE', 'ADS1x15', 'MCP342x']: self.measure_sensor = None elif self.device_type == 'RPiCPULoad': self.measure_sensor = RaspberryPiCPULoad() elif self.device_type == 'RPi': self.measure_sensor = RaspberryPiCPUTemp() elif self.device_type == 'DS18B20': self.measure_sensor = DS18B20(self.location) elif self.device_type == 'DHT11': self.measure_sensor = DHT11(pigpio.pi(), int(self.location)) elif self.device_type in ['DHT22', 'AM2302']: self.measure_sensor = DHT22(pigpio.pi(), int(self.location)) elif self.device_type == 'HTU21D': self.measure_sensor = HTU21D_read(self.i2c_bus) elif self.device_type == 'AM2315': self.measure_sensor = AM2315_read(self.i2c_bus) elif self.device_type == 'ATLAS_PT1000': self.measure_sensor = Atlas_PT1000(self.i2c_address, self.i2c_bus) elif self.device_type == 'K30': self.measure_sensor = K30() elif self.device_type == 'BMP': self.measure_sensor = BMP(self.i2c_bus) elif self.device_type == 'SHT1x_7x': self.measure_sensor = SHT1x_7x_read(self.location, self.sht_clock_pin, self.sht_voltage) elif self.device_type == 'SHT2x': self.measure_sensor = SHT2x_read(self.i2c_address, self.i2c_bus) elif self.device_type == 'TMP006': self.measure_sensor = TMP006_read(self.i2c_address, self.i2c_bus) elif self.device_type == 'TSL2561': self.measure_sensor = TSL2561_read(self.i2c_address, self.i2c_bus) else: self.device_recognized = False self.logger.debug("[Sensor {}] Device '{}' not " "recognized:".format(self.sensor_id, self.device_type)) raise Exception("{} is not a valid device type.".format( self.device_type)) self.edge_reset_timer = time.time() self.sensor_timer = time.time() self.running = False self.lastUpdate = None
def checkConditionals(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 each_cond: Object of SQL table entries for a specific column :type each_cond: sqlalchemy object """ attachment_file = False attachment_type = False last_measurement = self.getLastMeasurement( 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]))): now = time.time() timestamp = datetime.datetime.fromtimestamp(now).strftime( '%Y-%m-%d %H-%M-%S') 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]) 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_out, cmd_err, 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']: attachment_file = camera_record(INSTALL_DIRECTORY, 'photo') elif self.cond_camera_record[cond_id] in ['video', 'videoemail']: attachment_file = camera_record(INSTALL_DIRECTORY, 'video', 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' with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() send_email(self.logger, 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: self.logger.debug( "[Sensor Conditional {}] " "{:.0f} seconds left to be " "allowed to email again.".format( cond_id, (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() self.logger.debug(message) else: self.logger.debug("[Sensor Conditional {}] Last measurement " "not found".format(cond_id))
def __init__(self, ready, logger, pid_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.pid_id = pid_id self.control = DaemonControl() with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter(PID.id == self.pid_id).first() self.sensor_id = pid.sensor_id self.measure_type = pid.measure_type self.method_id = pid.method_id self.direction = pid.direction self.raise_relay_id = pid.raise_relay_id self.raise_min_duration = pid.raise_min_duration self.raise_max_duration = pid.raise_max_duration self.lower_relay_id = pid.lower_relay_id self.lower_min_duration = pid.lower_min_duration self.lower_max_duration = pid.lower_max_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.Integrator_min = pid.integrator_min self.Integrator_max = pid.integrator_max self.measure_interval = pid.period self.default_set_point = pid.setpoint self.set_point = pid.setpoint sensor = new_session.query(Sensor).filter(Sensor.id == self.sensor_id).first() self.sensor_duration = sensor.period self.Derivator = 0 self.Integrator = 0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.raise_seconds_on = 0 self.timer = t.time()+self.measure_interval # Check if a method is set for this PID if self.method_id: with session_scope(MYCODO_DB_PATH) as new_session: method = new_session.query(Method) method = method.filter(Method.method_id == self.method_id) method = method.filter(Method.method_order == 0).first() self.method_type = method.method_type self.method_start_time = method.start_time if self.method_type == 'Duration': if self.method_start_time == 'Ended': # Method has ended and hasn't been instructed to begin again pass elif self.method_start_time == 'Ready' or self.method_start_time == None: # Method has been instructed to begin with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(Method) mod_method = mod_method.filter(Method.method_id == self.method_id) mod_method = mod_method.filter(Method.method_order == 0).first() mod_method.start_time = datetime.datetime.now() self.method_start_time = mod_method.start_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( self.method_start_time, '%Y-%m-%d %H:%M:%S.%f') self.logger.warning("[PID {}] Resuming method {} started at {}".format( self.pid_id, self.method_id, self.method_start_time))
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: # Update setpoint if a method is selected if self.method_id != '': self.calculate_method_setpoint(self.method_id) self.addSetpointInfluxdb(self.pid_id, self.set_point) # Update PID and get control variable self.control_variable = self.update(self.last_measurement) # # 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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == self.lower_relay_id).first() 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("[PID {}] Setpoint: {} " "Output: {} to relay {}".format( self.pid_id, self.set_point, self.control_variable, 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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == self.raise_relay_id).first() 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("[PID {}] Setpoint: {} " "Output: {} to relay {}".format( self.pid_id, self.set_point, self.control_variable, 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 calculate_method_setpoint(self, method_id): with session_scope(MYCODO_DB_PATH) as new_session: method = new_session.query(Method) new_session.expunge_all() new_session.close() 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 = 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 = 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 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 timelapse active, take photo at predefined periods if (os.path.isfile(FILE_TIMELAPSE_PARAM) and os.path.isfile(LOCK_FILE_TIMELAPSE)): # Read user-defined timelapse 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: pass 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 next_capture < now: # Update last capture and image number to latest before capture next_capture += float(dict_timelapse_param['interval']) capture_number += 1 add_update_csv(logger, FILE_TIMELAPSE_PARAM, 'next_capture', next_capture) add_update_csv(logger, FILE_TIMELAPSE_PARAM, 'capture_number', capture_number) # Capture image with session_scope(MYCODO_DB_PATH) as new_session: camera = new_session.query(CameraStill).first() camera_record( INSTALL_DIRECTORY, '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: pass # 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("[Daemon] {} 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("[Daemon] Stopping all running controllers") self.stop_all_controllers() self.logger.info("[Daemon] 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 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 timelapse active, take photo at predefined periods if (os.path.isfile(FILE_TIMELAPSE_PARAM) and os.path.isfile(LOCK_FILE_TIMELAPSE)): # Read user-defined timelapse 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: pass 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 next_capture < now: # Update last capture and image number to latest before capture next_capture += float( dict_timelapse_param['interval']) capture_number += 1 add_update_csv(logger, FILE_TIMELAPSE_PARAM, 'next_capture', next_capture) add_update_csv(logger, FILE_TIMELAPSE_PARAM, 'capture_number', capture_number) # Capture image with session_scope(MYCODO_DB_PATH) as new_session: camera = new_session.query(Method) camera_record( INSTALL_DIRECTORY, '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: pass # 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("[Daemon] {} 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) 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("[Daemon] Stopping all running controllers") self.stop_all_controllers() self.logger.info("[Daemon] 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 __init__(self, ready, timer_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.timer_{id}".format(id=timer_id)) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.timer_id = timer_id self.control = DaemonControl() timer = db_retrieve_table_daemon(Timer, device_id=self.timer_id) self.timer_type = timer.timer_type self.output_unique_id = timer.relay_id self.method_id = timer.method_id self.method_period = timer.method_period self.state = timer.state self.time_start = timer.time_start self.time_end = timer.time_end self.duration_on = timer.duration_on self.duration_off = timer.duration_off self.output_id = db_retrieve_table_daemon( Output, unique_id=self.output_unique_id).id # Time of day split into hour and minute if self.time_start: time_split = self.time_start.split(":") self.start_hour = time_split[0] self.start_minute = time_split[1] else: self.start_hour = None self.start_minute = None if self.time_end: time_split = self.time_end.split(":") self.end_hour = time_split[0] self.end_minute = time_split[1] else: self.end_hour = None self.end_minute = None self.duration_timer = time.time() self.pwm_method_timer = time.time() self.date_timer_not_executed = True self.running = False if self.method_id: method = db_retrieve_table_daemon(Method, device_id=self.method_id) method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter( MethodData.method_id == self.method_id) method_data_repeat = method_data.filter( MethodData.duration_sec == 0).first() self.method_type = method.method_type self.method_start_act = timer.method_start_time self.method_start_time = None self.method_end_time = None if self.method_type == 'Duration': if self.method_start_act == 'Ended': self.stop_controller(ended_normally=False, deactivate_timer=True) self.logger.warning( "Method has ended. " "Activate the Timer controller to start it again.") elif self.method_start_act == 'Ready' or self.method_start_act is None: # Method has been instructed to begin now = datetime.datetime.now() self.method_start_time = now if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = now + datetime.timedelta( seconds=float(method_data_repeat.duration_end)) with session_scope(MYCODO_DB_PATH) as db_session: mod_timer = db_session.query(Timer) mod_timer = mod_timer.filter( Timer.id == self.timer_id).first() mod_timer.method_start_time = self.method_start_time mod_timer.method_end_time = self.method_end_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( str(timer.method_start_time), '%Y-%m-%d %H:%M:%S.%f') if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = datetime.datetime.strptime( str(timer.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if self.method_end_time > datetime.datetime.now(): self.logger.warning( "Resuming method {id}: started {start}, " "ends {end}".format(id=self.method_id, start=self.method_start_time, end=self.method_end_time)) else: self.method_start_act = 'Ended' else: self.method_start_act = 'Ended'
def 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 calculate_method_setpoint(method_id, table, controller, Method, MethodData, logger): """ Calculates the setpoint from a method :param method_id: ID of Method to be used :param table: Table of the controller using this function :param controller: The controller using this function :param logger: The logger to use :return: 0 (success) or 1 (error) and a setpoint value """ method = db_retrieve_table_daemon(Method) method_key = method.filter(Method.id == method_id).first() method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter(MethodData.method_id == method_id) method_data_all = method_data.filter(MethodData.relay_id == None).all() method_data_first = method_data.filter(MethodData.relay_id == None).first() 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_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%Y-%m-%d %H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%Y-%m-%d %H:%M:%S') if start_time < now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) total_seconds = (end_time - start_time).total_seconds() part_seconds = (now - start_time).total_seconds() percent_total = part_seconds / total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_total) else: new_setpoint = setpoint_start - (setpoint_diff * percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time, end=end_time)) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug( "[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False # 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_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%H:%M:%S') if start_time < daily_now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) total_seconds = (end_time - start_time).total_seconds() part_seconds = (daily_now - start_time).total_seconds() percent_total = part_seconds / total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_total) else: new_setpoint = setpoint_start - (setpoint_diff * percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time.strftime('%H:%M:%S'), end=end_time.strftime('%H:%M:%S'))) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug( "[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False # 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_data_first.amplitude, method_data_first.frequency, method_data_first.shift_angle, method_data_first.shift_y) return new_setpoint, False # 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_data_first.shift_angle, (method_data_first.x0, method_data_first.y0), (method_data_first.x1, method_data_first.y1), (method_data_first.x2, method_data_first.y2), (method_data_first.x3, method_data_first.y3)) return new_setpoint, False # Calculate the duration in the method based on self.method_start_time elif method_key.method_type == 'Duration': start_time = datetime.datetime.strptime( str(controller.method_start_time), '%Y-%m-%d %H:%M:%S.%f') ended = False # Check if method_end_time is not None if controller.method_end_time: # Convert time string to datetime object end_time = datetime.datetime.strptime( str(controller.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if now > start_time: ended = True seconds_from_start = (now - start_time).total_seconds() total_sec = 0 previous_total_sec = 0 previous_end = None method_restart = False for each_method in method_data_all: # If duration_sec is 0, method has instruction to restart if each_method.duration_sec == 0: method_restart = True else: previous_end = each_method.setpoint_end total_sec += each_method.duration_sec if previous_total_sec <= seconds_from_start < total_sec: row_start_time = float( start_time.strftime('%s')) + previous_total_sec row_since_start_sec = (now - (start_time + datetime.timedelta( 0, previous_total_sec))).total_seconds() percent_row = row_since_start_sec / each_method.duration_sec setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_row) else: new_setpoint = setpoint_start - (setpoint_diff * percent_row) logger.debug( "[Method] Start: {start} Seconds Since: {sec}".format( start=start_time, sec=seconds_from_start)) logger.debug("[Method] Start time of row: {start}".format( start=datetime.datetime.fromtimestamp(row_start_time))) logger.debug("[Method] Sec since start of row: {sec}".format( sec=row_since_start_sec)) logger.debug( "[Method] Percent of row: {per}".format(per=percent_row)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False previous_total_sec = total_sec if controller.method_start_time: if method_restart: if end_time and now > end_time: ended = True else: # Method has been instructed to restart controller.method_start_time = datetime.datetime.now() with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table) mod_method = mod_method.filter( table.method_id == method_id).first() mod_method.method_start_time = controller.method_start_time db_session.commit() return previous_end, False else: ended = True if ended: # Duration method has ended, reset method_start_time locally and in DB with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table).filter( table.method_id == method_id).first() mod_method.method_start_time = 'Ended' mod_method.method_end_time = None db_session.commit() return None, True # Setpoint not needing to be calculated, use default setpoint return None, False
def __init__(self, ready, logger, lcd_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.flash_lcd_on = False self.lcd_is_on = False try: with session_scope(MYCODO_DB_PATH) as new_session: lcd = new_session.query(LCD).filter(LCD.id == lcd_id).first() self.lcd_id = 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 == 2: 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: 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 = new_session.query(table).filter( table.id == lcd.line_1_sensor_id).first() 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: 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 = new_session.query(table).filter( table.id == lcd.line_2_sensor_id).first() self.lcd_line[2]['name'] = sensor_line_2.name if 'time' in lcd.line_2_measurement: self.lcd_line[2]['measurement'] = 'time' elif 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: 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 = new_session.query(table).filter( table.id == lcd.line_3_sensor_id).first() 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: 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 = new_session.query(table).filter( table.id == lcd.line_4_sensor_id).first() self.lcd_line[4]['name'] = sensor_line_4.name if 'time' in lcd.line_4_measurement: self.lcd_line[4]['measurement'] = 'time' self.measurement_unit = {} self.measurement_unit['metric'] = { "temperature": "C", "humidity": "%", "co2": "ppmv", "pressure": "Pa", "altitude": "m", "duration_sec": "s", "temperature_die": "C", "temperature_object": "C", "lux": "lux", } self.measurement_unit['standard'] = { "temperature": "F", "humidity": "%", "co2": "ppmv", "pressure": "atm", "altitude": "ft", "duration_sec": "s", "temperature_die": "F", "temperature_object": "F", "lux": "lux", } 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 # Maximum characters per line self.LCD_LINE = {} self.LCD_LINE[1] = 0x80 # LCD RAM address for the 1st line self.LCD_LINE[2] = 0xC0 # LCD RAM address for the 2nd line self.LCD_LINE[3] = 0x94 # LCD RAM address for the 3rd line self.LCD_LINE[4] = 0xD4 # LCD RAM address for the 4th line 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: {}".format( except_msg)) self.I2C_ADDR = int(self.lcd_pin, 16) self.lcd_init() self.lcd_string_write('Mycodo 3.6.0', 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("[LCD {}] Error: {}".format( self.lcd_id, except_msg))
def __init__(self, ready, logger, pid_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.pid_id = pid_id self.control = DaemonControl() with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter(PID.id == self.pid_id).first() self.sensor_id = pid.sensor_id self.measure_type = pid.measure_type self.method_id = pid.method_id self.direction = pid.direction self.raise_relay_id = pid.raise_relay_id self.raise_min_duration = pid.raise_min_duration self.raise_max_duration = pid.raise_max_duration self.lower_relay_id = pid.lower_relay_id self.lower_min_duration = pid.lower_min_duration self.lower_max_duration = pid.lower_max_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.Integrator_min = pid.integrator_min self.Integrator_max = pid.integrator_max self.measure_interval = pid.period self.default_set_point = pid.setpoint self.set_point = pid.setpoint sensor = new_session.query(Sensor).filter( Sensor.id == self.sensor_id).first() self.sensor_duration = sensor.period self.Derivator = 0 self.Integrator = 0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.raise_seconds_on = 0 self.timer = t.time() + self.measure_interval # Check if a method is set for this PID if self.method_id: with session_scope(MYCODO_DB_PATH) as new_session: method = new_session.query(Method) method = method.filter(Method.method_id == self.method_id) method = method.filter(Method.method_order == 0).first() self.method_type = method.method_type self.method_start_time = method.start_time if self.method_type == 'Duration': if self.method_start_time == 'Ended': # Method has ended and hasn't been instructed to begin again pass elif self.method_start_time == 'Ready' or self.method_start_time == None: # Method has been instructed to begin with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(Method) mod_method = mod_method.filter( Method.method_id == self.method_id) mod_method = mod_method.filter( Method.method_order == 0).first() mod_method.start_time = datetime.datetime.now() self.method_start_time = mod_method.start_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( self.method_start_time, '%Y-%m-%d %H:%M:%S.%f') self.logger.warning( "[PID {}] Resuming method {} started at {}".format( self.pid_id, self.method_id, self.method_start_time))
def setup_sensor_conditionals(self, cond_mod='setup', cond_id=None): # 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_detected.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) self.logger.debug("[Sensor Conditional {}] Deleted Conditional " "from Sensor {}".format(cond_id, self.sensor_id)) else: with session_scope(MYCODO_DB_PATH) as new_session: if cond_mod == 'setup': self.cond_id = {} self.cond_name = {} self.cond_activated = {} self.cond_period = {} self.cond_measurement_type = {} self.cond_edge_detected = {} 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 = new_session.query( SensorConditional).filter( SensorConditional.sensor_id == self.sensor_id) self.sensor_conditional = self.sensor_conditional.filter( SensorConditional.activated == 1) elif cond_mod == 'add': self.sensor_conditional = new_session.query( SensorConditional).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) self.logger.debug("[Sensor Conditional {}] Added " "Conditional to Sensor {}".format( cond_id, self.sensor_id)) elif cond_mod == 'mod': self.sensor_conditional = new_session.query( SensorConditional).filter( SensorConditional.sensor_id == self.sensor_id) self.sensor_conditional = self.sensor_conditional.filter( SensorConditional.id == cond_id) self.logger.debug("[Sensor Conditional {}] Modified " "Conditional from Sensor {}".format( cond_id, self.sensor_id)) else: return 1 for each_cond in self.sensor_conditional.all(): if cond_mod == 'setup': self.logger.debug("[Sensor Conditional {}] Activated " "Conditional from Sensor {}".format( each_cond.id, 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_detected[ each_cond.id] = each_cond.edge_detected 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 __init__(self, ready, pid_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.pid_{id}".format(id=pid_id)) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.pid_id = pid_id self.pid_unique_id = db_retrieve_table_daemon( PID, device_id=self.pid_id).unique_id self.control = DaemonControl() self.control_variable = 0.0 self.derivator = 0.0 self.integrator = 0.0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.set_point = 0.0 self.lower_seconds_on = 0.0 self.raise_seconds_on = 0.0 self.lower_duty_cycle = 0.0 self.raise_duty_cycle = 0.0 self.last_time = None self.last_measurement = None self.last_measurement_success = False self.is_activated = None self.is_held = None self.is_paused = None self.measurement = None self.method_id = None self.direction = None self.raise_output_id = None self.raise_min_duration = None self.raise_max_duration = None self.raise_min_off_duration = None self.lower_output_id = None self.lower_min_duration = None self.lower_max_duration = None self.lower_min_off_duration = None self.Kp = None self.Ki = None self.Kd = None self.integrator_min = None self.integrator_max = None self.period = None self.max_measure_age = None self.default_set_point = None self.set_point = None self.input_unique_id = None self.input_duration = None self.raise_output_type = None self.lower_output_type = None self.initialize_values() self.timer = t.time() + self.period # Check if a method is set for this PID self.method_start_act = None if self.method_id: method = db_retrieve_table_daemon(Method, device_id=self.method_id) method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter( MethodData.method_id == self.method_id) method_data_repeat = method_data.filter( MethodData.duration_sec == 0).first() pid = db_retrieve_table_daemon(PID, device_id=self.pid_id) self.method_type = method.method_type self.method_start_act = pid.method_start_time self.method_start_time = None self.method_end_time = None if self.method_type == 'Duration': if self.method_start_act == 'Ended': # Method has ended and hasn't been instructed to begin again pass elif self.method_start_act == 'Ready' or self.method_start_act is None: # Method has been instructed to begin now = datetime.datetime.now() self.method_start_time = now if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = now + datetime.timedelta( seconds=float(method_data_repeat.duration_end)) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID) mod_pid = mod_pid.filter(PID.id == self.pid_id).first() mod_pid.method_start_time = self.method_start_time mod_pid.method_end_time = self.method_end_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( str(pid.method_start_time), '%Y-%m-%d %H:%M:%S.%f') if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = datetime.datetime.strptime( str(pid.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if self.method_end_time > datetime.datetime.now(): self.logger.warning( "Resuming method {id}: started {start}, " "ends {end}".format( id=self.method_id, start=self.method_start_time, end=self.method_end_time)) else: self.method_start_act = 'Ended' else: self.method_start_act = 'Ended'
def relay_on_off(self, relay_id, state, duration=0.0, trigger_conditionals=True): """ Turn a relay on or off The GPIO may be either HIGH or LOW to activate a relay. This trigger state will be referenced to determine if the GPIO needs to be high or low to turn the relay on or off. Conditionals will be checked for each action requested of a relay, and if true, those conditional actions will be executed. For example: 'If relay 1 turns on, turn relay 3 off' :param relay_id: Unique ID for relay :type relay_id: str :param state: What state is desired? 'on' or 'off' :type state: str :param duration: If state is 'on', a duration can be set to turn the relay off after :type duration: float :param trigger_conditionals: Whether to trigger condionals to act or not :type trigger_conditionals: bool """ # Check if relay exists if relay_id not in self.relay_id: self.logger.warning("[Relay] Cannot turn {} Relay with ID {}. It " "doesn't exist".format(state, relay_id)) return 1 if state == 'on': if not self.relay_pin[relay_id]: self.logger.warning("[Relay] Cannot turn a relay " "{} ({}) on with a pin of " "0.".format(self.relay_id[relay_id], self.relay_name[relay_id])) return 1 current_amps = self.current_amp_load() if current_amps+self.relay_amps[relay_id] > MAX_AMPS: self.logger.warning("[Relay] Cannot turn relay {} " "({}) On. If this relay turns on, " "there will be {} amps being drawn, " "which exceeds the maximum set draw of {}" " amps.".format(self.relay_id[relay_id], self.relay_name[relay_id], current_amps, MAX_AMPS)) return 1 else: if duration: time_now = datetime.datetime.now() if self.is_on(relay_id) and self.relay_on_duration[relay_id]: if self.relay_on_until[relay_id] > time_now: remaining_time = (self.relay_on_until[relay_id]-time_now).seconds else: remaining_time = 0 time_on = self.relay_last_duration[relay_id] - remaining_time self.logger.debug("[Relay] Relay {} ({}) is already " "on for a duration of {:.1f} seconds (with " "{:.1f} seconds remaining). Recording the " "amount of time the relay has been on ({:.1f} " "sec) and updating the on duration to {:.1f} " "seconds.".format(self.relay_id[relay_id], self.relay_name[relay_id], self.relay_last_duration[relay_id], remaining_time, time_on, duration)) if time_on > 0: write_db = threading.Thread( target=write_influxdb, args=(self.logger, INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, 'relay', relay_id, 'duration_sec', float(time_on),)) write_db.start() self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration) self.relay_last_duration[relay_id] = duration return 0 elif self.is_on(relay_id) and not self.relay_on_duration: self.relay_on_duration[relay_id] = True self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration) self.relay_last_duration[relay_id] = duration self.logger.debug("[Relay] Relay {} ({}) is currently" " on without a duration. Turning " "into a duration of {:.1f} " "seconds.".format(self.relay_id[relay_id], self.relay_name[relay_id], duration)) return 0 else: self.relay_on_until[relay_id] = time_now+datetime.timedelta(seconds=duration) self.relay_on_duration[relay_id] = True self.relay_last_duration[relay_id] = duration self.logger.debug("[Relay] Relay {} ({}) on for {:.1f} " "seconds.".format(self.relay_id[relay_id], self.relay_name[relay_id], duration)) GPIO.output(self.relay_pin[relay_id], self.relay_trigger[relay_id]) else: if self.is_on(relay_id): self.logger.warning("[Relay] Relay {} ({}) is already on.".format( self.relay_id[relay_id], self.relay_name[relay_id])) return 1 else: GPIO.output(self.relay_pin[relay_id], self.relay_trigger[relay_id]) else: if self._is_setup() and self.relay_pin[relay_id]: # if pin not 0 self.relay_on_duration[relay_id] = False self.relay_on_until[relay_id] = datetime.datetime.now() GPIO.output(self.relay_pin[relay_id], not self.relay_trigger[relay_id]) self.logger.debug("[Relay] Relay {} ({}) turned off.".format( self.relay_id[relay_id], self.relay_name[relay_id])) if trigger_conditionals: with session_scope(MYCODO_DB_PATH) as new_session: conditionals = new_session.query(RelayConditional) new_session.expunge_all() new_session.close() 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 == 0) 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): message = "[Relay Conditional {}] " \ "If relay {} ({}) turns " \ "{}: ".format(each_conditional.id, 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 += ". " if each_conditional.execute_command: ################################# # DANGEROUS CODE # ################################# # This code is not secure at all# # and could cause serious # # damage to your software and # # hardware. # ################################# # TODO: SECURITY: FIX THIS This runs arbitrary as ROOT # Make sure this works (currently untested) message += "Execute '{}'. ".format(each_conditional.execute_command) pass # p = subprocess.Popen(each_conditional.execute_command, shell=True, # stdout=subprocess.PIPE, stderr=subprocess.PIPE) # output, errors = p.communicate() # self.logger.debug("[Relay Conditional {} ({})] " # "Execute command: {} " # "Command output: {} " # "Command errors: {}".format(each_conditional.id, # each_conditional.name, # each_conditional.execute_command, # output, errors)) 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.id, each_conditional.email_notify) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() email(self.logger, 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_cond.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 send_stats(): """ Send anonymous usage statistics Example use: current_stat = return_stat_file_dict(csv_file) add_update_csv(csv_file, 'stat', current_stat['stat'] + 5) """ try: client = InfluxDBClient(STATS_HOST, STATS_PORT, STATS_USER, STATS_PASSWORD, STATS_DATABASE) # Prepare stats before sending with session_scope(MYCODO_DB_PATH) as new_session: alembic = new_session.query(AlembicVersion).first() add_update_csv(STATS_CSV, 'alembic_version', alembic.version_num) relays = new_session.query(Relay) add_update_csv(STATS_CSV, 'num_relays', get_count(relays)) sensors = new_session.query(Sensor) add_update_csv(STATS_CSV, 'num_sensors', get_count(sensors)) add_update_csv(STATS_CSV, 'num_sensors_active', get_count(sensors.filter(Sensor.activated == True))) pids = new_session.query(PID) add_update_csv(STATS_CSV, 'num_pids', get_count(pids)) add_update_csv(STATS_CSV, 'num_pids_active', get_count(pids.filter(PID.activated == True))) lcds = new_session.query(LCD) add_update_csv(STATS_CSV, 'num_lcds', get_count(lcds)) add_update_csv(STATS_CSV, 'num_lcds_active', get_count(lcds.filter(LCD.activated == True))) methods = new_session.query(Method) add_update_csv(STATS_CSV, 'num_methods', get_count(methods.filter(Method.method_order == 0))) add_update_csv(STATS_CSV, 'num_methods_in_pid', get_count(pids.filter(PID.method_id != ''))) timers = new_session.query(Timer) add_update_csv(STATS_CSV, 'num_timers', get_count(timers)) add_update_csv(STATS_CSV, 'num_timers_active', get_count(timers.filter(Timer.activated == True))) add_update_csv(STATS_CSV, 'country', geocoder.ip('me').country) add_update_csv( STATS_CSV, 'ram_use_mb', resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / float(1000)) user_count = 0 admin_count = 0 with session_scope(USER_DB_PATH) as db_session: try: users = db_session.query(Users).all() for each_user in users: user_count += 1 if each_user.user_restriction == 'admin': admin_count += 1 except Exception: pass add_update_csv(STATS_CSV, 'num_users_admin', admin_count) add_update_csv(STATS_CSV, 'num_users_guest', user_count - admin_count) add_update_csv(STATS_CSV, 'Mycodo_revision', MYCODO_VERSION) # Combine stats into list of dictionaries new_stats_dict = return_stat_file_dict(STATS_CSV) formatted_stat_dict = [] for each_key, each_value in new_stats_dict.iteritems(): if each_key != 'stat': # Do not send header row formatted_stat_dict = add_stat_dict(formatted_stat_dict, new_stats_dict['id'], each_key, each_value) # Send stats to secure, remote influxdb server client.write_points(formatted_stat_dict) logger.debug("Sent anonymous usage statistics") return 0 except requests.ConnectionError: logger.error("Could not send anonymous usage statistics: Connection " "timed out (expected if there's no internet)") except Exception as except_msg: logger.exception( "Could not send anonymous usage statistics: {err}".format( err=except_msg)) return 1
def setup_sensor_conditionals(self, cond_mod='setup', cond_id=None): # 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_detected.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) self.logger.debug("[Sensor Conditional {}] Deleted Conditional " "from Sensor {}".format(cond_id, self.sensor_id)) else: with session_scope(MYCODO_DB_PATH) as new_session: if cond_mod == 'setup': self.cond_id = {} self.cond_name = {} self.cond_activated = {} self.cond_period = {} self.cond_measurement_type = {} self.cond_edge_detected = {} 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 = new_session.query( SensorConditional).filter( SensorConditional.sensor_id == self.sensor_id) self.sensor_conditional = self.sensor_conditional.filter( SensorConditional.activated == 1) elif cond_mod == 'add': self.sensor_conditional = new_session.query( SensorConditional).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) self.logger.debug("[Sensor Conditional {}] Added " "Conditional to Sensor {}".format( cond_id, self.sensor_id)) elif cond_mod == 'mod': self.sensor_conditional = new_session.query( SensorConditional).filter( SensorConditional.sensor_id == self.sensor_id) self.sensor_conditional = self.sensor_conditional.filter( SensorConditional.id == cond_id) self.logger.debug("[Sensor Conditional {}] Modified " "Conditional from Sensor {}".format( cond_id, self.sensor_id)) else: return 1 for each_cond in self.sensor_conditional.all(): if cond_mod == 'setup': self.logger.debug("[Sensor Conditional {}] Activated " "Conditional from Sensor {}".format( each_cond.id, 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_detected[each_cond.id] = each_cond.edge_detected 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 activateController(self, cont_type, cont_id): """ Activate currently-inactive controller :return: 0 for success, 1 for fail, with success or error message :rtype: int, str :param cont_type: Which controller type is to be activated? :type cont_type: str :param cont_id: Unique ID for controller :type cont_id: str """ if cont_id in self.controller[cont_type]: if self.controller[cont_type][cont_id].isRunning(): self.logger.warning("[Daemon] Cannot activate {} controller " "with ID {}, it's already " "active.".format(cont_type, cont_id)) return 1, "Cannot activate {} controller with ID {}: "\ "Already active.".format(cont_type, cont_id) try: controller_manage = {} ready = threading.Event() if cont_type == 'LCD': controller_manage['type'] = LCD controller_manage['function'] = LCDController elif cont_type == 'Log': controller_manage['type'] = Log controller_manage['function'] = LogController elif cont_type == 'PID': controller_manage['type'] = PID controller_manage['function'] = PIDController elif cont_type == 'Sensor': controller_manage['type'] = Sensor controller_manage['function'] = SensorController elif cont_type == 'Timer': controller_manage['type'] = Timer controller_manage['function'] = TimerController else: return 1, "{} controller with ID {} not found.".format( cont_type, cont_id) # Check if the controller ID actually exists and start it with session_scope(MYCODO_DB_PATH) as new_session: if new_session.query(controller_manage['type']).filter( controller_manage['type'].id == cont_id).first(): self.controller[cont_type][cont_id] = controller_manage['function']( ready, self.logger, cont_id) self.controller[cont_type][cont_id].start() ready.wait() # wait for thread to return ready return 0, "{} controller with ID {} activated.".format( cont_type, cont_id) else: return 1, "{} controller with ID {} not found.".format( cont_type, cont_id) except Exception as except_msg: self.logger.exception("[Daemon] Could not activate {} controller " "with ID {}: {}".format(cont_type, cont_id, except_msg)) return 1, "Could not activate {} controller with ID "\ "{}: {}".format(cont_type, cont_id, except_msg)
def activateController(self, cont_type, cont_id): """ Activate currently-inactive controller :return: 0 for success, 1 for fail, with success or error message :rtype: int, str :param cont_type: Which controller type is to be activated? :type cont_type: str :param cont_id: Unique ID for controller :type cont_id: str """ if cont_id in self.controller[cont_type]: if self.controller[cont_type][cont_id].isRunning(): self.logger.warning("[Daemon] Cannot activate {} controller " "with ID {}, it's already " "active.".format(cont_type, cont_id)) return 1, "Cannot activate {} controller with ID {}: "\ "Already active.".format(cont_type, cont_id) try: controller_manage = {} ready = threading.Event() if cont_type == 'LCD': controller_manage['type'] = LCD controller_manage['function'] = LCDController elif cont_type == 'Log': controller_manage['type'] = Log controller_manage['function'] = LogController elif cont_type == 'PID': controller_manage['type'] = PID controller_manage['function'] = PIDController elif cont_type == 'Sensor': controller_manage['type'] = Sensor controller_manage['function'] = SensorController elif cont_type == 'Timer': controller_manage['type'] = Timer controller_manage['function'] = TimerController else: return 1, "{} controller with ID {} not found.".format( cont_type, cont_id) # Check if the controller ID actually exists and start it with session_scope(MYCODO_DB_PATH) as new_session: if new_session.query(controller_manage['type']).filter( controller_manage['type'].id == cont_id).first(): self.controller[cont_type][cont_id] = controller_manage[ 'function'](ready, self.logger, cont_id) self.controller[cont_type][cont_id].start() ready.wait() # wait for thread to return ready return 0, "{} controller with ID {} activated.".format( cont_type, cont_id) else: return 1, "{} controller with ID {} not found.".format( cont_type, cont_id) except Exception as except_msg: self.logger.exception("[Daemon] Could not activate {} controller " "with ID {}: {}".format( cont_type, cont_id, except_msg)) return 1, "Could not activate {} controller with ID "\ "{}: {}".format(cont_type, cont_id, 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 (from within the 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( INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, self.lcd_line[i]['id'], '/.*/').raw else: last_measurement = read_last_influxdb( INFLUXDB_HOST, INFLUXDB_PORT, INFLUXDB_USER, INFLUXDB_PASSWORD, INFLUXDB_DATABASE, 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( "[LCD {}] Latest {}: {} @ {}".format( self.lcd_id, 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("[LCD {}] No data returned " "from influxdb".format( self.lcd_id)) except Exception as except_msg: self.logger.debug( "[LCD {}] Failed to read " "measurement from the influxdb database: " "{}".format(self.lcd_id, 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': with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter( PID.id == self.lcd_line[i]['id']).first() new_session.expunge_all() new_session.close() 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( self.measurement_unit['metric'][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'], self.measurement_unit['metric'][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("[LCD {}] Error ({}): {}".format( self.lcd_id, except_msg)) else: self.lcd_string_line[i] = ''
def checkConditionals(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 each_cond: Object of SQL table entries for a specific column :type each_cond: sqlalchemy object """ attachment_file = False attachment_type = False last_measurement = self.getLastMeasurement(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]))): now = time.time() timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H-%M-%S') 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]) 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_out, cmd_err, 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']: attachment_file = camera_record('photo') elif self.cond_camera_record[cond_id] in ['video', 'videoemail']: attachment_file = camera_record('video', 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' with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() email(self.logger, 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: self.logger.debug("[Sensor Conditional {}] " "{:.0f} seconds left to be " "allowed to email again.".format( cond_id, (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() self.logger.debug(message) else: self.logger.debug("[Sensor Conditional {}] Last measurement " "not found".format(cond_id))
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: # Update setpoint if a method is selected if self.method_id != '': self.calculate_method_setpoint(self.method_id) self.addSetpointInfluxdb(self.pid_id, self.set_point) # Update PID and get control variable self.control_variable = self.update(self.last_measurement) # # 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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == self.lower_relay_id).first() 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("[PID {}] Setpoint: {} " "Output: {} to relay {}".format( self.pid_id, self.set_point, self.control_variable, 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: with session_scope(MYCODO_DB_PATH) as new_session: relay = new_session.query(Relay).filter( Relay.id == self.raise_relay_id).first() 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("[PID {}] Setpoint: {} " "Output: {} to relay {}".format( self.pid_id, self.set_point, self.control_variable, 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, logger, sensor_id): threading.Thread.__init__(self) self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.logger = logger self.lock = {} self.sensor_id = sensor_id self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.setup_sensor_conditionals() # Obtain database configuration options with session_scope(MYCODO_DB_PATH) as new_session: sensor = new_session.query(Sensor).filter( Sensor.id == self.sensor_id).first() self.location = sensor.location self.device_type = sensor.device self.sensor_type = sensor.device_type self.period = sensor.period self.multiplexer_address_raw = sensor.multiplexer_address 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 if self.device_type == 'EDGE': if sensor.switch_edge == 'rising': self.switch_edge_gpio = GPIO.RISING elif sensor.switch_edge == 'falling': self.switch_edge_gpio = GPIO.FALLING else: self.switch_edge_gpio = GPIO.BOTH self.switch_edge = sensor.switch_edge self.switch_bouncetime = sensor.switch_bouncetime self.switch_reset_period = sensor.switch_reset_period smtp = new_session.query(SMTP).first() self.smtp_max_count = smtp.hourly_max self.email_count = 0 self.allowed_to_send_notice = True # 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 = new_session.query(Relay).all() # Check if relay ID actually exists for each_relay in relay: if each_relay.id == self.pre_relay_id and self.pre_relay_duration: self.pre_relay_setup = True if self.device_type in ['AM2315', 'BMP'] 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_address) else: self.multiplexer = None if self.device_type in ['ADS1x15','MCP342x'] and self.location: self.adc_lock_file = "/var/lock/mycodo_adc_0x{:02X}.pid".format(int(str(self.location), 16)) else: self.adc = None self.device_recognized = True # Processes if self.device_type == 'RPiCPULoad': self.measure_sensor = RaspberryPiCPULoad() # Environmental Sensors elif self.device_type == 'RPi': self.measure_sensor = RaspberryPiCPUTemp() elif self.device_type == 'DS18B20': self.measure_sensor = DS18B20(self.location) elif self.device_type == 'DHT11': self.measure_sensor = DHT11(pigpio.pi(), int(self.location)) elif self.device_type in ['DHT22', 'AM2302']: self.measure_sensor = DHT22(pigpio.pi(), int(self.location)) elif self.device_type == 'AM2315': self.measure_sensor = AM2315_read() elif self.device_type == 'K30': self.measure_sensor = K30() elif self.device_type == 'BMP': self.measure_sensor = BMP() elif self.device_type == 'SHT1x_7x': self.measure_sensor = SHT1x_7x_read(self.location, self.sht_clock_pin, self.sht_voltage) elif self.device_type == 'SHT2x': self.measure_sensor = SHT2x_read(int(str(self.location), 16)) elif self.device_type == 'TMP006': self.measure_sensor = TMP006_read(self.location) elif self.device_type == 'TSL2561': self.measure_sensor = TSL2561_read(self.location) # Devices elif self.device_type == 'ADS1x15': self.adc = ADS1x15_read(self.logger, int(str(self.location), 16), self.adc_channel, self.adc_gain) elif self.device_type == 'MCP342x': self.adc = MCP342x_read(self.logger, int(str(self.location), 16), self.adc_channel, self.adc_gain, self.adc_resolution) # Other elif self.device_type in ['EDGE', 'ADS1x15', 'MCP342x']: self.measure_sensor = None else: self.device_recognized = False self.logger.debug("[Sensor {}] Device '{}' not " "recognized:".format(self.sensor_id, self.device_type)) raise Exception("{} is not a valid device type.".format( self.device_type)) self.edge_reset_timer = time.time() self.sensor_timer = time.time() self.running = False self.lastUpdate = None
def checkConditionals(self, relay_id, on_duration): with session_scope(MYCODO_DB_PATH) as new_session: conditionals = new_session.query(RelayConditional) new_session.expunge_all() new_session.close() 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_out, cmd_err, cmd_status = cmd_output( self.cond_execute_command[cond_id]) 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) with session_scope(MYCODO_DB_PATH) as new_session: smtp = new_session.query(SMTP).first() send_email(self.logger, 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_cond.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))