def action_email(logger_actions, cond_action, message, single_action, attachment_file, email_recipients, attachment_type): 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: # 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 and cond_action.do_action_string: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, [cond_action.do_action_string], message, attachment_file, attachment_type) else: logger_actions.error( "Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) else: email_recipients.append(cond_action.do_action_string) # Email the Conditional message to multiple recipients # Optionally capture a photo or video and attach to the email. if cond_action.action_type in ['email_multiple']: 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: # 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 and cond_action.do_action_string: smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, cond_action.do_action_string.split(','), message, attachment_file, attachment_type) else: logger.error("Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) else: email_recipients.extend(cond_action.do_action_string.split(',')) return message, email_recipients, attachment_type
def settings_alert_mod(form_mod_alert): """ Modify Alert settings """ action = '{action} {controller}'.format( action=gettext("Modify"), controller=gettext("Alert Settings")) error = [] try: if form_mod_alert.validate(): mod_smtp = SMTP.query.one() if form_mod_alert.send_test.data: send_email(mod_smtp.host, mod_smtp.ssl, mod_smtp.port, mod_smtp.user, mod_smtp.passw, mod_smtp.email_from, form_mod_alert.send_test_to_email.data, "This is a test email from Mycodo") flash( gettext( "Test email sent to %(recip)s. Check your " "inbox to see if it was successful.", recip=form_mod_alert.send_test_to_email.data), "success") return redirect(url_for('routes_settings.settings_alerts')) else: mod_smtp.host = form_mod_alert.smtp_host.data mod_smtp.port = form_mod_alert.smtp_port.data mod_smtp.ssl = form_mod_alert.smtp_ssl.data mod_smtp.user = form_mod_alert.smtp_user.data if form_mod_alert.smtp_password.data: mod_smtp.passw = form_mod_alert.smtp_password.data mod_smtp.email_from = form_mod_alert.smtp_from_email.data mod_smtp.hourly_max = form_mod_alert.smtp_hourly_max.data db.session.commit() else: flash_form_errors(form_mod_alert) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_settings.settings_alerts'))
def run_action(self, message, dict_vars): try: email_recipients = dict_vars["value"]["email_address"] except: if "," in self.email: email_recipients = self.email.split(",") else: email_recipients = [self.email] if not email_recipients: msg = f" Error: No recipients specified." self.logger.error(msg) message += msg return message try: message_send = dict_vars["value"]["message"] except: message_send = False # 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: message += f" Email '{self.email}'." if not message_send: message_send = message smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, email_recipients, message_send) else: self.logger.error( f"Wait {smtp_wait_timer - time.time():.0f} seconds to email again.") self.logger.debug(f"Message: {message}") return message
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 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 trigger_action(cond_action_id, message='', note_tags=None, email_recipients=None, attachment_file=None, attachment_type=None, single_action=False): cond_action = db_retrieve_table_daemon(Actions, unique_id=cond_action_id) if not cond_action: return logger_actions = logging.getLogger("mycodo.trigger_action_{id}".format( id=cond_action.unique_id.split('-')[0])) 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 message += "\n[Action {id}]:".format( id=cond_action.unique_id.split('-')[0], action_type=cond_action.action_type) # 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 cond_action.do_output_pwm): 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: # If controller is Conditional and is # run_pwm_method, activate method start is_conditional = db_retrieve_table_daemon( Conditional, unique_id=cond_action.do_unique_id, entry='first') if (is_conditional and is_conditional.conditional_type == 'run_pwm_method'): with session_scope(MYCODO_DB_PATH) as new_session: mod_cont_ready = new_session.query(Conditional).filter( Conditional.unique_id == cond_action.do_unique_id).first() mod_cont_ready.method_start_time = 'Ready' new_session.commit() 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() if single_action: return message else: return message, note_tags, email_recipients, attachment_file, attachment_type
def forgot_password(): """Page to send password reset email""" error = [] form_forgot_password = forms_authentication.ForgotPassword() if request.method == 'POST': if form_forgot_password.submit.data: if not form_forgot_password.username.data: user = None error.append("User name cannot be left blank") else: user = User.query.filter(User.name == form_forgot_password. username.data.lower()).first() if not error: # test if user name exists if user: # check last time requested if user.password_reset_last_request: difference = datetime.datetime.now( ) - user.password_reset_last_request if difference.seconds < 1800: error.append("Requesting too many password resets") role = Role.query.filter(Role.id == user.role_id).first() if not role or not role.reset_password: error.append("Cannot reset password of this user") if not error: user.password_reset_code = generate_reset_code(30) user.password_reset_code_expiration = datetime.datetime.now( ) + datetime.timedelta(minutes=30) db.session.commit() smtp = SMTP.query.one() hostname = socket.gethostname() subject = "Mycodo Password Reset ({})".format(hostname) msg = "A password reset has been requested for user {user} on host {host} with your email " \ "address.\n\nIf you did not initiate this, you can disregard this email.\n\nIf you would " \ "like to reset your password, the password reset code below will be good for the next 30 " \ "minutes.\n\n{code}".format( user=user.name, host=hostname, code=user.password_reset_code) send_email(smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, user.email, msg, subject=subject) if error: for each_error in error: flash(each_error, "error") else: flash( gettext( "If the user name exists, it has a valid email associated with it, and the email " "server settings are configured correctly, an email will be sent with instructions " "for resetting your password."), "success") return redirect( url_for('routes_password_reset.reset_password')) return render_template('forgot_password.html', form_forgot_password=form_forgot_password)
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 trigger_conditional_actions(self, cond_id, message='', last_measurement=None, device_id=None, device_measurement=None, edge=None, output_state=None, on_duration=None, duty_cycle=None): """ If a Conditional has been triggered, this function will execute the Conditional Actions :param self: self from the Controller class :param cond_id: The ID of the Conditional :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_cond = logging.getLogger("mycodo.conditional_{id}".format( id=cond_id)) # 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 = [] attachment_file = False attachment_type = False input_dev = None output = None device = None cond_actions = db_retrieve_table_daemon(ConditionalActions) cond_actions = cond_actions.filter( ConditionalActions.conditional_id == cond_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 cond_actions: message += "\n[Conditional Action {id}]:".format( id=cond_action.id, do_action=cond_action.do_action) # Actuate output if (cond_action.do_action == 'output' and cond_action.do_relay_id and cond_action.do_relay_state in ['on', 'off']): message += " Turn output {id} {state}".format( id=cond_action.do_relay_id, state=cond_action.do_relay_state) if (cond_action.do_relay_state == 'on' and cond_action.do_relay_duration): message += " for {sec} seconds".format( sec=cond_action.do_relay_duration) message += "." output_on_off = threading.Thread( target=self.control.output_on_off, args=(cond_action.do_relay_id, cond_action.do_relay_state,), kwargs={'duration': cond_action.do_relay_duration}) output_on_off.start() # Execute command in shell elif cond_action.do_action == 'command': # Replace string variables with actual values command_str = cond_action.do_action_string # 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.cmd_measurement: command_str = command_str.replace( "((measure_linux_command))", str(input_dev.location)) # 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) # Capture photo elif cond_action.do_action in ['photo', 'photo_email']: message += " Capturing photo with camera ({id}).".format( id=cond_action.do_camera_id) camera_still = db_retrieve_table_daemon( Camera, device_id=cond_action.do_camera_id) attachment_file = camera_record('photo', camera_still.unique_id) # Capture video elif cond_action.do_action in ['video', 'video_email']: message += " Capturing video with camera ({id}).".format( id=cond_action.do_camera_id) camera_stream = db_retrieve_table_daemon( Camera, device_id=cond_action.do_camera_id) attachment_file = camera_record( 'video', camera_stream.unique_id, duration_sec=cond_action.do_camera_duration) # Activate PID controller elif cond_action.do_action == 'activate_pid': message += " Activate PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon( PID, device_id=cond_action.do_pid_id, entry='first') if pid.is_activated: message += " Notice: PID is already active!" else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.id == cond_action.do_pid_id).first() mod_pid.is_activated = True new_session.commit() activate_pid = threading.Thread( target=self.control.controller_activate, args=('PID', cond_action.do_pid_id,)) activate_pid.start() # Deactivate PID controller elif cond_action.do_action == 'deactivate_pid': message += " Deactivate PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon( PID, device_id=cond_action.do_pid_id, entry='first') if not pid.is_activated: message += " Notice: PID is already inactive!" else: with session_scope(MYCODO_DB_PATH) as new_session: mod_pid = new_session.query(PID).filter( PID.id == cond_action.do_pid_id).first() mod_pid.is_activated = False new_session.commit() deactivate_pid = threading.Thread( target=self.control.controller_deactivate, args=('PID', cond_action.do_pid_id,)) deactivate_pid.start() # Resume PID controller elif cond_action.do_action == 'resume_pid': message += " Resume PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon( PID, device_id=cond_action.do_pid_id, entry='first') if not pid.is_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.id == cond_action.do_pid_id).first() mod_pid.is_paused = False new_session.commit() resume_pid = threading.Thread( target=self.control.pid_resume, args=(cond_action.do_pid_id,)) resume_pid.start() # Pause PID controller elif cond_action.do_action == 'pause_pid': message += " Pause PID ({id}).".format( id=cond_action.do_pid_id) pid = db_retrieve_table_daemon( PID, device_id=cond_action.do_pid_id, entry='first') if pid.is_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.id == cond_action.do_pid_id).first() mod_pid.is_paused = True new_session.commit() pause_pid = threading.Thread( target=self.control.pid_pause, args=(cond_action.do_pid_id,)) pause_pid.start() # Email the Conditional message. Optionally capture a photo or # video and attach to the email. elif cond_action.do_action in ['email', 'photo_email', 'video_email']: if (self.email_count >= self.smtp_max_count and time.time() < self.smtp_wait_timer[cond_id]): self.allowed_to_send_notice = False else: if time.time() > self.smtp_wait_timer[cond_id]: self.email_count = 0 self.smtp_wait_timer[cond_id] = time.time() + 3600 self.allowed_to_send_notice = True self.email_count += 1 # If the emails per hour limit has not been exceeded if self.allowed_to_send_notice: email_recipients.append(cond_action.do_action_string) message += " Notify {email}.".format( email=cond_action.do_action_string) # attachment_type != False indicates to # attach a photo or video if cond_action.do_action == 'photo_email': message += " Photo attached to email." attachment_type = 'still' elif cond_action.do_action == 'video_email': message += " Video attached to email." attachment_type = 'video' else: logger_cond.error( "Wait {sec:.0f} seconds to email again.".format( sec=self.smtp_wait_timer[cond_id] - time.time())) # TODO: rename flash_lcd in user databases to flash_lcd_on elif cond_action.do_action in ['flash_lcd', 'flash_lcd_on']: lcd = db_retrieve_table_daemon( LCD, device_id=cond_action.do_lcd_id) message += " Flash LCD On {id} ({name}).".format( id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=self.control.lcd_flash, args=(cond_action.do_lcd_id, True,)) start_flashing.start() elif cond_action.do_action == 'flash_lcd_off': lcd = db_retrieve_table_daemon( LCD, device_id=cond_action.do_lcd_id) message += " Flash LCD Off {id} ({name}).".format( id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=self.control.lcd_flash, args=(cond_action.do_lcd_id, False,)) start_flashing.start() elif cond_action.do_action == 'lcd_backlight_off': lcd = db_retrieve_table_daemon( LCD, device_id=cond_action.do_lcd_id) message += " LCD Backlight Off {id} ({name}).".format( id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=self.control.lcd_backlight, args=(cond_action.do_lcd_id, False,)) start_flashing.start() elif cond_action.do_action == 'lcd_backlight_on': lcd = db_retrieve_table_daemon( LCD, device_id=cond_action.do_lcd_id) message += " LCD Backlight On {id} ({name}).".format( id=lcd.id, name=lcd.name) start_flashing = threading.Thread( target=self.control.lcd_backlight, args=(cond_action.do_lcd_id, True,)) start_flashing.start() # 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) logger_cond.debug(message)
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 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
def trigger_action(cond_action_id, message='', note_tags=None, email_recipients=None, attachment_file=None, attachment_type=None, single_action=False, debug=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 :param debug: determine if logging level should be DEBUG :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])) if debug: logger_actions.setLevel(logging.DEBUG) else: logger_actions.setLevel(logging.INFO) message += "\n[Action {id}]:".format( id=cond_action.unique_id.split('-')[0], action_type=cond_action.action_type) try: control = DaemonControl() # 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) # Infrared Send if cond_action.action_type == 'infrared_send': command = 'irsend SEND_ONCE {remote} {code}'.format( remote=cond_action.remote, code=cond_action.code) output, err, stat = cmd_output(command) # Send more than once if cond_action.send_times > 1: for _ in range(cond_action.send_times - 1): time.sleep(0.5) output, err, stat = cmd_output(command) message += " [{id}] Infrared Send " \ "code '{code}', remote '{remote}', times: {times}:" \ "\nOutput: {out}" \ "\nError: {err}" \ "\nStatus: {stat}'.".format( id=cond_action.id, code=cond_action.code, remote=cond_action.remote, times=cond_action.send_times, out=output, err=err, stat=stat) # 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 = float(cond_action.do_action_string) new_session.commit() # Raise PID Setpoint if cond_action.action_type == 'setpoint_pid_raise': pid = db_retrieve_table_daemon(PID, unique_id=cond_action.do_unique_id, entry='first') new_setpoint = pid.setpoint + float(cond_action.do_action_string) message += " Raise Setpoint of PID {unique_id} by {amt}, to {sp} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, amt=float(cond_action.do_action_string), sp=new_setpoint, id=pid.id, name=pid.name) if pid.is_activated: setpoint_pid = threading.Thread(target=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 == cond_action.do_unique_id).first() mod_pid.setpoint = new_setpoint new_session.commit() # Lower PID Setpoint if cond_action.action_type == 'setpoint_pid_lower': pid = db_retrieve_table_daemon(PID, unique_id=cond_action.do_unique_id, entry='first') new_setpoint = pid.setpoint - float(cond_action.do_action_string) message += " Lower Setpoint of PID {unique_id} by {amt}, to {sp} ({id}, {name}).".format( unique_id=cond_action.do_unique_id, amt=float(cond_action.do_action_string), sp=new_setpoint, id=pid.id, name=pid.name) if pid.is_activated: setpoint_pid = threading.Thread(target=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 == cond_action.do_unique_id).first() mod_pid.setpoint = new_setpoint 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 to a single recipient. # Optionally capture a photo or video and attach to the email. if cond_action.action_type in ['email', 'photo_email', 'video_email']: 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: # 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 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: logger_actions.error( "Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) else: email_recipients.append(cond_action.do_action_string) # Email the Conditional message to multiple recipients # Optionally capture a photo or video and attach to the email. if cond_action.action_type in ['email_multiple']: 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: # 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 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.split(','), message, attachment_file, attachment_type) else: logger_actions.error( "Wait {sec:.0f} seconds to email again.".format( sec=smtp_wait_timer - time.time())) else: email_recipients.extend( cond_action.do_action_string.split(',')) 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." logger_actions.debug("Message: {}".format(message)) logger_actions.debug("Note Tags: {}".format(note_tags)) logger_actions.debug("Email Recipients: {}".format(email_recipients)) logger_actions.debug("Attachment Files: {}".format(attachment_file)) logger_actions.debug("Attachment Type: {}".format(attachment_type)) if single_action: return message else: return message, note_tags, email_recipients, attachment_file, attachment_type
def forgot_password(): """Page to send password reset email""" error = [] form_forgot_password = forms_authentication.ForgotPassword() if request.method == 'POST': if form_forgot_password.submit.data: if not form_forgot_password.username.data: user = None error.append("User name cannot be left blank") else: user = User.query.filter(User.name == form_forgot_password.username.data.lower()).first() if not error: # test if user name exists if user: # check last time requested if user.password_reset_last_request: difference = datetime.datetime.now() - user.password_reset_last_request if difference.seconds < 1800: error.append("Requesting too many password resets") role = Role.query.filter(Role.id == user.role_id).first() if not role or not role.reset_password: error.append("Cannot reset password of this user") if not error: if user: user.password_reset_code = generate_reset_code(30) user.password_reset_code_expiration = datetime.datetime.now() + datetime.timedelta(minutes=30) db.session.commit() hostname = socket.gethostname() now = datetime.datetime.now() if form_forgot_password.reset_method.data == 'email': smtp = SMTP.query.first() if user and smtp.host and smtp.protocol and smtp.port and smtp.user and smtp.passw: subject = "Mycodo Password Reset ({})".format(hostname) msg = "A password reset has been requested for user {user} on host {host} at {time} " \ "with your email address.\n\nIf you did not initiate this, you can disregard " \ "this email.\n\nIf you would like to reset your password, the password reset " \ "code below will be good for the next 30 minutes.\n\n{code}".format( user=user.name, host=hostname, time=now.strftime("%d/%m/%Y %H:%M"), code=user.password_reset_code) send_email( smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, user.email, msg, subject=subject) flash("If the user name exists, it has a valid email associated with it, and the email " "server settings are configured correctly, an email will be sent with instructions " "for resetting your password.", "success") elif form_forgot_password.reset_method.data == 'file': save_path = os.path.join(INSTALL_DIRECTORY, "password_reset.txt") if user: with open(save_path, 'w') as out_file: msg = "A password reset has been requested for user {user} on host {host} at " \ "{time}.\n\nIf you would like to reset your password, the password reset " \ "code below will be good for the next 30 minutes.\n\n{code}\n".format( user=user.name, host=hostname, time=now.strftime("%d/%m/%Y %H:%M"), code=user.password_reset_code) out_file.write(msg) flash("If the user name exists, a file will be created at {} with instructions " "for resetting your password.".format(save_path), "success") if error: for each_error in error: flash(each_error, "error") else: return redirect(url_for('routes_password_reset.reset_password')) return render_template('forgot_password.html', form_forgot_password=form_forgot_password)
def run_action(self, message, dict_vars): try: controller_id = dict_vars["value"]["camera_id"] except: controller_id = self.controller_id try: email_recipients = dict_vars["value"]["email_address"] except: if "," in self.email: email_recipients = self.email.split(",") else: email_recipients = [self.email] if not email_recipients: msg = f" Error: No recipients specified." self.logger.error(msg) message += msg return message try: message_send = dict_vars["value"]["message"] except: message_send = None this_camera = db_retrieve_table_daemon(Camera, unique_id=controller_id, entry='first') if not this_camera: msg = f" Error: Camera with ID '{controller_id}' not found." message += msg self.logger.error(msg) return message path, filename = camera_record('photo', this_camera.unique_id) if path and filename: attachment_file = os.path.join(path, filename) # 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: message += f" Email '{','.join(email_recipients)}' with photo attached." if not message_send: message_send = message smtp = db_retrieve_table_daemon(SMTP, entry='first') send_email(smtp.host, smtp.protocol, smtp.port, smtp.user, smtp.passw, smtp.email_from, email_recipients, message_send, attachment_file=attachment_file, attachment_type="still") else: self.logger.error( f"Wait {smtp_wait_timer - time.time():.0f} seconds to email again." ) else: message += " An image could not be acquired. Not sending email." self.logger.debug(f"Message: {message}") return message