def upgrade(): with session_scope(MYCODO_DB_PATH) as conditional_sess: for each_conditional in conditional_sess.query(Conditional).all(): if each_conditional.conditional_statement: # Get conditions for this conditional with session_scope(MYCODO_DB_PATH) as condition_sess: for each_condition in condition_sess.query(ConditionalConditions).all(): # Replace {ID} with measure("{ID}") id_str = '{{{id}}}'.format(id=each_condition.unique_id.split('-')[0]) new_str = 'measure("{{{id}}}")'.format(id=each_condition.unique_id.split('-')[0]) if id_str in each_conditional.conditional_statement: each_conditional.conditional_statement = each_conditional.conditional_statement.replace(id_str, new_str) # Replace print(1) with run_all_actions() new_str = 'run_all_actions()' if id_str in each_conditional.conditional_statement: each_conditional.conditional_statement = each_conditional.conditional_statement.replace( 'print(1)', new_str) each_conditional.conditional_statement = each_conditional.conditional_statement.replace( 'print("1")', new_str) each_conditional.conditional_statement = each_conditional.conditional_statement.replace( "print('1')", new_str) conditional_sess.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 check_mycodo_upgrade_exists(self, now): """Check for any new Mycodo releases on github""" releases = [] upgrade_available = False try: maj_version = int(MYCODO_VERSION.split('.')[0]) releases = github_releases(maj_version) except Exception: self.logger.error("Could not determine local mycodo version or " "online release versions. Upgrade checks can " "be disabled in the Mycodo configuration.") try: if len(releases): if parse_version(releases[0]) > parse_version(MYCODO_VERSION): upgrade_available = True if now > self.timer_upgrade_message: # Only display message in log every 10 days self.timer_upgrade_message = time.time() + 864000 self.logger.info( "A new version of Mycodo is available. Upgrade " "through the web interface under Config -> Upgrade. " "This message will repeat every 10 days unless " "Mycodo is upgraded or upgrade checks are disabled.") with session_scope(MYCODO_DB_PATH) as new_session: mod_misc = new_session.query(Misc).first() if mod_misc.mycodo_upgrade_available != upgrade_available: mod_misc.mycodo_upgrade_available = upgrade_available new_session.commit() except Exception: self.logger.exception("Mycodo Upgrade Check ERROR")
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 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_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_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 create_admin_user(user_db_uri): """ mycodo_flask exits if there is no user called admin. So we create one """ with session_scope(user_db_uri) as db_session: if not db_session.query(Users).filter_by(user_restriction='admin').count(): logging.info("--> Creating new 'test' user as an admin") db_session.add(Users(user_name='test', user_restriction='admin')) db_session.commit() else: logging.warning("--> Dirty User DB: Admin user was already setup in: '{uri}'".format(uri=user_db_uri))
def timelapse_check(self, camera, now): """ If time-lapses are active, take photo at predefined periods """ try: if (camera.timelapse_started and now > camera.timelapse_end_time): with session_scope(MYCODO_DB_PATH) as new_session: mod_camera = new_session.query(Camera).filter( Camera.unique_id == camera.unique_id).first() mod_camera.timelapse_started = False mod_camera.timelapse_paused = False mod_camera.timelapse_start_time = None mod_camera.timelapse_end_time = None mod_camera.timelapse_interval = None mod_camera.timelapse_next_capture = None mod_camera.timelapse_capture_number = None new_session.commit() self.refresh_daemon_camera_settings() self.logger.debug( "Camera {id}: End of time-lapse.".format(id=camera.id)) elif ((camera.timelapse_started and not camera.timelapse_paused) and now > camera.timelapse_next_capture): # Ensure next capture is greater than now (in case of power failure/reboot) next_capture = camera.timelapse_next_capture capture_number = camera.timelapse_capture_number while now > next_capture: # Update last capture and image number to latest before capture next_capture += camera.timelapse_interval capture_number += 1 with session_scope(MYCODO_DB_PATH) as new_session: mod_camera = new_session.query(Camera).filter( Camera.unique_id == camera.unique_id).first() mod_camera.timelapse_next_capture = next_capture mod_camera.timelapse_capture_number = capture_number new_session.commit() self.refresh_daemon_camera_settings() self.logger.debug( "Camera {id}: Capturing time-lapse image".format(id=camera.id)) # Capture image camera_record('timelapse', camera.unique_id) except Exception as except_msg: message = "Could not execute timelapse:" \ " {err}".format(err=except_msg) self.logger.exception(message)
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 upgrade(): with session_scope(MYCODO_DB_PATH) as new_session: for each_input in new_session.query(Input).all(): if each_input.device in ['DS18B20', 'DS18S20']: if 'library' not in each_input.custom_options: if each_input.custom_options in [None, '']: each_input.custom_options = 'library,w1thermsensor' else: each_input.custom_options += ';library,w1thermsensor' new_session.commit()
def delete_user(username): if query_yes_no("Confirm delete user '{}' from user database.".format(username)): try: with session_scope(USER_DB_PATH) as db_session: user = db_session.query(Users).filter(Users.user_name == username).one() db_session.delete(user) print("User deleted.") sys.exit(0) except sqlalchemy.orm.exc.NoResultFound: print("No user found with this name.") sys.exit(1)
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 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 deactivate_self(self): self.logger.info("Deactivating Autotune Function") from mycodo.databases.utils import session_scope from mycodo.config import SQL_DATABASE_MYCODO MYCODO_DB_PATH = 'sqlite:///' + SQL_DATABASE_MYCODO with session_scope(MYCODO_DB_PATH) as new_session: mod_cont = new_session.query(CustomController).filter( CustomController.unique_id == self.unique_id).first() mod_cont.is_activated = False new_session.commit() deactivate_controller = threading.Thread( target=self.control.controller_deactivate, args=(self.unique_id, )) deactivate_controller.start()
def check_allowed_to_email(): smtp_table = db_retrieve_table_daemon(SMTP, entry='first') smtp_max_count = smtp_table.hourly_max smtp_wait_timer = smtp_table.smtp_wait_timer email_count = smtp_table.email_count if (email_count >= smtp_max_count and time.time() < smtp_wait_timer): allowed_to_send_notice = False else: if time.time() > smtp_wait_timer: with session_scope(MYCODO_DB_PATH) as new_session: mod_smtp = new_session.query(SMTP).first() mod_smtp.email_count = 0 mod_smtp.smtp_wait_timer = time.time() + 3600 new_session.commit() allowed_to_send_notice = True with session_scope(MYCODO_DB_PATH) as new_session: mod_smtp = new_session.query(SMTP).first() mod_smtp.email_count += 1 new_session.commit() return smtp_wait_timer, allowed_to_send_notice
def stop_method(self): self.method_start_time = None self.method_end_time = None with session_scope(MYCODO_DB_PATH) as db_session: this_controller = db_session.query(PID) this_controller = this_controller.filter(PID.unique_id == self.unique_id).first() this_controller.is_activated = False this_controller.method_start_time = None this_controller.method_end_time = None db_session.commit() self.stop_controller() self.is_activated = False self.logger.warning( "Method has ended. " "Activate the Trigger controller to start it again.")
def _delete_custom_option(self, controller, unique_id, option): try: with session_scope(MYCODO_DB_PATH) as new_session: mod_function = new_session.query(controller).filter( controller.unique_id == unique_id).first() try: dict_custom_options = json.loads( mod_function.custom_options) except: dict_custom_options = {} dict_custom_options.pop(option) mod_function.custom_options = json.dumps(dict_custom_options) new_session.commit() except Exception: self.logger.exception("delete_custom_option")
def db_retrieve_table_daemon(table, entry=None, device_id=None, unique_id=None): """ Return SQL database query object with optional filtering Used in daemon (For Flask, see db_retrieve_table() above) If entry='first', only the first table entry is returned. If entry='all', all table entries are returned. If device_id is set, the first entry with that device ID is returned. Otherwise, the table object is returned. """ tries = 5 while tries > 0: try: with session_scope(MYCODO_DB_PATH) as new_session: if device_id: return_table = new_session.query(table).filter( table.id == int(device_id)) elif unique_id: return_table = new_session.query(table).filter( table.unique_id == unique_id) else: return_table = new_session.query(table) if entry == 'first' or device_id or unique_id: return_table = return_table.first() elif entry == 'all': return_table = return_table.all() new_session.expunge_all() new_session.close() return return_table except OperationalError: pass except sqlalchemy.exc.OperationalError: pass if tries == 1: logger.exception( "Could not read the Mycodo database. " "Please submit a New Issue at " "https://github.com/kizniche/Mycodo/issues/new") else: logger.error( "The Mycodo database is locked. " "Trying to access again in 1 second...") time.sleep(1) tries -= 1
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.unique_id).first() mod_pid.setpoint_tracking_id = method_id if method_id == '': self.setpoint_tracking_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_method(self, method_id): """ Set the method of PID """ self.method_id = int(method_id) with session_scope(MYCODO_DB_PATH) as db_session: mod_pid = db_session.query(PID).filter( PID.id == self.pid_id).first() mod_pid.method_id = method_id mod_pid.method_start_time = 'Ready' mod_pid.method_end_time = None db_session.commit() if self.method_id: self.setup_method(method_id) return "Method set to {me} and started".format(me=method_id)
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 save_conditional_code(): with session_scope(MYCODO_DB_PATH) as conditional_sess: for each_conditional in conditional_sess.query(Conditional).all(): try: indented_code = textwrap.indent( each_conditional.conditional_statement, ' ' * 8) cond_statement_run = pre_statement_run + indented_code cond_statement_run = cond_statement_replace(cond_statement_run) assure_path_exists(PATH_PYTHON_CODE_USER) file_run = '{}/conditional_{}.py'.format( PATH_PYTHON_CODE_USER, each_conditional.unique_id) with open(file_run, 'w') as fw: fw.write('{}\n'.format(cond_statement_run)) fw.close() except Exception as msg: print("Exception: {}".format(msg))
def add_user(admin=False): new_user = Users() print('\nAdd user to database') while True: user_name = raw_input('User (a-z, A-Z, 2-64 chars): ') if test_username(user_name): new_user.user_name = user_name break while True: user_password = getpass.getpass('Password: '******'Password (again): ') if user_password != user_password_again: print("Passwords don't match") else: if test_password(user_password): new_user.set_password(user_password) break while True: user_email = raw_input('Email: ') if is_email(user_email): new_user.user_email = user_email break if admin: new_user.user_restriction = 'admin' else: new_user.user_restriction = 'guest' new_user.user_theme = 'dark' try: with session_scope(USER_DB_PATH) as db_session: db_session.add(new_user) sys.exit(0) except sqlalchemy.exc.OperationalError: print("Failed to create user. You most likely need to " "create the DB before trying to create users.") sys.exit(1) except sqlalchemy.exc.IntegrityError: print("Username already exists.") sys.exit(1)
def add_user(admin=False): new_user = Users() print('\nAdd user to database') while True: user_name = raw_input('User (a-z, A-Z, 2-64 chars): ') if test_username(user_name): new_user.user_name = user_name break while True: user_password = getpass.getpass('Password: '******'Password (again): ') if user_password != user_password_again: print("Passwords don't match") else: if test_password(user_password): new_user.set_password(user_password) break while True: user_email = raw_input('Email: ') if is_email(user_email): new_user.user_email = user_email break if admin: new_user.user_restriction = 'admin' else: new_user.user_restriction = 'guest' new_user.user_theme = 'slate' try: with session_scope(USER_DB_PATH) as db_session: db_session.add(new_user) sys.exit(0) except sqlalchemy.exc.OperationalError: print("Failed to create user. You most likely need to " "create the DB before trying to create users.") sys.exit(1) except sqlalchemy.exc.IntegrityError: print("Username already exists.") sys.exit(1)
def cond_statement_replace(cond_statement): cond_statement_replaced = cond_statement with session_scope(MYCODO_DB_PATH) as conditional_sess: for each_condition in conditional_sess.query( ConditionalConditions).all(): condition_id_short = each_condition.unique_id.split('-')[0] cond_statement_replaced = cond_statement_replaced.replace( '{{{id}}}'.format(id=condition_id_short), each_condition.unique_id) for each_action in conditional_sess.query(Actions).all(): action_id_short = each_action.unique_id.split('-')[0] cond_statement_replaced = cond_statement_replaced.replace( '{{{id}}}'.format(id=action_id_short), each_action.unique_id) conditional_sess.expunge_all() conditional_sess.close() return cond_statement_replaced
def action_pause_pid(cond_action, message): control = DaemonControl() pid = db_retrieve_table_daemon(PID, unique_id=cond_action.do_unique_id, entry='first') message += " Pause PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) if pid.is_paused: message += " Notice: PID is already paused!" elif pid.is_activated: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.is_paused = True new_session.commit() pause_pid = threading.Thread(target=control.pid_pause, args=(cond_action.do_unique_id, )) pause_pid.start() return message
def change_password(username): print('Changing password for {}'.format(username)) with session_scope(USER_DB_PATH) as db_session: user = db_session.query(Users).filter(Users.user_name == username).one() while True: user_password = getpass.getpass('Password: '******'Password (again): ') if user_password != user_password_again: print("Passwords don't match") else: try: if test_password(user_password): user.set_password(user_password) sys.exit(0) except sqlalchemy.orm.exc.NoResultFound: print("No user found with this name.") sys.exit(1)
def execute_at_modification(messages, mod_input, request_form, custom_options_dict_presave, custom_options_channels_dict_presave, custom_options_dict_postsave, custom_options_channels_dict_postsave): try: if (custom_options_dict_postsave['adc_channel_ph'] == custom_options_dict_postsave['adc_channel_ec']): messages["error"].append( "Cannot set pH and EC to be measured from the same channel.") else: with session_scope(MYCODO_DB_PATH) as new_session: measurements = new_session.query(DeviceMeasurements).filter( DeviceMeasurements.device_id == mod_input.unique_id).all() for each_measure in measurements: if each_measure.channel == int( custom_options_dict_postsave['adc_channel_ph']): if each_measure.measurement != 'ion_concentration': messages["page_refresh"] = True each_measure.conversion_id = '' each_measure.measurement = 'ion_concentration' each_measure.unit = 'pH' elif each_measure.channel == int( custom_options_dict_postsave['adc_channel_ec']): if each_measure.measurement != 'electrical_conductivity': messages["page_refresh"] = True each_measure.conversion_id = '' each_measure.measurement = 'electrical_conductivity' each_measure.unit = 'uS_cm' else: if each_measure.measurement != 'electrical_potential': messages["page_refresh"] = True each_measure.conversion_id = '' each_measure.measurement = 'electrical_potential' each_measure.unit = 'V' new_session.commit() except Exception: messages["error"].append("execute_at_modification() Error: {}".format( traceback.print_exc())) return (messages, mod_input, custom_options_dict_postsave, custom_options_channels_dict_postsave)
def run_action(self, message, dict_vars): try: controller_id = dict_vars["value"]["pid_id"] except: controller_id = self.controller_id try: amount = dict_vars["value"]["amount"] except: amount = self.amount pid = db_retrieve_table_daemon(PID, unique_id=controller_id, entry='first') if not pid: msg = f" Error: PID Controller with ID '{controller_id}' not found." message += msg self.logger.error(msg) return message new_setpoint = pid.setpoint - amount message += f" Lower Setpoint of PID {controller_id} ({pid.name}) by {amount}, to {new_setpoint}." if pid.is_activated: setpoint_pid = threading.Thread(target=self.control.pid_set, args=( pid.unique_id, 'setpoint', new_setpoint, )) setpoint_pid.start() else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == controller_id).first() mod_pid.setpoint = new_setpoint new_session.commit() self.logger.debug(f"Message: {message}") return message
def change_password(username): print('Changing password for {}'.format(username)) with session_scope(USER_DB_PATH) as db_session: user = db_session.query(Users).filter( Users.user_name == username).one() while True: user_password = getpass.getpass('Password: '******'Password (again): ') if user_password != user_password_again: print("Passwords don't match") else: try: if test_password(user_password): user.set_password(user_password) sys.exit(0) except sqlalchemy.orm.exc.NoResultFound: print("No user found with this name.") sys.exit(1)
def pid_set(self, pid_unique_id, setting, value): try: with session_scope(MYCODO_DB_PATH) as new_session: pid = new_session.query(PID).filter( PID.unique_id == pid_unique_id).first() pid_id = pid.id if setting == 'setpoint': return self.controller['PID'][pid_id].set_setpoint(value) elif setting == 'integrator': return self.controller['PID'][pid_id].set_integrator(value) elif setting == 'derivator': return self.controller['PID'][pid_id].set_derivator(value) elif setting == 'kp': return self.controller['PID'][pid_id].set_kp(value) elif setting == 'ki': return self.controller['PID'][pid_id].set_ki(value) elif setting == 'kd': return self.controller['PID'][pid_id].set_kd(value) except Exception as except_msg: message = "Could not set PID {option}:" \ " {err}".format(option=setting, err=except_msg) self.logger.exception(message)
def action_setpoint_pid(cond_action, message): control = DaemonControl() pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') message += " Set Setpoint of PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) if pid.is_activated: setpoint_pid = threading.Thread( target=control.pid_set, args=(pid.unique_id, 'setpoint', float(cond_action.do_action_string),)) setpoint_pid.start() else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.setpoint = float(cond_action.do_action_string) new_session.commit() return message
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 action_create_note(cond_action, message, single_action, note_tags): tag_name = db_retrieve_table_daemon( NoteTags, unique_id=cond_action.do_action_string).name message += " Create note with tag '{}'.".format(tag_name) if single_action and cond_action.do_action_string: list_tags = [] check_tag = db_retrieve_table_daemon( NoteTags, unique_id=cond_action.do_action_string) if check_tag: list_tags.append(cond_action.do_action_string) if list_tags: with session_scope(MYCODO_DB_PATH) as db_session: new_note = Notes() new_note.name = 'Action' new_note.tags = ','.join(list_tags) new_note.note = message db_session.add(new_note) else: note_tags.append(cond_action.do_action_string) return message, note_tags
def start_method(self, method_id): """ Instruct a method to start running """ if method_id: this_controller = db_retrieve_table_daemon( Trigger, unique_id=self.unique_id) method = load_method_handler(method_id, self.logger) if parse_db_time(this_controller.method_start_time) is None: self.method_start_time = datetime.datetime.now() self.method_end_time = method.determine_end_time(self.method_start_time) self.logger.info("Starting method {} {}".format(self.method_start_time, self.method_end_time)) with session_scope(MYCODO_DB_PATH) as db_session: this_controller = db_session.query(Trigger) this_controller = this_controller.filter(Trigger.unique_id == self.unique_id).first() this_controller.method_start_time = self.method_start_time this_controller.method_end_time = self.method_end_time db_session.commit() else: # already running, potentially the daemon has been restarted self.method_start_time = this_controller.method_start_time self.method_end_time = this_controller.method_end_time
else: user_valid = True email = input("Email Address: ") while not passwords_match and not password_valid: password = getpass("Password: "******"Repeat Password: "******"Password don't math. Try again.") else: passwords_match = True try: with session_scope(MYCODO_DB_PATH) as db_session: new_user = User() new_user.unique_id = set_uuid() new_user.name = user_name.lower() new_user.password_hash = set_password(password) new_user.email = email new_user.role_id = 1 new_user.theme = 'slate' new_user.landing_page = 'live' new_user.language = 'en' db_session.add(new_user) print("Admin user '{}' successfully created.".format(user_name.lower())) except Exception: print( "Error creating admin user. Refer the the traceback, below, for the error."
def run(self): try: self.running = True # This log line will appear in the Daemon log under Config -> Mycodo Logs self.logger.info("Function running") # Make sure the option "Log Level: Debug" is enabled for these debug # log lines to appear in the Daemon log. self.logger.debug( "Custom controller started with options: " "{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}".format( self.text_1, self.integer_1, self.float_1, self.bool_1, self.select_1, self.select_measurement_1_device_id, self.select_measurement_1_measurement_id, self.output_1_device_id, self.output_1_measurement_id, self.output_1_channel_id, self.select_device_1_id)) # You can specify different log levels to indicate things such as errors self.logger.error( "This is an error line that will appear in the log") # And Warnings self.logger.warning( "This is a warning line that will appear in the log") # Get last measurement for select_measurement_1 last_measurement = self.get_last_measurement( self.select_measurement_1_device_id, self.select_measurement_1_measurement_id) if last_measurement: self.logger.debug( "Most recent timestamp and measurement for " "select_measurement_1: {timestamp}, {meas}".format( timestamp=last_measurement[0], meas=last_measurement[1])) else: self.logger.debug( "Could not find a measurement in the database for " "select_measurement_1 device ID {} and measurement " "ID {}".format(self.select_measurement_1_device_id, self.select_measurement_1_measurement_id)) # Turn Output select_device_1 on for 15 seconds self.logger.debug( "Turning select_device_1 with ID {} on for 15 seconds...". format(self.select_device_1_id)) self.control.output_on(self.select_device_1_id, output_type='sec', output_channel=self.output_1_channel, amount=15) # Deactivate controller in the SQL database self.logger.debug( "Deactivating (SQL) Custom controller select_device_2 with ID {}" .format(self.select_device_2_id)) from mycodo.databases.utils import session_scope from mycodo.config import SQL_DATABASE_MYCODO MYCODO_DB_PATH = 'sqlite:///' + SQL_DATABASE_MYCODO with session_scope(MYCODO_DB_PATH) as new_session: mod_cont = new_session.query(CustomController).filter( CustomController.unique_id == self.select_device_2_id).first() mod_cont.is_activated = False new_session.commit() # Deactivate select_device_1_id in the dameon # Since we're deactivating this controller (itself), we need to thread this command # Note: this command will only deactivate the controller in the Daemon. It will still # be activated in the database, so the next restart of the daemon, this controller # will start back up again. This is why the previous action deactivated the controller # in the database prior to deactivating it in the daemon. self.logger.debug( "Deactivating (Daemon) Custom controller select_device_2 with" " ID {} ...".format(self.select_device_2_id)) deactivate_controller = threading.Thread( target=self.control.controller_deactivate, args=(self.select_device_2_id, )) deactivate_controller.start() # Start a loop while self.running: time.sleep(1) except: self.logger.exception("Run Error") finally: self.running = False self.logger.error("Deactivated unexpectedly")
def trigger_function_actions(function_id, message='', debug=False): """ Execute the Actions belonging to a particular Function :param function_id: :param message: The message generated from the conditional check :param debug: determine if logging level should be DEBUG :return: """ logger_actions = logging.getLogger( "mycodo.trigger_function_actions_{id}".format( id=function_id.split('-')[0])) if debug: logger_actions.setLevel(logging.DEBUG) else: logger_actions.setLevel(logging.INFO) # List of all email notification recipients # List is appended with TO email addresses when an email Action is # encountered. An email is sent to all recipients after all actions # have been executed. email_recipients = [] # List of tags to add to a note note_tags = [] attachment_file = None attachment_type = None actions = db_retrieve_table_daemon(Actions) actions = actions.filter( Actions.function_id == function_id).all() for cond_action in actions: (message, note_tags, email_recipients, attachment_file, attachment_type) = trigger_action( cond_action.unique_id, message=message, single_action=False, note_tags=note_tags, email_recipients=email_recipients, attachment_file=attachment_file, attachment_type=attachment_type, debug=debug) # Send email after all conditional actions have been checked # In order to append all action messages to send in the email # send_email_at_end will be None or the TO email address if email_recipients: # If the emails per hour limit has not been exceeded smtp_wait_timer, allowed_to_send_notice = check_allowed_to_email() if allowed_to_send_notice: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, email_recipients, message, attachment_file, attachment_type) else: logger_actions.error( "Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) # Create a note with the tags from the unique_ids in the list note_tags if note_tags: list_tags = [] for each_note_tag_id in note_tags: check_tag = db_retrieve_table_daemon( NoteTags, unique_id=each_note_tag_id) if check_tag: list_tags.append(each_note_tag_id) if list_tags: with session_scope(MYCODO_DB_PATH) as db_session: new_note = Notes() new_note.name = 'Action' new_note.tags = ','.join(list_tags) new_note.note = message db_session.add(new_note) logger_actions.debug("Message: {}".format(message))
def controller_activate(self, cont_type, cont_id): """ Activate currently-inactive controller :return: 0 for success, 1 for fail, with success or error message :rtype: int, str :param cont_type: Which controller type is to be activated? :type cont_type: str :param cont_id: Unique ID for controller :type cont_id: str """ try: if cont_id in self.controller[cont_type]: if self.controller[cont_type][cont_id].is_running(): message = "Cannot activate {type} controller with ID {id}: " \ "It's already active.".format(type=cont_type, id=cont_id) self.logger.warning(message) return 1, message controller_manage = {} ready = threading.Event() if cont_type == 'Conditional': controller_manage['type'] = Conditional controller_manage['function'] = ConditionalController elif cont_type == 'LCD': controller_manage['type'] = LCD controller_manage['function'] = LCDController elif cont_type == 'Input': controller_manage['type'] = Input controller_manage['function'] = InputController elif cont_type == 'Math': controller_manage['type'] = Math controller_manage['function'] = MathController elif cont_type == 'PID': controller_manage['type'] = PID controller_manage['function'] = PIDController elif cont_type == 'Trigger': controller_manage['type'] = Trigger controller_manage['function'] = TriggerController else: return 1, "'{type}' not a valid controller type.".format( type=cont_type) # Check if the controller actually exists controller = db_retrieve_table_daemon(controller_manage['type'], unique_id=cont_id) if not controller: return 1, "{type} controller with ID {id} not found.".format( type=cont_type, id=cont_id) # set as active in SQL database with session_scope(MYCODO_DB_PATH) as new_session: mod_cont = new_session.query(controller_manage['type']).filter( controller_manage['type'].unique_id == cont_id).first() mod_cont.is_activated = True new_session.commit() self.controller[cont_type][cont_id] = controller_manage['function']( ready, cont_id) self.controller[cont_type][cont_id].daemon = True self.controller[cont_type][cont_id].start() ready.wait() # wait for thread to return ready return 0, "{type} controller with ID {id} " \ "activated.".format(type=cont_type, id=cont_id) except Exception as except_msg: message = "Could not activate {type} controller with ID {id}:" \ " {err}".format(type=cont_type, id=cont_id, err=except_msg) self.logger.exception(message) return 1, message
def get_new_data(self, past_seconds): # Basic implementation. Future development may use more complex library to access API endpoint = "https://{app}.data.thethingsnetwork.org/api/v2/query/{dev}?last={time}".format( app=self.application_id, dev=self.device_id, time="{}s".format(int(past_seconds))) headers = {"Authorization": "key {k}".format(k=self.app_api_key)} timestamp_format = '%Y-%m-%dT%H:%M:%S.%f' response = requests.get(endpoint, headers=headers) try: response.json() except ValueError: # No data returned self.logger.debug( "Response Error. Response: {}. Likely there is no data to be retrieved on TTN" .format(response.content)) return for each_resp in response.json(): if not self.running: break try: datetime_utc = datetime.datetime.strptime( each_resp['time'][:-7], timestamp_format) except Exception: # Sometimes the original timestamp is in milliseconds # instead of nanoseconds. Therefore, remove 3 less digits # past the decimal and try again to parse. try: datetime_utc = datetime.datetime.strptime( each_resp['time'][:-4], timestamp_format) except Exception as e: self.logger.error( "Could not parse timestamp '{}': {}".format( each_resp['time'], e)) continue # Malformed timestamp encountered. Discard measurement. if (not self.latest_datetime or self.latest_datetime < datetime_utc): self.latest_datetime = datetime_utc measurements = {} for channel in self.channels_measurement: if (self.is_enabled(channel) and self.options_channels['variable_name'][channel] in each_resp and each_resp[self.options_channels['variable_name'] [channel]] is not None): # Original value/unit measurements[channel] = {} measurements[channel][ 'measurement'] = self.channels_measurement[ channel].measurement measurements[channel]['unit'] = self.channels_measurement[ channel].unit measurements[channel]['value'] = each_resp[ self.options_channels['variable_name'][channel]] measurements[channel]['timestamp_utc'] = datetime_utc # Convert value/unit is conversion_id present and valid if self.channels_conversion[channel]: conversion = db_retrieve_table_daemon( Conversion, unique_id=self.channels_measurement[channel]. conversion_id) if conversion: meas = parse_measurement( self.channels_conversion[channel], self.channels_measurement[channel], measurements, channel, measurements[channel], timestamp=datetime_utc) measurements[channel]['measurement'] = meas[ channel]['measurement'] measurements[channel]['unit'] = meas[channel][ 'unit'] measurements[channel]['value'] = meas[channel][ 'value'] if measurements: self.logger.debug( "Adding measurements to influxdb: {}".format(measurements)) add_measurements_influxdb( self.unique_id, measurements, use_same_timestamp=INPUT_INFORMATION[ 'measurements_use_same_timestamp']) else: self.logger.debug("No measurements to add to influxdb.") # set datetime to latest timestamp if self.running: with session_scope(MYCODO_DB_PATH) as new_session: mod_input = new_session.query(Input).filter( Input.unique_id == self.unique_id).first() if not mod_input.datetime or mod_input.datetime < self.latest_datetime: mod_input.datetime = self.latest_datetime new_session.commit()
def do_admin_login(): """Authenticate users of the web-UI""" # Check if the user is banned from logging in if flaskutils.banned_from_login(): return redirect('/') form = flaskforms.Login() form_notice = flaskforms.InstallNotice() with session_scope(current_app.config['MYCODO_DB_PATH']) as db_session: misc = db_session.query(Misc).first() dismiss_notification = misc.dismiss_notification stats_opt_out = misc.stats_opt_out if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'acknowledge': try: with session_scope( current_app.config['MYCODO_DB_PATH']) as db_session: mod_misc = db_session.query(Misc).first() mod_misc.dismiss_notification = 1 db_session.commit() except Exception as except_msg: flash("Acknowledgement not saved: {}".format(except_msg), "error") elif form_name == 'login' and form.validate_on_submit(): with session_scope( current_app.config['USER_DB_PATH']) as new_session: user = new_session.query(Users).filter( Users.user_name == form.username.data).first() new_session.expunge_all() new_session.close() if not user: flaskutils.login_log( form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'NOUSER') flaskutils.failed_login() elif Users().check_password( form.password.data, user.user_password_hash) == user.user_password_hash: flaskutils.login_log( user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'LOGIN') session['logged_in'] = True session['user_group'] = user.user_restriction session['user_name'] = user.user_name session['user_theme'] = user.user_theme if form.remember.data: response = make_response(redirect('/')) expire_date = datetime.datetime.now() expire_date = expire_date + datetime.timedelta(days=90) response.set_cookie('user_name', user.user_name, expires=expire_date) response.set_cookie('user_pass_hash', user.user_password_hash, expires=expire_date) return response return redirect('/') else: flaskutils.login_log( user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') flaskutils.failed_login() else: flaskutils.login_log( form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') flaskutils.failed_login() return redirect('/login') return render_template('login.html', form=form, formNotice=form_notice, dismiss_notification=dismiss_notification, stats_opt_out=stats_opt_out)
def calculate_method_setpoint(method_id, table, controller, Method, MethodData, logger): """ Calculates the setpoint from a method :param method_id: ID of Method to be used :param table: Table of the controller using this function :param controller: The controller using this function :param logger: The logger to use :return: 0 (success) or 1 (error) and a setpoint value """ method = db_retrieve_table_daemon(Method) method_key = method.filter(Method.id == method_id).first() method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter(MethodData.method_id == method_id) method_data_all = method_data.filter(MethodData.relay_id == None).all() method_data_first = method_data.filter(MethodData.relay_id == None).first() now = datetime.datetime.now() # Calculate where the current time/date is within the time/date method if method_key.method_type == 'Date': for each_method in method_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%Y-%m-%d %H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%Y-%m-%d %H:%M:%S') if start_time < now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) total_seconds = (end_time - start_time).total_seconds() part_seconds = (now - start_time).total_seconds() percent_total = part_seconds / total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_total) else: new_setpoint = setpoint_start - (setpoint_diff * percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time, end=end_time)) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug( "[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False # Calculate where the current Hour:Minute:Seconds is within the Daily method elif method_key.method_type == 'Daily': daily_now = datetime.datetime.now().strftime('%H:%M:%S') daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S') for each_method in method_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%H:%M:%S') if start_time < daily_now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) total_seconds = (end_time - start_time).total_seconds() part_seconds = (daily_now - start_time).total_seconds() percent_total = part_seconds / total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_total) else: new_setpoint = setpoint_start - (setpoint_diff * percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time.strftime('%H:%M:%S'), end=end_time.strftime('%H:%M:%S'))) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug( "[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False # Calculate sine y-axis value from the x-axis (seconds of the day) elif method_key.method_type == 'DailySine': new_setpoint = sine_wave_y_out(method_data_first.amplitude, method_data_first.frequency, method_data_first.shift_angle, method_data_first.shift_y) return new_setpoint, False # Calculate Bezier curve y-axis value from the x-axis (seconds of the day) elif method_key.method_type == 'DailyBezier': new_setpoint = bezier_curve_y_out( method_data_first.shift_angle, (method_data_first.x0, method_data_first.y0), (method_data_first.x1, method_data_first.y1), (method_data_first.x2, method_data_first.y2), (method_data_first.x3, method_data_first.y3)) return new_setpoint, False # Calculate the duration in the method based on self.method_start_time elif method_key.method_type == 'Duration': start_time = datetime.datetime.strptime( str(controller.method_start_time), '%Y-%m-%d %H:%M:%S.%f') ended = False # Check if method_end_time is not None if controller.method_end_time: # Convert time string to datetime object end_time = datetime.datetime.strptime( str(controller.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if now > start_time: ended = True seconds_from_start = (now - start_time).total_seconds() total_sec = 0 previous_total_sec = 0 previous_end = None method_restart = False for each_method in method_data_all: # If duration_sec is 0, method has instruction to restart if each_method.duration_sec == 0: method_restart = True else: previous_end = each_method.setpoint_end total_sec += each_method.duration_sec if previous_total_sec <= seconds_from_start < total_sec: row_start_time = float( start_time.strftime('%s')) + previous_total_sec row_since_start_sec = (now - (start_time + datetime.timedelta( 0, previous_total_sec))).total_seconds() percent_row = row_since_start_sec / each_method.duration_sec setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_row) else: new_setpoint = setpoint_start - (setpoint_diff * percent_row) logger.debug( "[Method] Start: {start} Seconds Since: {sec}".format( start=start_time, sec=seconds_from_start)) logger.debug("[Method] Start time of row: {start}".format( start=datetime.datetime.fromtimestamp(row_start_time))) logger.debug("[Method] Sec since start of row: {sec}".format( sec=row_since_start_sec)) logger.debug( "[Method] Percent of row: {per}".format(per=percent_row)) logger.debug( "[Method] New Setpoint: {sp}".format(sp=new_setpoint)) return new_setpoint, False previous_total_sec = total_sec if controller.method_start_time: if method_restart: if end_time and now > end_time: ended = True else: # Method has been instructed to restart controller.method_start_time = datetime.datetime.now() with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table) mod_method = mod_method.filter( table.method_id == method_id).first() mod_method.method_start_time = controller.method_start_time db_session.commit() return previous_end, False else: ended = True if ended: # Duration method has ended, reset method_start_time locally and in DB with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table).filter( table.method_id == method_id).first() mod_method.method_start_time = 'Ended' mod_method.method_end_time = None db_session.commit() return None, True # Setpoint not needing to be calculated, use default setpoint return None, False
def trigger_action( cond_action_id, message='', note_tags=None, email_recipients=None, attachment_file=None, attachment_type=None, single_action=False): """ Trigger individual action If single_action == False, message, note_tags, email_recipients, attachment_file, and attachment_type are returned and may be passed back to this function in order to append to those lists. :param cond_action_id: unique_id of action :param message: message string to append to that will be sent back :param note_tags: list of note tags to use if creating a note :param email_recipients: list of email addresses to notify if sending an email :param attachment_file: string location of a file to attach to an email :param attachment_type: string type of email attachment :param single_action: True if only one action is being triggered, False if only one of multiple actions :return: message or (message, note_tags, email_recipients, attachment_file, attachment_type) """ cond_action = db_retrieve_table_daemon(Actions, unique_id=cond_action_id) if not cond_action: message += 'Error: Action with ID {} not found!'.format(cond_action_id) if single_action: return message else: return message, note_tags, email_recipients, attachment_file, attachment_type logger_actions = logging.getLogger("mycodo.trigger_action_{id}".format( id=cond_action.unique_id.split('-')[0])) message += "\n[Action {id}]:".format( id=cond_action.unique_id.split('-')[0], action_type=cond_action.action_type) try: control = DaemonControl() smtp_table = db_retrieve_table_daemon( SMTP, entry='first') smtp_max_count = smtp_table.hourly_max smtp_wait_timer = smtp_table.smtp_wait_timer email_count = smtp_table.email_count # Pause if cond_action.action_type == 'pause_actions': message += " [{id}] Pause actions for {sec} seconds.".format( id=cond_action.id, sec=cond_action.pause_duration) time.sleep(cond_action.pause_duration) # Actuate output (duration) if (cond_action.action_type == 'output' and cond_action.do_unique_id and cond_action.do_output_state in ['on', 'off']): this_output = db_retrieve_table_daemon( Output, unique_id=cond_action.do_unique_id, entry='first') message += " Turn output {unique_id} ({id}, {name}) {state}".format( unique_id=cond_action.do_unique_id, id=this_output.id, name=this_output.name, state=cond_action.do_output_state) if (cond_action.do_output_state == 'on' and cond_action.do_output_duration): message += " for {sec} seconds".format( sec=cond_action.do_output_duration) message += "." output_on_off = threading.Thread( target=control.output_on_off, args=(cond_action.do_unique_id, cond_action.do_output_state,), kwargs={'duration': cond_action.do_output_duration}) output_on_off.start() # Actuate output (PWM) if (cond_action.action_type == 'output_pwm' and cond_action.do_unique_id and 0 <= cond_action.do_output_pwm <= 100): this_output = db_retrieve_table_daemon( Output, unique_id=cond_action.do_unique_id, entry='first') message += " Turn output {unique_id} ({id}, {name}) duty cycle to {duty_cycle}%.".format( unique_id=cond_action.do_unique_id, id=this_output.id, name=this_output.name, duty_cycle=cond_action.do_output_pwm) output_on = threading.Thread( target=control.output_on, args=(cond_action.do_unique_id,), kwargs={'duty_cycle': cond_action.do_output_pwm}) output_on.start() # Execute command in shell if cond_action.action_type == 'command': # Replace string variables with actual values command_str = cond_action.do_action_string # TODO: Maybe get this working again with the new measurement system # # Replace measurement variables # if last_measurement: # command_str = command_str.replace( # "((measure_{var}))".format( # var=device_measurement), str(last_measurement)) # if device and device.period: # command_str = command_str.replace( # "((measure_period))", str(device.period)) # if input_dev: # command_str = command_str.replace( # "((measure_location))", str(input_dev.location)) # if input_dev and device_measurement == input_dev.measurements: # command_str = command_str.replace( # "((measure_linux_command))", str(last_measurement)) # # # Replace output variables # if output: # if output.pin: # command_str = command_str.replace( # "((output_pin))", str(output.pin)) # if output_state: # command_str = command_str.replace( # "((output_action))", str(output_state)) # if on_duration: # command_str = command_str.replace( # "((output_duration))", str(on_duration)) # if duty_cycle: # command_str = command_str.replace( # "((output_pwm))", str(duty_cycle)) # # # Replace edge variables # if edge: # command_str = command_str.replace( # "((edge_state))", str(edge)) message += " Execute '{com}' ".format( com=command_str) _, _, cmd_status = cmd_output(command_str) message += "(return status: {stat}).".format(stat=cmd_status) # Create Note if cond_action.action_type == 'create_note': tag_name = db_retrieve_table_daemon( NoteTags, unique_id=cond_action.do_action_string).name message += " Create note with tag '{}'.".format(tag_name) if single_action and cond_action.do_action_string: list_tags = [] check_tag = db_retrieve_table_daemon( NoteTags, unique_id=cond_action.do_action_string) if check_tag: list_tags.append(cond_action.do_action_string) if list_tags: with session_scope(MYCODO_DB_PATH) as db_session: new_note = Notes() new_note.name = 'Action' new_note.tags = ','.join(list_tags) new_note.note = message db_session.add(new_note) else: note_tags.append(cond_action.do_action_string) # Capture photo # If emailing later, store location of photo as attachment_file if cond_action.action_type in ['photo', 'photo_email']: this_camera = db_retrieve_table_daemon( Camera, unique_id=cond_action.do_unique_id, entry='first') message += " Capturing photo with camera {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=this_camera.id, name=this_camera.name) camera_still = db_retrieve_table_daemon( Camera, unique_id=cond_action.do_unique_id) attachment_path_file = camera_record('photo', camera_still.unique_id) attachment_file = os.path.join(attachment_path_file[0], attachment_path_file[1]) # Capture video if cond_action.action_type in ['video', 'video_email']: this_camera = db_retrieve_table_daemon( Camera, unique_id=cond_action.do_unique_id, entry='first') message += " Capturing video with camera {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=this_camera.id, name=this_camera.name) camera_stream = db_retrieve_table_daemon( Camera, unique_id=cond_action.do_unique_id) attachment_path_file = camera_record( 'video', camera_stream.unique_id, duration_sec=cond_action.do_camera_duration) attachment_file = os.path.join(attachment_path_file[0], attachment_path_file[1]) # Activate Controller if cond_action.action_type == 'activate_controller': (controller_type, controller_object, controller_entry) = which_controller( cond_action.do_unique_id) message += " Activate Controller {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=controller_entry.id, name=controller_entry.name) if controller_entry.is_activated: message += " Notice: Controller is already active!" 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 = True new_session.commit() activate_controller = threading.Thread( target=control.controller_activate, args=(controller_type, cond_action.do_unique_id,)) activate_controller.start() # Deactivate Controller if cond_action.action_type == 'deactivate_controller': (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=(controller_type, cond_action.do_unique_id,)) deactivate_controller.start() # Resume PID controller if cond_action.action_type == 'resume_pid': pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') message += " Resume PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) if not pid.is_paused: message += " Notice: PID is not paused!" elif pid.is_activated: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.is_paused = False new_session.commit() resume_pid = threading.Thread( target=control.pid_resume, args=(cond_action.do_unique_id,)) resume_pid.start() # Pause PID controller if cond_action.action_type == 'pause_pid': pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') message += " Pause PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) if pid.is_paused: message += " Notice: PID is already paused!" elif pid.is_activated: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.is_paused = True new_session.commit() pause_pid = threading.Thread( target=control.pid_pause, args=(cond_action.do_unique_id,)) pause_pid.start() # Set PID Setpoint if cond_action.action_type == 'setpoint_pid': pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') message += " Set Setpoint of PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) if pid.is_activated: setpoint_pid = threading.Thread( target=control.pid_set, args=(pid.unique_id, 'setpoint', float(cond_action.do_action_string),)) setpoint_pid.start() else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.setpoint = cond_action.do_action_string new_session.commit() # Set PID Method and start method from beginning if cond_action.action_type == 'method_pid': pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') message += " Set Method of PID {unique_id} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, id=pid.id, name=pid.name) # Instruct method to start with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.method_start_time = 'Ready' new_session.commit() pid = db_retrieve_table_daemon( PID, unique_id=cond_action.do_unique_id, entry='first') if pid.is_activated: method_pid = threading.Thread( target=control.pid_set, args=(pid.unique_id, 'method', cond_action.do_action_string,)) method_pid.start() else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.unique_id == cond_action.do_unique_id).first() mod_pid.method_id = cond_action.do_action_string new_session.commit() # Email the Conditional message. Optionally capture a photo or # video and attach to the email. if cond_action.action_type in [ 'email', 'photo_email', 'video_email']: if (email_count >= smtp_max_count and time.time() < smtp_wait_timer): allowed_to_send_notice = False else: if time.time() > smtp_wait_timer: with session_scope(MYCODO_DB_PATH) as new_session: mod_smtp = new_session.query(SMTP).first() mod_smtp.email_count = 0 mod_smtp.smtp_wait_timer = time.time() + 3600 new_session.commit() allowed_to_send_notice = True with session_scope(MYCODO_DB_PATH) as new_session: mod_smtp = new_session.query(SMTP).first() mod_smtp.email_count += 1 new_session.commit() # If the emails per hour limit has not been exceeded if allowed_to_send_notice: message += " Notify {email}.".format( email=cond_action.do_action_string) # attachment_type != False indicates to # attach a photo or video if cond_action.action_type == 'photo_email': message += " Photo attached to email." attachment_type = 'still' elif cond_action.action_type == 'video_email': message += " Video attached to email." attachment_type = 'video' if single_action and cond_action.do_action_string: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, [cond_action.do_action_string], message, attachment_file, attachment_type) else: email_recipients.append(cond_action.do_action_string) else: logger_actions.error( "Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) if cond_action.action_type == 'flash_lcd_on': lcd = db_retrieve_table_daemon( LCD, unique_id=cond_action.do_unique_id) message += " LCD {unique_id} ({id}, {name}) Flash On.".format( unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=control.lcd_flash, args=(cond_action.do_unique_id, True,)) start_flashing.start() if cond_action.action_type == 'flash_lcd_off': lcd = db_retrieve_table_daemon( LCD, unique_id=cond_action.do_unique_id) message += " LCD {unique_id} ({id}, {name}) Flash Off.".format( unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=control.lcd_flash, args=(cond_action.do_unique_id, False,)) start_flashing.start() if cond_action.action_type == 'lcd_backlight_off': lcd = db_retrieve_table_daemon( LCD, unique_id=cond_action.do_unique_id) message += " LCD {unique_id} ({id}, {name}) Backlight Off.".format( unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=control.lcd_backlight, args=(cond_action.do_unique_id, False,)) start_flashing.start() if cond_action.action_type == 'lcd_backlight_on': lcd = db_retrieve_table_daemon( LCD, unique_id=cond_action.do_unique_id) message += " LCD {unique_id} ({id}, {name}) Backlight On.".format( unique_id=cond_action.do_unique_id, id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=control.lcd_backlight, args=(cond_action.do_unique_id, True,)) start_flashing.start() except Exception: logger_actions.exception("Error triggering action:") message += " Error while executing action! See Daemon log for details." if single_action: return message else: return message, note_tags, email_recipients, attachment_file, attachment_type
else: user_valid = True email = input("Email Address: ") while not passwords_match and not password_valid: password = getpass("Password: "******"Repeat Password: "******"Password don't math. Try again.") else: passwords_match = True try: with session_scope(MYCODO_DB_PATH) as db_session: new_user = User() new_user.unique_id = set_uuid() new_user.name = user_name.lower() new_user.password_hash = set_password(password) new_user.email = email new_user.role_id = 1 new_user.theme = 'slate' new_user.landing_page = 'live' new_user.language = 'en' db_session.add(new_user) print("Admin user '{}' successfully created.".format(user_name.lower())) except Exception: print("Error creating admin user. Refer the the traceback, below, for the error.") traceback.print_exc()
def get_new_data(self, past_seconds): # Basic implementation. Future development may use more complex library to access API endpoint = "https://nam1.cloud.thethings.network/api/v3/as/applications/{app}/devices/{dev}/packages/storage/uplink_message?last={time}&field_mask=up.uplink_message.decoded_payload".format( app=self.application_id, dev=self.device_id, time="{}s".format(int(past_seconds))) headers = { "Authorization": "Bearer {k}".format(k=self.app_api_key), 'Content-Type': 'application/json' } timestamp_format = '%Y-%m-%dT%H:%M:%S.%f' response = requests.get(endpoint, headers=headers) if response.status_code != 200: self.logger.info("response.status_code != 200: {}".format( response.reason)) self.logger.debug("response.content: {}".format(response.content)) list_dicts = response.content.decode().split("\n") self.logger.debug("list_dicts: {}".format(list_dicts)) cpm_value = None cpm_ts = None usv_h_value = None usv_h_ts = None for each_resp in list_dicts: if not each_resp: continue self.logger.debug("each_resp: {}".format(each_resp)) cpm_value = None usv_h_value = None try: resp_json = json.loads(each_resp) except: resp_json = {} self.logger.debug("resp_json: {}".format(resp_json)) self.return_dict = measurements_dict.copy() try: datetime_utc = datetime.datetime.strptime( resp_json['result']['received_at'][:-7], timestamp_format) except: # Sometimes the original timestamp is in milliseconds # instead of nanoseconds. Therefore, remove 3 less digits # past the decimal and try again to parse. try: datetime_utc = datetime.datetime.strptime( resp_json['result']['received_at'][:-4], timestamp_format) except: self.logger.error("Could not parse timestamp: {}".format( resp_json['result']['received_at'])) return if (not self.latest_datetime or self.latest_datetime < datetime_utc): self.latest_datetime = datetime_utc for channel in self.return_dict: if (self.is_enabled(channel) and self.return_dict[channel]['name'] in resp_json['result']['uplink_message'] ['decoded_payload'] and resp_json['result']['uplink_message'] ['decoded_payload'][self.return_dict[channel]['name']] is not None): self.return_dict[channel]['value'] = resp_json['result'][ 'uplink_message']['decoded_payload'][ self.return_dict[channel]['name']] self.return_dict[channel]['timestamp_utc'] = datetime_utc if self.return_dict[channel]['unit'] == 'cpm': cpm_value = float(self.return_dict[channel]['value']) cpm_ts = self.return_dict[channel]['timestamp_utc'] elif self.return_dict[channel]['unit'] == 'uSv_hr': usv_h_value = float(self.return_dict[channel]['value']) usv_h_ts = self.return_dict[channel]['timestamp_utc'] # Convert value/unit if conversion_id present and valid if self.channels_conversion[channel]: conversion = db_retrieve_table_daemon( Conversion, unique_id=self.channels_measurement[channel]. conversion_id) if conversion: meas = parse_measurement( self.channels_conversion[channel], self.channels_measurement[channel], self.return_dict, channel, self.return_dict[channel], timestamp=datetime_utc) self.return_dict[channel]['unit'] = meas[channel][ 'unit'] self.return_dict[channel]['value'] = meas[channel][ 'value'] if 'value' in self.return_dict[0] and 'value' in self.return_dict[ 1]: self.logger.debug("Adding measurements to influxdb: {}".format( self.return_dict)) add_measurements_influxdb( self.unique_id, self.return_dict, use_same_timestamp=INPUT_INFORMATION[ 'measurements_use_same_timestamp']) else: self.logger.debug("No measurements to add to influxdb.") # Send uSv/hr to Safecast if self.send_safecast and cpm_value and usv_h_value: try: safecast = self.safecastpy.SafecastPy( api_key=self.safecast_api_key) measurement_usv = safecast.add_measurement( json={ 'latitude': self.safecast_latitude, 'longitude': self.safecast_longitude, 'value': usv_h_value, 'unit': self.safecastpy.UNIT_USV, 'captured_at': usv_h_ts.isoformat() + '+00:00', 'device_id': self.safecast_device_id, 'location_name': self.safecast_location_name }) measurement_cpm = safecast.add_measurement( json={ 'latitude': self.safecast_latitude, 'longitude': self.safecast_longitude, 'value': cpm_value, 'unit': self.safecastpy.UNIT_CPM, 'captured_at': cpm_ts.isoformat() + '+00:00', 'device_id': self.safecast_device_id, 'location_name': self.safecast_location_name }) self.logger.debug('uSv/hr measurement id: {0}'.format( measurement_usv['id'])) self.logger.debug('CPM measurement id: {0}'.format( measurement_cpm['id'])) except Exception as e: self.logger.error( "Error adding data to Safecast: {}".format(e)) # Send to GMC Map (doesn't accept time, so can only send the latest measurement) if (self.send_gmcmap and cpm_value and usv_h_value): try: gmcmap = 'http://www.GMCmap.com/log2.asp?AID=02376&GID=22044260632&CPM={cpm:.0f}&uSV={usv:.3f}'.format( aid=self.gmcmap_account_id, gcid=self.gmcmap_geiger_counter_id, cpm=cpm_value, usv=usv_h_value) contents = urllib.request.urlopen(gmcmap).read() self.logger.debug("GMCMap: {}".format(contents)) except Exception as e: self.logger.error("Error adding data to GMC Map: {}".format(e)) # set datetime to latest timestamp if self.running: with session_scope(MYCODO_DB_PATH) as new_session: mod_input = new_session.query(Input).filter( Input.unique_id == self.unique_id).first() if not mod_input.datetime or mod_input.datetime < self.latest_datetime: mod_input.datetime = self.latest_datetime new_session.commit()
def trigger_function_actions(function_id, message=''): """ Execute the Actions belonging to a particular Function :param function_id: :param message: The message generated from the conditional check :return: """ logger_actions = logging.getLogger("mycodo.trigger_function_actions_{id}".format( id=function_id.split('-')[0])) # List of all email notification recipients # List is appended with TO email addresses when an email Action is # encountered. An email is sent to all recipients after all actions # have been executed. email_recipients = [] # List of tags to add to a note note_tags = [] attachment_file = None attachment_type = None actions = db_retrieve_table_daemon(Actions) actions = actions.filter( Actions.function_id == function_id).all() for cond_action in actions: (message, note_tags, email_recipients, attachment_file, attachment_type) = trigger_action(cond_action.unique_id, message=message, single_action=False, note_tags=note_tags, email_recipients=email_recipients, attachment_file=attachment_file, attachment_type=attachment_type) # Send email after all conditional actions have been checked # In order to append all action messages to send in the email # send_email_at_end will be None or the TO email address if email_recipients: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, email_recipients, message, attachment_file, attachment_type) # Create a note with the tags from the unique_ids in the list note_tags if note_tags: list_tags = [] for each_note_tag_id in note_tags: check_tag = db_retrieve_table_daemon( NoteTags, unique_id=each_note_tag_id) if check_tag: list_tags.append(each_note_tag_id) if list_tags: with session_scope(MYCODO_DB_PATH) as db_session: new_note = Notes() new_note.name = 'Action' new_note.tags = ','.join(list_tags) new_note.note = message db_session.add(new_note) logger_actions.debug(message)
def do_admin_login(): """Authenticate users of the web-UI""" # Check if the user is banned from logging in if flaskutils.banned_from_login(): return redirect('/') form = flaskforms.Login() form_notice = flaskforms.InstallNotice() with session_scope(current_app.config['MYCODO_DB_PATH']) as db_session: misc = db_session.query(Misc).first() dismiss_notification = misc.dismiss_notification stats_opt_out = misc.stats_opt_out if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'acknowledge': try: with session_scope(current_app.config['MYCODO_DB_PATH']) as db_session: mod_misc = db_session.query(Misc).first() mod_misc.dismiss_notification = 1 db_session.commit() except Exception as except_msg: flash("Acknowledgement not saved: {}".format(except_msg), "error") elif form_name == 'login' and form.validate_on_submit(): with session_scope(current_app.config['USER_DB_PATH']) as new_session: user = new_session.query(Users).filter(Users.user_name == form.username.data).first() new_session.expunge_all() new_session.close() if not user: flaskutils.login_log(form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'NOUSER') flaskutils.failed_login() elif Users().check_password(form.password.data, user.user_password_hash) == user.user_password_hash: flaskutils.login_log(user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'LOGIN') session['logged_in'] = True session['user_group'] = user.user_restriction session['user_name'] = user.user_name session['user_theme'] = user.user_theme if form.remember.data: response = make_response(redirect('/')) expire_date = datetime.datetime.now() expire_date = expire_date + datetime.timedelta(days=90) response.set_cookie('user_name', user.user_name, expires=expire_date) response.set_cookie('user_pass_hash', user.user_password_hash, expires=expire_date) return response return redirect('/') else: flaskutils.login_log(user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') flaskutils.failed_login() else: flaskutils.login_log(form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') flaskutils.failed_login() return redirect('/login') return render_template('login.html', form=form, formNotice=form_notice, dismiss_notification=dismiss_notification, stats_opt_out=stats_opt_out)
# try: # pass # Code goes here # except Exception: # msg = "ERROR: post-alembic revision {}: {}".format( # each_revision, traceback.format_exc()) # error.append(msg) # print(msg) elif each_revision == 'd66e33093e8e': # convert database entries to JSON string for custom_options entry print("Executing post-alembic code for revision {}".format( each_revision)) import json from mycodo.databases.models import Widget try: with session_scope(MYCODO_DB_PATH) as session: for each_widget in session.query(Widget).all(): custom_options = {} if each_widget.graph_type == 'graph': each_widget.graph_type = 'widget_graph_synchronous' custom_options['measurements_math'] = each_widget.math_ids.split(";") custom_options['measurements_note_tag'] = each_widget.note_tag_ids.split(";") custom_options['measurements_input'] = each_widget.input_ids_measurements.split(";") custom_options['measurements_output'] = each_widget.output_ids.split(";") custom_options['measurements_pid'] = each_widget.pid_ids.split(";") elif each_widget.graph_type == 'spacer': each_widget.graph_type = 'widget_spacer' elif each_widget.graph_type == 'gauge_angular': each_widget.graph_type = 'widget_gauge_angular' custom_options['measurement'] = each_widget.input_ids_measurements elif each_widget.graph_type == 'gauge_solid':
def trigger_function_actions(function_id, message='', last_measurement=None, device_id=None, device_measurement=None, edge=None, output_state=None, on_duration=None, duty_cycle=None): """ Execute the Actions belonging to a particular Function :param function_id: :param device_id: The unique ID associated with the device_measurement :param message: The message generated from the conditional check :param last_measurement: The last measurement value :param device_measurement: The measurement (i.e. "temperature") :param edge: If edge conditional, rise/on (1) or fall/off (0) :param output_state: If output conditional, the output state (on/off) to trigger the action :param on_duration: If output conditional, the ON duration :param duty_cycle: If output conditional, the duty cycle :return: """ logger_actions = logging.getLogger( "mycodo.trigger_function_actions_{id}".format( id=function_id.split('-')[0])) # List of all email notification recipients # List is appended with TO email addresses when an email Action is # encountered. An email is sent to all recipients after all actions # have been executed. email_recipients = [] # List of tags to add to a note note_tags = [] attachment_file = None attachment_type = None input_dev = None output = None device = None actions = db_retrieve_table_daemon(Actions) actions = actions.filter(Actions.function_id == function_id).all() if device_id: 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 for cond_action in actions: (message, note_tags, email_recipients, attachment_file, attachment_type) = trigger_action(cond_action.unique_id, message=message, single_action=False, note_tags=note_tags, email_recipients=email_recipients, attachment_file=attachment_file, attachment_type=attachment_type) # Send email after all conditional actions have been checked # In order to append all action messages to send in the email # send_email_at_end will be None or the TO email address if email_recipients: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.ssl, smtp.port, smtp.user, smtp.passw, smtp.email_from, email_recipients, message, attachment_file, attachment_type) # Create a note with the tags from the unique_ids in the list note_tags if note_tags: list_tags = [] for each_note_tag_id in note_tags: check_tag = db_retrieve_table_daemon(NoteTags, unique_id=each_note_tag_id) if check_tag: list_tags.append(each_note_tag_id) if list_tags: with session_scope(MYCODO_DB_PATH) as db_session: new_note = Notes() new_note.name = 'Action' new_note.tags = ','.join(list_tags) new_note.note = message db_session.add(new_note) logger_actions.debug(message)
def register_extensions(app): """ register extensions to the app """ app.jinja_env.add_extension('jinja2.ext.do') # Global values in jinja # Uncomment to enable profiler # See scripts/profile_analyzer.py to analyze output # app = setup_profiler(app) # Compress app responses with gzip compress = Compress() compress.init_app(app) # Influx db time-series database db.init_app(app) influx_db.init_app(app) # Limit authentication blueprint requests to 200 per minute limiter = Limiter(app, key_func=get_ip_address) limiter.limit("200/minute")(routes_authentication.blueprint) # Language translations babel = Babel(app) @babel.localeselector def get_locale(): try: user = User.query.filter( User.id == flask_login.current_user.id).first() if user and user.language != '': for key in LANGUAGES: if key == user.language: return key # Bypass endpoint test error "'AnonymousUserMixin' object has no attribute 'id'" except AttributeError: pass return request.accept_languages.best_match(LANGUAGES.keys()) # User login management login_manager = flask_login.LoginManager() login_manager.init_app(app) @login_manager.user_loader def user_loader(user_id): user = User.query.filter(User.id == user_id).first() if not user: return return user @login_manager.unauthorized_handler def unauthorized(): flash(gettext('Please log in to access this page'), "error") return redirect(url_for('routes_authentication.do_login')) # Create and populate database if it doesn't exist with app.app_context(): db.create_all() populate_db() # This is disabled because there's a bug that messes up user databases # The upgrade script will execute alembic to upgrade the database # alembic_upgrade_db() # Check user option to force all web connections to use SSL # Fail if the URI is empty (pytest is running) if app.config['SQLALCHEMY_DATABASE_URI'] != 'sqlite://': with session_scope(app.config['SQLALCHEMY_DATABASE_URI']) as new_session: misc = new_session.query(Misc).first() if misc and misc.force_https: SSLify(app)
name_str]: # Multiple sets of dependencies, append library inputs_info[name_str]['dependencies_module'].append( input_data['dependencies_module']) else: # Only one set of dependencies inputs_info[name_str] = input_data if 'dependencies_module' in input_data: inputs_info[name_str]['dependencies_module'] = [ input_data['dependencies_module'] ] # turn into list inputs_info = dict( OrderedDict(sorted(inputs_info.items(), key=lambda t: t[0]))) with session_scope(MYCODO_DB_PATH) as new_session: dict_measurements = add_custom_measurements( new_session.query(Measurement).all()) dict_units = add_custom_units(new_session.query(Unit).all()) dict_inputs = {} for name, data in inputs_info.items(): if 'measurements_dict' not in data: continue for channel, measure in data['measurements_dict'].items(): if measure["measurement"]: if measure["measurement"] not in dict_inputs: dict_inputs[measure["measurement"]] = {} dict_inputs[measure["measurement"]][name] = data
# # Contact at kylegabriel.com import RPi.GPIO as GPIO from mycodo.config import SQL_DATABASE_MYCODO from mycodo.databases.mycodo_db.models import Relays from mycodo.databases.utils import session_scope MYCODO_DB_PATH = 'sqlite:///' + SQL_DATABASE_MYCODO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) # Start the session with session_scope(MYCODO_DB_PATH) as the_session: # Get all them relays relays = the_session.query(Relays).all() for each_relay in relays: # Setup all the pins as an output GPIO.setup(each_relay.pin, GPIO.OUT) # First turn the relay's off that need to be off if not each_relay.start_state: GPIO.output(each_relay.pin, not each_relay.trigger) # Turn on relays that are set to startup on if each_relay.start_state: # Only push the trigger to the relay with the start_state GPIO.output(each_relay.pin, each_relay.trigger)
def upgrade(): # Add note and tag tables op.create_table( 'notes', sa.Column('id', sa.Integer, nullable=False, unique=True), sa.Column('unique_id', sa.String, nullable=False, unique=True), sa.Column('date_time', sa.DateTime), sa.Column('name', sa.Text), sa.Column('tags', sa.Text), sa.Column('files', sa.Text), sa.Column('note', sa.Text), sa.PrimaryKeyConstraint('id'), keep_existing=True) op.create_table( 'note_tags', sa.Column('id', sa.Integer, nullable=False, unique=True), sa.Column('unique_id', sa.String, nullable=False, unique=True), sa.Column('name', sa.Text), sa.PrimaryKeyConstraint('id'), keep_existing=True) # Add note column to graphs table with op.batch_alter_table("dashboard") as batch_op: batch_op.add_column(sa.Column('note_tag_ids', sa.Text)) # New input options with op.batch_alter_table("input") as batch_op: batch_op.add_column(sa.Column('i2c_location', sa.Text)) batch_op.add_column(sa.Column('uart_location', sa.Text)) batch_op.add_column(sa.Column('gpio_location', sa.Integer)) # Rename input names with session_scope(MYCODO_DB_PATH) as new_session: for each_input in new_session.query(Input).all(): if each_input.device == 'ATLAS_PT1000_I2C': each_input.interface = 'I2C' each_input.device = 'ATLAS_PT1000' elif each_input.device == 'ATLAS_PT1000_UART': each_input.interface = 'UART' each_input.device = 'ATLAS_PT1000' elif each_input.device == 'ATLAS_EC_I2C': each_input.interface = 'I2C' each_input.device = 'ATLAS_EC' elif each_input.device == 'ATLAS_EC_UART': each_input.interface = 'UART' each_input.device = 'ATLAS_EC' elif each_input.device == 'ATLAS_PH_I2C': each_input.interface = 'I2C' each_input.device = 'ATLAS_PH' elif each_input.device == 'ATLAS_PH_UART': each_input.interface = 'UART' each_input.device = 'ATLAS_PH' elif each_input.device == 'K30_I2C': each_input.interface = 'I2C' each_input.device = 'K30' elif each_input.device == 'K30_UART': each_input.interface = 'UART' each_input.device = 'K30' elif each_input.device == 'MH_Z16_I2C': each_input.interface = 'I2C' each_input.device = 'MH_Z16' elif each_input.device == 'MH_Z16_UART': each_input.interface = 'UART' each_input.device = 'MH_Z16' elif each_input.device == 'MH_Z19_I2C': each_input.interface = 'I2C' each_input.device = 'MH_Z19' elif each_input.device == 'MH_Z19_UART': each_input.interface = 'UART' each_input.device = 'MH_Z19' elif each_input.device == 'COZIR_CO2': each_input.measurements = '{meas},dewpoint'.format(meas=each_input.measurements) each_input.convert_to_unit = '{meas};dewpoint,C'.format(meas=each_input.convert_to_unit) if (each_input.location == 'RPi' or each_input.device == 'RPiFreeSpace'): each_input.interface = 'RPi' elif each_input.location == 'Mycodo_daemon': each_input.interface = 'Mycodo' # Move values to new respective device locations if each_input.device_loc: each_input.uart_location = each_input.device_loc if each_input.device in [ 'MCP3008', 'MAX31855', 'MAX31856', 'MAX31865']: each_input.interface = 'UART' if each_input.location and each_input.device in [ 'ATLAS_EC', 'TSL2591', 'ATLAS_PH', 'BH1750', 'SHT2x', 'MH_Z16', 'CHIRP', 'BMP280', 'TMP006', 'AM2315', 'BME280', 'ATLAS_PT1000', 'BMP180', 'TSL2561', 'HTU21D', 'HDC1000', 'CCS811', 'MCP342x', 'ADS1x15']: each_input.i2c_location = each_input.location each_input.interface = 'I2C' if each_input.location and each_input.device in [ 'DHT11', 'DHT22', 'SIGNAL_PWM', 'SIGNAL_RPM', 'SHT1x_7x', 'GPIO_STATE']: try: each_input.gpio_location = int(each_input.location) except: pass new_session.commit()
def calculate_method_setpoint(method_id, table, this_controller, Method, MethodData, logger): """ Calculates the setpoint from a method :param method_id: ID of Method to be used :param table: Table of the this_controller using this function :param this_controller: The this_controller using this function :param logger: The logger to use :return: 0 (success) or 1 (error) and a setpoint value """ method = db_retrieve_table_daemon(Method) method_key = method.filter(Method.unique_id == method_id).first() method_data = db_retrieve_table_daemon(MethodData) method_data = method_data.filter(MethodData.method_id == method_id) method_data_all = method_data.filter(MethodData.output_id == None).all() method_data_first = method_data.filter(MethodData.output_id == None).first() now = datetime.datetime.now() # Calculate where the current time/date is within the time/date method if method_key.method_type == 'Date': for each_method in method_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%Y-%m-%d %H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%Y-%m-%d %H:%M:%S') if start_time < now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) total_seconds = (end_time - start_time).total_seconds() part_seconds = (now - start_time).total_seconds() percent_total = part_seconds / total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_total) else: new_setpoint = setpoint_start - (setpoint_diff * percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time, end=end_time)) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug("[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug("[Method] New Setpoint: {sp}".format( sp=new_setpoint)) return new_setpoint, False # Calculate where the current Hour:Minute:Seconds is within the Daily method elif method_key.method_type == 'Daily': daily_now = datetime.datetime.now().strftime('%H:%M:%S') daily_now = datetime.datetime.strptime(str(daily_now), '%H:%M:%S') for each_method in method_data_all: start_time = datetime.datetime.strptime(each_method.time_start, '%H:%M:%S') end_time = datetime.datetime.strptime(each_method.time_end, '%H:%M:%S') if start_time < daily_now < end_time: setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end-setpoint_start) total_seconds = (end_time-start_time).total_seconds() part_seconds = (daily_now-start_time).total_seconds() percent_total = part_seconds/total_seconds if setpoint_start < setpoint_end: new_setpoint = setpoint_start+(setpoint_diff*percent_total) else: new_setpoint = setpoint_start-(setpoint_diff*percent_total) logger.debug("[Method] Start: {start} End: {end}".format( start=start_time.strftime('%H:%M:%S'), end=end_time.strftime('%H:%M:%S'))) logger.debug("[Method] Start: {start} End: {end}".format( start=setpoint_start, end=setpoint_end)) logger.debug("[Method] Total: {tot} Part total: {par} ({per}%)".format( tot=total_seconds, par=part_seconds, per=percent_total)) logger.debug("[Method] New Setpoint: {sp}".format( sp=new_setpoint)) return new_setpoint, False # Calculate sine y-axis value from the x-axis (seconds of the day) elif method_key.method_type == 'DailySine': new_setpoint = sine_wave_y_out(method_data_first.amplitude, method_data_first.frequency, method_data_first.shift_angle, method_data_first.shift_y) return new_setpoint, False # Calculate Bezier curve y-axis value from the x-axis (seconds of the day) elif method_key.method_type == 'DailyBezier': new_setpoint = bezier_curve_y_out( method_data_first.shift_angle, (method_data_first.x0, method_data_first.y0), (method_data_first.x1, method_data_first.y1), (method_data_first.x2, method_data_first.y2), (method_data_first.x3, method_data_first.y3)) return new_setpoint, False # Calculate the duration in the method based on self.method_start_time elif method_key.method_type == 'Duration': start_time = datetime.datetime.strptime( str(this_controller.method_start_time), '%Y-%m-%d %H:%M:%S.%f') ended = False # Check if method_end_time is not None if this_controller.method_end_time: # Convert time string to datetime object end_time = datetime.datetime.strptime( str(this_controller.method_end_time), '%Y-%m-%d %H:%M:%S.%f') if now > start_time: ended = True seconds_from_start = (now - start_time).total_seconds() total_sec = 0 previous_total_sec = 0 previous_end = None method_restart = False for each_method in method_data_all: # If duration_sec is 0, method has instruction to restart if each_method.duration_sec == 0: method_restart = True else: previous_end = each_method.setpoint_end total_sec += each_method.duration_sec if previous_total_sec <= seconds_from_start < total_sec: row_start_time = float(start_time.strftime('%s')) + previous_total_sec row_since_start_sec = (now - (start_time + datetime.timedelta(0, previous_total_sec))).total_seconds() percent_row = row_since_start_sec / each_method.duration_sec setpoint_start = each_method.setpoint_start if each_method.setpoint_end: setpoint_end = each_method.setpoint_end else: setpoint_end = each_method.setpoint_start setpoint_diff = abs(setpoint_end - setpoint_start) if setpoint_start < setpoint_end: new_setpoint = setpoint_start + (setpoint_diff * percent_row) else: new_setpoint = setpoint_start - (setpoint_diff * percent_row) logger.debug( "[Method] Start: {start} Seconds Since: {sec}".format( start=start_time, sec=seconds_from_start)) logger.debug( "[Method] Start time of row: {start}".format( start=datetime.datetime.fromtimestamp(row_start_time))) logger.debug( "[Method] Sec since start of row: {sec}".format( sec=row_since_start_sec)) logger.debug( "[Method] Percent of row: {per}".format( per=percent_row)) logger.debug( "[Method] New Setpoint: {sp}".format( sp=new_setpoint)) return new_setpoint, False previous_total_sec = total_sec if this_controller.method_start_time: if method_restart: if end_time and now > end_time: ended = True else: # Method has been instructed to restart with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table) mod_method = mod_method.filter( table.unique_id == this_controller.unique_id).first() mod_method.method_start_time = datetime.datetime.now() db_session.commit() return previous_end, False else: ended = True if ended: # Duration method has ended, reset method_start_time locally and in DB with session_scope(MYCODO_DB_PATH) as db_session: mod_method = db_session.query(table).filter( table.unique_id == this_controller.unique_id).first() mod_method.method_start_time = 'Ended' mod_method.method_end_time = None db_session.commit() return None, True # Setpoint not needing to be calculated, use default setpoint return None, False