backend rework- full test 1
This commit is contained in:
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"spellright.language": [
|
||||||
|
"de",
|
||||||
|
"en"
|
||||||
|
],
|
||||||
|
"spellright.documentTypes": [
|
||||||
|
"markdown",
|
||||||
|
"latex",
|
||||||
|
"plaintext"
|
||||||
|
]
|
||||||
|
}
|
||||||
20
db/README.md
Normal file
20
db/README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# COMMANDS DB description
|
||||||
|
|
||||||
|
## one command contains:
|
||||||
|
- command
|
||||||
|
- status
|
||||||
|
- command_id
|
||||||
|
- tstamp
|
||||||
|
|
||||||
|
### command may contain one out of following strings:
|
||||||
|
```["toggle_machine","","","","","",""]```
|
||||||
|
|
||||||
|
### status may contain one out of following strings:
|
||||||
|
```["pending","failed","served","rejected"]```
|
||||||
|
|
||||||
|
### command_id is a random generated 4 chars long integer to identify the exact command between Server frontend Database and ESP
|
||||||
|
|
||||||
|
### tstamp is the exact Date at the first ever creation of the db entry and does not say anything about completion or rejection.
|
||||||
|
|
||||||
|
## NOTE:
|
||||||
|
### A created command is marked as failed after 5 minutes if its status is still pending, to ensure that no processes continue after a communication failure in the chain.
|
||||||
BIN
db/commands.db
BIN
db/commands.db
Binary file not shown.
@@ -1,3 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Dieses Skript erstellt eine SQLite-Datenbank mit einer Tabelle für Befehle.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
|||||||
29
init.py
Normal file
29
init.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from modules.persistence import save_dict
|
||||||
|
from datetime import datetime, UTC
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
water = {
|
||||||
|
"lastFilled": str(datetime.now(UTC)),
|
||||||
|
"fill": 100,
|
||||||
|
"coffeesOnFill": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
beans = {
|
||||||
|
"lastFilled": str(datetime.now(UTC)),
|
||||||
|
"fill": 100,
|
||||||
|
"coffeesOnFill": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
machine = {
|
||||||
|
"state": "idle",
|
||||||
|
"connected": False,
|
||||||
|
"ready": False,
|
||||||
|
"peding_command": False,
|
||||||
|
"error": False,
|
||||||
|
"lastConnectionProof": str(datetime.now(UTC))
|
||||||
|
}
|
||||||
|
|
||||||
|
# In JSON-Dateien speichern
|
||||||
|
save_dict("water", water)
|
||||||
|
save_dict("beans", beans)
|
||||||
|
save_dict("machine", machine)
|
||||||
23
modules/persistence.py
Normal file
23
modules/persistence.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
BASE_PATH = os.path.join(os.path.dirname(__file__), "..", "persistence")
|
||||||
|
BASE_PATH = os.path.abspath(BASE_PATH)
|
||||||
|
|
||||||
|
|
||||||
|
def save_dict(name, data):
|
||||||
|
path = os.path.join(BASE_PATH, f"{name}.json")
|
||||||
|
os.makedirs(BASE_PATH, exist_ok=True)
|
||||||
|
with open(path, "w") as f:
|
||||||
|
json.dump(data, f, default=str, indent=2)
|
||||||
|
|
||||||
|
def load_dict(name):
|
||||||
|
path = os.path.join(BASE_PATH, f"{name}.json")
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {} # fallback falls Datei fehlt
|
||||||
|
|
||||||
|
# no persistence but global variable important for tracking the esp-connection over runtime
|
||||||
|
esp_conn_infos = {"ip_global": None, "ip_local": None, "last_seen": None, "connection_valid": False}
|
||||||
16
modules/socketio.py
Normal file
16
modules/socketio.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# extensions.py
|
||||||
|
from flask_socketio import SocketIO
|
||||||
|
from modules.persistence import esp_conn_infos,load_dict, save_dict
|
||||||
|
|
||||||
|
socketio = SocketIO(cors_allowed_origins="*", async_mode='threading')
|
||||||
|
|
||||||
|
def resend_static_data():
|
||||||
|
water = load_dict("water")
|
||||||
|
beans = load_dict("beans")
|
||||||
|
machine = load_dict("machine")
|
||||||
|
socketio.emit('static_data', {
|
||||||
|
'water': water,
|
||||||
|
'beans': beans,
|
||||||
|
'machine': machine,
|
||||||
|
'esp_conn_infos': esp_conn_infos
|
||||||
|
})
|
||||||
6
persistence/beans.json
Normal file
6
persistence/beans.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"lastFilled": "2025-05-06 20:56:49.436340+00:00",
|
||||||
|
"fill": 100,
|
||||||
|
"coffeesOnFill": 0,
|
||||||
|
"refilled": 0
|
||||||
|
}
|
||||||
8
persistence/machine.json
Normal file
8
persistence/machine.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"state": "idle",
|
||||||
|
"connected": false,
|
||||||
|
"ready": false,
|
||||||
|
"peding_command": false,
|
||||||
|
"error": false,
|
||||||
|
"lastConnectionProof": "2025-05-06 20:56:49.436343+00:00"
|
||||||
|
}
|
||||||
6
persistence/water.json
Normal file
6
persistence/water.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"lastFilled": "2025-05-06 20:56:49.436328+00:00",
|
||||||
|
"fill": 21,
|
||||||
|
"coffeesOnFill": 0,
|
||||||
|
"refilled": 0
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify
|
from flask import Blueprint, render_template, request, jsonify
|
||||||
import routes.shared as shared
|
|
||||||
from flask import Flask, jsonify, request
|
from flask import Flask, jsonify, request
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
|
from modules.persistence import esp_conn_infos
|
||||||
|
import datetime
|
||||||
|
from modules.socketio import resend_static_data
|
||||||
|
|
||||||
esp = Blueprint('eps', __name__, url_prefix='/unsecure/esp')
|
esp = Blueprint('eps', __name__, url_prefix='/unsecure/esp')
|
||||||
|
|
||||||
@@ -15,11 +17,6 @@ MQTT_BROKER = "localhost" # oder IP/Domain
|
|||||||
MQTT_PORT = 1883
|
MQTT_PORT = 1883
|
||||||
MQTT_TOPIC = "coffee/command"
|
MQTT_TOPIC = "coffee/command"
|
||||||
|
|
||||||
@esp.route('/')
|
|
||||||
def fetch_command():
|
|
||||||
pCd = shared.pending_command
|
|
||||||
shared.reset_command()
|
|
||||||
return jsonify(pCd)
|
|
||||||
|
|
||||||
@esp.route('/online', methods=['POST'])
|
@esp.route('/online', methods=['POST'])
|
||||||
def esp_online():
|
def esp_online():
|
||||||
@@ -27,6 +24,12 @@ def esp_online():
|
|||||||
sender_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
|
sender_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||||
esp_ip = data.get("ip", "unknown")
|
esp_ip = data.get("ip", "unknown")
|
||||||
|
|
||||||
|
esp_conn_infos["ip_local"] = esp_ip
|
||||||
|
esp_conn_infos["ip_global"] = sender_ip
|
||||||
|
esp_conn_infos["last_seen"] = datetime.now()
|
||||||
|
esp_conn_infos["connection_valid"] = True
|
||||||
|
resend_static_data()
|
||||||
|
|
||||||
print(f"ESP ONLINE von IP: {esp_ip}, roher IP: {sender_ip}")
|
print(f"ESP ONLINE von IP: {esp_ip}, roher IP: {sender_ip}")
|
||||||
return jsonify({"status": "ok"})
|
return jsonify({"status": "ok"})
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
pending_command = {'command': None, 'command-URL': None, 'command-expected': None, 'command-expected-URL': None}
|
|
||||||
|
|
||||||
def reset_command():
|
|
||||||
global pending_command
|
|
||||||
pending_command = {
|
|
||||||
'command': None,
|
|
||||||
'command-URL': None,
|
|
||||||
'command-expected': None,
|
|
||||||
'command-expected-topic': None
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,30 @@
|
|||||||
from flask import Blueprint, render_template, request, jsonify
|
from flask import Blueprint, render_template, request, jsonify
|
||||||
import routes.shared as shared
|
|
||||||
unsecure = Blueprint('unsecure', __name__, url_prefix='/unsecure')
|
unsecure = Blueprint('unsecure', __name__, url_prefix='/unsecure')
|
||||||
|
from modules.persistence import load_dict, save_dict
|
||||||
|
from modules.persistence import esp_conn_infos
|
||||||
|
# from flask_socketio import SocketIO
|
||||||
|
from modules.socketio import resend_static_data
|
||||||
|
|
||||||
|
# def resend_static_data():
|
||||||
|
# water = load_dict("water")
|
||||||
|
# beans = load_dict("beans")
|
||||||
|
# machine = load_dict("machine")
|
||||||
|
# socketio.emit('static_data', {
|
||||||
|
# 'water': water,
|
||||||
|
# 'beans': beans,
|
||||||
|
# 'machine': machine,
|
||||||
|
# 'esp_conn_infos': esp_conn_infos
|
||||||
|
# })
|
||||||
|
|
||||||
@unsecure.route('/')
|
@unsecure.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html', title='gimmiCoffee')
|
water = load_dict("water")
|
||||||
|
beans = load_dict("beans")
|
||||||
|
machine = load_dict("machine")
|
||||||
|
print(f"Water: {water}, Beans: {beans}, Machine: {machine}")
|
||||||
|
return render_template('index.html', title='gimmiCoffee', water=water, beans=beans, machine=machine, esp_conn_infos=esp_conn_infos)
|
||||||
|
|
||||||
@unsecure.route('/send')
|
@unsecure.route('/update')
|
||||||
def send_command():
|
def update():
|
||||||
pCd = shared.pending_command
|
resend_static_data()
|
||||||
befehl = request.args.get('befehl')
|
return jsonify({"status": "ok"})
|
||||||
if befehl:
|
|
||||||
pCd['command'] = befehl
|
|
||||||
pCd['command-URL'] = '/unsecure/esp/someURI'
|
|
||||||
pCd.update({'extra': 'test'})
|
|
||||||
print(pCd)
|
|
||||||
return f"Befehl '{pCd}' gespeichert."
|
|
||||||
return "Kein Befehl angegeben.", 400
|
|
||||||
|
|
||||||
# @unsecure.route('/live')
|
|
||||||
# def test():
|
|
||||||
# return render_template('live.html', users=[{'name': 'Max'}, {'name': 'Moritz'}, {'name': 'Hans'}])
|
|
||||||
|
|||||||
86
server.py
86
server.py
@@ -1,19 +1,26 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_socketio import SocketIO
|
|
||||||
from routes.unsecure_routes import unsecure
|
from routes.unsecure_routes import unsecure
|
||||||
from routes.esp_routes import esp
|
from routes.esp_routes import esp
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
import os
|
||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import sqlite3
|
||||||
|
from modules.persistence import load_dict, save_dict, esp_conn_infos
|
||||||
|
from modules.socketio import socketio, resend_static_data
|
||||||
|
|
||||||
MQTT_BROKER = "localhost"
|
MQTT_BROKER = "localhost"
|
||||||
MQTT_PORT = 1883
|
MQTT_PORT = 1883
|
||||||
MQTT_TOPIC_SUB = "coffee/status"
|
MQTT_TOPIC_SUB = "coffee/status"
|
||||||
MQTT_TOPIC_SEND = "coffee/command"
|
MQTT_TOPIC_SEND = "coffee/command"
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__, static_url_path='/unsecure/static')
|
app = Flask(__name__, static_url_path='/unsecure/static')
|
||||||
app.config['SECRET_KEY'] = 'super-secret-key'
|
app.config['SECRET_KEY'] = 'super-secret-key'
|
||||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
|
||||||
|
socketio.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
# Blueprints registrieren
|
# Blueprints registrieren
|
||||||
app.register_blueprint(unsecure)
|
app.register_blueprint(unsecure)
|
||||||
@@ -33,7 +40,7 @@ def on_message(client, userdata, msg):
|
|||||||
'message': msg.payload.decode()
|
'message': msg.payload.decode()
|
||||||
})
|
})
|
||||||
|
|
||||||
# MQTT-Thread starten
|
# MQTT-Thread
|
||||||
def mqtt_thread():
|
def mqtt_thread():
|
||||||
client = mqtt.Client()
|
client = mqtt.Client()
|
||||||
client.on_connect = on_connect
|
client.on_connect = on_connect
|
||||||
@@ -41,24 +48,69 @@ def mqtt_thread():
|
|||||||
client.connect(MQTT_BROKER, MQTT_PORT, 60)
|
client.connect(MQTT_BROKER, MQTT_PORT, 60)
|
||||||
client.loop_forever()
|
client.loop_forever()
|
||||||
|
|
||||||
# Dummy-Daten-Thread
|
# DB-Cleanup-Thread
|
||||||
# def send_data():
|
def cleanup_old_commands():
|
||||||
# counter = 0
|
|
||||||
# while True:
|
|
||||||
# data = {
|
|
||||||
# 'test': 'Live-Daten',
|
|
||||||
# 'status': 'OK',
|
|
||||||
# 'counter': counter
|
|
||||||
# }
|
|
||||||
# socketio.emit('update_data', data)
|
|
||||||
# counter += 1
|
|
||||||
# time.sleep(2)
|
|
||||||
|
|
||||||
# Beide Threads starten
|
db_path = os.path.join(os.path.dirname(__file__), "db", "commands.db")
|
||||||
#threading.Thread(target=send_data, daemon=True).start()
|
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
five_minutes_ago = (datetime.utcnow() - timedelta(minutes=5)).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE commands
|
||||||
|
SET status = 'failed'
|
||||||
|
WHERE status = 'pending' AND tstamp <= ?
|
||||||
|
""", (five_minutes_ago,))
|
||||||
|
|
||||||
|
updated_rows = cursor.rowcount
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if updated_rows > 0:
|
||||||
|
print(f"[Cleanup] {updated_rows} Einträge als 'failed' markiert.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Cleanup-Fehler] {e}")
|
||||||
|
|
||||||
|
time.sleep(60) # jede Minute prüfen ob es pending Einträge gibt, die älter als 5 Minuten sind
|
||||||
|
|
||||||
|
# Clear commands DB
|
||||||
|
def clear_commands_db():
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
db_path = os.path.join(os.path.dirname(__file__), "db", "commands.db")
|
||||||
|
|
||||||
|
if os.path.exists(db_path):
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM commands")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("[DB] commands-Tabelle geleert.")
|
||||||
|
else:
|
||||||
|
print("[DB] Keine Datenbank gefunden – nichts geleert.")
|
||||||
|
|
||||||
|
# Motitior ESP-Connection
|
||||||
|
def monitor_esp_connection():
|
||||||
|
while True:
|
||||||
|
if esp_conn_infos["last_seen"]:
|
||||||
|
time_diff = datetime.now() - esp_conn_infos["last_seen"]
|
||||||
|
if time_diff > timedelta(minutes=30):
|
||||||
|
esp_conn_infos["connection_valid"] = False
|
||||||
|
resend_static_data()
|
||||||
|
time.sleep(60) # einmal pro Minute die Verbindung zum ESP prüfen
|
||||||
|
|
||||||
|
### THREADS START ###
|
||||||
|
threading.Thread(target=cleanup_old_commands, daemon=True).start()
|
||||||
|
threading.Thread(target=monitor_esp_connection, daemon=True).start()
|
||||||
|
|
||||||
#threading.Thread(target=mqtt_thread, daemon=True).start()
|
#threading.Thread(target=mqtt_thread, daemon=True).start()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
#clear_commands_db()
|
||||||
socketio.run(app, host='0.0.0.0', port=3060, allow_unsafe_werkzeug=True)
|
socketio.run(app, host='0.0.0.0', port=3060, allow_unsafe_werkzeug=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,55 @@
|
|||||||
//im empty
|
function gebId(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const water = JSON.parse(document.getElementById("waterData").innerText)
|
||||||
|
const beans = JSON.parse(document.getElementById("beansData").innerText)
|
||||||
|
const machine = JSON.parse(document.getElementById("machineData").innerText)
|
||||||
|
const esp_conn_infos = JSON.parse(document.getElementById("espData").innerText)
|
||||||
|
|
||||||
|
// console.log(water)
|
||||||
|
// console.log(beans)
|
||||||
|
// console.log(machine)
|
||||||
|
// console.log(esp_conn_infos)
|
||||||
|
|
||||||
|
gebId("beans-fill").innerText = beans.fill + "%"
|
||||||
|
gebId("water-fill").innerText = water.fill + "%"
|
||||||
|
gebId("beans-filled").innerText = beans.refilled
|
||||||
|
gebId("water-filled").innerText = water.refilled
|
||||||
|
gebId("ip_global").innerText = esp_conn_infos.ip_global
|
||||||
|
gebId("ip_local").innerText = esp_conn_infos.ip_local
|
||||||
|
gebId("valid_connection").innerText = esp_conn_infos.connection_valid
|
||||||
|
gebId("last_seen").innerText = esp_conn_infos.last_seen
|
||||||
|
|
||||||
|
if (esp_conn_infos.connection_valid) {
|
||||||
|
gebId("validButt").classList.add("deniePress");
|
||||||
|
gebId("machine-status-butt").classList.remove("deniePress");
|
||||||
|
}else {
|
||||||
|
gebId("infoMain").classList.add("blink-orange");
|
||||||
|
}
|
||||||
|
if (water.fill < 20) {
|
||||||
|
gebId("water-fill").parentElement.classList.add("blink-orange");
|
||||||
|
}
|
||||||
|
if (beans.fill < 20) {
|
||||||
|
gebId("beans-fill").parentElement.classList.add("blink-orange");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMachine() {
|
||||||
|
if (gebId("machine-status-butt").classList.contains("deniePress")){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// console.log("toggleMachine");
|
||||||
|
const result = confirm("Möchtest du den Vorgang wirklich ausführen?");
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("toggleMachine");
|
||||||
|
document.getElementById("machine-status").innerText = "PENDING";
|
||||||
|
document.getElementById("machine-status-butt").classList.add("blink-orange");
|
||||||
|
fetch('/unsecure/esp/toggle-machine', { method: 'POST' })
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
31
static/socketio.js
Normal file
31
static/socketio.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
function gebId(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = io();
|
||||||
|
|
||||||
|
socket.on('static_data', (data) => {
|
||||||
|
gebId("beans-fill").innerText = data.beans.fill + "%"
|
||||||
|
gebId("water-fill").innerText = data.water.fill + "%"
|
||||||
|
gebId("beans-filled").innerText = data.beans.refilled
|
||||||
|
gebId("water-filled").innerText = data.water.refilled
|
||||||
|
if (data.water.fill < 20) {
|
||||||
|
gebId("water-fill").parentElement.classList.add("blink-orange");
|
||||||
|
}else {
|
||||||
|
gebId("water-fill").parentElement.classList.remove("blink-orange");
|
||||||
|
}
|
||||||
|
if (data.beans.fill < 20) {
|
||||||
|
gebId("beans-fill").parentElement.classList.add("blink-orange");
|
||||||
|
}else {
|
||||||
|
gebId("beans-fill").parentElement.classList.remove("blink-orange");
|
||||||
|
}
|
||||||
|
if (esp_conn_infos.connection_valid) {
|
||||||
|
gebId("validButt").classList.add("deniePress");
|
||||||
|
gebId("infoMain").classList.remove("blink-orange");
|
||||||
|
gebId("machine-status-butt").classList.remove("deniePress");
|
||||||
|
}else {
|
||||||
|
gebId("validButt").classList.remove("deniePress");
|
||||||
|
gebId("infoMain").classList.add("blink-orange");
|
||||||
|
gebId("machine-status-butt").classList.add("deniePress");
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -115,16 +115,15 @@ header {
|
|||||||
.button-grid {
|
.button-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 30px;
|
gap: 20px;
|
||||||
max-height: 70vh;
|
max-height: 73vh;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-button {
|
.grid-button {
|
||||||
flex: 1 1 calc(50% - 15px);
|
flex: 1 1 calc(50% - 15px);
|
||||||
background: #95a5a6;
|
background: #818e8f;
|
||||||
color: white;
|
/* color: white; */
|
||||||
height: 23vh;
|
height: 22vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -139,7 +138,7 @@ header {
|
|||||||
border: 2px solid #000;
|
border: 2px solid #000;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #f0f0f0;
|
background: #c4c4c4;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,13 +155,13 @@ header {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #2ecc71;
|
background: #5f5f5f;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: rgb(0, 0, 0);
|
color: rgb(255, 255, 255);
|
||||||
flex: none;
|
flex: none;
|
||||||
height: 25vh;
|
height: 25vh;
|
||||||
transition: background 0.3s;
|
transition: background 0.3s;
|
||||||
@@ -170,21 +169,24 @@ header {
|
|||||||
|
|
||||||
|
|
||||||
.clickable:hover {
|
.clickable:hover {
|
||||||
background: #27ae60;
|
background: #505050;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.not:hover {
|
||||||
|
background: #5f5f5f;
|
||||||
|
}
|
||||||
.clickable .top-left-text {
|
.clickable .top-left-text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #000000;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clickable .center-number {
|
.clickable .center-number {
|
||||||
font-size: 48px;
|
font-size: 48px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #222;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.grid-button .top-left-text {
|
.grid-button .top-left-text {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -202,9 +204,11 @@ header {
|
|||||||
color: #222;
|
color: #222;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.deniePress:hover{
|
.defaultGray:hover {
|
||||||
cursor: not-allowed;
|
background: #6a7274;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.blink-orange {
|
.blink-orange {
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
animation: pulse-orange 1.0s infinite;
|
animation: pulse-orange 1.0s infinite;
|
||||||
@@ -219,6 +223,34 @@ header {
|
|||||||
background-color: red;
|
background-color: red;
|
||||||
}
|
}
|
||||||
.initBackRed:Hover{
|
.initBackRed:Hover{
|
||||||
background-color: rgb(255, 37, 37);
|
background-color: rgb(252, 47, 47);
|
||||||
}
|
}
|
||||||
|
.deniePress:hover{
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.initBackRed:hover.deniePress {
|
||||||
|
background-color: red !important;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.validationButtonOuter{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.validButt{
|
||||||
|
background-color: green;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
.validButt:hover{
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgb(0, 151, 0);
|
||||||
|
}
|
||||||
|
.validButt:hover.deniePress{
|
||||||
|
background-color: green !important;
|
||||||
|
}
|
||||||
|
.deniePress:hover{
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
<link rel="icon" href="{{ url_for('static', filename='gimmiCoffee_Logo.png') }}" type="image/png">
|
<link rel="icon" href="{{ url_for('static', filename='gimmiCoffee_Logo.png') }}" type="image/png">
|
||||||
|
<script src="https://cdn.socket.io/4.6.1/socket.io.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
@@ -16,23 +17,43 @@
|
|||||||
<a href="/logout" class="logout">Logout</a>
|
<a href="/logout" class="logout">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
<div id="waterData" style="display: none;">{{ water | tojson }}</div>
|
||||||
|
<div id="beansData" style="display: none;">{{ beans | tojson }}</div>
|
||||||
|
<div id="machineData" style="display: none;">{{ machine | tojson }}</div>
|
||||||
|
<div id="espData" style="display: none;">{{ esp_conn_infos | tojson }}</div>
|
||||||
<main class="main-container">
|
<main class="main-container">
|
||||||
|
|
||||||
<section class="left">
|
<section class="left">
|
||||||
<div class="info">ESP-Conn Infos</div>
|
<div class="info" id="infoMain">
|
||||||
|
<div style="flex: 1; display: flex; align-items: center;">
|
||||||
|
<strong>ESP-Connection Infos</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<p><strong>Global IP:</strong> <span id="ip_global">–</span></p>
|
||||||
|
<p><strong>Local IP:</strong> <span id="ip_local">–</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="flex: 1;">
|
||||||
|
<p><strong>Valid Connection:</strong> <span id="valid_connection">–</span></p>
|
||||||
|
<p><strong>Last Seen:</strong> <span id="last_seen">–</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="validationButtonOuter">
|
||||||
|
<div onclick="tryValidateConnection()" class="validButt" id="validButt">Validate Connection</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="button-grid">
|
<div class="button-grid">
|
||||||
<div class="grid-button deniePress">
|
<div class="grid-button deniePress">
|
||||||
<div class="top-left-text">Kaffeeee</div>
|
<div class="top-left-text">Kaffeeee</div>
|
||||||
<div class="center-number">Kaffee Machen</div>
|
<div class="center-number">Kaffee Machen</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-button initBackRed" id="machine-status-butt" onclick="toggleMachine()">
|
<div class="grid-button initBackRed deniePress" id="machine-status-butt" onclick="toggleMachine()">
|
||||||
<div class="top-left-text">Maschine</div>
|
<div class="top-left-text">Maschine</div>
|
||||||
<div class="center-number" id="machine-status">AUS</div>
|
<div class="center-number" id="machine-status">AUS</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-button deniePress">
|
<div class="grid-button deniePress">
|
||||||
<div class="top-left-text">Maschiene</div>
|
<div class="top-left-text">Maschine</div>
|
||||||
<div class="center-number">Nicht Bereit</div>
|
<div class="center-number">Nicht Bereit</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -40,13 +61,13 @@
|
|||||||
<div class="top-left-text">Fehler</div>
|
<div class="top-left-text">Fehler</div>
|
||||||
<div class="center-number">Wasser leer?</div>
|
<div class="center-number">Wasser leer?</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-button deniePress">
|
<div class="grid-button defaultGray">
|
||||||
<div class="top-left-text">Wasser</div>
|
<div class="top-left-text">Wasser</div>
|
||||||
<div class="center-number">Aufgefüllt</div>
|
<div class="center-number">Nachgefüllt?</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-button deniePress">
|
<div class="grid-button defaultGray">
|
||||||
<div class="top-left-text">Bohnen</div>
|
<div class="top-left-text">Bohnen</div>
|
||||||
<div class="center-number">Nachgefüllt</div>
|
<div class="center-number">Nachgefüllt?</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -59,34 +80,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="clickable">
|
<div class="clickable">
|
||||||
<div class="top-left-text">Bohnen Füllstand</div>
|
<div class="top-left-text">Bohnen Füllstand</div>
|
||||||
<div class="center-number">XX%</div>
|
<div class="center-number" id="beans-fill">XX%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clickable">
|
<div class="clickable">
|
||||||
<div class="top-left-text">Wasser Füllstand</div>
|
<div class="top-left-text">Wasser Füllstand</div>
|
||||||
<div class="center-number">XX%</div>
|
<div class="center-number" id="water-fill">XX%</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clickable">
|
<div class="clickable not">
|
||||||
<div class="top-left-text">Bohnen nachgefüllt</div>
|
<div class="top-left-text">Bohnen nachgefüllt</div>
|
||||||
<div class="center-number">XX</div>
|
<div class="center-number" id="beans-filled">XX</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="clickable">
|
<div class="clickable not">
|
||||||
<div class="top-left-text">Wasser nachgefüllt</div>
|
<div class="top-left-text">Wasser nachgefüllt</div>
|
||||||
<div class="center-number">XX</div>
|
<div class="center-number" id="water-filled">XX</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
<script>
|
|
||||||
function toggleMachine() {
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
document.getElementById("machine-status").innerText = "PENDING";
|
<script src="{{ url_for('static', filename='socketio.js') }}"></script>
|
||||||
document.getElementById("machine-status-butt").classList.add("blink-orange");
|
|
||||||
fetch('/unsecure/esp/toggle-machine', { method: 'POST' })
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
@@ -94,4 +107,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--<script src="{{ url_for('static', filename='script.js') }}"></script>-->
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Live Update</title>
|
|
||||||
<script src="https://cdn.socket.io/4.6.1/socket.io.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h2>Daten vom Server</h2>
|
|
||||||
<p><strong>Test:</strong> <span id="test"></span></p>
|
|
||||||
<p><strong>Status:</strong> <span id="status"></span></p>
|
|
||||||
<p><strong>Counter:</strong> <span id="counter"></span></p>
|
|
||||||
<h1>Benutzer</h1>
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li>{{ user.name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<script>
|
|
||||||
const socket = io();
|
|
||||||
|
|
||||||
socket.on('update_data', (data) => {
|
|
||||||
document.getElementById('test').textContent = data.test;
|
|
||||||
document.getElementById('status').textContent = data.status;
|
|
||||||
document.getElementById('counter').textContent = data.counter;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user