import json from flask import Flask, Response from flask import render_template, render_template_string, escape from flask import request, send_file from flask_socketio import SocketIO, join_room from random import randint from urllib.parse import urlparse, urlunparse from os.path import exists from phasectrl import State, ManualIntervention from sensors import sensors import phasectrl from time import perf_counter, sleep def create_app(): app = Flask('hakkoso') app.config['DATABASE'] = 'server.db' return app app = create_app() socketio = SocketIO(app, async_mode='threading') def updateState(): socketio.emit('state', make_state()) # print('SENT STATE UPDATE', make_state()) statemachine = State('recipes.json', updateState) def make_state(): return { 'manual': statemachine.manual.getState(), # TODO: Add current recipe time, consumption 'recipes': tuple(r.getState() for r in statemachine.recipes), # TODO: Add current phase time, consumption 'recipe': (statemachine.recipe.getState(statemachine.phase) if statemachine.recipe else None), 'state': statemachine.getState(), 'sensors': { 'units': { 'Temperature': '°C', 'Acidity': 'pH', 'Humidity': 'RH', 'Durezza Acqua': 'BOH', 'Switch': 'On/Off', }, 'found': sensors.list() }, 'actuators': { 'consumption': statemachine.get_total_consumption() } } @app.route('/') def index(): return render_template('index.html') @app.route('/edit-recipe/') def recipe_editor(id): return render_template('recipe-editor.html', recipe=statemachine.recipeById(id)) @app.route('/edit-recipe/') def recipes_list(): return render_template('recipes-list.html', recipes=statemachine.recipes) @app.route('/status') def status(): return make_state() @app.route('/dist/') def dist(filename): # TODO: check file exists and path is under src filename = 'dist/' + filename if exists(filename): return send_file(filename) print('Missing', filename) return '' @app.route('/recipe') def recipe(): return render_template('recipe.html', recipe=phasectrl.recipe) @socketio.on('new client') def handle_new_client(): updateState() @socketio.on('eval scheme') def eval_scheme(code): output = phasectrl.safe_eval(code, statemachine.env) phasectrl.safe_eval(f'(notify "{output}")', statemachine.env) socketio.emit('eval output', output) @socketio.on('get sensors history') def send_sensors_history(elements=1000): socketio.emit('sensor history', sensors.get_history(elements)) @socketio.on('manual response') def handle_manual_response(response): if statemachine.recipe is not None: statemachine.manual.set(response) updateState() @socketio.on('load recipe idx') def load_recipe_idx(idx): statemachine.loadByIdx(idx) @socketio.on('update recipe phase') def update_recipe_phase(recipeid, phaseid, content): recipe = statemachine.recipeById(recipeid) recipe.phases[phaseid] = phasectrl.load_phase(content) statemachine.recipes[recipeid] = recipe phasectrl.store_recipes("recipes.json", [r.getState() for r in statemachine.recipes]) @socketio.on('stop recipe') def stop_recipe(): statemachine.stop() @socketio.on('next phase') def next_phase(): done = statemachine.next() is None if done: statemachine.done() @socketio.on('reload recipes') def reload_recipes(): statemachine.reloadRecipes() updateState() def run_recipes(): while True: while statemachine.recipe is not None: done = statemachine.run_step() if done: statemachine.done() sleep(1) def read_sensors(): periodic_counter = 0 scantime = 0.5 periodic_state_every_s = 5 periodic_state_every_loop = int(periodic_state_every_s/scantime) while True: sensors.read() socketio.emit('sensors', (sensors.get(),)) if periodic_counter % periodic_state_every_loop == 0: ## peridoic refresh other data, too, like: # - current phase (time in this phase) # - actuators (consumption) # Issuing a full state update is easiest updateState() periodic_counter += 1 sleep(scantime) import threading if __name__ == '__main__': thread = threading.Thread(target=run_recipes, daemon=True) thread.start() sensors_thread = threading.Thread(target=read_sensors, daemon=True) sensors_thread.start() print('RUN APP') socketio.run(app, host='0.0.0.0')