def camera_img_acquire(image_type, camera_unique_id, max_age): """Capture an image and return the filename.""" if not current_user.is_authenticated: return "You are not logged in and cannot access this endpoint" if image_type == 'new': tmp_filename = None elif image_type == 'tmp': tmp_filename = f'{camera_unique_id}_tmp.jpg' else: return path, filename = camera_record('photo', camera_unique_id, tmp_filename=tmp_filename) if not path and not filename: msg = "Could not acquire image." logger.error(msg) return msg else: image_path = os.path.join(path, filename) time_max_age = datetime.datetime.now() - datetime.timedelta(seconds=int(max_age)) timestamp = os.path.getctime(image_path) if datetime.datetime.fromtimestamp(timestamp) > time_max_age: date_time = datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') return_values = f'["{filename}","{date_time}"]' else: return_values = '["max_age_exceeded"]' return Response(return_values, mimetype='text/json')
def run_action(self, message, dict_vars): try: controller_id = dict_vars["value"]["camera_id"] except: controller_id = self.controller_id camera = db_retrieve_table_daemon( Camera, unique_id=controller_id, entry='first') if not camera: msg = f" Error: Camera with ID '{controller_id}' not found." message += msg self.logger.error(msg) return message message += f" Capturing photo with camera {controller_id} ({camera.name})." path, filename = camera_record('photo', controller_id) if not path and not filename: msg = " Could not acquire image." self.logger.error(msg) message += msg self.logger.debug(f"Message: {message}") return message
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.id == camera.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.id == camera.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 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 action_photo(cond_action, message): 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]) return message, attachment_file
def camera_img_acquire(image_type, camera_unique_id, max_age): """Capture an image and return the filename""" if image_type == 'new': tmp_filename = None elif image_type == 'tmp': tmp_filename = '{id}_tmp.jpg'.format(id=camera_unique_id) else: return path, filename = camera_record('photo', camera_unique_id, tmp_filename=tmp_filename) image_path = os.path.join(path, filename) time_max_age = datetime.datetime.now() - datetime.timedelta(seconds=int(max_age)) timestamp = os.path.getctime(image_path) if datetime.datetime.fromtimestamp(timestamp) > time_max_age: date_time = datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') return_values = '["{}","{}"]'.format(filename, date_time) else: return_values = '["max_age_exceeded"]' return Response(return_values, mimetype='text/json')
def camera_img_acquire(image_type, camera_unique_id, max_age): """Capture an image and resturn the filename""" if image_type == 'new': tmp_filename = None elif image_type == 'tmp': tmp_filename = '{id}_tmp.jpg'.format(id=camera_unique_id) else: return path, filename = camera_record('photo', camera_unique_id, tmp_filename=tmp_filename) image_path = os.path.join(path, filename) time_max_age = datetime.datetime.now() - datetime.timedelta(seconds=int(max_age)) timestamp = os.path.getctime(image_path) if datetime.datetime.fromtimestamp(timestamp) > time_max_age: date_time = datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S') return_values = '["{}","{}"]'.format(filename, date_time) else: return_values = '["max_age_exceeded"]' return Response(return_values, mimetype='text/json')
def action_video(cond_action, message): 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) path, filename = camera_record('video', camera_stream.unique_id, duration_sec=cond_action.do_camera_duration) if path and filename: attachment_file = os.path.join(path, filename) return message, attachment_file else: message += " A video could not be acquired." return message, None
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 page_camera(): """ Page to start/stop video stream or time-lapse, or capture a still image. Displays most recent still image and time-lapse image. """ if not flaskutils.user_has_permission('view_camera'): return redirect(url_for('general_routes.home')) form_camera = flaskforms.Camera() camera = Camera.query.all() # Check if a video stream is active for each_camera in camera: if each_camera.stream_started and not CameraStream().is_running(): each_camera.stream_started = False db.session.commit() if request.method == 'POST': if not flaskutils.user_has_permission('edit_settings'): return redirect(url_for('page_routes.page_camera')) control = DaemonControl() mod_camera = Camera.query.filter( Camera.id == form_camera.camera_id.data).first() if form_camera.capture_still.data: if mod_camera.stream_started: flash( gettext( u"Cannot capture still image if stream is active.")) return redirect('/camera') if CameraStream().is_running(): CameraStream().terminate_controller() # Stop camera stream time.sleep(2) camera_record('photo', mod_camera) elif form_camera.start_timelapse.data: if mod_camera.stream_started: flash(gettext(u"Cannot start time-lapse if stream is active.")) return redirect('/camera') now = time.time() mod_camera.timelapse_started = True mod_camera.timelapse_start_time = now mod_camera.timelapse_end_time = now + float( form_camera.timelapse_runtime_sec.data) mod_camera.timelapse_interval = form_camera.timelapse_interval.data mod_camera.timelapse_next_capture = now mod_camera.timelapse_capture_number = 0 db.session.commit() control.refresh_daemon_camera_settings() elif form_camera.pause_timelapse.data: mod_camera.timelapse_paused = True db.session.commit() control.refresh_daemon_camera_settings() elif form_camera.resume_timelapse.data: mod_camera.timelapse_paused = False db.session.commit() control.refresh_daemon_camera_settings() elif form_camera.stop_timelapse.data: mod_camera.timelapse_started = 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 db.session.commit() control.refresh_daemon_camera_settings() elif form_camera.start_stream.data: if mod_camera.timelapse_started: flash(gettext(u"Cannot start stream if time-lapse is active.")) return redirect('/camera') elif CameraStream().is_running(): flash( gettext( u"Cannot start stream. The stream is already running.") ) return redirect('/camera') elif (not (mod_camera.camera_type == 'Raspberry Pi' and mod_camera.library == 'picamera')): flash( gettext(u"Streaming is only supported with the Raspberry" u" Pi camera using the picamera library.")) return redirect('/camera') elif Camera.query.filter_by(stream_started=True).count(): flash( gettext(u"Cannot start stream if another stream is " u"already in progress.")) return redirect('/camera') else: mod_camera.stream_started = True db.session.commit() elif form_camera.stop_stream.data: if CameraStream().is_running(): CameraStream().terminate_controller() mod_camera.stream_started = False db.session.commit() return redirect('/camera') # Get the full path and timestamps of latest still and time-lapse images latest_img_still_ts = {} latest_img_still = {} latest_img_tl_ts = {} latest_img_tl = {} for each_camera in camera: camera_path = os.path.join( PATH_CAMERAS, '{id}-{uid}'.format(id=each_camera.id, uid=each_camera.unique_id)) try: latest_still_img_full_path = max(glob.iglob( '{path}/still/Still-{cam_id}-*.jpg'.format( path=camera_path, cam_id=each_camera.id)), key=os.path.getmtime) except ValueError: latest_still_img_full_path = None if latest_still_img_full_path: ts = os.path.getmtime(latest_still_img_full_path) latest_img_still_ts[ each_camera.id] = datetime.datetime.fromtimestamp(ts).strftime( "%Y-%m-%d %H:%M:%S") latest_img_still[each_camera.id] = os.path.basename( latest_still_img_full_path) else: latest_img_still[each_camera.id] = None try: latest_time_lapse_img_full_path = max(glob.iglob( '{path}/timelapse/Timelapse-{cam_id}-*.jpg'.format( path=camera_path, cam_id=each_camera.id)), key=os.path.getmtime) except ValueError: latest_time_lapse_img_full_path = None if latest_time_lapse_img_full_path: ts = os.path.getmtime(latest_time_lapse_img_full_path) latest_img_tl_ts[each_camera.id] = datetime.datetime.fromtimestamp( ts).strftime("%Y-%m-%d %H:%M:%S") latest_img_tl[each_camera.id] = os.path.basename( latest_time_lapse_img_full_path) else: latest_img_tl[each_camera.id] = None time_now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') return render_template('pages/camera.html', camera=camera, form_camera=form_camera, latest_img_still=latest_img_still, latest_img_still_ts=latest_img_still_ts, latest_img_tl=latest_img_tl, latest_img_tl_ts=latest_img_tl_ts, time_now=time_now)
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_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 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