def gpio_state(): """Return the GPIO state, for output page status""" output = Output.query.all() daemon_control = DaemonControl() state = {} GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) for each_output in output: if each_output.output_type == 'wired' and each_output.pin and -1 < each_output.pin < 40: GPIO.setup(each_output.pin, GPIO.OUT) if GPIO.input(each_output.pin) == each_output.trigger: state[each_output.unique_id] = 'on' else: state[each_output.unique_id] = 'off' elif (each_output.output_type in ['command', 'command_pwm', 'python', 'python_pwm', 'atlas_ezo_pmp'] or (each_output.output_type in ['pwm', 'wireless_rpi_rf'] and each_output.pin and -1 < each_output.pin < 40)): state[each_output.unique_id] = daemon_control.output_state(each_output.unique_id) else: state[each_output.unique_id] = None return jsonify(state)
def manipulate_output(action, output_id): """ Add, delete, and modify output settings while the daemon is active :param output_id: output ID in the SQL database :type output_id: str :param action: "add", "del", or "mod" :type action: str """ try: control = DaemonControl() return_values = control.output_setup(action, output_id) if return_values and len(return_values) > 1: if return_values[0]: flash(gettext("%(err)s", err='{action} Output: Daemon response: {msg}'.format( action=action, msg=return_values[1])), "error") else: flash(gettext("%(err)s", err='{action} Output: Daemon response: {msg}'.format( action=gettext(action), msg=return_values[1])), "success") except Exception as msg: flash(gettext("%(err)s", err='{action} Output: Could not connect to Daemon: {error}'.format( action=action, error=msg)), "error")
def gpio_state_unique_id(unique_id): """Return the GPIO state, for dashboard output """ output = Output.query.filter( Output.unique_id == unique_id).first() daemon_control = DaemonControl() GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) if output.output_type == 'wired' and output.pin and -1 < output.pin < 40: GPIO.setup(output.pin, GPIO.OUT) if GPIO.input(output.pin) == output.trigger: state = 'on' else: state = 'off' elif (output.output_type in ['command', 'command_pwm', 'python', 'python_pwm', 'atlas_ezo_pmp'] or (output.output_type in ['pwm', 'wireless_rpi_rf'] and output.pin and -1 < output.pin < 40)): state = daemon_control.output_state(output.unique_id) else: state = None return jsonify(state)
def lcd_reset_flashing(lcd_id): control = DaemonControl() return_value, return_msg = control.lcd_flash(lcd_id, False) if return_value: flash(gettext("%(msg)s", msg=return_msg), "success") else: flash(gettext("%(msg)s", msg=return_msg), "error")
def daemon_active(): """Return 'alive' if the daemon is running""" try: control = DaemonControl() return control.daemon_status() except Exception as e: logger.error("URL for 'daemon_active' raised and error: " "{err}".format(err=e)) return '0'
def output_on_off(form_output): action = '{action} {controller}'.format( action=gettext("Actuate"), controller=TRANSLATIONS['output']['title']) error = [] try: control = DaemonControl() output = Output.query.filter_by(unique_id=form_output.output_id.data).first() if output.output_type == 'wired' and int(form_output.output_pin.data) == 0: error.append(gettext("Cannot modulate output with a GPIO of 0")) elif form_output.on_submit.data: if output.output_type in ['wired', 'wireless_rpi_rf', 'command']: if float(form_output.sec_on.data) <= 0: error.append(gettext("Value must be greater than 0")) else: return_value = control.output_on(form_output.output_id.data, duration=float(form_output.sec_on.data)) flash(gettext("Output turned on for %(sec)s seconds: %(rvalue)s", sec=form_output.sec_on.data, rvalue=return_value), "success") if output.output_type == 'pwm': if int(form_output.output_pin.data) == 0: error.append(gettext("Invalid pin")) if output.pwm_hertz <= 0: error.append(gettext("PWM Hertz must be a positive value")) if float(form_output.pwm_duty_cycle_on.data) <= 0: error.append(gettext("PWM duty cycle must be a positive value")) if not error: return_value = control.output_on( form_output.output_id.data, duty_cycle=float(form_output.pwm_duty_cycle_on.data)) flash(gettext("PWM set to %(dc)s %% at %(hertz)s Hz: %(rvalue)s", dc=float(form_output.pwm_duty_cycle_on.data), hertz=output.pwm_hertz, rvalue=return_value), "success") elif form_output.turn_on.data: return_value = control.output_on(form_output.output_id.data, 0) flash(gettext("Output turned on: %(rvalue)s", rvalue=return_value), "success") elif form_output.turn_off.data: return_value = control.output_off(form_output.output_id.data) flash(gettext("Output turned off: %(rvalue)s", rvalue=return_value), "success") except ValueError as except_msg: error.append('{err}: {msg}'.format( err=gettext("Invalid value"), msg=except_msg)) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_output'))
def output_mod(output_id, state, out_type, amount): """ Manipulate output (using non-unique ID) """ if not utils_general.user_has_permission('edit_controllers'): return 'Insufficient user permissions to manipulate outputs' daemon = DaemonControl() if (state in ['on', 'off'] and out_type == 'sec' and (str_is_float(amount) and float(amount) >= 0)): return daemon.output_on_off(output_id, state, float(amount)) elif (state == 'on' and out_type in ['pwm', 'command_pwm'] and (str_is_float(amount) and float(amount) >= 0)): return daemon.output_on(output_id, duty_cycle=float(amount))
def pid_manipulate(pid_id, action): if action not in ['Hold', 'Pause', 'Resume']: flash('{}: {}'.format(TRANSLATIONS['invalid']['title'], action), "error") return 1 try: mod_pid = PID.query.filter( PID.unique_id == pid_id).first() if action == 'Hold': mod_pid.is_held = True mod_pid.is_paused = False elif action == 'Pause': mod_pid.is_paused = True mod_pid.is_held = False elif action == 'Resume': mod_pid.is_activated = True mod_pid.is_held = False mod_pid.is_paused = False db.session.commit() control = DaemonControl() return_value = None if action == 'Hold': return_value = control.pid_hold(pid_id) elif action == 'Pause': return_value = control.pid_pause(pid_id) elif action == 'Resume': return_value = control.pid_resume(pid_id) if return_value: flash( '{}: {}: {}: {}'.format( TRANSLATIONS['controller']['title'], TRANSLATIONS['pid']['title'], action, return_value), "success") except Exception as err: flash( "{}: {}: {}".format( TRANSLATIONS['Error']['title'], TRANSLATIONS['PID']['title'], err), "error")
def conditional_condition_mod(form): """Modify a Conditional condition""" error = [] action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller='{} {}'.format(TRANSLATIONS['conditional']['title'], gettext("Condition"))) try: conditional = Conditional.query.filter( Conditional.unique_id == form.conditional_id.data).first() cond_mod = ConditionalConditions.query.filter( ConditionalConditions.unique_id == form.conditional_condition_id.data).first() if cond_mod.condition_type == 'measurement': error = check_form_measurements(form, error) cond_mod.measurement = form.measurement.data cond_mod.max_age = form.max_age.data elif cond_mod.condition_type == 'gpio_state': cond_mod.gpio_pin = form.gpio_pin.data if not error: db.session.commit() if conditional.is_activated: control = DaemonControl() return_value = control.refresh_daemon_conditional_settings( form.conditional_id.data) flash(gettext( "Daemon response: %(resp)s", resp=return_value), "success") except sqlalchemy.exc.OperationalError as except_msg: error.append(except_msg) except sqlalchemy.exc.IntegrityError as except_msg: error.append(except_msg) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def __init__(self, input_dev, testing=False): """ Instantiate with the Pi and gpio to which the DHT22 output pin is connected. Optionally a gpio used to power the sensor may be specified. This gpio will be set high to power the sensor. If the sensor locks it will be power cycled to restart the readings. Taking readings more often than about once every two seconds will eventually cause the DHT22 to hang. A 3 second interval seems OK. """ super(InputModule, self).__init__() self.logger = logging.getLogger('mycodo.inputs.dht22') self.temp_temperature = None self.temp_humidity = None self.temp_dew_point = None self.temp_vpd = None self.power_output_id = None self.powered = False self.pi = None if not testing: import pigpio from mycodo.mycodo_client import DaemonControl self.logger = logging.getLogger( 'mycodo.dht22_{id}'.format(id=input_dev.unique_id.split('-')[0])) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == input_dev.unique_id) self.power_output_id = input_dev.power_output_id self.control = DaemonControl() self.pigpio = pigpio self.pi = self.pigpio.pi() self.gpio = int(input_dev.gpio_location) self.bad_CS = 0 # Bad checksum count self.bad_SM = 0 # Short message count self.bad_MM = 0 # Missing message count self.bad_SR = 0 # Sensor reset count # Power cycle if timeout > MAX_NO_RESPONSE self.MAX_NO_RESPONSE = 3 self.no_response = None self.tov = None self.high_tick = None self.bit = None self.either_edge_cb = None self.start_sensor()
def force_acquire_measurements(unique_id): action = '{action}, {controller}'.format( action=gettext("Force Measurements"), controller=TRANSLATIONS['input']['title']) error = [] try: mod_input = Input.query.filter( Input.unique_id == unique_id).first() if not mod_input.is_activated: error.append(gettext( "Activate controller before attempting to force the acquisition of measurements")) if not error: control = DaemonControl() status = control.input_force_measurements(unique_id) flash("Daemon response: {}".format(status), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_data'))
def action_execute_all(form): """Execute All Conditional Actions""" error = [] func_type = None func = None if form.function_type.data == 'conditional': func_type = TRANSLATIONS['conditional']['title'] func = Conditional.query.filter( Conditional.unique_id == form.function_id.data).first() elif form.function_type.data == 'trigger': func_type = TRANSLATIONS['trigger']['title'] func = Trigger.query.filter( Trigger.unique_id == form.function_id.data).first() elif form.function_type.data == 'function_actions': func_type = TRANSLATIONS['function']['title'] func = Function.query.filter( Function.unique_id == form.function_id.data).first() else: error.append("Unknown Function type: '{}'".format( form.function_type.data)) action = '{action} {controller}'.format( action=gettext("Execute All"), controller='{} {}'.format(func_type, TRANSLATIONS['actions']['title'])) if form.function_type.data != 'function_actions' and func and not func.is_activated: error.append("Activate the Conditional before testing all Actions") try: if not error: control = DaemonControl() control.test_trigger_actions( form.function_id.data, message="Test triggering all actions of function {}".format(form.function_id.data)) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def inject_variables(): """Variables to send with every page request""" try: control = DaemonControl() daemon_status = control.daemon_status() except Exception as e: logger.debug("URL for 'inject_variables' raised and error: " "{err}".format(err=e)) daemon_status = '0' misc = Misc.query.first() return dict(dark_themes=THEMES_DARK, daemon_status=daemon_status, hide_alert_info=misc.hide_alert_info, hide_alert_success=misc.hide_alert_success, hide_alert_warning=misc.hide_alert_warning, hide_tooltips=misc.hide_tooltips, host=socket.gethostname(), mycodo_version=MYCODO_VERSION, dict_translation=TRANSLATIONS, upgrade_available=misc.mycodo_upgrade_available, username=flask_login.current_user.name)
def pid_mod_unique_id(unique_id, state): """ Manipulate output (using unique ID) """ if not utils_general.user_has_permission('edit_controllers'): return 'Insufficient user permissions to manipulate PID' pid = PID.query.filter(PID.unique_id == unique_id).first() daemon = DaemonControl() if state == 'activate_pid': pid.is_activated = True pid.save() return_val, return_str = daemon.controller_activate('PID', pid.unique_id) return return_str elif state == 'deactivate_pid': pid.is_activated = False pid.is_paused = False pid.is_held = False pid.save() return_val, return_str = daemon.controller_deactivate('PID', pid.unique_id) return return_str elif state == 'pause_pid': pid.is_paused = True pid.save() if pid.is_activated: return_str = daemon.pid_pause(pid.unique_id) else: return_str = "PID Paused (Note: PID is not currently active)" return return_str elif state == 'hold_pid': pid.is_held = True pid.save() if pid.is_activated: return_str = daemon.pid_hold(pid.unique_id) else: return_str = "PID Held (Note: PID is not currently active)" return return_str elif state == 'resume_pid': pid.is_held = False pid.is_paused = False pid.save() if pid.is_activated: return_str = daemon.pid_resume(pid.unique_id) else: return_str = "PID Resumed (Note: PID is not currently active)" return return_str elif 'set_setpoint_pid' in state: pid.setpoint = state.split('|')[1] pid.save() if pid.is_activated: return_str = daemon.pid_set(pid.unique_id, 'setpoint', float(state.split('|')[1])) else: return_str = "PID Setpoint changed (Note: PID is not currently active)" return return_str
def __init__(self, input_dev, testing=False): super(InputModule, self).__init__() self.logger = logging.getLogger('mycodo.inputs.am2315') self.powered = False self.am = None if not testing: from mycodo.mycodo_client import DaemonControl self.logger = logging.getLogger( 'mycodo.am2315_{id}'.format(id=input_dev.unique_id.split('-')[0])) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == input_dev.unique_id) self.i2c_bus = input_dev.i2c_bus self.power_output_id = input_dev.power_output_id self.control = DaemonControl() self.start_sensor() self.am = AM2315(self.i2c_bus)
def __init__(self, function, testing=False): super(CustomModule, self).__init__(function, testing=testing, name=__name__) self.timer_loop = time.time() self.control = DaemonControl() # Initialize custom options self.period = None self.select_measurement_device_id = None self.select_measurement_measurement_id = None self.measurement_max_age = None self.equation = None # Set custom options custom_function = db_retrieve_table_daemon( CustomController, unique_id=self.unique_id) self.setup_custom_options( FUNCTION_INFORMATION['custom_options'], custom_function) if not testing: self.initialize_variables()
def action_deactivate_controller(cond_action, message): control = DaemonControl() (controller_type, controller_object, controller_entry) = which_controller(cond_action.do_unique_id) message += " Deactivate Controller {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=controller_entry.id, name=controller_entry.name) if not controller_entry.is_activated: message += " Notice: Controller is already inactive!" else: with session_scope(MYCODO_DB_PATH) as new_session: mod_cont = new_session.query(controller_object).filter( controller_object.unique_id == cond_action.do_unique_id).first() mod_cont.is_activated = False new_session.commit() deactivate_controller = threading.Thread( target=control.controller_deactivate, args=(cond_action.do_unique_id, )) deactivate_controller.start() return message
def __init__(self, ready, function_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.trigger_{id}".format(id=function_id.split('-')[0])) self.function_id = function_id self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.pause_loop = False self.verify_pause_loop = True self.ready = ready self.control = DaemonControl() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_conditional self.trigger_type = None self.is_activated = None self.smtp_max_count = None self.email_count = None self.allowed_to_send_notice = None self.smtp_wait_timer = None self.timer_period = None self.period = None self.smtp_wait_timer = None self.timer_start_time = None self.timer_end_time = None self.unique_id_1 = None self.unique_id_2 = None self.trigger_actions_at_period = None self.trigger_actions_at_start = None self.method_start_time = None self.method_end_time = None self.method_start_act = None self.setup_settings()
def __init__(self): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.conditional") self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.pause_loop = False self.verify_pause_loop = True self.control = DaemonControl() self.is_activated = {} self.period = {} self.timer = {} self.smtp_max_count = db_retrieve_table_daemon( SMTP, entry='first').hourly_max self.email_count = 0 self.allowed_to_send_notice = True self.smtp_wait_timer = {} self.setup_conditionals()
def __init__(self, ready, unique_id): threading.Thread.__init__(self) super(ConditionalController, self).__init__(ready, unique_id=unique_id, name=__name__) self.unique_id = unique_id self.sample_rate = None self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.is_activated = None self.smtp_max_count = None self.email_count = None self.allowed_to_send_notice = None self.smtp_wait_timer = None self.timer_period = None self.period = None self.start_offset = None self.refractory_period = None self.log_level_debug = None self.conditional_statement = None self.timer_refractory_period = None self.smtp_wait_timer = None self.timer_period = None self.timer_start_time = None self.timer_end_time = None self.unique_id_1 = None self.unique_id_2 = None self.trigger_actions_at_period = None self.trigger_actions_at_start = None self.method_start_time = None self.method_end_time = None self.method_start_act = None
def pid_mod_unique_id(unique_id, state): """ Manipulate output (using unique ID) """ if not utils_general.user_has_permission('edit_controllers'): return 'Insufficient user permissions to manipulate PID' pid = PID.query.filter(PID.unique_id == unique_id).first() daemon = DaemonControl() if state == 'activate_pid': pid.is_activated = True pid.save() return_val, return_str = daemon.controller_activate('PID', pid.id) return return_str elif state == 'deactivate_pid': pid.is_activated = False pid.is_paused = False pid.is_held = False pid.save() return_val, return_str = daemon.controller_deactivate('PID', pid.id) return return_str elif state == 'pause_pid': pid.is_paused = True pid.save() return_str = daemon.pid_pause(pid.id) return return_str elif state == 'hold_pid': pid.is_held = True pid.save() return_str = daemon.pid_hold(pid.id) return return_str elif state == 'resume_pid': pid.is_held = False pid.is_paused = False pid.save() return_str = daemon.pid_resume(pid.id) return return_str
def __init__(self, ready, pid_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.pid_{id}".format( id=pid_id.split('-')[0])) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.pid_id = pid_id self.control = DaemonControl() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_pid self.device_measurements = db_retrieve_table_daemon(DeviceMeasurements) self.PID_Controller = None self.control_variable = 0.0 self.derivator = 0.0 self.integrator = 0.0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.lower_seconds_on = 0.0 self.raise_seconds_on = 0.0 self.lower_duty_cycle = 0.0 self.raise_duty_cycle = 0.0 self.last_time = None self.last_measurement = None self.last_measurement_success = False self.is_activated = None self.is_held = None self.is_paused = None self.measurement = None self.method_id = None self.direction = None self.raise_output_id = None self.raise_min_duration = None self.raise_max_duration = None self.raise_min_off_duration = None self.lower_output_id = None self.lower_min_duration = None self.lower_max_duration = None self.lower_min_off_duration = None self.Kp = None self.Ki = None self.Kd = None self.integrator_min = None self.integrator_max = None self.period = None self.start_offset = None self.max_measure_age = None self.default_setpoint = None self.setpoint = None self.store_lower_as_negative = None # Hysteresis options self.band = None self.allow_raising = False self.allow_lowering = False # PID Autotune self.autotune = None self.autotune_activated = False self.autotune_debug = False self.autotune_noiseband = None self.autotune_outstep = None self.autotune_timestamp = None self.device_id = None self.measurement_id = None self.input_duration = None self.raise_output_type = None self.lower_output_type = None self.first_start = True self.initialize_values() self.timer = time.time() + self.start_offset # Check if a method is set for this PID self.method_type = None self.method_start_act = None self.method_start_time = None self.method_end_time = None if self.method_id != '': self.setup_method(self.method_id)
def camera_record(record_type, unique_id, duration_sec=None, tmp_filename=None): """ Record still image from cameras :param record_type: :param unique_id: :param duration_sec: :param tmp_filename: :return: """ daemon_control = None settings = db_retrieve_table_daemon(Camera, unique_id=unique_id) timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') assure_path_exists(PATH_CAMERAS) camera_path = assure_path_exists( os.path.join(PATH_CAMERAS, '{uid}'.format(uid=settings.unique_id))) if record_type == 'photo': if settings.path_still != '': save_path = settings.path_still else: save_path = assure_path_exists(os.path.join(camera_path, 'still')) filename = 'Still-{cam_id}-{cam}-{ts}.jpg'.format( cam_id=settings.id, cam=settings.name, ts=timestamp).replace(" ", "_") elif record_type == 'timelapse': if settings.path_timelapse != '': save_path = settings.path_timelapse else: save_path = assure_path_exists(os.path.join(camera_path, 'timelapse')) start = datetime.datetime.fromtimestamp( settings.timelapse_start_time).strftime("%Y-%m-%d_%H-%M-%S") filename = 'Timelapse-{cam_id}-{cam}-{st}-img-{cn:05d}.jpg'.format( cam_id=settings.id, cam=settings.name, st=start, cn=settings.timelapse_capture_number).replace(" ", "_") elif record_type == 'video': if settings.path_video != '': save_path = settings.path_video else: save_path = assure_path_exists(os.path.join(camera_path, 'video')) filename = 'Video-{cam}-{ts}.h264'.format( cam=settings.name, ts=timestamp).replace(" ", "_") else: return assure_path_exists(save_path) if tmp_filename: filename = tmp_filename path_file = os.path.join(save_path, filename) # Turn on output, if configured if settings.output_id: daemon_control = DaemonControl() daemon_control.output_on(settings.output_id) # Pause while the output remains on for the specified duration. # Used for instance to allow fluorescent lights to fully turn on before # capturing an image. if settings.output_duration: time.sleep(settings.output_duration) if settings.library == 'picamera': # Try 5 times to access the pi camera (in case another process is accessing it) for _ in range(5): try: with picamera.PiCamera() as camera: camera.resolution = (settings.width, settings.height) camera.hflip = settings.hflip camera.vflip = settings.vflip camera.rotation = settings.rotation camera.brightness = int(settings.brightness) camera.contrast = int(settings.contrast) camera.exposure_compensation = int(settings.exposure) camera.saturation = int(settings.saturation) camera.start_preview() time.sleep(2) # Camera warm-up time if record_type in ['photo', 'timelapse']: camera.capture(path_file, use_video_port=False) elif record_type == 'video': camera.start_recording(path_file, format='h264', quality=20) camera.wait_recording(duration_sec) camera.stop_recording() else: return break except picamera.exc.PiCameraMMALError: logger.error("The camera is already open by picamera. Retrying 4 times.") time.sleep(1) elif settings.library == 'fswebcam': cmd = "/usr/bin/fswebcam --device {dev} --resolution {w}x{h} --set brightness={bt}% " \ "--no-banner --save {file}".format(dev=settings.device, w=settings.width, h=settings.height, bt=settings.brightness, file=path_file) if settings.hflip: cmd += " --flip h" if settings.vflip: cmd += " --flip h" if settings.rotation: cmd += " --rotate {angle}".format(angle=settings.rotation) if settings.custom_options: cmd += " " + settings.custom_options out, err, status = cmd_output(cmd, stdout_pipe=False) # logger.error("TEST01: {}; {}; {}; {}".format(cmd, out, err, status)) # Turn off output, if configured if settings.output_id and daemon_control: daemon_control.output_off(settings.output_id) try: set_user_grp(path_file, 'mycodo', 'mycodo') return save_path, filename except Exception as e: logger.exception( "Exception raised in 'camera_record' when setting user grp: " "{err}".format(err=e))
class InputModule(AbstractInput): """ A sensor support class that measures the AM2315's humidity and temperature and calculates the dew point """ def __init__(self, input_dev, testing=False): super(InputModule, self).__init__() self.logger = logging.getLogger('mycodo.inputs.am2315') self.powered = False self.am = None if not testing: from mycodo.mycodo_client import DaemonControl self.logger = logging.getLogger( 'mycodo.am2315_{id}'.format(id=input_dev.unique_id.split('-')[0])) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == input_dev.unique_id) self.i2c_bus = input_dev.i2c_bus self.power_output_id = input_dev.power_output_id self.control = DaemonControl() self.start_sensor() self.am = AM2315(self.i2c_bus) def get_measurement(self): """ Gets the humidity and temperature """ return_dict = measurements_dict.copy() temperature = None humidity = None dew_point = None measurements_success = False # Ensure if the power pin turns off, it is turned back on if (self.power_output_id and db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and self.control.output_state(self.power_output_id) == 'off'): self.logger.error( 'Sensor power output {rel} detected as being off. ' 'Turning on.'.format(rel=self.power_output_id)) self.start_sensor() time.sleep(2) # Try twice to get measurement. This prevents an anomaly where # the first measurement fails if the sensor has just been powered # for the first time. for _ in range(2): dew_point, humidity, temperature = self.return_measurements() if dew_point is not None: measurements_success = True break time.sleep(2) # Measurement failure, power cycle the sensor (if enabled) # Then try two more times to get a measurement if self.power_output_id and not measurements_success: self.stop_sensor() time.sleep(2) self.start_sensor() for _ in range(2): dew_point, humidity, temperature = self.return_measurements() if dew_point is not None: measurements_success = True break time.sleep(2) if measurements_success: if self.is_enabled(0): return_dict[0]['value'] = temperature if self.is_enabled(1): return_dict[1]['value'] = humidity if (self.is_enabled(2) and self.is_enabled(0) and self.is_enabled(1)): return_dict[2]['value'] = calculate_dewpoint( return_dict[0]['value'], return_dict[1]['value']) if (self.is_enabled(3) and self.is_enabled(0) and self.is_enabled(1)): return_dict[3]['value'] = calculate_vapor_pressure_deficit( return_dict[0]['value'], return_dict[1]['value']) return return_dict else: self.logger.debug("Could not acquire a measurement") def return_measurements(self): # Retry measurement if CRC fails for num_measure in range(3): humidity, temperature = self.am.data() if humidity is None: self.logger.debug( "Measurement {num} returned failed CRC".format( num=num_measure)) pass else: dew_pt = calculate_dewpoint(temperature, humidity) return dew_pt, humidity, temperature time.sleep(2) self.logger.error("All measurements returned failed CRC") return None, None, None def start_sensor(self): """ Turn the sensor on """ if self.power_output_id: self.logger.info("Turning on sensor") self.control.output_on(self.power_output_id, 0) time.sleep(2) self.powered = True def stop_sensor(self): """ Turn the sensor off """ if self.power_output_id: self.logger.info("Turning off sensor") self.control.output_off(self.power_output_id) self.powered = False
class TriggerController(threading.Thread): """ Class to operate Trigger controller Triggers are events that are used to signal when a set of actions should be executed. The main loop in this class will continually check if any timer Triggers have elapsed. If any have, trigger_all_actions() will be ran to execute all actions associated with that particular trigger. Edge and Output conditionals are triggered from the Input and Output controllers, respectively, and the trigger_all_actions() function in this class will be ran. """ def __init__(self, ready, function_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.trigger_{id}".format(id=function_id.split('-')[0])) self.function_id = function_id self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.pause_loop = False self.verify_pause_loop = True self.ready = ready self.control = DaemonControl() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_conditional self.trigger_type = None self.is_activated = None self.smtp_max_count = None self.email_count = None self.allowed_to_send_notice = None self.smtp_wait_timer = None self.timer_period = None self.period = None self.smtp_wait_timer = None self.timer_start_time = None self.timer_end_time = None self.unique_id_1 = None self.unique_id_2 = None self.trigger_actions_at_period = None self.trigger_actions_at_start = None self.method_start_time = None self.method_end_time = None self.method_start_act = None self.setup_settings() def run(self): try: 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: # Pause loop to modify trigger. # Prevents execution of trigger while variables are # being modified. if self.pause_loop: self.verify_pause_loop = True while self.pause_loop: time.sleep(0.1) if (self.is_activated and self.timer_period and self.timer_period < time.time()): check_approved = False # Check if the trigger period has elapsed if self.trigger_type in ['trigger_sunrise_sunset', 'trigger_run_pwm_method']: while self.running and self.timer_period < time.time(): self.timer_period += self.period if self.trigger_type == 'trigger_run_pwm_method': # Only execute trigger actions when started # Now only set PWM output pwm_duty_cycle, ended = self.get_method_output( self.unique_id_1) if not ended: self.set_output_duty_cycle( self.unique_id_2, pwm_duty_cycle) if self.trigger_actions_at_period: trigger_function_actions(self.function_id) else: check_approved = True elif (self.trigger_type in [ 'trigger_timer_daily_time_point', 'trigger_timer_daily_time_span', 'trigger_timer_duration']): if self.trigger_type == 'trigger_timer_daily_time_point': self.timer_period = epoch_of_next_time( '{hm}:00'.format(hm=self.timer_start_time)) elif self.trigger_type in ['trigger_timer_duration', 'trigger_timer_daily_time_span']: while self.running and self.timer_period < time.time(): self.timer_period += self.period check_approved = True if check_approved: self.check_triggers() time.sleep(self.sample_rate) 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 refresh_settings(self): """ Signal to pause the main loop and wait for verification, the refresh settings """ self.pause_loop = True while not self.verify_pause_loop: time.sleep(0.1) self.logger.info("Refreshing trigger settings") self.setup_settings() self.pause_loop = False self.verify_pause_loop = False return "Trigger settings successfully refreshed" def setup_settings(self): """ Define all settings """ trigger = db_retrieve_table_daemon( Trigger, unique_id=self.function_id) self.trigger_type = trigger.trigger_type self.is_activated = trigger.is_activated self.smtp_max_count = db_retrieve_table_daemon( SMTP, entry='first').hourly_max self.email_count = 0 self.allowed_to_send_notice = True now = time.time() self.smtp_wait_timer = now + 3600 self.timer_period = None # Set up trigger timer (daily time point) if self.trigger_type == 'trigger_timer_daily_time_point': self.timer_start_time = trigger.timer_start_time self.timer_period = epoch_of_next_time( '{hm}:00'.format(hm=trigger.timer_start_time)) # Set up trigger timer (daily time span) elif self.trigger_type == 'trigger_timer_daily_time_span': self.timer_start_time = trigger.timer_start_time self.timer_end_time = trigger.timer_end_time self.period = trigger.period self.timer_period = now # Set up trigger timer (duration) elif self.trigger_type == 'trigger_timer_duration': self.period = trigger.period if trigger.timer_start_offset: self.timer_period = now + trigger.timer_start_offset else: self.timer_period = now # Set up trigger Run PWM Method elif self.trigger_type == 'trigger_run_pwm_method': self.unique_id_1 = trigger.unique_id_1 self.unique_id_2 = trigger.unique_id_2 self.period = trigger.period self.trigger_actions_at_period = trigger.trigger_actions_at_period self.trigger_actions_at_start = trigger.trigger_actions_at_start self.method_start_time = trigger.method_start_time self.method_end_time = trigger.method_end_time if self.is_activated: self.start_method(trigger.unique_id_1) if self.trigger_actions_at_start: self.timer_period = now + trigger.period if self.is_activated: pwm_duty_cycle = self.get_method_output( trigger.unique_id_1) self.set_output_duty_cycle( trigger.unique_id_2, pwm_duty_cycle) trigger_function_actions(self.function_id) else: self.timer_period = now # Set up trigger sunrise/sunset elif self.trigger_type == 'trigger_sunrise_sunset': self.period = 60 # Set the next trigger at the specified sunrise/sunset time (+-offsets) self.timer_period = calculate_sunrise_sunset_epoch(trigger) def start_method(self, method_id): """ Instruct a method to start running """ if method_id: method = db_retrieve_table_daemon(Method, unique_id=method_id) method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter(MethodData.method_id == method_id) method_data_repeat = method_data.filter(MethodData.duration_sec == 0).first() self.method_start_act = self.method_start_time self.method_start_time = None self.method_end_time = None if method.method_type == 'Duration': if self.method_start_act == 'Ended': with session_scope(MYCODO_DB_PATH) as db_session: mod_conditional = db_session.query(Trigger) mod_conditional = mod_conditional.filter( Trigger.unique_id == self.function_id).first() mod_conditional.is_activated = False db_session.commit() self.stop_controller() self.logger.warning( "Method has ended. " "Activate the Trigger controller to start it again.") elif (self.method_start_act == 'Ready' or self.method_start_act is None): # Method has been instructed to begin now = datetime.datetime.now() self.method_start_time = now if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = now + datetime.timedelta( seconds=float(method_data_repeat.duration_end)) with session_scope(MYCODO_DB_PATH) as db_session: mod_conditional = db_session.query(Trigger) mod_conditional = mod_conditional.filter( Trigger.unique_id == self.function_id).first() mod_conditional.method_start_time = self.method_start_time mod_conditional.method_end_time = self.method_end_time db_session.commit() def get_method_output(self, method_id): """ Get output variable from method """ this_controller = db_retrieve_table_daemon( Trigger, unique_id=self.function_id) setpoint, ended = calculate_method_setpoint( method_id, Trigger, this_controller, Method, MethodData, self.logger) if setpoint is not None: if setpoint > 100: setpoint = 100 elif setpoint < 0: setpoint = 0 if ended: with session_scope(MYCODO_DB_PATH) as db_session: mod_conditional = db_session.query(Trigger) mod_conditional = mod_conditional.filter( Trigger.unique_id == self.function_id).first() mod_conditional.is_activated = False db_session.commit() self.is_activated = False self.stop_controller() return setpoint, ended def set_output_duty_cycle(self, output_id, duty_cycle): """ Set PWM Output duty cycle """ self.control.output_on(output_id, duty_cycle=duty_cycle) def check_triggers(self): """ Check if any Triggers are activated and execute their actions if so. For example, if measured temperature is above 30C, notify [email protected] "if measured temperature is above 30C" is the Trigger to check. "notify [email protected]" is the Trigger Action to execute if the Trigger is True. """ last_measurement = None gpio_state = None logger_cond = logging.getLogger("mycodo.conditional_{id}".format( id=self.function_id)) trigger = db_retrieve_table_daemon( Trigger, unique_id=self.function_id, entry='first') now = time.time() timestamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S') message = "{ts}\n[Trigger {id} ({name})]".format( ts=timestamp, name=trigger.name, id=self.function_id) device_id = trigger.measurement.split(',')[0] if len(trigger.measurement.split(',')) > 1: device_measurement = trigger.measurement.split(',')[1] else: device_measurement = None device = None input_dev = db_retrieve_table_daemon( Input, unique_id=device_id, entry='first') if input_dev: device = input_dev math = db_retrieve_table_daemon( Math, unique_id=device_id, entry='first') if math: device = math output = db_retrieve_table_daemon( Output, unique_id=device_id, entry='first') if output: device = output pid = db_retrieve_table_daemon( PID, unique_id=device_id, entry='first') if pid: device = pid if not device: message += " Error: Controller not Input, Math, Output, or PID" logger_cond.error(message) return # If the edge detection variable is set, calling this function will # trigger an edge detection event. This will merely produce the correct # message based on the edge detection settings. elif trigger.trigger_type == 'trigger_edge': try: GPIO.setmode(GPIO.BCM) GPIO.setup(int(input_dev.pin), GPIO.IN) gpio_state = GPIO.input(int(input_dev.pin)) except: gpio_state = None logger_cond.error("Exception reading the GPIO pin") if (gpio_state is not None and gpio_state == trigger.if_sensor_gpio_state): message += " GPIO State Detected (state = {state}).".format( state=trigger.if_sensor_gpio_state) else: logger_cond.error("GPIO not configured correctly or GPIO state not verified") return # Calculate the sunrise/sunset times and find the next time this trigger should trigger elif trigger.trigger_type == 'trigger_sunrise_sunset': # Since the check time is the trigger time, we will only calculate and set the next trigger time self.timer_period = calculate_sunrise_sunset_epoch(trigger) # Check if the current time is between the start and end time if trigger.trigger_type == 'trigger_timer_daily_time_span': if not time_between_range(self.timer_start_time, self.timer_end_time): return # If the code hasn't returned by now, action should be executed trigger_function_actions(self.function_id, message=message) def is_running(self): return self.running def stop_controller(self): self.thread_shutdown_timer = timeit.default_timer() self.running = False
def __init__(self, ready, input_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.input_{id}".format(id=input_id.split('-')[0])) 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.measurement_success = False self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.force_measurements_trigger = False self.dict_inputs = parse_input_information() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_input self.input_id = input_id input_dev = db_retrieve_table_daemon( Input, unique_id=self.input_id) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == self.input_id) self.conversions = db_retrieve_table_daemon(Conversion) self.input_dev = input_dev self.input_name = input_dev.name self.unique_id = input_dev.unique_id self.gpio_location = input_dev.gpio_location self.device = input_dev.device self.interface = input_dev.interface self.period = input_dev.period # Edge detection self.switch_edge = input_dev.switch_edge self.switch_bouncetime = input_dev.switch_bouncetime self.switch_reset_period = input_dev.switch_reset_period # Pre-Output: Activates prior to input measurement self.pre_output_id = input_dev.pre_output_id self.pre_output_duration = input_dev.pre_output_duration self.pre_output_during_measure = input_dev.pre_output_during_measure self.pre_output_setup = False self.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_locked = False self.pre_output_timer = time.time() # Check if Pre-Output ID actually exists output = db_retrieve_table_daemon(Output, entry='all') for each_output in output: if each_output.unique_id == self.pre_output_id and self.pre_output_duration: self.pre_output_setup = True smtp = db_retrieve_table_daemon(SMTP, entry='first') self.smtp_max_count = smtp.hourly_max self.email_count = 0 self.allowed_to_send_notice = True # Set up input lock self.input_lock = None self.lock_file = '/var/lock/input_pre_output_{id}'.format(id=self.pre_output_id) # Convert string I2C address to base-16 int if self.interface == 'I2C': self.i2c_address = int(str(self.input_dev.i2c_location), 16) # Set up edge detection of a GPIO pin if self.device == 'EDGE': 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 self.device_recognized = True if self.device in self.dict_inputs: input_loaded = load_module_from_file(self.dict_inputs[self.device]['file_path']) if self.device == 'EDGE': # Edge detection handled internally, no module to load self.measure_input = None else: self.measure_input = input_loaded.InputModule(self.input_dev) else: self.device_recognized = False self.logger.debug("Device '{device}' not recognized".format( device=self.device)) raise Exception("'{device}' is not a valid device type.".format( device=self.device)) self.edge_reset_timer = time.time() self.input_timer = time.time() self.running = False self.lastUpdate = None
class InputModule(AbstractInput): """ A sensor support class that measures the DHT22's humidity and temperature and calculates the dew point An adaptation of DHT22 code from https://github.com/joan2937/pigpio The sensor is also known as the AM2302. The sensor can be powered from the Pi 3.3-volt or 5-volt rail. Powering from the 3.3-volt rail is simpler and safer. You may need to power from 5 if the sensor is connected via a long cable. For 3.3-volt operation connect pin 1 to 3.3 volts and pin 4 to ground. Connect pin 2 to a gpio. For 5-volt operation connect pin 1 to the 5 volts and pin 4 to ground. The following pin 2 connection works for me. Use at YOUR OWN RISK. 5V--5K_resistor--+--10K_resistor--Ground | DHT22 pin 2 -----+ | gpio ------------+ """ def __init__(self, input_dev, testing=False): """ Instantiate with the Pi and gpio to which the DHT22 output pin is connected. Optionally a gpio used to power the sensor may be specified. This gpio will be set high to power the sensor. If the sensor locks it will be power cycled to restart the readings. Taking readings more often than about once every two seconds will eventually cause the DHT22 to hang. A 3 second interval seems OK. """ super(InputModule, self).__init__() self.logger = logging.getLogger('mycodo.inputs.dht22') self.temp_temperature = None self.temp_humidity = None self.temp_dew_point = None self.temp_vpd = None self.power_output_id = None self.powered = False self.pi = None if not testing: import pigpio from mycodo.mycodo_client import DaemonControl self.logger = logging.getLogger( 'mycodo.dht22_{id}'.format(id=input_dev.unique_id.split('-')[0])) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == input_dev.unique_id) self.power_output_id = input_dev.power_output_id self.control = DaemonControl() self.pigpio = pigpio self.pi = self.pigpio.pi() self.gpio = int(input_dev.gpio_location) self.bad_CS = 0 # Bad checksum count self.bad_SM = 0 # Short message count self.bad_MM = 0 # Missing message count self.bad_SR = 0 # Sensor reset count # Power cycle if timeout > MAX_NO_RESPONSE self.MAX_NO_RESPONSE = 3 self.no_response = None self.tov = None self.high_tick = None self.bit = None self.either_edge_cb = None self.start_sensor() def get_measurement(self): """ Gets the humidity and temperature """ return_dict = measurements_dict.copy() if not self.pi.connected: # Check if pigpiod is running self.logger.error('Could not connect to pigpiod. ' 'Ensure it is running and try again.') return None, None, None # Ensure if the power pin turns off, it is turned back on if (self.power_output_id and db_retrieve_table_daemon(Output, unique_id=self.power_output_id) and self.control.output_state(self.power_output_id) == 'off'): self.logger.error( 'Sensor power output {rel} detected as being off. ' 'Turning on.'.format(rel=self.power_output_id)) self.start_sensor() time.sleep(2) # Try twice to get measurement. This prevents an anomaly where # the first measurement fails if the sensor has just been powered # for the first time. for _ in range(4): self.measure_sensor() if self.temp_dew_point is not None: if self.is_enabled(0): return_dict[0]['value'] = self.temp_temperature if self.is_enabled(1): return_dict[1]['value'] = self.temp_humidity if (self.is_enabled(2) and self.is_enabled(0) and self.is_enabled(1)): return_dict[2]['value'] = self.temp_dew_point if (self.is_enabled(3) and self.is_enabled(0) and self.is_enabled(1)): return_dict[3]['value'] = self.temp_vpd return return_dict # success - no errors time.sleep(2) # Measurement failure, power cycle the sensor (if enabled) # Then try two more times to get a measurement if self.power_output_id is not None and self.running: self.stop_sensor() time.sleep(3) self.start_sensor() for _ in range(2): self.measure_sensor() if self.temp_dew_point is not None: if self.is_enabled(0): return_dict[0]['value'] = self.temp_temperature if self.is_enabled(1): return_dict[1]['value'] = self.temp_humidity if (self.is_enabled(2) and self.is_enabled(0) and self.is_enabled(1)): return_dict[2]['value'] = self.temp_dew_point if (self.is_enabled(3) and self.is_enabled(0) and self.is_enabled(1)): return_dict[3]['value'] = self.temp_vpd return return_dict # success - no errors time.sleep(2) self.logger.debug("Could not acquire a measurement") return None def measure_sensor(self): self.temp_temperature = None self.temp_humidity = None self.temp_dew_point = None self.temp_vpd = None initialized = False try: self.close() time.sleep(0.2) self.setup() time.sleep(0.2) initialized = True except Exception as except_msg: self.logger.error( "Could not initialize sensor. Check if it's connected " "properly and pigpiod is running. Error: {msg}".format( msg=except_msg)) if initialized: try: self.pi.write(self.gpio, self.pigpio.LOW) time.sleep(0.017) # 17 ms self.pi.set_mode(self.gpio, self.pigpio.INPUT) self.pi.set_watchdog(self.gpio, 200) time.sleep(0.2) if (self.temp_humidity is not None and self.temp_temperature is not None): self.temp_dew_point = calculate_dewpoint( self.temp_temperature, self.temp_humidity) self.temp_vpd = calculate_vapor_pressure_deficit( self.temp_temperature, self.temp_humidity) except Exception as e: self.logger.exception( "Exception when taking a reading: {err}".format( err=e)) finally: self.close() def setup(self): """ Clears the internal gpio pull-up/down resistor. Kills any watchdogs. Setup callbacks """ self.no_response = 0 self.tov = None self.high_tick = 0 self.bit = 40 self.either_edge_cb = None self.pi.set_pull_up_down(self.gpio, self.pigpio.PUD_OFF) self.pi.set_watchdog(self.gpio, 0) # Kill any watchdogs self.register_callbacks() def register_callbacks(self): """ Monitors RISING_EDGE changes using callback """ self.either_edge_cb = self.pi.callback(self.gpio, self.pigpio.EITHER_EDGE, self.either_edge_callback) def either_edge_callback(self, gpio, level, tick): """ Either Edge callbacks, called each time the gpio edge changes. Accumulate the 40 data bits from the DHT22 sensor. Format into 5 bytes, humidity high, humidity low, temperature high, temperature low, checksum. """ level_handlers = { self.pigpio.FALLING_EDGE: self._edge_fall, self.pigpio.RISING_EDGE: self._edge_rise, self.pigpio.EITHER_EDGE: self._edge_either } handler = level_handlers[level] diff = self.pigpio.tickDiff(self.high_tick, tick) handler(tick, diff) def _edge_rise(self, tick, diff): """ Handle Rise signal """ # Edge length determines if bit is 1 or 0. if diff >= 50: val = 1 if diff >= 200: # Bad bit? self.CS = 256 # Force bad checksum. else: val = 0 if self.bit >= 40: # Message complete. self.bit = 40 elif self.bit >= 32: # In checksum byte. self.CS = (self.CS << 1) + val if self.bit == 39: # 40th bit received. self.pi.set_watchdog(self.gpio, 0) self.no_response = 0 total = self.hH + self.hL + self.tH + self.tL if (total & 255) == self.CS: # Is checksum ok? self.temp_humidity = ((self.hH << 8) + self.hL) * 0.1 if self.tH & 128: # Negative temperature. mult = -0.1 self.tH &= 127 else: mult = 0.1 self.temp_temperature = ((self.tH << 8) + self.tL) * mult self.tov = time.time() else: self.bad_CS += 1 elif self.bit >= 24: # in temp low byte self.tL = (self.tL << 1) + val elif self.bit >= 16: # in temp high byte self.tH = (self.tH << 1) + val elif self.bit >= 8: # in humidity low byte self.hL = (self.hL << 1) + val elif self.bit >= 0: # in humidity high byte self.hH = (self.hH << 1) + val self.bit += 1 def _edge_fall(self, tick, diff): """ Handle Fall signal """ # Edge length determines if bit is 1 or 0. self.high_tick = tick if diff <= 250000: return self.bit = -2 self.hH = 0 self.hL = 0 self.tH = 0 self.tL = 0 self.CS = 0 def _edge_either(self, tick, diff): """ Handle Either signal or Timeout """ self.pi.set_watchdog(self.gpio, 0) if self.bit < 8: # Too few data bits received. self.bad_MM += 1 # Bump missing message count. self.no_response += 1 if self.no_response > self.MAX_NO_RESPONSE: self.no_response = 0 self.bad_SR += 1 # Bump sensor reset count. if self.power_output_id is not None: self.logger.error( "Invalid data, power cycling sensor.") self.stop_sensor() time.sleep(2) self.start_sensor() elif self.bit < 39: # Short message received. self.bad_SM += 1 # Bump short message count. self.no_response = 0 else: # Full message received. self.no_response = 0 def staleness(self): """ Return time since measurement made """ if self.tov is not None: return time.time() - self.tov else: return -999 def bad_checksum(self): """ Return count of messages received with bad checksums """ return self.bad_CS def short_message(self): """ Return count of short messages """ return self.bad_SM def missing_message(self): """ Return count of missing messages """ return self.bad_MM def sensor_resets(self): """ Return count of power cycles because of sensor hangs """ return self.bad_SR def close(self): """ Stop reading sensor, remove callbacks """ self.pi.set_watchdog(self.gpio, 0) if self.either_edge_cb: self.either_edge_cb.cancel() self.either_edge_cb = None def start_sensor(self): """ Turn the sensor on """ if self.power_output_id: self.logger.info("Turning on sensor") self.control.output_on(self.power_output_id, 0) time.sleep(2) self.powered = True def stop_sensor(self): """ Turn the sensor off """ if self.power_output_id: self.logger.info("Turning off sensor") self.control.output_off(self.power_output_id) self.powered = False
class PIDController(threading.Thread): """ Class to operate discrete PID controller in Mycodo """ def __init__(self, ready, pid_id): threading.Thread.__init__(self) self.logger = logging.getLogger("mycodo.pid_{id}".format( id=pid_id.split('-')[0])) self.running = False self.thread_startup_timer = timeit.default_timer() self.thread_shutdown_timer = 0 self.ready = ready self.pid_id = pid_id self.control = DaemonControl() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_pid self.device_measurements = db_retrieve_table_daemon(DeviceMeasurements) self.PID_Controller = None self.control_variable = 0.0 self.derivator = 0.0 self.integrator = 0.0 self.error = 0.0 self.P_value = None self.I_value = None self.D_value = None self.lower_seconds_on = 0.0 self.raise_seconds_on = 0.0 self.lower_duty_cycle = 0.0 self.raise_duty_cycle = 0.0 self.last_time = None self.last_measurement = None self.last_measurement_success = False self.is_activated = None self.is_held = None self.is_paused = None self.measurement = None self.method_id = None self.direction = None self.raise_output_id = None self.raise_min_duration = None self.raise_max_duration = None self.raise_min_off_duration = None self.lower_output_id = None self.lower_min_duration = None self.lower_max_duration = None self.lower_min_off_duration = None self.Kp = None self.Ki = None self.Kd = None self.integrator_min = None self.integrator_max = None self.period = None self.start_offset = None self.max_measure_age = None self.default_setpoint = None self.setpoint = None self.store_lower_as_negative = None # Hysteresis options self.band = None self.allow_raising = False self.allow_lowering = False # PID Autotune self.autotune = None self.autotune_activated = False self.autotune_debug = False self.autotune_noiseband = None self.autotune_outstep = None self.autotune_timestamp = None self.device_id = None self.measurement_id = None self.input_duration = None self.raise_output_type = None self.lower_output_type = None self.first_start = True self.initialize_values() self.timer = time.time() + self.start_offset # Check if a method is set for this PID self.method_type = None self.method_start_act = None self.method_start_time = None self.method_end_time = None if self.method_id != '': self.setup_method(self.method_id) def run(self): try: self.running = True startup_str = "Activated in {time:.1f} ms".format( time=(timeit.default_timer() - self.thread_startup_timer) * 1000) if self.is_paused: startup_str += ", started Paused" elif self.is_held: startup_str += ", started Held" self.logger.info(startup_str) # Initialize PID Controller self.PID_Controller = PIDControl( self.period, self.Kp, self.Ki, self.Kd, integrator_min=self.integrator_min, integrator_max=self.integrator_max) # If activated, initialize PID Autotune if self.autotune_activated: self.autotune_timestamp = time.time() try: self.autotune = PIDAutotune( self.setpoint, out_step=self.autotune_outstep, sampletime=self.period, out_min=0, out_max=self.period, noiseband=self.autotune_noiseband) except Exception as msg: self.logger.error(msg) self.stop_controller(deactivate_pid=True) 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 time.time() > self.timer: self.check_pid() time.sleep(self.sample_rate) except Exception as except_msg: self.logger.exception("Run Error: {err}".format( err=except_msg)) finally: # Turn off output used in PID when the controller is deactivated if self.raise_output_id and self.direction in ['raise', 'both']: self.control.output_off(self.raise_output_id, trigger_conditionals=True) if self.lower_output_id and self.direction in ['lower', 'both']: self.control.output_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)) def initialize_values(self): """Set PID parameters""" pid = db_retrieve_table_daemon(PID, unique_id=self.pid_id) self.is_activated = pid.is_activated self.is_held = pid.is_held self.is_paused = pid.is_paused self.method_id = pid.method_id self.direction = pid.direction self.raise_output_id = pid.raise_output_id self.raise_min_duration = pid.raise_min_duration self.raise_max_duration = pid.raise_max_duration self.raise_min_off_duration = pid.raise_min_off_duration self.lower_output_id = pid.lower_output_id self.lower_min_duration = pid.lower_min_duration self.lower_max_duration = pid.lower_max_duration self.lower_min_off_duration = pid.lower_min_off_duration self.Kp = pid.p self.Ki = pid.i self.Kd = pid.d self.integrator_min = pid.integrator_min self.integrator_max = pid.integrator_max self.period = pid.period self.start_offset = pid.start_offset self.max_measure_age = pid.max_measure_age self.default_setpoint = pid.setpoint self.setpoint = pid.setpoint self.band = pid.band self.store_lower_as_negative = pid.store_lower_as_negative # Autotune self.autotune_activated = pid.autotune_activated self.autotune_noiseband = pid.autotune_noiseband self.autotune_outstep = pid.autotune_outstep self.device_id = pid.measurement.split(',')[0] self.measurement_id = pid.measurement.split(',')[1] input_dev = db_retrieve_table_daemon(Input, unique_id=self.device_id) math = db_retrieve_table_daemon(Math, unique_id=self.device_id) if input_dev: self.input_duration = input_dev.period elif math: self.input_duration = math.period try: self.raise_output_type = db_retrieve_table_daemon( Output, unique_id=self.raise_output_id).output_type except AttributeError: self.raise_output_type = None try: self.lower_output_type = db_retrieve_table_daemon( Output, unique_id=self.lower_output_id).output_type except AttributeError: self.lower_output_type = None self.logger.info("PID Settings: {}".format(self.pid_parameters_str())) return "success" def check_pid(self): """ Get measurement and apply to PID controller """ # Ensure the timer ends in the future while time.time() > self.timer: self.timer = self.timer + self.period # If PID is active, retrieve measurement and update # the control variable. # A PID on hold will sustain the current output and # not update the control variable. if self.is_activated and (not self.is_paused or not self.is_held): self.get_last_measurement() if self.last_measurement_success: if self.method_id != '': # Update setpoint using a method this_pid = db_retrieve_table_daemon( PID, unique_id=self.pid_id) setpoint, ended = calculate_method_setpoint( self.method_id, PID, this_pid, Method, MethodData, self.logger) if ended: self.method_start_act = 'Ended' if setpoint is not None: self.setpoint = setpoint else: self.setpoint = self.default_setpoint # If autotune activated, determine control variable (output) from autotune if self.autotune_activated: if not self.autotune.run(self.last_measurement): self.control_variable = self.autotune.output if self.autotune_debug: self.logger.info('') self.logger.info("state: {}".format(self.autotune.state)) self.logger.info("output: {}".format(self.autotune.output)) else: # Autotune has finished timestamp = time.time() - self.autotune_timestamp self.logger.info('') self.logger.info('time: {0} min'.format(round(timestamp / 60))) self.logger.info('state: {0}'.format(self.autotune.state)) if self.autotune.state == PIDAutotune.STATE_SUCCEEDED: for rule in self.autotune.tuning_rules: params = self.autotune.get_pid_parameters(rule) self.logger.info('') self.logger.info('rule: {0}'.format(rule)) self.logger.info('Kp: {0}'.format(params.Kp)) self.logger.info('Ki: {0}'.format(params.Ki)) self.logger.info('Kd: {0}'.format(params.Kd)) self.stop_controller(deactivate_pid=True) else: # Calculate new control variable (output) from PID Controller # Original PID method self.control_variable = self.update_pid_output( self.last_measurement) # New PID method (untested) # self.control_variable = self.PID_Controller.calc( # self.last_measurement, self.setpoint) self.write_pid_values() # Write variables to database # Is PID in a state that allows manipulation of outputs if self.is_activated and (not self.is_paused or self.is_held): self.manipulate_output() def setup_method(self, method_id): """ Initialize method variables to start running a method """ self.method_id = '' method = db_retrieve_table_daemon(Method, unique_id=method_id) method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter(MethodData.method_id == method_id) method_data_repeat = method_data.filter(MethodData.duration_sec == 0).first() pid = db_retrieve_table_daemon(PID, unique_id=self.pid_id) self.method_type = method.method_type self.method_start_act = pid.method_start_time self.method_start_time = None self.method_end_time = None if self.method_type == 'Duration': if self.method_start_act == 'Ended': # Method has ended and hasn't been instructed to begin again pass elif (self.method_start_act == 'Ready' or self.method_start_act is None): # Method has been instructed to begin now = datetime.datetime.now() self.method_start_time = now if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = now + datetime.timedelta( seconds=float(method_data_repeat.duration_end)) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.method_start_time = self.method_start_time mod_pid.method_end_time = self.method_end_time db_session.commit() else: # Method neither instructed to begin or not to # Likely there was a daemon restart ot power failure # Resume method with saved start_time self.method_start_time = datetime.datetime.strptime( str(pid.method_start_time), '%Y-%m-%d %H:%M:%S.%f') if method_data_repeat and method_data_repeat.duration_end: self.method_end_time = datetime.datetime.strptime( str(pid.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if self.method_end_time > datetime.datetime.now(): self.logger.warning( "Resuming method {id}: started {start}, " "ends {end}".format( id=method_id, start=self.method_start_time, end=self.method_end_time)) else: self.method_start_act = 'Ended' else: self.method_start_act = 'Ended' self.method_id = method_id def write_pid_values(self): """ Write PID values to the measurement database """ if self.band: setpoint_band_lower = self.setpoint - self.band setpoint_band_upper = self.setpoint + self.band else: setpoint_band_lower = None setpoint_band_upper = None list_measurements = [ self.setpoint, setpoint_band_lower, setpoint_band_upper, self.P_value, self.I_value, self.D_value ] measurement_dict = {} measurements = self.device_measurements.filter( DeviceMeasurements.device_id == self.pid_id).all() for each_channel, each_measurement in enumerate(measurements): if (each_measurement.channel not in measurement_dict and each_measurement.channel < len(list_measurements)): # If setpoint, get unit from PID measurement if each_measurement.measurement_type == 'setpoint': setpoint_pid = db_retrieve_table_daemon( PID, unique_id=each_measurement.device_id) if setpoint_pid and ',' in setpoint_pid.measurement: pid_measurement = setpoint_pid.measurement.split(',')[1] setpoint_measurement = db_retrieve_table_daemon( DeviceMeasurements, unique_id=pid_measurement) if setpoint_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=setpoint_measurement.conversion_id) _, unit, _ = return_measurement_info( setpoint_measurement, conversion) measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': unit, 'value': list_measurements[each_channel] } else: measurement_dict[each_channel] = { 'measurement': each_measurement.measurement, 'unit': each_measurement.unit, 'value': list_measurements[each_channel] } add_measurements_influxdb(self.pid_id, measurement_dict) def update_pid_output(self, current_value): """ Calculate PID output value from reference input and feedback :return: Manipulated, or control, variable. This is the PID output. :rtype: float :param current_value: The input, or process, variable (the actual measured condition by the input) :type current_value: float """ # Determine if hysteresis is enabled and if the PID should be applied setpoint = self.check_hysteresis(current_value) if setpoint is None: # Prevent PID variables form being manipulated and # restrict PID from operating. return 0 self.error = setpoint - current_value # Calculate P-value self.P_value = self.Kp * self.error # Calculate I-value self.integrator += self.error # First method for managing integrator if self.integrator > self.integrator_max: self.integrator = self.integrator_max elif self.integrator < self.integrator_min: self.integrator = self.integrator_min # Second method for regulating integrator # if self.period is not None: # if self.integrator * self.Ki > self.period: # self.integrator = self.period / self.Ki # elif self.integrator * self.Ki < -self.period: # self.integrator = -self.period / self.Ki self.I_value = self.integrator * self.Ki # Prevent large initial D-value if self.first_start: self.derivator = self.error self.first_start = False # Calculate D-value self.D_value = self.Kd * (self.error - self.derivator) self.derivator = self.error # Produce output form P, I, and D values pid_value = self.P_value + self.I_value + self.D_value return pid_value def check_hysteresis(self, measure): """ Determine if hysteresis is enabled and if the PID should be applied :return: float if the setpoint if the PID should be applied, None to restrict the PID :rtype: float or None :param measure: The PID input (or process) variable :type measure: float """ if self.band == 0: # If band is disabled, return setpoint return self.setpoint band_min = self.setpoint - self.band band_max = self.setpoint + self.band if self.direction == 'raise': if (measure < band_min or (band_min < measure < band_max and self.allow_raising)): self.allow_raising = True setpoint = band_max # New setpoint return setpoint # Apply the PID elif measure > band_max: self.allow_raising = False return None # Restrict the PID elif self.direction == 'lower': if (measure > band_max or (band_min < measure < band_max and self.allow_lowering)): self.allow_lowering = True setpoint = band_min # New setpoint return setpoint # Apply the PID elif measure < band_min: self.allow_lowering = False return None # Restrict the PID elif self.direction == 'both': if measure < band_min: setpoint = band_min # New setpoint if not self.allow_raising: # Reset integrator and derivator upon direction switch self.integrator = 0.0 self.derivator = 0.0 self.allow_raising = True self.allow_lowering = False elif measure > band_max: setpoint = band_max # New setpoint if not self.allow_lowering: # Reset integrator and derivator upon direction switch self.integrator = 0.0 self.derivator = 0.0 self.allow_raising = False self.allow_lowering = True else: return None # Restrict the PID return setpoint # Apply the PID def get_last_measurement(self): """ Retrieve the latest input measurement from InfluxDB :rtype: None """ self.last_measurement_success = False # Get latest measurement from influxdb try: device_measurement = get_measurement(self.measurement_id) if device_measurement: conversion = db_retrieve_table_daemon( Conversion, unique_id=device_measurement.conversion_id) else: conversion = None channel, unit, measurement = return_measurement_info( device_measurement, conversion) self.last_measurement = read_last_influxdb( self.device_id, unit, measurement, channel, int(self.max_measure_age)) if self.last_measurement: self.last_time = self.last_measurement[0] self.last_measurement = self.last_measurement[1] utc_dt = datetime.datetime.strptime( self.last_time.split(".")[0], '%Y-%m-%dT%H:%M:%S') utc_timestamp = calendar.timegm(utc_dt.timetuple()) local_timestamp = str(datetime.datetime.fromtimestamp(utc_timestamp)) self.logger.debug("Latest (CH{ch}, Unit: {unit}): {last} @ {ts}".format( ch=channel, unit=unit, last=self.last_measurement, ts=local_timestamp)) if calendar.timegm(time.gmtime()) - utc_timestamp > self.max_measure_age: self.logger.error( "Last measurement was {last_sec} seconds ago, however" " the maximum measurement age is set to {max_sec}" " seconds.".format( last_sec=calendar.timegm(time.gmtime()) - utc_timestamp, max_sec=self.max_measure_age )) self.last_measurement_success = True else: self.logger.warning("No data returned from influxdb") except requests.ConnectionError: self.logger.error("Failed to read measurement from the " "influxdb database: Could not connect.") except Exception as except_msg: self.logger.exception( "Exception while reading measurement from the influxdb " "database: {err}".format(err=except_msg)) def manipulate_output(self): """ Activate output based on PID 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_output_id: if self.control_variable > 0: # Determine if the output should be PWM or a duration if self.raise_output_type in ['pwm', 'command_pwm', 'python_pwm']: self.raise_duty_cycle = float("{0:.1f}".format( self.control_var_to_duty_cycle(self.control_variable))) # Ensure the duty cycle doesn't exceed the min/max if (self.raise_max_duration and self.raise_duty_cycle > self.raise_max_duration): self.raise_duty_cycle = self.raise_max_duration elif (self.raise_min_duration and self.raise_duty_cycle < self.raise_min_duration): self.raise_duty_cycle = self.raise_min_duration self.logger.debug( "Setpoint: {sp}, Control Variable: {cv}, Output: PWM output " "{id} to {dc:.1f}%".format( sp=self.setpoint, cv=self.control_variable, id=self.raise_output_id, dc=self.raise_duty_cycle)) # Activate pwm with calculated duty cycle self.control.output_on(self.raise_output_id, duty_cycle=self.raise_duty_cycle) self.write_pid_output_influxdb( 'percent', 'duty_cycle', 7, self.control_var_to_duty_cycle(self.control_variable)) elif self.raise_output_type in ['command', 'python', 'wired', 'wireless_rpi_rf']: # Ensure the output 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)) if self.raise_seconds_on > self.raise_min_duration: # Activate raise_output for a duration self.logger.debug( "Setpoint: {sp} Output: {cv} to output " "{id}".format( sp=self.setpoint, cv=self.control_variable, id=self.raise_output_id)) self.control.output_on( self.raise_output_id, duration=self.raise_seconds_on, min_off=self.raise_min_off_duration) self.write_pid_output_influxdb( 's', 'duration_time', 6, self.control_variable) else: if self.raise_output_type in ['pwm', 'command_pwm', 'python_pwm']: self.control.output_on(self.raise_output_id, duty_cycle=0) # # PID control variable is negative, indicating a desire to lower # the environmental condition # if self.direction in ['lower', 'both'] and self.lower_output_id: if self.control_variable < 0: # Determine if the output should be PWM or a duration if self.lower_output_type in ['pwm', 'command_pwm', 'python_pwm']: self.lower_duty_cycle = float("{0:.1f}".format( self.control_var_to_duty_cycle(abs(self.control_variable)))) # Ensure the duty cycle doesn't exceed the min/max if (self.lower_max_duration and self.lower_duty_cycle > self.lower_max_duration): self.lower_duty_cycle = self.lower_max_duration elif (self.lower_min_duration and self.lower_duty_cycle < self.lower_min_duration): self.lower_duty_cycle = self.lower_min_duration self.logger.debug( "Setpoint: {sp}, Control Variable: {cv}, " "Output: PWM output {id} to {dc:.1f}%".format( sp=self.setpoint, cv=self.control_variable, id=self.lower_output_id, dc=self.lower_duty_cycle)) if self.store_lower_as_negative: stored_duty_cycle = -abs(self.lower_duty_cycle) stored_control_variable = -self.control_var_to_duty_cycle(abs(self.control_variable)) else: stored_duty_cycle = abs(self.lower_duty_cycle) stored_control_variable = self.control_var_to_duty_cycle(abs(self.control_variable)) # Activate pwm with calculated duty cycle self.control.output_on( self.lower_output_id, duty_cycle=stored_duty_cycle) self.write_pid_output_influxdb( 'percent', 'duty_cycle', 7, stored_control_variable) elif self.lower_output_type in ['command', 'python', 'wired', 'wireless_rpi_rf']: # Ensure the output 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 = float("{0:.2f}".format( abs(self.control_variable))) if self.store_lower_as_negative: stored_seconds_on = -abs(self.lower_seconds_on) stored_control_variable = -abs(self.control_variable) else: stored_seconds_on = abs(self.lower_seconds_on) stored_control_variable = abs(self.control_variable) if self.lower_seconds_on > self.lower_min_duration: # Activate lower_output for a duration self.logger.debug("Setpoint: {sp} Output: {cv} to " "output {id}".format( sp=self.setpoint, cv=self.control_variable, id=self.lower_output_id)) self.control.output_on( self.lower_output_id, duration=stored_seconds_on, min_off=self.lower_min_off_duration) self.write_pid_output_influxdb( 's', 'duration_time', 6, stored_control_variable) else: if self.lower_output_type in ['pwm', 'command_pwm', 'python_pwm']: self.control.output_on(self.lower_output_id, duty_cycle=0) else: if self.direction in ['raise', 'both'] and self.raise_output_id: self.control.output_off(self.raise_output_id) if self.direction in ['lower', 'both'] and self.lower_output_id: self.control.output_off(self.lower_output_id) def pid_parameters_str(self): return "Device ID: {did}, " \ "Measurement ID: {mid}, " \ "Direction: {dir}, " \ "Period: {per}, " \ "Setpoint: {sp}, " \ "Band: {band}, " \ "Kp: {kp}, " \ "Ki: {ki}, " \ "Kd: {kd}, " \ "Integrator Min: {imn}, " \ "Integrator Max {imx}, " \ "Output Raise: {opr}, " \ "Output Raise Min On: {oprmnon}, " \ "Output Raise Max On: {oprmxon}, " \ "Output Raise Min Off: {oprmnoff}, " \ "Output Lower: {opl}, " \ "Output Lower Min On: {oplmnon}, " \ "Output Lower Max On: {oplmxon}, " \ "Output Lower Min Off: {oplmnoff}, " \ "Setpoint Tracking: {spt}".format( did=self.device_id, mid=self.measurement_id, dir=self.direction, per=self.period, sp=self.setpoint, band=self.band, kp=self.Kp, ki=self.Ki, kd=self.Kd, imn=self.integrator_min, imx=self.integrator_max, opr=self.raise_output_id, oprmnon=self.raise_min_duration, oprmxon=self.raise_max_duration, oprmnoff=self.raise_min_off_duration, opl=self.lower_output_id, oplmnon=self.lower_min_duration, oplmxon=self.lower_max_duration, oplmnoff=self.lower_min_off_duration, spt=self.method_id) def control_var_to_duty_cycle(self, control_variable): # Convert control variable to duty cycle if control_variable > self.period: return 100.0 else: return float((control_variable / self.period) * 100) def write_pid_output_influxdb(self, unit, measurement, channel, value): write_pid_out_db = threading.Thread( target=write_influxdb_value, args=(self.pid_id, unit, value,), kwargs={'measure': measurement, 'channel': channel}) write_pid_out_db.start() def pid_mod(self): if self.initialize_values(): return "success" else: return "error" def pid_hold(self): self.is_held = True self.logger.info("Hold") return "success" def pid_pause(self): self.is_paused = True self.logger.info("Pause") return "success" def pid_resume(self): self.is_activated = True self.is_held = False self.is_paused = False self.logger.info("Resume") return "success" def set_setpoint(self, setpoint): """ Set the setpoint of PID """ self.setpoint = float(setpoint) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.setpoint = setpoint db_session.commit() return "Setpoint set to {sp}".format(sp=setpoint) def set_method(self, method_id): """ Set the method of PID """ with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.method_id = method_id if method_id == '': self.method_id = '' db_session.commit() else: mod_pid.method_start_time = 'Ready' mod_pid.method_end_time = None db_session.commit() self.setup_method(method_id) return "Method set to {me}".format(me=method_id) def set_integrator(self, integrator): """ Set the integrator of the controller """ self.integrator = float(integrator) return "Integrator set to {i}".format(i=self.integrator) def set_derivator(self, derivator): """ Set the derivator of the controller """ self.derivator = float(derivator) return "Derivator set to {d}".format(d=self.derivator) def set_kp(self, p): """ Set Kp gain of the controller """ self.Kp = float(p) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.p = p db_session.commit() return "Kp set to {kp}".format(kp=self.Kp) def set_ki(self, i): """ Set Ki gain of the controller """ self.Ki = float(i) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.i = i db_session.commit() return "Ki set to {ki}".format(ki=self.Ki) def set_kd(self, d): """ Set Kd gain of the controller """ self.Kd = float(d) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.d = d db_session.commit() return "Kd set to {kd}".format(kd=self.Kd) def get_setpoint(self): return self.setpoint def get_error(self): return self.error def get_integrator(self): return self.integrator def get_derivator(self): return self.derivator def get_kp(self): return self.Kp def get_ki(self): return self.Ki def get_kd(self): return self.Kd def is_running(self): return self.running 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.unique_id == self.pid_id).first() mod_pid.method_start_time = 'Ended' mod_pid.method_end_time = None db_session.commit() # Deactivate PID and Autotune if deactivate_pid: with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.unique_id == self.pid_id).first() mod_pid.is_activated = False mod_pid.autotune_activated = False db_session.commit()
def conditional_mod(form): """Modify a Conditional""" error = [] action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['conditional']['title']) try: pre_statement = """import os, random, sys sys.path.append(os.path.abspath('/var/mycodo-root')) from mycodo.mycodo_client import DaemonControl control = DaemonControl() message = '' def measure(condition_id): # pylint: disable=unused-argument return random.choice([None, -100000, -10000, -1000, -100, -10, 0, 1, 10, 100, 1000, 10000, 100000]) def run_all_actions(message=message): # pylint: disable=unused-argument pass def run_action(action_id, message=message): # pylint: disable=unused-argument pass ########################### ##### BEGIN USER CODE ##### ########################### """ cond_statement = (pre_statement + form.conditional_statement.data) if len(cond_statement.splitlines()) > 999: error.append("Too many lines in code. Reduce code to less than 1000 lines.") lines_code = '' for line_num, each_line in enumerate(cond_statement.splitlines(), 1): if len(str(line_num)) == 3: line_spacing = '' elif len(str(line_num)) == 2: line_spacing = ' ' else: line_spacing = ' ' lines_code += '{sp}{ln}: {line}\n'.format( sp=line_spacing, ln=line_num, line=each_line) path_file = '/tmp/conditional_code_{}.py'.format( str(uuid.uuid4()).split('-')[0]) with open(path_file, 'w') as out: out.write('{}\n'.format(cond_statement)) cmd_test = 'export PYTHONPATH=$PYTHONPATH:/var/mycodo-root && ' \ 'pylint3 -d I,W0621,C0103,C0111,C0301,C0327,C0410,C0413 {path}'.format( path=path_file) cmd_out, cmd_err, cmd_status = cmd_output(cmd_test) os.remove(path_file) message = Markup( '<pre>\n\n' 'Full Conditional Statement code:\n\n{code}\n\n' 'Conditional Statement code analysis:\n\n{report}' '</pre>'.format( code=lines_code, report=cmd_out.decode("utf-8"))) if cmd_status: flash('Error(s) were found while evaluating your code. Review ' 'the error(s), below, and fix them before activating your ' 'Conditional.', 'error') flash(message, 'error') else: flash( "No errors were found while evaluating your code. However, " "this doesn't mean your code will perform as expected. " "Review your code for issues and test your Conditional " "before putting it into a production environment.", 'success') flash(message, 'success') cond_mod = Conditional.query.filter( Conditional.unique_id == form.function_id.data).first() cond_mod.name = form.name.data cond_mod.conditional_statement = form.conditional_statement.data cond_mod.period = form.period.data cond_mod.start_offset = form.start_offset.data cond_mod.refractory_period = form.refractory_period.data if not error: db.session.commit() if cond_mod.is_activated: control = DaemonControl() return_value = control.refresh_daemon_conditional_settings( form.function_id.data) flash(gettext( "Daemon response: %(resp)s", resp=return_value), "success") except sqlalchemy.exc.OperationalError as except_msg: error.append(except_msg) except sqlalchemy.exc.IntegrityError as except_msg: error.append(except_msg) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
class InputController(threading.Thread): """ Class for controlling the input """ def __init__(self, ready, input_id): threading.Thread.__init__(self) self.logger = logging.getLogger( "mycodo.input_{id}".format(id=input_id.split('-')[0])) 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.measurement_success = False self.control = DaemonControl() self.pause_loop = False self.verify_pause_loop = True self.force_measurements_trigger = False self.dict_inputs = parse_input_information() self.sample_rate = db_retrieve_table_daemon( Misc, entry='first').sample_rate_controller_input self.input_id = input_id input_dev = db_retrieve_table_daemon( Input, unique_id=self.input_id) self.device_measurements = db_retrieve_table_daemon( DeviceMeasurements).filter( DeviceMeasurements.device_id == self.input_id) self.conversions = db_retrieve_table_daemon(Conversion) self.input_dev = input_dev self.input_name = input_dev.name self.unique_id = input_dev.unique_id self.gpio_location = input_dev.gpio_location self.device = input_dev.device self.interface = input_dev.interface self.period = input_dev.period # Edge detection self.switch_edge = input_dev.switch_edge self.switch_bouncetime = input_dev.switch_bouncetime self.switch_reset_period = input_dev.switch_reset_period # Pre-Output: Activates prior to input measurement self.pre_output_id = input_dev.pre_output_id self.pre_output_duration = input_dev.pre_output_duration self.pre_output_during_measure = input_dev.pre_output_during_measure self.pre_output_setup = False self.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_locked = False self.pre_output_timer = time.time() # Check if Pre-Output ID actually exists output = db_retrieve_table_daemon(Output, entry='all') for each_output in output: if each_output.unique_id == self.pre_output_id and self.pre_output_duration: self.pre_output_setup = True smtp = db_retrieve_table_daemon(SMTP, entry='first') self.smtp_max_count = smtp.hourly_max self.email_count = 0 self.allowed_to_send_notice = True # Set up input lock self.input_lock = None self.lock_file = '/var/lock/input_pre_output_{id}'.format(id=self.pre_output_id) # Convert string I2C address to base-16 int if self.interface == 'I2C': self.i2c_address = int(str(self.input_dev.i2c_location), 16) # Set up edge detection of a GPIO pin if self.device == 'EDGE': 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 self.device_recognized = True if self.device in self.dict_inputs: input_loaded = load_module_from_file(self.dict_inputs[self.device]['file_path']) if self.device == 'EDGE': # Edge detection handled internally, no module to load self.measure_input = None else: self.measure_input = input_loaded.InputModule(self.input_dev) else: self.device_recognized = False self.logger.debug("Device '{device}' not recognized".format( device=self.device)) raise Exception("'{device}' is not a valid device type.".format( device=self.device)) self.edge_reset_timer = time.time() self.input_timer = time.time() self.running = False self.lastUpdate = None def run(self): try: self.running = True self.logger.info("Activated in {:.1f} ms".format( (timeit.default_timer() - self.thread_startup_timer) * 1000)) self.ready.set() # Set up edge detection if self.device == 'EDGE': GPIO.setmode(GPIO.BCM) GPIO.setup(int(self.gpio_location), GPIO.IN) GPIO.add_event_detect(int(self.gpio_location), self.switch_edge_gpio, callback=self.edge_detected, bouncetime=self.switch_bouncetime) while self.running: # Pause loop to modify conditional statements. # Prevents execution of conditional while variables are # being modified. if self.pause_loop: self.verify_pause_loop = True while self.pause_loop: time.sleep(0.1) if self.force_measurements_trigger: self.acquire_measurements_now() self.force_measurements_trigger = False if self.device not in ['EDGE']: now = time.time() # Signal that a measurement needs to be obtained if now > self.next_measurement and not self.get_new_measurement: self.get_new_measurement = True self.trigger_cond = True while self.next_measurement < now: self.next_measurement += self.period # if signaled and a pre output is set up correctly, turn the # output on or on for the set duration if (self.get_new_measurement and self.pre_output_setup and not self.pre_output_activated): # Set up lock self.input_lock = locket.lock_file(self.lock_file, timeout=30) try: self.input_lock.acquire() self.pre_output_locked = True except locket.LockError: self.logger.error("Could not acquire input lock. Breaking for future locking.") try: os.remove(self.lock_file) except OSError: self.logger.error("Can't delete lock file: Lock file doesn't exist.") self.pre_output_timer = time.time() + self.pre_output_duration self.pre_output_activated = True # Only run the pre-output before measurement # Turn on for a duration, measure after it turns off if not self.pre_output_during_measure: output_on = threading.Thread( target=self.control.output_on, args=(self.pre_output_id, self.pre_output_duration,)) output_on.start() # Run the pre-output during the measurement # Just turn on, then off after the measurement else: output_on = threading.Thread( target=self.control.output_on, args=(self.pre_output_id,)) output_on.start() # If using a pre output, wait for it to complete before # querying the input for a measurement if self.get_new_measurement: if (self.pre_output_setup and self.pre_output_activated and now > self.pre_output_timer): if self.pre_output_during_measure: # Measure then turn off pre-output self.update_measure() output_off = threading.Thread( target=self.control.output_off, args=(self.pre_output_id,)) output_off.start() else: # Pre-output has turned off, now measure self.update_measure() self.pre_output_activated = False self.get_new_measurement = False # release pre-output lock try: if self.pre_output_locked: self.input_lock.release() self.pre_output_locked = False except AttributeError: self.logger.error("Can't release lock: " "Lock file not present.") elif not self.pre_output_setup: # Pre-output not enabled, just measure self.update_measure() self.get_new_measurement = False # Add measurement(s) to influxdb if self.measurement_success: add_measurements_influxdb( self.unique_id, self.create_measurements_dict()) self.measurement_success = False self.trigger_cond = False time.sleep(self.sample_rate) self.running = False if self.device == 'EDGE': GPIO.setmode(GPIO.BCM) GPIO.cleanup(int(self.gpio_location)) self.logger.info("Deactivated in {:.1f} ms".format( (timeit.default_timer() - self.thread_shutdown_timer) * 1000)) except requests.ConnectionError: self.logger.error("Could not connect to influxdb. Check that it " "is running and accepting connections") except Exception as except_msg: self.logger.exception("Error: {err}".format( err=except_msg)) def update_measure(self): """ Retrieve measurement from input :return: None if success, 0 if fail :rtype: int or None """ measurements = None if not self.device_recognized: self.logger.debug("Device not recognized: {device}".format( device=self.device)) self.measurement_success = False return 1 try: # Get measurement from input measurements = self.measure_input.next() # Reset StopIteration counter on successful read if self.stop_iteration_counter: self.stop_iteration_counter = 0 except StopIteration: self.stop_iteration_counter += 1 # Notify after 3 consecutive errors. Prevents filling log # with many one-off errors over long periods of time if self.stop_iteration_counter > 2: self.stop_iteration_counter = 0 self.logger.error( "StopIteration raised. Possibly could not read " "input. Ensure it's connected properly and " "detected.") except Exception as except_msg: self.logger.exception( "Error while attempting to read input: {err}".format( err=except_msg)) if self.device_recognized and measurements is not None: self.measurement = Measurement(measurements) self.measurement_success = True else: self.measurement_success = False self.lastUpdate = time.time() def edge_detected(self, bcm_pin): """ Callback function from GPIO.add_event_detect() for when an edge is detected Write rising (1) or falling (-1) edge to influxdb database Trigger any conditionals that match the rising/falling/both edge :param bcm_pin: BMC pin of rising/falling edge (required parameter) :return: None """ gpio_state = GPIO.input(int(self.gpio_location)) if time.time() > self.edge_reset_timer: self.edge_reset_timer = time.time()+self.switch_reset_period if (self.switch_edge == 'rising' or (self.switch_edge == 'both' and gpio_state)): rising_or_falling = 1 # Rising edge detected state_str = 'Rising' else: rising_or_falling = -1 # Falling edge detected state_str = 'Falling' write_db = threading.Thread( target=write_influxdb_value, args=(self.unique_id, 'edge', rising_or_falling,)) write_db.start() trigger = db_retrieve_table_daemon(Trigger) trigger = trigger.filter( Trigger.trigger_type == 'trigger_edge') trigger = trigger.filter( Trigger.measurement == self.unique_id) trigger = trigger.filter( Trigger.is_activated == True) for each_trigger in trigger.all(): if each_trigger.edge_detected in ['both', state_str.lower()]: now = time.time() timestamp = datetime.datetime.fromtimestamp( now).strftime('%Y-%m-%d %H-%M-%S') message = "{ts}\n[Trigger {cid} ({cname})] " \ "Input {oid} ({name}) {state} edge detected " \ "on pin {pin} (BCM)".format( ts=timestamp, cid=each_trigger.id, cname=each_trigger.name, oid=self.input_id, name=self.input_name, state=state_str, pin=bcm_pin) self.control.trigger_all_actions( each_trigger.unique_id, message=message) def create_measurements_dict(self): measurements_record = {} for each_channel, each_measurement in self.measurement.values.items(): measurement = self.device_measurements.filter( DeviceMeasurements.channel == each_channel).first() if 'value' in each_measurement: conversion = self.conversions.filter( Conversion.unique_id == measurement.conversion_id).first() measurements_record = parse_measurement( conversion, measurement, measurements_record, each_channel, each_measurement) return measurements_record def force_measurements(self): self.force_measurements_trigger = True return "Input instructed to begin acquiring measurements" def acquire_measurements_now(self): try: self.update_measure() add_measurements_influxdb( self.unique_id, self.create_measurements_dict()) return "Success" except Exception as msg: return "Failure: {}".format(msg) def is_running(self): return self.running def stop_controller(self): self.thread_shutdown_timer = timeit.default_timer() # Execute stop_sensor() if not EDGE or ADC if self.device != 'EDGE': self.measure_input.stop_sensor() # Ensure pre-output is off if self.pre_output_setup: output_on = threading.Thread( target=self.control.output_off, args=(self.pre_output_id,)) output_on.start() self.running = False
def trigger_mod(form): """Modify a Trigger""" error = [] action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['trigger']['title']) try: trigger = Trigger.query.filter( Trigger.unique_id == form.function_id.data).first() trigger.name = form.name.data if trigger.trigger_type == 'trigger_edge': error = check_form_edge(form, error) trigger.measurement = form.measurement.data trigger.edge_detected = form.edge_detected.data elif trigger.trigger_type == 'trigger_output': error = check_form_output(form, error) trigger.unique_id_1 = form.unique_id_1.data trigger.output_state = form.output_state.data trigger.output_duration = form.output_duration.data elif trigger.trigger_type == 'trigger_output_duration': error = check_form_output_duration(form, error) trigger.unique_id_1 = form.unique_id_1.data trigger.output_state = form.output_state.data trigger.output_duration = form.output_duration.data elif trigger.trigger_type == 'trigger_output_pwm': error = check_form_output_pwm(form, error) trigger.unique_id_1 = form.unique_id_1.data trigger.output_state = form.output_state.data trigger.output_duty_cycle = form.output_duty_cycle.data elif trigger.trigger_type == 'trigger_run_pwm_method': error = check_form_run_pwm_method(form, error) trigger.unique_id_1 = form.unique_id_1.data trigger.unique_id_2 = form.unique_id_2.data trigger.period = form.period.data trigger.trigger_actions_at_start = form.trigger_actions_at_start.data trigger.trigger_actions_at_period = form.trigger_actions_at_period.data elif trigger.trigger_type == 'trigger_sunrise_sunset': error = check_form_sunrise_sunset(form, error) trigger.rise_or_set = form.rise_or_set.data trigger.latitude = form.latitude.data trigger.longitude = form.longitude.data trigger.zenith = form.zenith.data trigger.date_offset_days = form.date_offset_days.data trigger.time_offset_minutes = form.time_offset_minutes.data elif trigger.trigger_type == 'trigger_timer_daily_time_point': error = check_form_timer_daily_time_point(form, error) trigger.timer_start_time = form.timer_start_time.data elif trigger.trigger_type == 'trigger_timer_daily_time_span': error = check_form_timer_daily_time_span(form, error) trigger.period = form.period.data trigger.timer_start_time = form.timer_start_time.data trigger.timer_end_time = form.timer_end_time.data elif trigger.trigger_type == 'trigger_timer_duration': error = check_form_timer_duration(form, error) trigger.period = form.period.data trigger.timer_start_offset = form.timer_start_offset.data if not error: db.session.commit() if trigger.is_activated: control = DaemonControl() return_value = control.refresh_daemon_trigger_settings( form.function_id.data) flash(gettext( "Daemon response: %(resp)s", resp=return_value), "success") except sqlalchemy.exc.OperationalError as except_msg: error.append(except_msg) except sqlalchemy.exc.IntegrityError as except_msg: error.append(except_msg) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def pid_mod(form_mod_pid_base, form_mod_pid_pwm_raise, form_mod_pid_pwm_lower, form_mod_pid_output_raise, form_mod_pid_output_lower): action = '{action} {controller}'.format( action=TRANSLATIONS['modify']['title'], controller=TRANSLATIONS['pid']['title']) error = [] if not form_mod_pid_base.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_base) mod_pid = PID.query.filter( PID.unique_id == form_mod_pid_base.function_id.data).first() # Check if a specific setting can be modified if the PID is active if mod_pid.is_activated: error = can_set_output( error, form_mod_pid_base.function_id.data, form_mod_pid_base.raise_output_id.data, form_mod_pid_base.lower_output_id.data) mod_pid.name = form_mod_pid_base.name.data mod_pid.measurement = form_mod_pid_base.measurement.data mod_pid.direction = form_mod_pid_base.direction.data mod_pid.period = form_mod_pid_base.period.data mod_pid.start_offset = form_mod_pid_base.start_offset.data mod_pid.max_measure_age = form_mod_pid_base.max_measure_age.data mod_pid.setpoint = form_mod_pid_base.setpoint.data mod_pid.band = abs(form_mod_pid_base.band.data) mod_pid.store_lower_as_negative = form_mod_pid_base.store_lower_as_negative.data mod_pid.p = form_mod_pid_base.k_p.data mod_pid.i = form_mod_pid_base.k_i.data mod_pid.d = form_mod_pid_base.k_d.data mod_pid.integrator_min = form_mod_pid_base.integrator_max.data mod_pid.integrator_max = form_mod_pid_base.integrator_min.data mod_pid.method_id = form_mod_pid_base.method_id.data # Change measurement information if ',' in form_mod_pid_base.measurement.data: measurement_id = form_mod_pid_base.measurement.data.split(',')[1] selected_measurement = get_measurement(measurement_id) measurements = DeviceMeasurements.query.filter( DeviceMeasurements.device_id == form_mod_pid_base.function_id.data).all() for each_measurement in measurements: # Only set channels 0, 1, 2 if each_measurement.channel in [0, 1, 2]: each_measurement.measurement = selected_measurement.measurement each_measurement.unit = selected_measurement.unit if form_mod_pid_base.raise_output_id.data: raise_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.raise_output_id.data).first().output_type if mod_pid.raise_output_id == form_mod_pid_base.raise_output_id.data: if raise_output_type in ['pwm', 'command_pwm']: if not form_mod_pid_pwm_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_pwm_raise) else: mod_pid.raise_min_duration = form_mod_pid_pwm_raise.raise_min_duty_cycle.data mod_pid.raise_max_duration = form_mod_pid_pwm_raise.raise_max_duty_cycle.data else: if not form_mod_pid_output_raise.validate(): error.append(TRANSLATIONS['error']['title']) flash_form_errors(form_mod_pid_output_raise) else: mod_pid.raise_min_duration = form_mod_pid_output_raise.raise_min_duration.data mod_pid.raise_max_duration = form_mod_pid_output_raise.raise_max_duration.data mod_pid.raise_min_off_duration = form_mod_pid_output_raise.raise_min_off_duration.data else: if raise_output_type in ['pwm', 'command_pwm']: mod_pid.raise_min_duration = 2 mod_pid.raise_max_duration = 98 else: mod_pid.raise_min_duration = 0 mod_pid.raise_max_duration = 0 mod_pid.raise_min_off_duration = 0 mod_pid.raise_output_id = form_mod_pid_base.raise_output_id.data else: mod_pid.raise_output_id = None if form_mod_pid_base.lower_output_id.data: lower_output_type = Output.query.filter( Output.unique_id == form_mod_pid_base.lower_output_id.data).first().output_type if mod_pid.lower_output_id == form_mod_pid_base.lower_output_id.data: if lower_output_type in ['pwm', 'command_pwm']: if not form_mod_pid_pwm_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_pwm_lower) else: mod_pid.lower_min_duration = form_mod_pid_pwm_lower.lower_min_duty_cycle.data mod_pid.lower_max_duration = form_mod_pid_pwm_lower.lower_max_duty_cycle.data else: if not form_mod_pid_output_lower.validate(): error.append(gettext("Error in form field(s)")) flash_form_errors(form_mod_pid_output_lower) else: mod_pid.lower_min_duration = form_mod_pid_output_lower.lower_min_duration.data mod_pid.lower_max_duration = form_mod_pid_output_lower.lower_max_duration.data mod_pid.lower_min_off_duration = form_mod_pid_output_lower.lower_min_off_duration.data else: if lower_output_type in ['pwm', 'command_pwm']: mod_pid.lower_min_duration = 2 mod_pid.lower_max_duration = 98 else: mod_pid.lower_min_duration = 0 mod_pid.lower_max_duration = 0 mod_pid.lower_min_off_duration = 0 mod_pid.lower_output_id = form_mod_pid_base.lower_output_id.data else: mod_pid.lower_output_id = None if (mod_pid.raise_output_id and mod_pid.lower_output_id and mod_pid.raise_output_id == mod_pid.lower_output_id): error.append(gettext("Raise and lower outputs cannot be the same")) try: if not error: db.session.commit() # If the controller is active or paused, refresh variables in thread if mod_pid.is_activated: control = DaemonControl() return_value = control.pid_mod(form_mod_pid_base.function_id.data) flash("PID Controller settings refresh response: " "{resp}".format(resp=return_value), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_function'))
def controller_activate_deactivate(controller_action, controller_type, controller_id): """ Activate or deactivate controller :param controller_action: Activate or deactivate :type controller_action: str :param controller_type: The controller type (Conditional, LCD, Math, PID, Input) :type controller_type: str :param controller_id: Controller with ID to activate or deactivate :type controller_id: str """ if not user_has_permission('edit_controllers'): return redirect(url_for('routes_general.home')) error = [] activated = bool(controller_action == 'activate') translated_names = { "Conditional": TRANSLATIONS['conditional']['title'], "Input": TRANSLATIONS['input']['title'], "LCD": TRANSLATIONS['lcd']['title'], "Math": TRANSLATIONS['math']['title'], "PID": TRANSLATIONS['pid']['title'], "Trigger": TRANSLATIONS['trigger']['title'] } mod_controller = None if controller_type == 'Conditional': mod_controller = Conditional.query.filter( Conditional.unique_id == controller_id).first() elif controller_type == 'Input': mod_controller = Input.query.filter( Input.unique_id == controller_id).first() if activated: error = check_for_valid_unit_and_conversion(controller_id, error) elif controller_type == 'LCD': mod_controller = LCD.query.filter( LCD.unique_id == controller_id).first() elif controller_type == 'Math': mod_controller = Math.query.filter( Math.unique_id == controller_id).first() if activated: error = check_for_valid_unit_and_conversion(controller_id, error) elif controller_type == 'PID': mod_controller = PID.query.filter( PID.unique_id == controller_id).first() if activated: error = check_for_valid_unit_and_conversion(controller_id, error) elif controller_type == 'Trigger': mod_controller = Trigger.query.filter( Trigger.unique_id == controller_id).first() if mod_controller is None: flash("{type} Controller {id} doesn't exist".format( type=controller_type, id=controller_id), "error") return redirect(url_for('routes_general.home')) try: if not error: mod_controller.is_activated = activated db.session.commit() if activated: flash( "{} {} (SQL)".format( translated_names[controller_type], TRANSLATIONS['activate']['title']), "success") else: flash( "{} {} (SQL)".format( translated_names[controller_type], TRANSLATIONS['deactivate']['title']), "success") except Exception as except_msg: flash(gettext("Error: %(err)s", err='SQL: {msg}'.format(msg=except_msg)), "error") try: if not error: control = DaemonControl() if controller_action == 'activate': return_values = control.controller_activate( controller_type,controller_id) else: return_values = control.controller_deactivate( controller_type, controller_id) if return_values[0]: flash("{err}".format(err=return_values[1]), "error") else: flash("{err}".format(err=return_values[1]), "success") except Exception as except_msg: flash('{}: {}'.format(TRANSLATIONS['error']['title'], except_msg), "error") for each_error in error: flash(each_error, 'error')