hakkoso/app.py

177 lines
5.0 KiB
Python

import json
from flask import Flask, Response
from flask import render_template, render_template_string, escape
from flask import request, send_file
from flask import jsonify
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
from scm import stringify
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/<id>')
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/<filename>')
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('/sensors.json')
def get_sensors():
return jsonify(sensors.get_history())
@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, notify=False)
socketio.emit('eval output', stringify(output, quote=False))
@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)
last_scan = 0
while True:
# This should be more precise than sleep(0.5) and gives the OS the
# control to do other things
sleep(0.01)
if perf_counter() - last_scan > scantime:
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
last_scan = perf_counter()
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')