def __init__(self): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.output") self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.control = DaemonControl() self.output_id = {} self.output_unique_id = {} self.output_type = {} self.output_name = {} self.output_pin = {} self.output_amps = {} self.output_trigger = {} self.output_on_at_start = {} self.output_on_until = {} self.output_last_duration = {} self.output_on_duration = {} # wireless self.output_protocol = {} self.output_pulse_length = {} self.output_bit_length = {} self.output_on_command = {} self.output_off_command = {} self.wireless_pi_switch = {} # PWM self.pwm_hertz = {} self.pwm_library = {} self.pwm_output = {} self.pwm_state = {} self.pwm_time_turned_on = {} self.output_time_turned_on = {} self.logger.debug("Initializing Outputs") try: smtp = db_retrieve_table_daemon(SMTP, entry='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 outputs = db_retrieve_table_daemon(Output, entry='all') self.all_outputs_initialize(outputs) # Turn all outputs off self.all_outputs_off() # Turn outputs on that are set to be on at start self.all_outputs_on() self.logger.debug("Outputs Initialized") except Exception as except_msg: self.logger.exception( "Problem initializing outputs: {err}".format(err=except_msg)) self.running = False
def initialize_values(self): """Set PID parameters""" pid = db_retrieve_table_daemon(PID, device_id=self.pid_id) self.is_activated = pid.is_activated self.is_held = pid.is_held self.is_paused = pid.is_paused self.measurement = pid.measurement 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.raise_min_off_duration = pid.raise_min_off_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.lower_min_off_duration = pid.lower_min_off_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.integrator_min = pid.integrator_min self.integrator_max = pid.integrator_max self.measure_interval = pid.period self.max_measure_age = pid.max_measure_age self.default_set_point = pid.setpoint self.set_point = pid.setpoint sensor = db_retrieve_table_daemon(Sensor, device_id=pid.sensor_id) self.sensor_unique_id = sensor.unique_id self.sensor_duration = sensor.period return "success"
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 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.initialize_values() self.timer = t.time() + self.measure_interval # Check if a method is set for this PID if self.method_id: method = db_retrieve_table_daemon(Method, device_id=self.method_id) 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.id == self.method_id).first() mod_method.time_start = 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 relay_state(relay_id): relay = db_retrieve_table_daemon(Relay, unique_id=relay_id) GPIO.setmode(GPIO.BCM) if GPIO.input(relay.pin) == relay.trigger: gpio_state = 'On' else: gpio_state = 'Off' return gpio_state
def output_state(output_id): output = db_retrieve_table_daemon(Output, unique_id=output_id) GPIO.setmode(GPIO.BCM) if GPIO.input(output.pin) == output.trigger: gpio_state = 'On' else: gpio_state = 'Off' return gpio_state
def add_mod_output(self, output_id): """ Add or modify local dictionary of output settings form SQL database When a output is added or modified while the output controller is running, these local variables need to also be modified to maintain consistency between the SQL database and running controller. :param output_id: Unique ID for each output :type output_id: int :return: 0 for success, 1 for fail, with success for fail message :rtype: int, str """ output_id = int(output_id) try: output = db_retrieve_table_daemon(Output, device_id=output_id) self.output_type[output_id] = output.relay_type # Turn current pin off if output_id in self.output_pin and self.output_state(output_id) != 'off': self.output_switch(output_id, 'off') self.output_id[output_id] = output.id self.output_unique_id[output_id] = output.unique_id self.output_type[output_id] = output.relay_type self.output_name[output_id] = output.name self.output_pin[output_id] = output.pin self.output_amps[output_id] = output.amps self.output_trigger[output_id] = output.trigger self.output_on_at_start[output_id] = output.on_at_start self.output_on_until[output_id] = datetime.datetime.now() self.output_time_turned_on[output_id] = None self.output_last_duration[output_id] = 0 self.output_on_duration[output_id] = False self.output_protocol[output_id] = output.protocol self.output_pulse_length[output_id] = output.pulse_length self.output_bit_length[output_id] = output.bit_length self.output_on_command[output_id] = output.on_command self.output_off_command[output_id] = output.off_command self.pwm_hertz[output_id] = output.pwm_hertz self.pwm_library[output_id] = output.pwm_library if self.output_pin[output_id]: self.setup_pin(output.id) message = u"Output {id} ({name}) initialized".format( id=self.output_id[output_id], name=self.output_name[output_id]) self.logger.debug(message) return 0, "success" except Exception as except_msg: self.logger.exception(1) return 1, "Add_Mod_Output Error: ID {id}: {err}".format( id=output_id, err=except_msg)
def initialize_values(self): """Set PID parameters""" pid = db_retrieve_table_daemon(PID, device_id=self.pid_id) self.is_activated = pid.is_activated self.is_held = pid.is_held self.is_paused = pid.is_paused self.method_id = pid.method_id self.direction = pid.direction self.raise_output_id = pid.raise_relay_id self.raise_min_duration = pid.raise_min_duration self.raise_max_duration = pid.raise_max_duration self.raise_min_off_duration = pid.raise_min_off_duration self.lower_output_id = pid.lower_relay_id self.lower_min_duration = pid.lower_min_duration self.lower_max_duration = pid.lower_max_duration self.lower_min_off_duration = pid.lower_min_off_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.integrator_min = pid.integrator_min self.integrator_max = pid.integrator_max self.period = pid.period self.max_measure_age = pid.max_measure_age self.default_set_point = pid.setpoint self.set_point = pid.setpoint input_unique_id = pid.measurement.split(',')[0] self.measurement = pid.measurement.split(',')[1] input_dev = db_retrieve_table_daemon(Input, unique_id=input_unique_id) self.input_unique_id = input_dev.unique_id self.input_duration = input_dev.period try: self.raise_output_type = db_retrieve_table_daemon( Output, device_id=self.raise_output_id).relay_type except AttributeError: self.raise_output_type = None try: self.lower_output_type = db_retrieve_table_daemon( Output, device_id=self.lower_output_id).relay_type except AttributeError: self.lower_output_type = None return "success"
def __init__(self): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.relay") self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.control = DaemonControl() self.relay_id = {} self.relay_unique_id = {} self.relay_name = {} self.relay_pin = {} self.relay_amps = {} self.relay_trigger = {} self.relay_on_at_start = {} self.relay_on_until = {} self.relay_last_duration = {} self.relay_on_duration = {} self.relay_time_turned_on = {} self.logger.debug("Initializing Relays") try: smtp = db_retrieve_table_daemon(SMTP, entry='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 = db_retrieve_table_daemon(Relay, entry='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.debug("Relays Initialized") except Exception as except_msg: self.logger.exception( "Problem initializing relays: {err}", err=except_msg) self.running = False
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.name = timer.name self.relay_unique_id = timer.relay_id 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.relay_id = db_retrieve_table_daemon( Relay, unique_id=self.relay_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.date_timer_not_executed = True self.running = False
def setup_sensor_conditionals(self, cond_mod='setup'): # Signal to pause the main loop and wait for verification self.pause_loop = True while not self.verify_pause_loop: time.sleep(0.1) self.cond_id = {} self.cond_action_id = {} self.cond_name = {} self.cond_is_activated = {} self.cond_if_sensor_period = {} self.cond_if_sensor_measurement = {} self.cond_if_sensor_edge_select = {} self.cond_if_sensor_edge_detected = {} self.cond_if_sensor_gpio_state = {} self.cond_if_sensor_direction = {} self.cond_if_sensor_setpoint = {} sensor_conditional = db_retrieve_table_daemon( Conditional) sensor_conditional = sensor_conditional.filter( Conditional.sensor_id == self.sensor_id) sensor_conditional = sensor_conditional.filter( Conditional.is_activated == True).all() if cond_mod == 'setup': self.cond_timer = {} self.smtp_wait_timer = {} elif cond_mod == 'add': self.logger.debug("Added Conditional") elif cond_mod == 'del': self.logger.debug("Deleted Conditional") elif cond_mod == 'mod': self.logger.debug("Modified Conditional") else: return 1 for each_cond in sensor_conditional: if cond_mod == 'setup': self.logger.info("Activated Conditional ({id})".format(id=each_cond.id)) self.cond_id[each_cond.id] = each_cond.id self.cond_is_activated[each_cond.id] = each_cond.is_activated self.cond_if_sensor_period[each_cond.id] = each_cond.if_sensor_period self.cond_if_sensor_measurement[each_cond.id] = each_cond.if_sensor_measurement self.cond_if_sensor_edge_select[each_cond.id] = each_cond.if_sensor_edge_select self.cond_if_sensor_edge_detected[each_cond.id] = each_cond.if_sensor_edge_detected self.cond_if_sensor_gpio_state[each_cond.id] = each_cond.if_sensor_gpio_state self.cond_if_sensor_direction[each_cond.id] = each_cond.if_sensor_direction self.cond_if_sensor_setpoint[each_cond.id] = each_cond.if_sensor_setpoint self.cond_timer[each_cond.id] = time.time() + each_cond.if_sensor_period self.smtp_wait_timer[each_cond.id] = time.time() + 3600 self.pause_loop = False self.verify_pause_loop = False
def create_lcd_line(self, last_measurement_success, i): try: if last_measurement_success: # Determine if the LCD output will have a value unit measurement = '' if self.lcd_line[i]['measurement'] == 'setpoint': pid = db_retrieve_table_daemon( PID, unique_id=self.lcd_line[i]['id']) measurement = pid.measurement self.lcd_line[i]['measurement_value'] = '{:.2f}'.format( self.lcd_line[i]['measurement_value']) elif self.lcd_line[i]['measurement'] == 'duration_sec': measurement = 'duration_sec' self.lcd_line[i]['measurement_value'] = '{:.2f}'.format( self.lcd_line[i]['measurement_value']) elif self.lcd_line[i]['measurement'] in MEASUREMENT_UNITS: measurement = self.lcd_line[i]['measurement'] # Produce the line that will be displayed on the LCD number_characters = self.lcd_x_characters if self.lcd_line[i]['measurement'] == 'time': # Convert UTC timestamp to local timezone utc_dt = datetime.datetime.strptime( self.lcd_line[i]['time'].split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) self.lcd_string_line[i] = str( datetime.datetime.fromtimestamp(utc_timestamp)) elif measurement: value_length = len( str(self.lcd_line[i]['measurement_value'])) unit_length = len(MEASUREMENT_UNITS[measurement]['unit']) 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] = u'{name} {value} {unit}'.format( name=name_cropped, value=self.lcd_line[i]['measurement_value'], unit=MEASUREMENT_UNITS[measurement]['unit']) 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] = u'{name} {value}'.format( name=name_cropped, value=self.lcd_line[i]['measurement_value']) else: self.lcd_string_line[i] = 'ERROR: NO DATA' except Exception as except_msg: self.logger.exception("Error: {err}".format(err=except_msg))
def setup_lcd_line(self, line, device_id, measurement): self.lcd_line[line]['id'] = device_id self.lcd_line[line]['measurement'] = measurement if device_id: table = None if measurement in self.list_relays: table = Relay elif measurement in self.list_pids: table = PID elif measurement in self.list_sensors: table = Sensor sensor_line = db_retrieve_table_daemon(table, unique_id=device_id) self.lcd_line[line]['name'] = sensor_line.name if 'time' in measurement: self.lcd_line[line]['measurement'] = 'time'
def setup_lcd_line(self, display_id, line, lcd_id, measurement): self.lcd_line[display_id][line]['id'] = lcd_id self.lcd_line[display_id][line]['measurement'] = measurement if lcd_id: table = None if measurement in self.list_outputs: table = Output elif measurement in self.list_pids: table = PID elif measurement in self.list_inputs: table = Input input_line = db_retrieve_table_daemon(table, unique_id=lcd_id) self.lcd_line[display_id][line]['name'] = input_line.name if 'time' in measurement: self.lcd_line[display_id][line]['measurement'] = 'time'
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 """ relay_id = int(relay_id) try: relay = db_retrieve_table_daemon(Relay, device_id=relay_id) self.relay_id[relay_id] = relay.id self.relay_unique_id[relay_id] = relay.unique_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_on_at_start[relay_id] = relay.on_at_start self.relay_on_until[relay_id] = datetime.datetime.now() self.relay_time_turned_on[relay_id] = None self.relay_last_duration[relay_id] = 0 self.relay_on_duration[relay_id] = False message = "Relay {id} ({name}) ".format( id=self.relay_id[relay_id], name=self.relay_name[relay_id]) if do_setup_pin and relay.pin: self.setup_pin(relay.id, relay.pin, relay.trigger) message += "initialized" else: message += "added" self.logger.debug(message) return 0, "success" except Exception as except_msg: return 1, "Add_Mod_Relay Error: ID {id}: {err}".format( id=relay_id, err=except_msg)
def __init__(self, ready, math_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.math_{id}".format(id=math_id)) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.math_id = math_id math = db_retrieve_table_daemon(Math, device_id=self.math_id) self.math_unique_id = math.unique_id self.name = math.name self.math_type = math.math_type self.is_activated = math.is_activated self.period = math.period self.inputs = math.inputs self.max_measure_age = math.max_measure_age self.measure = math.measure self.measure_units = math.measure_units self.timer = t.time() + self.period
def __init__(self, ready, lcd_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.lcd_{id}".format(id=lcd_id)) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.flash_lcd_on = False self.lcd_is_on = False self.lcd_id = lcd_id try: lcd = db_retrieve_table_daemon(LCD, device_id=self.lcd_id) self.lcd_name = lcd.name self.lcd_location = lcd.location self.lcd_period = lcd.period self.lcd_x_characters = lcd.x_characters self.lcd_y_lines = lcd.y_lines self.timer = time.time() + self.lcd_period self.backlight_timer = time.time() 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] = {} self.list_sensors = MEASUREMENT_UNITS self.list_sensors.update( {'sensor_time': { 'unit': None, 'name': 'Time' }}) self.list_pids = ['setpoint', 'pid_time'] self.list_relays = ['duration_sec', 'relay_time', 'relay_state'] if self.lcd_y_lines in [2, 4]: self.setup_lcd_line(1, lcd.line_1_sensor_id, lcd.line_1_measurement) self.setup_lcd_line(2, lcd.line_2_sensor_id, lcd.line_2_measurement) if self.lcd_y_lines == 4: self.setup_lcd_line(3, lcd.line_3_sensor_id, lcd.line_3_measurement) self.setup_lcd_line(4, lcd.line_4_sensor_id, lcd.line_4_measurement) self.lcd_string_line = {} for i in range(1, self.lcd_y_lines + 1): self.lcd_string_line[i] = '' self.LCD_WIDTH = self.lcd_x_characters # Max characters per line self.LCD_LINE = {1: 0x80, 2: 0xC0, 3: 0x94, 4: 0xD4} self.LCD_CHR = 1 # Mode - Sending data self.LCD_CMD = 0 # Mode - SenLCDding command self.LCD_BACKLIGHT = 0x08 # On self.LCD_BACKLIGHT_OFF = 0x00 # Off self.ENABLE = 0b00000100 # Enable bit # Timing constants self.E_PULSE = 0.0005 self.E_DELAY = 0.0005 # Setup I2C bus try: if GPIO.RPI_REVISION == 2 or GPIO.RPI_REVISION == 3: i2c_bus_number = 1 else: i2c_bus_number = 0 self.bus = smbus.SMBus(i2c_bus_number) except Exception as except_msg: self.logger.exception( "Could not initialize I2C bus: {err}".format( err=except_msg)) self.I2C_ADDR = int(self.lcd_location, 16) self.lcd_init() self.lcd_string_write('Mycodo {}'.format(MYCODO_VERSION), self.LCD_LINE[1]) self.lcd_string_write(u'Start {}'.format(self.lcd_name), self.LCD_LINE[2]) except Exception as except_msg: self.logger.exception("Error: {err}".format(err=except_msg))
def __init__(self, ready, input_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.input_{id}".format(id=input_id)) self.stop_iteration_counter = 0 self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.lock = {} self.measurement = None self.updateSuccess = False self.input_id = input_id self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.cond_id = {} self.cond_action_id = {} self.cond_name = {} self.cond_is_activated = {} self.cond_if_input_period = {} self.cond_if_input_measurement = {} self.cond_if_input_edge_select = {} self.cond_if_input_edge_detected = {} self.cond_if_input_gpio_state = {} self.cond_if_input_direction = {} self.cond_if_input_setpoint = {} self.cond_do_output_id = {} self.cond_do_output_state = {} self.cond_do_output_duration = {} self.cond_execute_command = {} self.cond_email_notify = {} self.cond_do_lcd_id = {} self.cond_do_camera_id = {} self.cond_timer = {} self.smtp_wait_timer = {} self.setup_input_conditionals() input_dev = db_retrieve_table_daemon(Input, device_id=self.input_id) self.input_sel = input_dev self.unique_id = input_dev.unique_id self.i2c_bus = input_dev.i2c_bus self.location = input_dev.location self.power_output_id = input_dev.power_relay_id self.measurements = input_dev.measurements self.device = input_dev.device self.interface = input_dev.interface self.device_loc = input_dev.device_loc self.baud_rate = input_dev.baud_rate self.period = input_dev.period self.resolution = input_dev.resolution self.sensitivity = input_dev.sensitivity self.cmd_command = input_dev.cmd_command self.cmd_measurement = input_dev.cmd_measurement self.cmd_measurement_units = input_dev.cmd_measurement_units self.mux_address_raw = input_dev.multiplexer_address self.mux_bus = input_dev.multiplexer_bus self.mux_chan = input_dev.multiplexer_channel self.adc_chan = input_dev.adc_channel self.adc_gain = input_dev.adc_gain self.adc_resolution = input_dev.adc_resolution self.adc_measure = input_dev.adc_measure self.adc_measure_units = input_dev.adc_measure_units self.adc_volts_min = input_dev.adc_volts_min self.adc_volts_max = input_dev.adc_volts_max self.adc_units_min = input_dev.adc_units_min self.adc_units_max = input_dev.adc_units_max self.adc_inverse_unit_scale = input_dev.adc_inverse_unit_scale self.sht_clock_pin = input_dev.sht_clock_pin self.sht_voltage = input_dev.sht_voltage # Edge detection self.switch_edge = input_dev.switch_edge self.switch_bouncetime = input_dev.switch_bouncetime self.switch_reset_period = input_dev.switch_reset_period # PWM and RPM options self.weighting = input_dev.weighting self.rpm_pulses_per_rev = input_dev.rpm_pulses_per_rev self.sample_time = input_dev.sample_time # Output that will activate prior to input read self.pre_output_id = input_dev.pre_relay_id self.pre_output_duration = input_dev.pre_relay_duration self.pre_output_setup = False self.next_measurement = time.time() self.get_new_measurement = False self.trigger_cond = False self.measurement_acquired = False self.pre_output_activated = False self.pre_output_timer = time.time() output = db_retrieve_table_daemon(Output, entry='all') for each_output in output: # Check if output ID actually exists if each_output.id == self.pre_output_id and self.pre_output_duration: self.pre_output_setup = True smtp = db_retrieve_table_daemon(SMTP, entry='first') self.smtp_max_count = smtp.hourly_max self.email_count = 0 self.allowed_to_send_notice = True # Convert string I2C address to base-16 int if self.device in LIST_DEVICES_I2C: self.i2c_address = int(str(self.location), 16) # Set up multiplexer if enabled if self.device in LIST_DEVICES_I2C and self.mux_address_raw: self.mux_address_string = self.mux_address_raw self.mux_address = int(str(self.mux_address_raw), 16) self.mux_lock = "/var/lock/mycodo_multiplexer_0x{i2c:02X}.pid".format( i2c=self.mux_address) self.mux_lock = fasteners.InterProcessLock(self.mux_lock) self.mux_lock_acquired = False self.multiplexer = TCA9548A(self.mux_bus, self.mux_address) else: self.multiplexer = None # Set up edge detection of a GPIO pin if self.device == 'EDGE': if self.switch_edge == 'rising': self.switch_edge_gpio = GPIO.RISING elif self.switch_edge == 'falling': self.switch_edge_gpio = GPIO.FALLING else: self.switch_edge_gpio = GPIO.BOTH # Lock multiplexer, if it's enabled if self.multiplexer: self.lock_multiplexer() # Set up analog-to-digital converter if self.device in ['ADS1x15', 'MCP342x'] and self.location: self.adc_lock_file = "/var/lock/mycodo_adc_bus{bus}_0x{i2c:02X}.pid".format( bus=self.i2c_bus, i2c=self.i2c_address) if self.device == 'ADS1x15': self.adc = ADS1x15Read(self.i2c_address, self.i2c_bus, self.adc_chan, self.adc_gain) elif self.device == 'MCP342x': self.adc = MCP342xRead(self.i2c_address, self.i2c_bus, self.adc_chan, self.adc_gain, self.adc_resolution) else: self.adc = None self.device_recognized = True # Set up inputs or devices if self.device in ['EDGE', 'ADS1x15', 'MCP342x']: self.measure_input = None elif self.device == 'MYCODO_RAM': self.measure_input = MycodoRam() elif self.device == 'RPiCPULoad': self.measure_input = RaspberryPiCPULoad() elif self.device == 'RPi': self.measure_input = RaspberryPiCPUTemp() elif self.device == 'RPiFreeSpace': self.measure_input = RaspberryPiFreeSpace(self.location) elif self.device == 'AM2302': self.measure_input = DHT22Sensor(self.input_id, int(self.location)) elif self.device == 'AM2315': self.measure_input = AM2315Sensor(self.input_id, self.i2c_bus, power=self.power_output_id) elif self.device == 'ATLAS_PH_I2C': self.measure_input = AtlaspHSensor(self.interface, i2c_address=self.i2c_address, i2c_bus=self.i2c_bus, sensor_sel=self.input_sel) elif self.device == 'ATLAS_PH_UART': self.measure_input = AtlaspHSensor(self.interface, device_loc=self.device_loc, baud_rate=self.baud_rate, sensor_sel=self.input_sel) elif self.device == 'ATLAS_PT1000_I2C': self.measure_input = AtlasPT1000Sensor( self.interface, i2c_address=self.i2c_address, i2c_bus=self.i2c_bus) elif self.device == 'ATLAS_PT1000_UART': self.measure_input = AtlasPT1000Sensor(self.interface, device_loc=self.device_loc, baud_rate=self.baud_rate) elif self.device == 'BH1750': self.measure_input = BH1750Sensor(self.i2c_address, self.i2c_bus, self.resolution, self.sensitivity) elif self.device == 'BME280': self.measure_input = BME280Sensor(self.i2c_address, self.i2c_bus) # TODO: BMP is an old designation and will be removed in the future elif self.device in ['BMP', 'BMP180']: self.measure_input = BMP180Sensor(self.i2c_bus) elif self.device == 'BMP280': self.measure_input = BMP280Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'CHIRP': self.measure_input = ChirpSensor(self.i2c_address, self.i2c_bus) elif self.device == 'DS18B20': self.measure_input = DS18B20Sensor(self.location) elif self.device == 'DHT11': self.measure_input = DHT11Sensor(self.input_id, int(self.location), power=self.power_output_id) elif self.device == 'DHT22': self.measure_input = DHT22Sensor(self.input_id, int(self.location), power=self.power_output_id) elif self.device == 'HTU21D': self.measure_input = HTU21DSensor(self.i2c_bus) elif self.device == 'K30_UART': self.measure_input = K30Sensor(self.device_loc, baud_rate=self.baud_rate) elif self.device == 'MH_Z16_I2C': self.measure_input = MHZ16Sensor(self.interface, i2c_address=self.i2c_address, i2c_bus=self.i2c_bus) elif self.device == 'MH_Z16_UART': self.measure_input = MHZ16Sensor(self.interface, device_loc=self.device_loc, baud_rate=self.baud_rate) elif self.device == 'MH_Z19_UART': self.measure_input = MHZ19Sensor(self.device_loc, baud_rate=self.baud_rate) elif self.device == 'SHT1x_7x': self.measure_input = SHT1x7xSensor(int(self.location), self.sht_clock_pin, self.sht_voltage) elif self.device == 'SHT2x': self.measure_input = SHT2xSensor(self.i2c_address, self.i2c_bus) elif self.device == 'SIGNAL_PWM': self.measure_input = SignalPWMInput(int(self.location), self.weighting, self.sample_time) elif self.device == 'SIGNAL_RPM': self.measure_input = SignalRPMInput(int(self.location), self.weighting, self.rpm_pulses_per_rev, self.sample_time) elif self.device == 'TMP006': self.measure_input = TMP006Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'TSL2561': self.measure_input = TSL2561Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'TSL2591': self.measure_input = TSL2591Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'LinuxCommand': self.measure_input = LinuxCommand(self.cmd_command, self.cmd_measurement) else: self.device_recognized = False self.logger.debug( "Device '{device}' not recognized".format(device=self.device)) raise Exception("'{device}' is not a valid device type.".format( device=self.device)) if self.multiplexer: self.unlock_multiplexer() self.edge_reset_timer = time.time() self.input_timer = time.time() self.running = False self.lastUpdate = None
def check_conditionals(self, cond_id): """ Check if any sensor conditional statements are activated and execute their actions if the conditional is true. For example, if measured temperature is above 30C, notify [email protected] :rtype: None :param cond_id: ID of conditional to check :type cond_id: str """ logger_cond = logging.getLogger( "mycodo.sensor_cond_{id}".format(id=cond_id)) attachment_file = False attachment_type = False cond = db_retrieve_table_daemon(Conditional, device_id=cond_id, entry='first') message = u"[Sensor Conditional: {name} ({id})]".format(name=cond.name, id=cond_id) if cond.if_sensor_direction: last_measurement = self.get_last_measurement( cond.if_sensor_measurement) if (last_measurement and ((cond.if_sensor_direction == 'above' and last_measurement > cond.if_sensor_setpoint) or (cond.if_sensor_direction == 'below' and last_measurement < cond.if_sensor_setpoint))): message += u" {meas}: {value} ".format( meas=cond.if_sensor_measurement, value=last_measurement) if cond.if_sensor_direction == 'above': message += "(>" elif cond.if_sensor_direction == 'below': message += "(<" message += u" {sp} set value).".format( sp=cond.if_sensor_setpoint) else: logger_cond.debug("Last measurement not found") return 1 elif cond.if_sensor_edge_detected: if cond.if_sensor_edge_select == 'edge': message += u" {edge} Edge Detected.".format( edge=cond.if_sensor_edge_detected) elif cond.if_sensor_edge_select == 'state': if GPIO.input(int(self.location)) == cond.if_sensor_gpio_state: message += u" {state} GPIO State Detected.".format( state=cond.if_sensor_gpio_state) else: return 0 cond_actions = db_retrieve_table_daemon(ConditionalActions) cond_actions = cond_actions.filter( ConditionalActions.conditional_id == cond_id).all() for cond_action in cond_actions: message += u" Conditional Action ({id}): {do_action}.".format( id=cond_action.id, do_action=cond_action.do_action) # Actuate relay if (cond_action.do_relay_id and cond_action.do_relay_state in ['on', 'off']): message += u" Turn relay {id} {state}".format( id=cond_action.do_relay_id, state=cond_action.do_relay_state) if (cond_action.do_relay_state == 'on' and cond_action.do_relay_duration): message += u" for {sec} seconds".format( sec=cond_action.do_relay_duration) message += "." relay_on_off = threading.Thread( target=self.control.relay_on_off, args=( cond_action.do_relay_id, cond_action.do_relay_state, cond_action.do_relay_duration, )) relay_on_off.start() # Execute command in shell elif cond_action.do_action == 'command': message += u" Execute '{com}' ".format( com=cond_action.do_action_string) _, _, cmd_status = cmd_output(cond_action.do_action_string) message += u"(Status: {stat}).".format(stat=cmd_status) # Capture photo elif cond_action.do_action in ['photo', 'photo_email']: message += u" Capturing photo with camera ({id}).".format( id=cond_action.do_camera_id) camera_still = db_retrieve_table_daemon( Camera, device_id=cond_action.do_camera_id) attachment_file = camera_record('photo', camera_still) # Capture video elif cond_action.do_action in ['video', 'video_email']: message += u" Capturing video with camera ({id}).".format( id=cond_action.do_camera_id) camera_stream = db_retrieve_table_daemon( Camera, device_id=cond_action.do_camera_id) attachment_file = camera_record( 'video', camera_stream, duration_sec=cond_action.do_camera_duration) # Activate PID controller elif cond_action.do_action == 'activate_pid': message += u" Activate PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon(PID, device_id=cond_action.do_pid_id, entry='first') if pid.is_activated: message += u" Notice: PID is already active!" else: activate_pid = threading.Thread( target=self.control.controller_activate, args=( 'PID', cond_action.do_pid_id, )) activate_pid.start() # Deactivate PID controller elif cond_action.do_action == 'deactivate_pid': message += u" Deactivate PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon(PID, device_id=cond_action.do_pid_id, entry='first') if not pid.is_activated: message += u" Notice: PID is already inactive!" else: deactivate_pid = threading.Thread( target=self.control.controller_deactivate, args=( 'PID', cond_action.do_pid_id, )) deactivate_pid.start() elif cond_action.do_action in [ 'email', 'photo_email', 'video_email' ]: 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 += u" Notify {email}.".format( email=cond_action.do_action_string) # attachment_type != False indicates to # attach a photo or video if cond_action.do_action == 'photo_email': message += u" Photo attached to email." attachment_type = 'still' elif cond_action.do_action == 'video_email': message += u" Video attached to email." attachment_type = 'video' smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, cond_action.do_action_string, message, attachment_file, attachment_type) else: logger_cond.debug( "Wait {sec:.0f} seconds to email again.".format( sec=self.smtp_wait_timer[cond_id] - time.time())) elif cond_action.do_action == 'flash_lcd': message += u" Flashing LCD ({id}).".format( id=cond_action.do_lcd_id) start_flashing = threading.Thread( target=self.control.flash_lcd, args=( cond_action.do_lcd_id, 1, )) start_flashing.start() logger_cond.debug(message)
def __init__(self, ready, lcd_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.lcd_{id}".format(id=lcd_id)) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.flash_lcd_on = False self.lcd_initilized = False self.lcd_is_on = False self.lcd_id = lcd_id self.display_ids = [] self.display_count = 0 try: lcd = db_retrieve_table_daemon(LCD, device_id=self.lcd_id) self.lcd_name = lcd.name self.lcd_location = lcd.location self.lcd_i2c_bus = lcd.i2c_bus self.lcd_period = lcd.period self.lcd_x_characters = lcd.x_characters self.lcd_y_lines = lcd.y_lines self.timer = time.time() + self.lcd_period self.backlight_timer = time.time() 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.list_pids = ['setpoint', 'pid_time'] self.list_outputs = ['duration_sec', 'relay_time', 'relay_state'] self.list_inputs = MEASUREMENT_UNITS self.list_inputs.update( {'sensor_time': { 'unit': None, 'name': 'Time' }}) # Add custom measurement and units to list (From linux command input) input_dev = db_retrieve_table_daemon(Input) self.list_inputs = add_custom_measurements(input_dev, self.list_inputs, MEASUREMENT_UNITS) lcd_data = db_retrieve_table_daemon(LCDData).filter( LCDData.lcd_id == lcd.id).all() self.lcd_string_line = {} self.lcd_line = {} self.lcd_max_age = {} for each_lcd_display in lcd_data: self.display_ids.append(each_lcd_display.id) self.lcd_string_line[each_lcd_display.id] = {} self.lcd_line[each_lcd_display.id] = {} self.lcd_max_age[each_lcd_display.id] = {} for i in range(1, self.lcd_y_lines + 1): self.lcd_string_line[each_lcd_display.id][i] = '' self.lcd_line[each_lcd_display.id][i] = {} self.lcd_max_age[each_lcd_display. id][i] = each_lcd_display.line_1_max_age if self.lcd_y_lines in [2, 4]: self.setup_lcd_line(each_lcd_display.id, 1, each_lcd_display.line_1_id, each_lcd_display.line_1_measurement) self.setup_lcd_line(each_lcd_display.id, 2, each_lcd_display.line_2_id, each_lcd_display.line_2_measurement) if self.lcd_y_lines == 4: self.setup_lcd_line(each_lcd_display.id, 3, each_lcd_display.line_3_id, each_lcd_display.line_3_measurement) self.setup_lcd_line(each_lcd_display.id, 4, each_lcd_display.line_4_id, each_lcd_display.line_4_measurement) self.LCD_WIDTH = self.lcd_x_characters # Max characters per line self.LCD_LINE = {1: 0x80, 2: 0xC0, 3: 0x94, 4: 0xD4} self.LCD_CHR = 1 # Mode - Sending data self.LCD_CMD = 0 # Mode - SenLCDding command self.LCD_BACKLIGHT = 0x08 # On self.LCD_BACKLIGHT_OFF = 0x00 # Off self.ENABLE = 0b00000100 # Enable bit # Timing constants self.E_PULSE = 0.0005 self.E_DELAY = 0.0005 # Setup I2C bus try: self.bus = smbus.SMBus(self.lcd_i2c_bus) except Exception as except_msg: self.logger.exception( "Could not initialize I2C bus: {err}".format( err=except_msg)) self.I2C_ADDR = int(self.lcd_location, 16) self.lcd_init() if self.lcd_initilized: self.lcd_string_write('Mycodo {}'.format(MYCODO_VERSION), self.LCD_LINE[1]) self.lcd_string_write(u'Start {}'.format(self.lcd_name), self.LCD_LINE[2]) except Exception as except_msg: self.logger.exception("Error: {err}".format(err=except_msg))
def check_conditionals(self, relay_id, on_duration): conditionals = db_retrieve_table_daemon(Conditional) conditionals = conditionals.filter( Conditional.if_relay_id == relay_id) conditionals = conditionals.filter( Conditional.is_activated == True) if self.is_on(relay_id): conditionals = conditionals.filter( Conditional.if_relay_state == 'on') conditionals = conditionals.filter( Conditional.if_relay_duration == on_duration) else: conditionals = conditionals.filter( Conditional.if_relay_state == 'off') for each_conditional in conditionals.all(): conditional_actions = db_retrieve_table_daemon(ConditionalActions) conditional_actions = conditional_actions.filter( ConditionalActions.conditional_id == each_conditional.id).all() for each_cond_action in conditional_actions: now = time.time() timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H-%M-%S') message = u"{ts}\n[Relay Conditional {id}] {name}\n".format( ts=timestamp, id=each_cond_action.id, name=each_conditional.name) if each_cond_action.do_action == 'relay': if each_cond_action.do_relay_id not in self.relay_name: message += u"Error: Invalid relay ID {id}.".format( id=each_cond_action.do_relay_id) else: message += u"If relay {id} ({name}) turns {state}, Then ".format( id=each_conditional.if_relay_id, name=self.relay_name[each_conditional.if_relay_id], state=each_conditional.if_relay_state) message += u"turn relay {id} ({name}) {state}".format( id=each_cond_action.do_relay_id, name=self.relay_name[each_cond_action.do_relay_id], state=each_cond_action.do_relay_state) if each_cond_action.do_relay_duration == 0: self.relay_on_off(each_cond_action.do_relay_id, each_cond_action.do_relay_state) else: message += u" for {dur} seconds".format( dur=each_cond_action.do_relay_duration) self.relay_on_off(each_cond_action.do_relay_id, each_cond_action.do_relay_state, duration=each_cond_action.do_relay_duration) message += ".\n" elif each_cond_action.do_action == 'command': # Execute command as user mycodo message += u"Execute: '{}'. ".format( each_cond_action.do_action_string) _, _, cmd_status = cmd_output( each_cond_action.do_action_string) message += u"Status: {}. ".format(cmd_status) elif each_cond_action.do_action == 'email': 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 += u"Notify {}.".format( each_cond_action.email_notify) smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email( smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, each_cond_action.do_action_string, message) else: self.logger.debug( "[Relay Conditional {}] True: {:.0f} seconds " "left to be allowed to email again.".format( each_conditional.id, self.smtp_wait_time-time.time())) elif each_cond_action.do_action == 'flash_lcd': start_flashing = threading.Thread( target=self.control.flash_lcd, args=(each_cond_action.do_lcd_id, 1,)) start_flashing.start() # TODO: Implement photo/video actions for relay conditionals elif each_cond_action.do_action == 'photo': pass elif each_cond_action.do_action == 'video': pass self.logger.debug(u"{}".format(message))
def __init__(self, ready, sensor_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.sensor_{id}".format(id=sensor_id)) self.stop_iteration_counter = 0 self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.lock = {} self.measurement = None self.updateSuccess = False self.sensor_id = sensor_id self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.cond_id = {} self.cond_action_id = {} self.cond_name = {} self.cond_is_activated = {} self.cond_if_sensor_period = {} self.cond_if_sensor_measurement = {} self.cond_if_sensor_edge_select = {} self.cond_if_sensor_edge_detected = {} self.cond_if_sensor_gpio_state = {} self.cond_if_sensor_direction = {} self.cond_if_sensor_setpoint = {} self.cond_do_relay_id = {} self.cond_do_relay_state = {} self.cond_do_relay_duration = {} self.cond_execute_command = {} self.cond_email_notify = {} self.cond_do_lcd_id = {} self.cond_do_camera_id = {} self.cond_timer = {} self.smtp_wait_timer = {} self.setup_sensor_conditionals() sensor = db_retrieve_table_daemon(Sensor, device_id=self.sensor_id) self.unique_id = sensor.unique_id self.i2c_bus = sensor.i2c_bus self.location = sensor.location self.power_relay_id = sensor.power_relay_id self.measurements = sensor.measurements self.device = sensor.device self.period = sensor.period self.resolution = sensor.resolution self.sensitivity = sensor.sensitivity self.mux_address_raw = sensor.multiplexer_address self.mux_bus = sensor.multiplexer_bus self.mux_chan = sensor.multiplexer_channel self.adc_chan = sensor.adc_channel self.adc_gain = sensor.adc_gain self.adc_resolution = sensor.adc_resolution self.adc_measure = sensor.adc_measure self.adc_measure_units = sensor.adc_measure_units self.adc_volts_min = sensor.adc_volts_min self.adc_volts_max = sensor.adc_volts_max self.adc_units_min = sensor.adc_units_min self.adc_units_max = sensor.adc_units_max self.sht_clock_pin = sensor.sht_clock_pin self.sht_voltage = sensor.sht_voltage # Edge detection self.switch_edge = sensor.switch_edge self.switch_bouncetime = sensor.switch_bouncetime self.switch_reset_period = sensor.switch_reset_period # Relay that will activate prior to sensor read self.pre_relay_id = sensor.pre_relay_id self.pre_relay_duration = sensor.pre_relay_duration self.pre_relay_setup = False self.next_measurement = time.time() self.get_new_measurement = False self.measurement_acquired = False self.pre_relay_activated = False self.pre_relay_timer = time.time() relay = db_retrieve_table_daemon(Relay, entry='all') for each_relay in relay: # Check if relay ID actually exists if each_relay.id == self.pre_relay_id and self.pre_relay_duration: self.pre_relay_setup = True smtp = db_retrieve_table_daemon(SMTP, entry='first') self.smtp_max_count = smtp.hourly_max self.email_count = 0 self.allowed_to_send_notice = True # Convert string I2C address to base-16 int if self.device in LIST_DEVICES_I2C: self.i2c_address = int(str(self.location), 16) # Set up multiplexer if enabled if self.device in LIST_DEVICES_I2C and self.mux_address_raw: self.mux_address_string = self.mux_address_raw self.mux_address = int(str(self.mux_address_raw), 16) self.mux_lock = "/var/lock/mycodo_multiplexer_0x{i2c:02X}.pid".format( i2c=self.mux_address) self.multiplexer = TCA9548A(self.mux_bus, self.mux_address) else: self.multiplexer = None if self.device in ['ADS1x15', 'MCP342x'] and self.location: self.adc_lock_file = "/var/lock/mycodo_adc_bus{bus}_0x{i2c:02X}.pid".format( bus=self.i2c_bus, i2c=self.i2c_address) # Set up edge detection of a GPIO pin if self.device == 'EDGE': if self.switch_edge == 'rising': self.switch_edge_gpio = GPIO.RISING elif self.switch_edge == 'falling': self.switch_edge_gpio = GPIO.FALLING else: self.switch_edge_gpio = GPIO.BOTH # Set up analog-to-digital converter elif self.device == 'ADS1x15': self.adc = ADS1x15Read(self.i2c_address, self.i2c_bus, self.adc_chan, self.adc_gain) elif self.device == 'MCP342x': self.adc = MCP342xRead(self.i2c_address, self.i2c_bus, self.adc_chan, self.adc_gain, self.adc_resolution) else: self.adc = None self.device_recognized = True # Set up sensors or devices if self.device in ['EDGE', 'ADS1x15', 'MCP342x']: self.measure_sensor = None elif self.device == 'MYCODO_RAM': self.measure_sensor = MycodoRam() elif self.device == 'RPiCPULoad': self.measure_sensor = RaspberryPiCPULoad() elif self.device == 'RPi': self.measure_sensor = RaspberryPiCPUTemp() elif self.device == 'RPiFreeSpace': self.measure_sensor = RaspberryPiFreeSpace(self.location) elif self.device == 'AM2302': self.measure_sensor = DHT22Sensor(self.sensor_id, int(self.location)) elif self.device == 'AM2315': self.measure_sensor = AM2315Sensor(self.i2c_bus) elif self.device == 'ATLAS_PT1000': self.measure_sensor = AtlasPT1000Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'BH1750': self.measure_sensor = BH1750Sensor(self.i2c_address, self.i2c_bus, self.resolution, self.sensitivity) elif self.device == 'BME280': self.measure_sensor = BME280Sensor(self.i2c_address, self.i2c_bus) # TODO: BMP is an old designation and will be removed in the future elif self.device in ['BMP', 'BMP180']: self.measure_sensor = BMP180Sensor(self.i2c_bus) elif self.device == 'BMP280': self.measure_sensor = BMP280Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'CHIRP': self.measure_sensor = ChirpSensor(self.i2c_address, self.i2c_bus) elif self.device == 'DS18B20': self.measure_sensor = DS18B20Sensor(self.location) elif self.device == 'DHT11': self.measure_sensor = DHT11Sensor(self.sensor_id, int(self.location), power=self.power_relay_id) elif self.device == 'DHT22': self.measure_sensor = DHT22Sensor(self.sensor_id, int(self.location), power=self.power_relay_id) elif self.device == 'HTU21D': self.measure_sensor = HTU21DSensor(self.i2c_bus) elif self.device == 'K30': self.measure_sensor = K30Sensor() elif self.device == 'SHT1x_7x': self.measure_sensor = SHT1x7xSensor(self.location, self.sht_clock_pin, self.sht_voltage) elif self.device == 'SHT2x': self.measure_sensor = SHT2xSensor(self.i2c_address, self.i2c_bus) elif self.device == 'TMP006': self.measure_sensor = TMP006Sensor(self.i2c_address, self.i2c_bus) elif self.device == 'TSL2561': self.measure_sensor = TSL2561Sensor(self.i2c_address, self.i2c_bus) else: self.device_recognized = False self.logger.debug( "Device '{device}' not recognized".format(device=self.device)) raise Exception("'{device}' is not a valid device type.".format( device=self.device)) self.edge_reset_timer = time.time() self.sensor_timer = time.time() self.running = False self.lastUpdate = None
def check_conditionals(self, output_id, state=None, on_duration=None, duty_cycle=None): conditionals = db_retrieve_table_daemon(Conditional) conditionals = conditionals.filter( Conditional.if_relay_id == output_id) conditionals = conditionals.filter( Conditional.is_activated == True) if self.is_on(output_id): conditionals = conditionals.filter( or_(Conditional.if_relay_state == 'on', Conditional.if_relay_state == 'on_any')) on_with_duration = and_( Conditional.if_relay_state == 'on', Conditional.if_relay_duration == on_duration) conditionals = conditionals.filter( or_(Conditional.if_relay_state == 'on_any', on_with_duration)) else: conditionals = conditionals.filter( Conditional.if_relay_state == 'off') for each_conditional in conditionals.all(): conditional_actions = db_retrieve_table_daemon(ConditionalActions) conditional_actions = conditional_actions.filter( ConditionalActions.conditional_id == each_conditional.id).all() for each_cond_action in conditional_actions: now = time.time() timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H-%M-%S') message = u"{ts}\n[Output Conditional {id}] {name}\n".format( ts=timestamp, id=each_cond_action.id, name=each_conditional.name) if each_cond_action.do_action == 'relay': if each_cond_action.do_relay_id not in self.output_name: message += u"Error: Invalid output ID {id}.".format( id=each_cond_action.do_relay_id) else: message += u"If output {id} ({name}) turns {state}, Then ".format( id=each_conditional.if_relay_id, name=self.output_name[each_conditional.if_relay_id], state=each_conditional.if_relay_state) message += u"turn output {id} ({name}) {state}".format( id=each_cond_action.do_relay_id, name=self.output_name[each_cond_action.do_relay_id], state=each_cond_action.do_relay_state) if each_cond_action.do_relay_duration == 0: self.output_on_off(each_cond_action.do_relay_id, each_cond_action.do_relay_state) else: message += u" for {dur} seconds".format( dur=each_cond_action.do_relay_duration) self.output_on_off(each_cond_action.do_relay_id, each_cond_action.do_relay_state, duration=each_cond_action.do_relay_duration) message += ".\n" elif each_cond_action.do_action == 'command': # Execute command as user mycodo message += u"Execute: '{}'. ".format( each_cond_action.do_action_string) # Check command for variables to replace with values command_str = each_cond_action.do_action_string command_str = command_str.replace( "((output_pin))", str(self.output_pin[output_id])) command_str = command_str.replace( "((output_action))", str(state)) command_str = command_str.replace( "((output_duration))", str(on_duration)) command_str = command_str.replace( "((output_pwm))", str(duty_cycle)) _, _, cmd_status = cmd_output(command_str) message += u"Status: {}. ".format(cmd_status) elif each_cond_action.do_action == 'email': 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 += u"Notify {}.".format( each_cond_action.email_notify) smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email( smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, each_cond_action.do_action_string, message) else: self.logger.debug( "[Output Conditional {}] True: {:.0f} seconds " "left to be allowed to email again.".format( each_conditional.id, self.smtp_wait_time-time.time())) elif each_cond_action.do_action == 'flash_lcd': start_flashing = threading.Thread( target=self.control.flash_lcd, args=(each_cond_action.do_lcd_id, 1,)) start_flashing.start() # TODO: Implement photo/video actions for output conditionals elif each_cond_action.do_action == 'photo': self.logger.error("Photo action not currently implemented") elif each_cond_action.do_action == 'video': self.logger.error("Video action not currently implemented") self.logger.debug(u"{}".format(message))
def get_lcd_strings(self): """ Retrieve measurements and/or timestamps and create strings for LCDs If no data is retrieveable, create string "NO DATA RETURNED". """ # loop to acquire all measurements required to be displayed on the LCD for i in range(1, self.lcd_y_lines + 1): if self.lcd_line[i]['id']: # Get latest measurement (within past minute) from influxdb # FROM '/.*/' returns any measurement (for grabbing time of last measurement) last_measurement_success = False try: if self.lcd_line[i]['measurement'] == 'relay_state': self.lcd_line[i][ 'measurement_value'] = self.relay_state( self.lcd_line[i]['id']) last_measurement_success = True else: if self.lcd_line[i]['measurement'] == 'time': last_measurement = read_last_influxdb( self.lcd_line[i]['id'], '/.*/') else: last_measurement = read_last_influxdb( self.lcd_line[i]['id'], self.lcd_line[i]['measurement']) if last_measurement: self.lcd_line[i]['time'] = last_measurement[0] self.lcd_line[i][ 'measurement_value'] = last_measurement[1] utc_dt = datetime.datetime.strptime( self.lcd_line[i]['time'].split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) local_timestamp = str( datetime.datetime.fromtimestamp(utc_timestamp)) self.logger.debug("Latest {}: {} @ {}".format( self.lcd_line[i]['measurement'], self.lcd_line[i]['measurement_value'], local_timestamp)) last_measurement_success = True else: self.lcd_line[i]['time'] = None self.lcd_line[i]['measurement_value'] = None self.logger.debug("No data returned from " "influxdb") except Exception as except_msg: self.logger.debug( "Failed to read measurement from the " "influxdb database: {err}".format(err=except_msg)) try: if last_measurement_success: # Determine if the LCD output will have a value unit measurement = '' if self.lcd_line[i]['measurement'] == 'setpoint': pid = db_retrieve_table_daemon( PID, unique_id=self.lcd_line[i]['id']) measurement = pid.measurement elif self.lcd_line[i]['measurement'] == 'duration_sec': measurement = 'duration_sec' self.lcd_line[i][ 'measurement_value'] = '{:.2f}'.format( self.lcd_line[i]['measurement_value']) elif self.lcd_line[i][ 'measurement'] in MEASUREMENT_UNITS: measurement = self.lcd_line[i]['measurement'] # Produce the line that will be displayed on the LCD number_characters = self.lcd_x_characters if self.lcd_line[i]['measurement'] == 'time': # Convert UTC timestamp to local timezone utc_dt = datetime.datetime.strptime( self.lcd_line[i]['time'].split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) self.lcd_string_line[i] = str( datetime.datetime.fromtimestamp(utc_timestamp)) elif measurement: value_length = len( str(self.lcd_line[i]['measurement_value'])) unit_length = len( MEASUREMENT_UNITS[measurement]['unit']) 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] = u'{name} {value} {unit}'.format( name=name_cropped, value=self.lcd_line[i] ['measurement_value'], unit=MEASUREMENT_UNITS[measurement] ['unit']) 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] = u'{name} {value}'.format( name=name_cropped, value=self.lcd_line[i]['measurement_value']) else: self.lcd_string_line[i] = 'ERROR: NO DATA' except Exception as except_msg: self.logger.exception( "Error: {err}".format(err=except_msg)) else: self.lcd_string_line[i] = ''
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 output_on_off(self, output_id, state, duration=0.0, min_off=0.0, duty_cycle=0.0, trigger_conditionals=True): """ Turn a output on or off The GPIO may be either HIGH or LOW to activate a output. This trigger state will be referenced to determine if the GPIO needs to be high or low to turn the output on or off. Conditionals will be checked for each action requested of a output, and if true, those conditional actions will be executed. For example: 'If output 1 turns on, turn output 3 off' :param output_id: Unique ID for output :type output_id: int :param state: What state is desired? 'on' or 'off' :type state: str :param duration: If state is 'on', a duration can be set to turn the output off after :type duration: float :param min_off: Don't turn on if not off for at least this duration (0 = disabled) :type min_off: float :param duty_cycle: Duty cycle of PWM output :type duty_cycle: float :param trigger_conditionals: Whether to trigger conditionals to act or not :type trigger_conditionals: bool """ # Check if output exists output_id = int(output_id) if output_id not in self.output_id: self.logger.warning( "Cannot turn {state} Output with ID {id}. " "It doesn't exist".format( state=state, id=output_id)) return 1 # Signaled to turn output on if state == 'on': # Check if pin is valid if (self.output_type[output_id] in ['pwm', 'wired', 'wireless_433MHz_pi_switch'] and self.output_pin[output_id] is None): self.logger.warning( u"Invalid pin for output {id} ({name}): {pin}.".format( id=self.output_id[output_id], name=self.output_name[output_id], pin=self.output_pin[output_id])) return 1 # Check if max amperage will be exceeded if self.output_type[output_id] in ['command', 'wired', 'wireless_433MHz_pi_switch']: current_amps = self.current_amp_load() max_amps = db_retrieve_table_daemon(Misc, entry='first').max_amps if current_amps + self.output_amps[output_id] > max_amps: self.logger.warning( u"Cannot turn output {} ({}) On. If this output turns on, " u"there will be {} amps being drawn, which exceeds the " u"maximum set draw of {} amps.".format( self.output_id[output_id], self.output_name[output_id], current_amps, max_amps)) return 1 # If the output is used in a PID, a minimum off duration is set, # and if the off duration has surpassed that amount of time (i.e. # has it been off for longer then the minimum off duration?). current_time = datetime.datetime.now() if (min_off and not self.is_on(output_id) and current_time > self.output_on_until[output_id]): off_seconds = (current_time - self.output_on_until[output_id]).total_seconds() if off_seconds < min_off: self.logger.debug( u"Output {id} ({name}) instructed to turn on by PID, " u"however the minimum off period of {min_off_sec} " u"seconds has not been reached yet (it has only been " u"off for {off_sec} seconds).".format( id=self.output_id[output_id], name=self.output_name[output_id], min_off_sec=min_off, off_sec=off_seconds)) return 1 # Turn output on for a duration if (self.output_type[output_id] in ['command', 'wired', 'wireless_433MHz_pi_switch'] and duration): time_now = datetime.datetime.now() if self.is_on(output_id) and self.output_on_duration[output_id]: if self.output_on_until[output_id] > time_now: remaining_time = (self.output_on_until[output_id] - time_now).total_seconds() else: remaining_time = 0 time_on = self.output_last_duration[output_id] - remaining_time self.logger.debug( u"Output {rid} ({rname}) is already on for a duration " u"of {ron:.2f} seconds (with {rremain:.2f} seconds " u"remaining). Recording the amount of time the output " u"has been on ({rbeenon:.2f} sec) and updating the on " u"duration to {rnewon:.2f} seconds.".format( rid=self.output_id[output_id], rname=self.output_name[output_id], ron=self.output_last_duration[output_id], rremain=remaining_time, rbeenon=time_on, rnewon=duration)) self.output_on_until[output_id] = time_now + datetime.timedelta(seconds=duration) self.output_last_duration[output_id] = duration if time_on > 0: # Write the duration the output was ON to the # database at the timestamp it turned ON duration = float(time_on) timestamp = datetime.datetime.utcnow() - datetime.timedelta(seconds=duration) write_db = threading.Thread( target=write_influxdb_value, args=(self.output_unique_id[output_id], 'duration_sec', duration, timestamp,)) write_db.start() return 0 elif self.is_on(output_id) and not self.output_on_duration: self.output_on_duration[output_id] = True self.output_on_until[output_id] = time_now + datetime.timedelta(seconds=duration) self.output_last_duration[output_id] = duration self.logger.debug( u"Output {id} ({name}) is currently on without a " u"duration. Turning into a duration of {dur:.1f} " u"seconds.".format( id=self.output_id[output_id], name=self.output_name[output_id], dur=duration)) return 0 else: self.output_on_until[output_id] = time_now + datetime.timedelta(seconds=duration) self.output_on_duration[output_id] = True self.output_last_duration[output_id] = duration self.logger.debug( u"Output {id} ({name}) on for {dur:.1f} " u"seconds.".format( id=self.output_id[output_id], name=self.output_name[output_id], dur=duration)) self.output_switch(output_id, 'on') # Just turn output on elif self.output_type[output_id] in ['command', 'wired', 'wireless_433MHz_pi_switch']: if self.is_on(output_id): self.logger.warning( u"Output {id} ({name}) is already on.".format( id=self.output_id[output_id], name=self.output_name[output_id])) return 1 else: # Record the time the output was turned on in order to # calculate and log the total duration is was on, when # it eventually turns off. self.output_time_turned_on[output_id] = datetime.datetime.now() self.logger.debug( u"Output {id} ({name}) ON at {timeon}.".format( id=self.output_id[output_id], name=self.output_name[output_id], timeon=self.output_time_turned_on[output_id])) self.output_switch(output_id, 'on') # PWM output elif self.output_type[output_id] == 'pwm': # Record the time the PWM was turned on if self.pwm_hertz[output_id] <= 0: self.logger.warning(u"PWM Hertz must be a positive value") return 1 self.pwm_time_turned_on[output_id] = datetime.datetime.now() self.logger.debug( u"PWM {id} ({name}) ON with a duty cycle of {dc:.2f}% at {hertz} Hz".format( id=self.output_id[output_id], name=self.output_name[output_id], dc=abs(duty_cycle), hertz=self.pwm_hertz[output_id])) self.output_switch(output_id, 'on', duty_cycle=duty_cycle) # Write the duty cycle of the PWM to the database write_db = threading.Thread( target=write_influxdb_value, args=(self.output_unique_id[output_id], 'duty_cycle', duty_cycle,)) write_db.start() # Signaled to turn output off elif state == 'off': if not self._is_setup(output_id): return if (self.output_type[output_id] in ['pwm', 'wired', 'wireless_433MHz_pi_switch'] and self.output_pin[output_id] is None): return self.output_switch(output_id, 'off') self.logger.debug(u"Output {id} ({name}) turned off.".format( id=self.output_id[output_id], name=self.output_name[output_id])) # Write PWM duty cycle to database if (self.output_type[output_id] == 'pwm' and self.pwm_time_turned_on[output_id] is not None): # Write the duration the PWM was ON to the database # at the timestamp it turned ON duration = (datetime.datetime.now() - self.pwm_time_turned_on[output_id]).total_seconds() self.pwm_time_turned_on[output_id] = None timestamp = datetime.datetime.utcnow() - datetime.timedelta(seconds=duration) write_db = threading.Thread( target=write_influxdb_value, args=(self.output_unique_id[output_id], 'duty_cycle', duty_cycle, timestamp,)) write_db.start() # Write output duration on to database elif (self.output_time_turned_on[output_id] is not None or self.output_on_duration[output_id]): duration = 0 if self.output_on_duration[output_id]: remaining_time = 0 time_now = datetime.datetime.now() if self.output_on_until[output_id] > time_now: remaining_time = (self.output_on_until[output_id] - time_now).total_seconds() duration = self.output_last_duration[output_id] - remaining_time self.output_on_duration[output_id] = False self.output_on_until[output_id] = datetime.datetime.now() if self.output_time_turned_on[output_id] is not None: # Write the duration the output was ON to the database # at the timestamp it turned ON duration = (datetime.datetime.now() - self.output_time_turned_on[output_id]).total_seconds() self.output_time_turned_on[output_id] = None timestamp = datetime.datetime.utcnow() - datetime.timedelta(seconds=duration) write_db = threading.Thread( target=write_influxdb_value, args=(self.output_unique_id[output_id], 'duration_sec', duration, timestamp,)) write_db.start() if trigger_conditionals: self.check_conditionals(output_id, state=state, on_duration=duration, duty_cycle=duty_cycle)
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 the last measurement was able to be retrieved and was entered within the past minute if self.last_measurement_success: # # PID control variable is positive, indicating a desire to raise # the environmental condition # if self.direction in ['raise', 'both'] and self.raise_relay_id: if self.control_variable > 0: # Ensure the relay on duration doesn't exceed the set maximum if (self.raise_max_duration and self.control_variable > self.raise_max_duration): self.raise_seconds_on = self.raise_max_duration else: self.raise_seconds_on = float("{0:.2f}".format( self.control_variable)) # Turn off lower_relay if active, because we're now raising if self.lower_relay_id: relay = db_retrieve_table_daemon( Relay, device_id=self.lower_relay_id) if relay.is_on(): self.control.relay_off(self.lower_relay_id) if self.raise_seconds_on > self.raise_min_duration: # Activate raise_relay for a duration self.logger.debug( "Setpoint: {sp} Output: {op} to relay " "{relay}".format(sp=self.set_point, op=self.control_variable, relay=self.raise_relay_id)) self.control.relay_on( self.raise_relay_id, self.raise_seconds_on, min_off_duration=self.raise_min_off_duration) else: self.control.relay_off(self.raise_relay_id) # # PID control variable is negative, indicating a desire to lower # the environmental condition # if self.direction in ['lower', 'both'] and self.lower_relay_id: if self.control_variable < 0: # Ensure the relay on duration doesn't exceed the set maximum if (self.lower_max_duration and abs(self.control_variable) > self.lower_max_duration): self.lower_seconds_on = self.lower_max_duration else: self.lower_seconds_on = abs( float("{0:.2f}".format(self.control_variable))) # Turn off raise_relay if active, because we're now lowering if self.raise_relay_id: relay = db_retrieve_table_daemon( Relay, device_id=self.raise_relay_id) if relay.is_on(): self.control.relay_off(self.raise_relay_id) if self.lower_seconds_on > self.lower_min_duration: # Activate lower_relay for a duration self.logger.debug("Setpoint: {sp} Output: {op} to " "relay {relay}".format( sp=self.set_point, op=self.control_variable, relay=self.lower_relay_id)) self.control.relay_on( self.lower_relay_id, self.lower_seconds_on, min_off_duration=self.lower_min_off_duration) 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 run(self): try: self.running = True self.logger.info("Activated in {:.1f} ms".format( (timeit.default_timer() - self.thread_startup_timer) * 1000)) if self.is_paused: self.logger.info("Paused") elif self.is_held: self.logger.info("Held") self.ready.set() while self.running: if (self.method_start_act == 'Ended' and self.method_type == 'Duration'): self.stop_controller(ended_normally=False, deactivate_pid=True) self.logger.warning( "Method has ended. " "Activate the PID controller to start it again.") elif t.time() > self.timer: # Ensure the timer ends in the future while t.time() > self.timer: self.timer = self.timer + self.period # If PID is active, retrieve input measurement and update PID output if self.is_activated and not self.is_paused: self.get_last_measurement() if self.last_measurement_success: # Update setpoint using a method if one is selected if self.method_id: this_controller = db_retrieve_table_daemon( PID, device_id=self.pid_id) setpoint, ended = calculate_method_setpoint( self.method_id, PID, this_controller, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if setpoint is not None: self.set_point = setpoint else: self.set_point = self.default_set_point write_setpoint_db = threading.Thread( target=write_influxdb_value, args=( self.pid_unique_id, 'setpoint', self.set_point, )) write_setpoint_db.start() # Update PID and get control variable self.control_variable = self.update_pid_output( self.last_measurement) # If PID is active or on hold, activate outputs if ((self.is_activated and not self.is_paused) or (self.is_activated and self.is_held)): self.manipulate_output() t.sleep(0.1) # Turn off output used in PID when the controller is deactivated if self.raise_output_id and self.direction in ['raise', 'both']: self.control.relay_off(self.raise_output_id, trigger_conditionals=True) if self.lower_output_id and self.direction in ['lower', 'both']: self.control.relay_off(self.lower_output_id, trigger_conditionals=True) self.running = False self.logger.info("Deactivated in {:.1f} ms".format( (timeit.default_timer() - self.thread_shutdown_timer) * 1000)) except Exception as except_msg: self.logger.exception("Run Error: {err}".format(err=except_msg))
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 run(self): self.running = True self.logger.info("Activated in {:.1f} ms".format( (timeit.default_timer() - self.thread_startup_timer) * 1000)) self.ready.set() while self.running: # Timer is set to react at a specific hour and minute of the day if self.timer_type == 'time': if (int(self.start_hour) == datetime.datetime.now().hour and int(self.start_minute) == datetime.datetime.now().minute): # Ensure this is triggered only once at this specific time if self.date_timer_not_executed: self.date_timer_not_executed = False message = "At {st}, turn Output {id} {state}".format( st=self.time_start, id=self.output_id, state=self.state) if self.state == 'on' and self.duration_on: message += " for {sec} seconds".format( sec=self.duration_on) else: self.duration_on = 0 self.logger.debug(message) modulate_output = threading.Thread( target=self.control.output_on_off, args=( self.output_id, self.state, ), kwargs={'duration': self.duration_on}) modulate_output.start() elif not self.date_timer_not_executed: self.date_timer_not_executed = True # Timer is set to react at a specific time duration of the day elif self.timer_type == 'timespan': if time_between_range(self.time_start, self.time_end): current_output_state = self.control.relay_state( self.output_id) if self.state != current_output_state: message = "Output {output} should be {state}, but is " \ "{cstate}. Turning {state}.".format( output=self.output_id, state=self.state, cstate=current_output_state) modulate_output = threading.Thread( target=self.control.output_on_off, args=( self.output_id, self.state, )) modulate_output.start() self.logger.debug(message) # Timer is a simple on/off duration timer elif self.timer_type == 'duration': if time.time() > self.duration_timer: self.duration_timer = (time.time() + self.duration_on + self.duration_off) self.logger.debug("Turn Output {output} on for {onsec} " "seconds, then off for {offsec} " "seconds".format( output=self.output_id, onsec=self.duration_on, offsec=self.duration_off)) output_on = threading.Thread(target=self.control.relay_on, args=( self.output_id, self.duration_on, )) output_on.start() # Timer is a PWM Method timer elif self.timer_type == 'pwm_method': try: if time.time() > self.pwm_method_timer: if self.method_start_act == 'Ended': self.stop_controller(ended_normally=False, deactivate_timer=True) self.logger.info( "Method has ended. " "Activate the Timer controller to start it again." ) else: this_controller = db_retrieve_table_daemon( Timer, device_id=self.timer_id) setpoint, ended = calculate_method_setpoint( self.method_id, Timer, this_controller, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if setpoint > 100: setpoint = 100 elif setpoint < 0: setpoint = 0 self.logger.debug( "Turn Output {output} to a PWM duty cycle of " "{dc:.1f} %".format(output=self.output_id, dc=setpoint)) # Activate pwm with calculated duty cycle self.control.relay_on(self.output_id, duty_cycle=setpoint) self.pwm_method_timer = time.time( ) + self.method_period except Exception: self.logger.exception(1) time.sleep(0.1) self.control.relay_off(self.output_id) self.running = False self.logger.info("Deactivated in {:.1f} ms".format( (timeit.default_timer() - self.thread_shutdown_timer) * 1000))
def calculate_method_setpoint(self, method_id): 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) self.logger.debug("[Method] Start: {} End: {}".format( start_time, end_time)) self.logger.debug("[Method] Start: {} End: {}".format( setpoint_start, setpoint_end)) 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_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) 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( setpoint_start, setpoint_end)) 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_data_first.amplitude, method_data_first.frequency, method_data_first.shift_angle, method_data_first.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_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)) 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_data_all: 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 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) 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 method_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.id == self.method_id).first() mod_method.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