def page_relay(): """ Display relay status and config """ if not logged_in(): return redirect(url_for('general_routes.home')) lcd = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], LCD, entry='all') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') relayconditional = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], RelayConditional, entry='all') users = db_retrieve_table( current_app.config['USER_DB_PATH'], Users, entry='all') display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').relay if display_order_unsplit: display_order = display_order_unsplit.split(",") else: display_order = [] form_add_relay = flaskforms.AddRelay() form_del_relay = flaskforms.DelRelay() form_mod_relay = flaskforms.ModRelay() form_order_relay = flaskforms.OrderRelay() form_relay_on_off = flaskforms.RelayOnOff() form_add_relay_cond = flaskforms.AddRelayConditional() form_mod_relay_cond = flaskforms.ModRelayConditional() if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'RelayOnOff': flaskutils.relay_on_off(form_relay_on_off) elif form_name == 'addRelay': flaskutils.relay_add(form_add_relay, display_order) elif form_name == 'modRelay': flaskutils.relay_mod(form_mod_relay) elif form_name == 'delRelay': flaskutils.relay_del(form_del_relay, display_order) elif form_name == 'orderRelay': flaskutils.relay_reorder(form_order_relay, display_order) elif form_name == 'addRelayConditional': flaskutils.relay_conditional_add(form_add_relay_cond) elif form_name == 'modRelayConditional': flaskutils.relay_conditional_mod(form_mod_relay_cond) return redirect('/relay') return render_template('pages/relay.html', lcd=lcd, relay=relay, relayconditional=relayconditional, users=users, displayOrder=display_order, form_order_relay=form_order_relay, form_add_relay=form_add_relay, form_mod_relay=form_mod_relay, form_del_relay=form_del_relay, form_relay_on_off=form_relay_on_off, form_add_relay_cond=form_add_relay_cond, form_mod_relay_cond=form_mod_relay_cond)
def page_lcd(): """ Display LCD output settings """ if not logged_in(): return redirect(url_for('general_routes.home')) lcd = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], LCD, entry='all') pid = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], PID, entry='all') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') sensor = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').lcd if display_order_unsplit: display_order = display_order_unsplit.split(",") else: display_order = [] form_activate_lcd = flaskforms.ActivateLCD() form_add_lcd = flaskforms.AddLCD() form_deactivate_lcd = flaskforms.DeactivateLCD() form_del_lcd = flaskforms.DelLCD() form_mod_lcd = flaskforms.ModLCD() form_order_lcd = flaskforms.OrderLCD() form_reset_flashing_lcd = flaskforms.ResetFlashingLCD() if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'orderLCD': flaskutils.lcd_reorder(form_order_lcd, display_order) elif form_name == 'addLCD': flaskutils.lcd_add(form_add_lcd, display_order) elif form_name == 'modLCD': flaskutils.lcd_mod(form_mod_lcd) elif form_name == 'delLCD': flaskutils.lcd_del(form_del_lcd, display_order) elif form_name == 'activateLCD': flaskutils.lcd_activate(form_activate_lcd) elif form_name == 'deactivateLCD': flaskutils.lcd_deactivate(form_deactivate_lcd) elif form_name == 'resetFlashingLCD': flaskutils.lcd_reset_flashing(form_reset_flashing_lcd) return redirect('/lcd') return render_template('pages/lcd.html', lcd=lcd, pid=pid, relay=relay, sensor=sensor, displayOrder=display_order, form_order_lcd=form_order_lcd, form_add_lcd=form_add_lcd, form_mod_lcd=form_mod_lcd, form_del_lcd=form_del_lcd, form_activate_lcd=form_activate_lcd, form_deactivate_lcd=form_deactivate_lcd, form_reset_flashing_lcd=form_reset_flashing_lcd)
def output_mod(output_id, channel_id, state, output_type, amount): """ Manipulate output (using non-unique ID) """ if not utils_general.user_has_permission('edit_controllers'): return 'Insufficient user permissions to manipulate outputs' if channel_id == '0': # some parts of pages don't have access to the channel ID and only know there is 1 channel channel = db_retrieve_table(OutputChannel).filter(and_( OutputChannel.output_id == output_id, OutputChannel.channel == 0)).first() else: channel = db_retrieve_table(OutputChannel, unique_id=channel_id) daemon = DaemonControl() if (state in ['on', 'off'] and output_type in ['sec', 'pwm', 'vol'] and (str_is_float(amount) and float(amount) >= 0)): out_status = daemon.output_on_off( output_id, state, output_type=output_type, amount=float(amount), output_channel=channel.channel) if out_status[0]: return 'ERROR: {}'.format(out_status[1]) else: return 'SUCCESS: {}'.format(out_status[1])
def output_mod(output_id, channel, state, output_type, amount): """ Manipulate output (using non-unique ID) """ if not utils_general.user_has_permission('edit_controllers'): return 'Insufficient user permissions to manipulate outputs' if is_int(channel): # if an integer was returned output_channel = int(channel) else: # if a channel ID was returned channel_dev = db_retrieve_table(OutputChannel).filter( OutputChannel.unique_id == channel).first() if channel_dev: output_channel = channel_dev.channel else: return "Could not determine channel number from channel ID '{}'".format( channel) daemon = DaemonControl() if (state in ['on', 'off'] and str_is_float(amount) and ((output_type in ['sec', 'pwm', 'vol'] and float(amount) >= 0) or (output_type == 'value'))): out_status = daemon.output_on_off(output_id, state, output_type=output_type, amount=float(amount), output_channel=output_channel) if out_status[0]: return 'ERROR: {}'.format(out_status[1]) else: return 'SUCCESS: {}'.format(out_status[1]) else: return 'ERROR: unknown parameters: ' \ 'output_id: {}, channel: {}, state: {}, output_type: {}, amount: {}'.format( output_id, channel, state, output_type, amount)
def page_graph_async(): """ Generate graphs using asynchronous data retrieval """ if not logged_in(): return redirect(url_for('general_routes.home')) sensor = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') sensor_choices = flaskutils.choices_sensors(sensor) sensor_choices_split = OrderedDict() for key, _ in sensor_choices.iteritems(): order = key.split(",") # Separate sensor IDs and measurement types sensor_choices_split.update({order[0]: order[1]}) selected_id = None selected_measure = None if request.method == 'POST': selected_id = request.form['selected_measure'].split(",")[0] selected_measure = request.form['selected_measure'].split(",")[1] return render_template('pages/graph-async.html', sensor=sensor, sensor_choices=sensor_choices, sensor_choices_split=sensor_choices_split, selected_id=selected_id, selected_measure=selected_measure)
def settings_users(): """ Display user settings """ if not logged_in(): return redirect(url_for('general_routes.home')) if session['user_group'] == 'guest': flaskutils.deny_guest_user() return redirect(url_for('general_routes.home')) users = db_retrieve_table( current_app.config['USER_DB_PATH'], Users, entry='all') form_add_user = flaskforms.AddUser() form_mod_user = flaskforms.ModUser() form_del_user = flaskforms.DelUser() if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'addUser': flaskutils.user_add(form_add_user) elif form_name == 'delUser': if flaskutils.user_del(form_del_user) == 'logout': return redirect('/logout') elif form_name == 'modUser': if flaskutils.user_mod(form_mod_user) == 'logout': return redirect('/logout') return redirect('/settings/users') return render_template('settings/users.html', users=users, form_add_user=form_add_user, form_mod_user=form_mod_user, form_del_user=form_del_user)
def parse_custom_option_values_function_channels_json( controllers, dict_controller=None, key_name='custom_channel_options'): # Check if controllers is iterable or a single controller try: _ = iter(controllers) except TypeError: iter_controller = [controllers] # Not iterable else: iter_controller = controllers # iterable custom_options_values = {} for each_controller in iter_controller: if each_controller.function_id not in custom_options_values: custom_options_values[each_controller.function_id] = {} if each_controller.custom_options: custom_options_values[each_controller.function_id][ each_controller.channel] = json.loads( each_controller.custom_options) if dict_controller: # Set default values if option not saved in database entry function = db_retrieve_table(CustomController, unique_id=each_controller.function_id) if not function: continue try: function.unique_id except: continue dev_name = function.device if dev_name in dict_controller and key_name in dict_controller[ dev_name]: dict_custom_options = dict_controller[dev_name][key_name] else: dict_custom_options = {} for each_option in dict_custom_options: if 'id' in each_option and 'default_value' in each_option: if each_controller.channel not in custom_options_values[ each_controller.function_id]: custom_options_values[each_controller.function_id][ each_controller.channel] = {} if each_option['id'] not in custom_options_values[ each_controller.function_id][ each_controller.channel]: # If a select type has cast_value set, cast the value as that type if each_option[ 'type'] == 'select' and 'cast_value' in each_option: if each_option['cast_value'] == 'integer': each_option['default_value'] = int( each_option['default_value']) elif each_option['cast_value'] == 'float': each_option['default_value'] = float( each_option['default_value']) custom_options_values[each_controller.function_id][ each_controller.channel][each_option[ 'id']] = each_option['default_value'] return custom_options_values
def page_timer(): """ Display Timer settings """ if not logged_in(): return redirect(url_for('general_routes.home')) timer = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Timer, entry='all') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') relay_choices = flaskutils.choices_id_name(relay) display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').timer if display_order_unsplit: display_order = display_order_unsplit.split(",") else: display_order = [] form_timer = flaskforms.Timer() if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'addTimer': flaskutils.timer_add(form_timer, request.form['timer_type'], display_order) elif form_name == 'modTimer': if form_timer.timerDel.data: flaskutils.timer_del(form_timer, display_order) elif (form_timer.orderTimerUp.data or form_timer.orderTimerDown.data): flaskutils.timer_reorder(form_timer, display_order) elif form_timer.activate.data: flaskutils.timer_activate(form_timer) elif form_timer.deactivate.data: flaskutils.timer_deactivate(form_timer) elif form_timer.timerMod.data: flaskutils.timer_mod(form_timer) return redirect('/timer') return render_template('pages/timer.html', timer=timer, displayOrder=display_order, relay_choices=relay_choices, form_timer=form_timer)
def get_locale(): misc = db_retrieve_table(app.config['MYCODO_DB_PATH'], Misc, entry='first') if misc.language != '': for key, _ in LANGUAGES.iteritems(): if key == misc.language: return key return request.accept_languages.best_match(LANGUAGES.keys())
def page_export(): """ Export measurement data in CSV format """ if not logged_in(): return redirect(url_for('general_routes.home')) export_options = flaskforms.ExportOptions() relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') sensor = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') relay_choices = flaskutils.choices_id_name(relay) sensor_choices = flaskutils.choices_sensors(sensor) if request.method == 'POST': start_time = export_options.date_range.data.split(' - ')[0] start_seconds = int(time.mktime( time.strptime(start_time, '%m/%d/%Y %H:%M'))) end_time = export_options.date_range.data.split(' - ')[1] end_seconds = int(time.mktime( time.strptime(end_time, '%m/%d/%Y %H:%M'))) url = '/export_data/{meas}/{id}/{start}/{end}'.format( meas=export_options.measurement.data.split(',')[1], id=export_options.measurement.data.split(',')[0], start=start_seconds, end=end_seconds) return redirect(url) # Generate start end end times for date/time picker end_picker = datetime.datetime.now().strftime('%m/%d/%Y %H:%M') start_picker = datetime.datetime.now() - datetime.timedelta(hours=6) start_picker = start_picker.strftime('%m/%d/%Y %H:%M') return render_template('tools/export.html', start_picker=start_picker, end_picker=end_picker, exportOptions=export_options, relay_choices=relay_choices, sensor_choices=sensor_choices)
def page_live(): """ Page of recent and updating sensor data """ if not logged_in(): return redirect(url_for('general_routes.home')) # Retrieve tables for the data displayed on the live page pid = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], PID, entry='all') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') sensor = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') timer = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Timer, entry='all') # Retrieve the display order of the controllers pid_display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').pid if pid_display_order_unsplit: pid_display_order = pid_display_order_unsplit.split(",") else: pid_display_order = [] sensor_display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').sensor if sensor_display_order_unsplit: sensor_display_order = sensor_display_order_unsplit.split(",") else: sensor_display_order = [] # Filter only activated sensors sensor_order_sorted = [] for each_sensor_order in sensor_display_order: for each_sensor in sensor: if (each_sensor_order == each_sensor.id and each_sensor.activated): sensor_order_sorted.append(each_sensor.id) # Retrieve only parent method columns method = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Method) method = method.filter( Method.method_order == 0).all() return render_template('pages/live.html', method=method, pid=pid, relay=relay, sensor=sensor, timer=timer, pidDisplayOrder=pid_display_order, sensorDisplayOrderSorted=sensor_order_sorted)
def register_extensions(_app, config): """ register extensions to the app """ _app.jinja_env.add_extension('jinja2.ext.do') # Global values in jinja # create the databases if needed create_dbs(None, create_all=True, config=config, exit_when_done=False) # attach influx db influx_db.init_app(_app) # Check user option to force all web connections to use SSL misc = db_retrieve_table(_app.config['MYCODO_DB_PATH'], Misc, entry='first') if misc.force_https: SSLify(_app)
def camera_del(form_camera): messages = {"success": [], "info": [], "warning": [], "error": []} camera = db_retrieve_table(Camera, unique_id=form_camera.camera_id.data) if camera.timelapse_started: messages["error"].append( "Cannot delete camera if a time-lapse is currently " "active. Stop the time-lapse and try again.") if not messages["error"]: try: delete_entry_with_id(Camera, form_camera.camera_id.data) messages["success"].append("Camera deleted") except Exception as except_msg: messages["error"].append(except_msg) return messages
def camera_timelapse_video(form_camera): action = "Generate Timelapse Video" error = [] if not os.path.exists("/usr/bin/ffmpeg"): error.append( "ffmpeg not found. Install with 'sudo apt install ffmpeg'") if not error: try: camera = db_retrieve_table(Camera, unique_id=form_camera.camera_id.data) camera_path = assure_path_exists( os.path.join(PATH_CAMERAS, '{uid}'.format(uid=camera.unique_id))) timelapse_path = assure_path_exists( os.path.join(camera_path, 'timelapse')) video_path = assure_path_exists( os.path.join(camera_path, 'timelapse_video')) timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') path_file = os.path.join( video_path, "Video_{name}_{ts}.mp4".format( name=form_camera.timelapse_image_set.data, ts=timestamp)) cmd = "/usr/bin/ffmpeg " \ "-f image2 " \ "-r {fps} " \ "-i {path}/{seq}-%05d.jpg " \ "-vcodec {codec} " \ "-y {save}".format( seq=form_camera.timelapse_image_set.data, fps=form_camera.timelapse_fps.data, path=timelapse_path, codec=form_camera.timelapse_codec.data, save=path_file) subprocess.Popen(cmd, shell=True) flash( "The time-lapse video is being generated in the background with the command:\n" "{}".format(cmd), "success") flash("The video will be saved at " "{}".format(path_file), "success") except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_camera'))
def camera_del(form_camera): action = '{action} {controller}'.format(action=gettext("Delete"), controller=gettext("Camera")) error = [] camera = db_retrieve_table(Camera, device_id=form_camera.camera_id.data) if camera.timelapse_started: error.append("Cannot delete camera if a time-lapse is currently " "using it. Stop the time-lapse and try again.") if not error: try: delete_entry_with_id(Camera, int(form_camera.camera_id.data)) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_settings.settings_camera'))
def method_list(): """ List all methods on one page with a graph for each """ if not logged_in(): return redirect(url_for('general_routes.home')) form_create_method = flaskforms.CreateMethod() # TODO: Move to Flask-SQLAlchemy. This creates errors in the HTTP log: "SQLite objects created in a thread can only be used in that same thread" method = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Method) method_all = method.filter(Method.method_order > 0) method_all = method_all.filter(Method.relay_id == None).all() method = method.filter(Method.method_order == 0).all() return render_template('pages/method-list.html', method=method, method_all=method_all, form_create_method=form_create_method)
def camera_del(form_camera): action = '{action} {controller}'.format( action=TRANSLATIONS['delete']['title'], controller=TRANSLATIONS['camera']['title']) error = [] camera = db_retrieve_table(Camera, unique_id=form_camera.camera_id.data) if camera.timelapse_started: error.append("Cannot delete camera if a time-lapse is currently " "using it. Stop the time-lapse and try again.") if not error: try: delete_entry_with_id(Camera, form_camera.camera_id.data) except Exception as except_msg: error.append(except_msg) flash_success_errors(error, action, url_for('routes_page.page_camera'))
def settings_camera(): """ Display camera settings """ if not logged_in(): return redirect(url_for('general_routes.home')) camera = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], CameraStill, entry='first') form_settings_camera = flaskforms.SettingsCamera() if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'Camera': flaskutils.settings_camera_mod(form_settings_camera) return redirect('/settings/camera') return render_template('settings/camera.html', camera=camera, form_settings_camera=form_settings_camera)
def test_add_sensor_logged_in_as_admin(_, testapp, user_db): """ Verifies behavior of these endpoints for a logged in admin user """ # Create admin user and log in admin_user = create_user(user_db, 'admin', 'name_admin', 'secret_pass') login_user(testapp, admin_user.user_name, 'secret_pass') response = add_sensor(testapp) # Verify success message flashed assert "RPi Sensor with ID" in response assert "successfully added" in response # Verify data was entered into the database sensor = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') for each_sensor in sensor: assert 'RPi' in each_sensor.name, "Sensor name doesn't match: {}".format( each_sensor.name)
def authenticate_cookies(db_path, users): """Check for cookies to authenticate Login""" cookie_username = request.cookies.get('user_name') cookie_password_hash = request.cookies.get('user_pass_hash') if cookie_username is not None: user = db_retrieve_table(db_path, users) user = user.filter(Users.user_name == cookie_username).first() if user is None: return False elif cookie_password_hash == user.user_password_hash: session['logged_in'] = True session['user_group'] = user.user_restriction session['user_name'] = user.user_name session['user_theme'] = user.user_theme return True else: failed_login() return False
def check_database_version_issue(): alembic_version = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], AlembicVersion, entry='all') if len(alembic_version) > 1: flash( "A check of your database indicates there is an issue with your" " database version number. This issue first appeared in early " "4.1.x versions of Mycodo and has since been resolved. However," " even though things may seem okay, this issue prevents your " "database from being upgraded properly. Therefore, if you " "continue to use Mycodo without regenerating your database, you" " will assuredly experience issues. To resolve this issue, move" " your mycodo.db from ~/Mycodo/databases/mycodo.db to a " "different location (or delete it) and a new database will be " "generated in its place. You will need to configure Mycodo from" " scratch, but this is the only way to ensure your database is " "able to be upgraded when the time comes. Sorry for the " "inconvenience.", "error")
def camera_timelapse_video(form_camera): messages = {"success": [], "info": [], "warning": [], "error": []} if not os.path.exists("/usr/bin/ffmpeg"): messages["error"].append( "ffmpeg not found. Install with 'sudo apt install ffmpeg'") if not messages["error"]: try: camera = db_retrieve_table(Camera, unique_id=form_camera.camera_id.data) camera_path = assure_path_exists( os.path.join(PATH_CAMERAS, '{uid}'.format(uid=camera.unique_id))) timelapse_path = assure_path_exists( os.path.join(camera_path, 'timelapse')) video_path = assure_path_exists( os.path.join(camera_path, 'timelapse_video')) timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') path_file = os.path.join( video_path, "Video_{name}_{ts}.mp4".format( name=form_camera.timelapse_image_set.data, ts=timestamp)) cmd = "/usr/bin/ffmpeg " \ "-f image2 " \ "-r {fps} " \ "-i {path}/{seq}-%05d.jpg " \ "-vcodec {codec} " \ "-y {save}".format( seq=form_camera.timelapse_image_set.data, fps=form_camera.timelapse_fps.data, path=timelapse_path, codec=form_camera.timelapse_codec.data, save=path_file) subprocess.Popen(cmd, shell=True) messages["success"].append( "The time-lapse video is being generated in the background with the command: {}." " The video will be saved at {}".format(cmd, path_file)) except Exception as except_msg: messages["error"].append(except_msg) return messages
def settings_general(): """ Display general settings """ if not logged_in(): return redirect(url_for('general_routes.home')) misc = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Misc, entry='first') form_settings_general = flaskforms.SettingsGeneral() languages_sorted = sorted(LANGUAGES.items(), key=operator.itemgetter(1)) if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'General': flaskutils.settings_general_mod(form_settings_general) return redirect('/settings/general') return render_template('settings/general.html', misc=misc, languages=languages_sorted, form_settings_general=form_settings_general)
def settings_alerts(): """ Display alert settings """ if not logged_in(): return redirect(url_for('general_routes.home')) if session['user_group'] == 'guest': flaskutils.deny_guest_user() return redirect('/settings') smtp = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], SMTP, entry='first') form_email_alert = flaskforms.EmailAlert() if request.method == 'POST': form_name = request.form['form-name'] # Update smtp settings table in mycodo SQL database if form_name == 'EmailAlert': flaskutils.settings_alert_mod(form_email_alert) return redirect('/settings/alerts') return render_template('settings/alerts.html', smtp=smtp, form_email_alert=form_email_alert)
def do_login(): """Authenticate users of the web-UI""" if not admin_exists(): return redirect('/create_admin') if logged_in(): flash(gettext("Cannot access login page if you're already logged in"), "error") return redirect(url_for('general_routes.home')) form = flaskforms.Login() form_notice = flaskforms.InstallNotice() misc = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Misc, entry='first') dismiss_notification = misc.dismiss_notification stats_opt_out = misc.stats_opt_out # Check if the user is banned from logging in (too many incorrect attempts) if banned_from_login(): flash( gettext( "Too many failed login attempts. Please wait %(min)s " "minutes before attempting to log in again", min=(int(LOGIN_BAN_SECONDS - session['ban_time_left']) / 60) + 1), "info") else: if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'acknowledge': try: with session_scope(current_app.config['MYCODO_DB_PATH'] ) as db_session: mod_misc = db_session.query(Misc).first() mod_misc.dismiss_notification = 1 db_session.commit() except Exception as except_msg: flash( gettext( "Acknowledgement unable to be saved: " "%(err)s", err=except_msg), "error") elif form_name == 'login' and form.validate_on_submit(): with session_scope( current_app.config['USER_DB_PATH']) as new_session: user = new_session.query(Users).filter( Users.user_name == form.username.data).first() new_session.expunge_all() new_session.close() if not user: login_log( form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'NOUSER') failed_login() elif Users().check_password( form.password.data, user.user_password_hash) == user.user_password_hash: login_log( user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'LOGIN') session['logged_in'] = True session['user_group'] = user.user_restriction session['user_name'] = user.user_name session['user_theme'] = user.user_theme if form.remember.data: response = make_response(redirect('/')) expire_date = datetime.datetime.now() expire_date = expire_date + datetime.timedelta(days=90) response.set_cookie('user_name', user.user_name, expires=expire_date) response.set_cookie('user_pass_hash', user.user_password_hash, expires=expire_date) return response return redirect(url_for('general_routes.home')) else: login_log( user.user_name, user.user_restriction, request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') failed_login() else: login_log( form.username.data, 'NA', request.environ.get('REMOTE_ADDR', 'unknown address'), 'FAIL') failed_login() return redirect('/login') return render_template('login.html', form=form, formNotice=form_notice, dismiss_notification=dismiss_notification, stats_opt_out=stats_opt_out)
def page_sensor(): """ Display sensor settings """ if not logged_in(): return redirect(url_for('general_routes.home')) lcd = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], LCD, entry='all') pid = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], PID, entry='all') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') sensor = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Sensor, entry='all') sensor_conditional = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], SensorConditional, entry='all') users = db_retrieve_table( current_app.config['USER_DB_PATH'], Users, entry='all') display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').sensor if display_order_unsplit: display_order = display_order_unsplit.split(",") else: display_order = [] form_add_sensor = flaskforms.AddSensor() form_mod_sensor = flaskforms.ModSensor() form_mod_sensor_cond = flaskforms.ModSensorConditional() # Create list of file names from the sensor_options directory # Used in generating the correct options for each sensor/device sensor_template_list = [] sensor_path = "{path}/mycodo/mycodo_flask/templates/pages/sensor_options/".format( path=INSTALL_DIRECTORY) for (_, _, file_names) in os.walk(sensor_path): sensor_template_list.extend(file_names) break sensor_templates = [] for each_file_name in sensor_template_list: sensor_templates.append(each_file_name.split(".")[0]) if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'addSensor': flaskutils.sensor_add(form_add_sensor, display_order) elif form_name == 'modSensor': if form_mod_sensor.modSensorSubmit.data: flaskutils.sensor_mod(form_mod_sensor) elif form_mod_sensor.delSensorSubmit.data: flaskutils.sensor_del(form_mod_sensor, display_order) elif (form_mod_sensor.orderSensorUp.data or form_mod_sensor.orderSensorDown.data): flaskutils.sensor_reorder(form_mod_sensor, display_order) elif form_mod_sensor.activateSensorSubmit.data: flaskutils.sensor_activate(form_mod_sensor) elif form_mod_sensor.deactivateSensorSubmit.data: flaskutils.sensor_deactivate(form_mod_sensor) elif form_mod_sensor.sensorCondAddSubmit.data: flaskutils.sensor_conditional_add(form_mod_sensor) elif form_name == 'modSensorConditional': flaskutils.sensor_conditional_mod(form_mod_sensor_cond) return redirect('/sensor') return render_template('pages/sensor.html', lcd=lcd, pid=pid, relay=relay, sensor=sensor, sensor_conditional=sensor_conditional, sensor_templates=sensor_templates, users=users, displayOrder=display_order, form_add_sensor=form_add_sensor, form_mod_sensor=form_mod_sensor, form_mod_sensor_cond=form_mod_sensor_cond)
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 logged_in(): return redirect(url_for('general_routes.home')) form_camera = flaskforms.Camera() camera_enabled = False try: if 'start_x=1' in open('/boot/config.txt').read(): camera_enabled = True else: flash(gettext("Camera support doesn't appear to be enabled. " "Please enable it with 'sudo raspi-config'"), "error") except IOError as e: logger.error("Camera IOError raised in '/camera' endpoint: " "{err}".format(err=e)) # Check if a video stream is active stream_locked = os.path.isfile(LOCK_FILE_STREAM) if stream_locked and not CameraStream().is_running(): os.remove(LOCK_FILE_STREAM) stream_locked = os.path.isfile(LOCK_FILE_STREAM) if request.method == 'POST': form_name = request.form['form-name'] if session['user_group'] == 'guest': flaskutils.deny_guest_user() return redirect('/camera') elif form_name == 'camera': if form_camera.Still.data: if not stream_locked: try: if CameraStream().is_running(): CameraStream().terminate_controller() # Stop camera stream time.sleep(2) camera = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], CameraStill, entry='first') camera_record('photo', camera) except Exception as msg: flash("Camera Error: {}".format(msg), "error") else: flash(gettext("Cannot capture still if stream is active. " "If it is not active, delete %(file)s.", file=LOCK_FILE_STREAM), "error") elif form_camera.StartTimelapse.data: if not stream_locked: # Create lock file and file with time-lapse parameters open(LOCK_FILE_TIMELAPSE, 'a') # Save time-lapse parameters to a csv file to resume # if there is a power outage or reboot. now = time.time() timestamp = datetime.datetime.now().strftime( '%Y-%m-%d_%H-%M-%S') uid_gid = pwd.getpwnam('mycodo').pw_uid timelapse_data = [ ['start_time', timestamp], ['end_time', now + float(form_camera.TimelapseRunTime.data)], ['interval', form_camera.TimelapseInterval.data], ['next_capture', now], ['capture_number', 0]] with open(FILE_TIMELAPSE_PARAM, 'w') as time_lapse_file: write_csv = csv.writer(time_lapse_file) for row in timelapse_data: write_csv.writerow(row) os.chown(FILE_TIMELAPSE_PARAM, uid_gid, uid_gid) os.chmod(FILE_TIMELAPSE_PARAM, 0664) else: flash(gettext("Cannot start time-lapse if a video stream " "is active. If it is not active, delete " "%(file)s.", file=LOCK_FILE_STREAM), "error") elif form_camera.StopTimelapse.data: try: os.remove(FILE_TIMELAPSE_PARAM) os.remove(LOCK_FILE_TIMELAPSE) except IOError as e: logger.error("Camera IOError raised in '/camera' " "endpoint: {err}".format(err=e)) elif form_camera.StartStream.data: if not is_time_lapse_locked(): open(LOCK_FILE_STREAM, 'a') stream_locked = True else: flash(gettext("Cannot start stream if a time-lapse is " "active. If not active, delete %(file)s.", file=LOCK_FILE_TIMELAPSE), "error") elif form_camera.StopStream.data: if CameraStream().is_running(): CameraStream().terminate() if os.path.isfile(LOCK_FILE_STREAM): os.remove(LOCK_FILE_STREAM) stream_locked = False # Get the full path of latest still image try: latest_still_img_full_path = max(glob.iglob( '{path}/camera-stills/*.jpg'.format(path=INSTALL_DIRECTORY)), key=os.path.getmtime) ts = os.path.getmtime(latest_still_img_full_path) latest_still_img_ts = datetime.datetime.fromtimestamp(ts).strftime("%c") latest_still_img = os.path.basename(latest_still_img_full_path) except Exception as e: logger.error( "Exception raised in '/camera' endpoint: {err}".format(err=e)) latest_still_img_ts = None latest_still_img = None # Get the full path of latest timelapse image try: latest_time_lapse_img_full_path = max(glob.iglob( '{path}/camera-timelapse/*.jpg'.format(path=INSTALL_DIRECTORY)), key=os.path.getmtime) ts = os.path.getmtime(latest_time_lapse_img_full_path) latest_time_lapse_img_ts = datetime.datetime.fromtimestamp(ts).strftime("%c") latest_time_lapse_img = os.path.basename( latest_time_lapse_img_full_path) except Exception as e: logger.error( "Exception raised in '/camera' endpoint: {err}".format(err=e)) latest_time_lapse_img_ts = None latest_time_lapse_img = None # If time-lapse active, retrieve parameters for display dict_time_lapse = {} time_now = datetime.datetime.now().strftime('%c') if (os.path.isfile(FILE_TIMELAPSE_PARAM) and os.path.isfile(LOCK_FILE_TIMELAPSE)): with open(FILE_TIMELAPSE_PARAM, mode='r') as infile: reader = csv.reader(infile) dict_time_lapse = OrderedDict((row[0], row[1]) for row in reader) dict_time_lapse['start_time'] = datetime.datetime.strptime( dict_time_lapse['start_time'], "%Y-%m-%d_%H-%M-%S") dict_time_lapse['start_time'] = dict_time_lapse['start_time'].strftime('%c') dict_time_lapse['end_time'] = datetime.datetime.fromtimestamp( float(dict_time_lapse['end_time'])).strftime('%c') dict_time_lapse['next_capture'] = datetime.datetime.fromtimestamp( float(dict_time_lapse['next_capture'])).strftime('%c') return render_template('pages/camera.html', camera_enabled=camera_enabled, form_camera=form_camera, latest_still_img_ts=latest_still_img_ts, latest_still_img=latest_still_img, latest_time_lapse_img_ts=latest_time_lapse_img_ts, latest_time_lapse_img=latest_time_lapse_img, stream_locked=stream_locked, time_lapse_locked=is_time_lapse_locked(), time_now=time_now, tl_parameters_dict=dict_time_lapse)
def page_usage(): """ Display relay usage (duration and energy usage/cost) """ if not logged_in(): return redirect(url_for('general_routes.home')) misc = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Misc, entry='first') relay = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], Relay, entry='all') display_order_unsplit = db_retrieve_table( current_app.config['MYCODO_DB_PATH'], DisplayOrder, entry='first').relay if display_order_unsplit: display_order = display_order_unsplit.split(",") else: display_order = [] # Calculate the number of seconds since the (n)th day of tyhe month # Enables usage/cost assessments to align with a power bill cycle now = datetime.date.today() past_month_seconds = 0 day = misc.relay_stats_dayofmonth if 4 <= day <= 20 or 24 <= day <= 30: date_suffix = 'th' else: date_suffix = ['st', 'nd', 'rd'][day % 10 - 1] if misc.relay_stats_dayofmonth == datetime.datetime.today().day: dt_now = datetime.datetime.now() past_month_seconds = (dt_now - dt_now.replace( hour=0, minute=0, second=0, microsecond=0)).total_seconds() elif misc.relay_stats_dayofmonth > datetime.datetime.today().day: first_day = now.replace(day=1) last_month = first_day - datetime.timedelta(days=1) past_month = last_month.replace(day=misc.relay_stats_dayofmonth) past_month_seconds = (now - past_month).total_seconds() elif misc.relay_stats_dayofmonth < datetime.datetime.today().day: past_month = now.replace(day=misc.relay_stats_dayofmonth) past_month_seconds = (now - past_month).total_seconds() # Calculate relay on duration for different time periods relay_each_duration = {} relay_sum_duration = dict.fromkeys( ['1d', '1w', '1m', '1m-date', '1y'], 0) relay_sum_kwh = dict.fromkeys( ['1d', '1w', '1m', '1m-date', '1y'], 0) for each_relay in relay: relay_each_duration[each_relay.id] = {} relay_each_duration[each_relay.id]['1d'] = sum_relay_usage( each_relay.id, 86400) / 3600 relay_each_duration[each_relay.id]['1w'] = sum_relay_usage( each_relay.id, 604800) / 3600 relay_each_duration[each_relay.id]['1m'] = sum_relay_usage( each_relay.id, 2629743) / 3600 relay_each_duration[each_relay.id]['1m-date'] = sum_relay_usage( each_relay.id, int(past_month_seconds)) / 3600 relay_each_duration[each_relay.id]['1y'] = sum_relay_usage( each_relay.id, 31556926) / 3600 relay_sum_duration['1d'] += relay_each_duration[each_relay.id]['1d'] relay_sum_duration['1w'] += relay_each_duration[each_relay.id]['1w'] relay_sum_duration['1m'] += relay_each_duration[each_relay.id]['1m'] relay_sum_duration['1m-date'] += relay_each_duration[each_relay.id]['1m-date'] relay_sum_duration['1y'] += relay_each_duration[each_relay.id]['1y'] relay_sum_kwh['1d'] += ( misc.relay_stats_volts * each_relay.amps * relay_each_duration[each_relay.id]['1d'] / 1000) relay_sum_kwh['1w'] += ( misc.relay_stats_volts * each_relay.amps * relay_each_duration[each_relay.id]['1w'] / 1000) relay_sum_kwh['1m'] += ( misc.relay_stats_volts * each_relay.amps * relay_each_duration[each_relay.id]['1m'] / 1000) relay_sum_kwh['1m-date'] += ( misc.relay_stats_volts * each_relay.amps * relay_each_duration[each_relay.id]['1m-date'] / 1000) relay_sum_kwh['1y'] += ( misc.relay_stats_volts * each_relay.amps * relay_each_duration[each_relay.id]['1y'] / 1000) return render_template('tools/usage.html', display_order=display_order, misc=misc, relay=relay, relay_each_duration=relay_each_duration, relay_sum_duration=relay_sum_duration, relay_sum_kwh=relay_sum_kwh, date_suffix=date_suffix)
def method_data(method_type, method_id): """ Returns options for a particular method This includes sets of (time, setpoint) data. """ logger.debug('called method_data(method_type={type}, ' 'method_id={id})'.format(type=method_type, id=method_id)) if not logged_in(): return redirect(url_for('general_routes.home')) method = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Method) # First method column with general information about method method_key = method.filter(Method.method_id == method_id) method_key = method_key.filter(Method.method_order == 0).first() # User-edited lines of each method method = method.filter(Method.method_id == method_id) method = method.filter(Method.method_order > 0) method = method.filter(Method.relay_id == None) method = method.order_by(Method.method_order.asc()).all() method_list = [] if method_key.method_type == "Date": for each_method in method: if each_method.end_setpoint == None: end_setpoint = each_method.start_setpoint else: end_setpoint = each_method.end_setpoint start_time = datetime.datetime.strptime(each_method.start_time, '%Y-%m-%d %H:%M:%S') end_time = datetime.datetime.strptime(each_method.end_time, '%Y-%m-%d %H:%M:%S') is_dst = time.daylight and time.localtime().tm_isdst > 0 utc_offset_ms = (time.altzone if is_dst else time.timezone) method_list.append([ (int(start_time.strftime("%s")) - utc_offset_ms) * 1000, each_method.start_setpoint ]) method_list.append([ (int(end_time.strftime("%s")) - utc_offset_ms) * 1000, end_setpoint ]) method_list.append([ (int(start_time.strftime("%s")) - utc_offset_ms) * 1000, None ]) elif method_key.method_type == "Daily": for each_method in method: if each_method.end_setpoint is None: end_setpoint = each_method.start_setpoint else: end_setpoint = each_method.end_setpoint method_list.append([ get_sec(each_method.start_time) * 1000, each_method.start_setpoint ]) method_list.append( [get_sec(each_method.end_time) * 1000, end_setpoint]) method_list.append([get_sec(each_method.start_time) * 1000, None]) elif method_key.method_type == "DailyBezier": points_x = 700 seconds_in_day = 60 * 60 * 24 P0 = (method_key.x0, method_key.y0) P1 = (method_key.x1, method_key.y1) P2 = (method_key.x2, method_key.y2) P3 = (method_key.x3, method_key.y3) for n in range(points_x): percent = n / float(points_x) second_of_day = percent * seconds_in_day y = bezier_curve_y_out(method_key.shift_angle, P0, P1, P2, P3, second_of_day) method_list.append([percent * seconds_in_day * 1000, y]) elif method_key.method_type == "DailySine": points_x = 700 seconds_in_day = 60 * 60 * 24 for n in range(points_x): percent = n / float(points_x) angle = n / float(points_x) * 360 y = sine_wave_y_out(method_key.amplitude, method_key.frequency, method_key.shift_angle, method_key.shift_y, angle) method_list.append([percent * seconds_in_day * 1000, y]) elif method_key.method_type == "Duration": first_entry = True start_duration = 0 end_duration = 0 for each_method in method: if each_method.end_setpoint is None: end_setpoint = each_method.start_setpoint else: end_setpoint = each_method.end_setpoint if first_entry: method_list.append([0, each_method.start_setpoint]) method_list.append([each_method.duration_sec, end_setpoint]) start_duration += each_method.duration_sec first_entry = False else: end_duration = start_duration + each_method.duration_sec method_list.append( [start_duration, each_method.start_setpoint]) method_list.append([end_duration, end_setpoint]) start_duration += each_method.duration_sec return jsonify(method_list)
def method_builder(method_type, method_id): """ Page to edit the details of each method This includes the (time, setpoint) data sets """ logger.debug('called method_builder(method_type={type}, ' 'method_id={id})'.format(type=method_type, id=method_id)) if not logged_in(): return redirect(url_for('general_routes.home')) # Used in software tests to verify function is executing as admin if method_type == '1': return 'admin logged in' if method_type in [ 'Date', 'Duration', 'Daily', 'DailySine', 'DailyBezier', '0' ]: form_create_method = flaskforms.CreateMethod() form_add_method = flaskforms.AddMethod() form_mod_method = flaskforms.ModMethod() # Create new method if method_type == '0': random_id = ''.join([ random.choice(string.ascii_letters + string.digits) for _ in xrange(8) ]) method_id = random_id method_type = form_create_method.method_type.data form_fail = flaskutils.method_create(form_create_method, method_id) if not form_fail: flash( gettext("New Method successfully created. You may now " "add time points"), "success") return redirect('/method-build/{}/{}'.format( method_type, method_id)) else: flash(gettext("Could not create method"), "error") method = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Method) # The single table entry that holds the method type information method_key = method.filter(Method.method_id == method_id) method_key = method_key.filter(Method.method_order == 0).first() # The table entries with time, setpoint, and relay data, sorted by order method_list = method.filter(Method.method_order > 0) method_list = method_list.order_by(Method.method_order.asc()).all() last_end_time = '' last_setpoint = '' if method_type in ['Date', 'Daily']: last_method = method.filter( Method.method_id == method_key.method_id) last_method = last_method.filter(Method.method_order > 0) last_method = last_method.filter(Method.relay_id == None) last_method = last_method.order_by( Method.method_order.desc()).first() # Get last entry end time and setpoint to populate the form if last_method is None: last_end_time = '' last_setpoint = '' else: last_end_time = last_method.end_time if last_method.end_setpoint is not None: last_setpoint = last_method.end_setpoint else: last_setpoint = last_method.start_setpoint # method = db_retrieve_table( # current_app.config['MYCODO_DB_PATH'], Method) relay = db_retrieve_table(current_app.config['MYCODO_DB_PATH'], Relay, entry='all') if request.method == 'POST': form_name = request.form['form-name'] if form_name == 'addMethod': form_fail = flaskutils.method_add(form_add_method, method) elif form_name in ['modMethod', 'renameMethod']: form_fail = flaskutils.method_mod(form_mod_method, method) if (form_name in ['addMethod', 'modMethod', 'renameMethod'] and not form_fail): return redirect('/method-build/{}/{}'.format( method_type, method_id)) return render_template('pages/method-build.html', method=method, relay=relay, method_key=method_key, method_list=method_list, method_id=method_id, method_type=method_type, last_end_time=last_end_time, last_setpoint=last_setpoint, form_create_method=form_create_method, form_add_method=form_add_method, form_mod_method=form_mod_method) return redirect('/method')