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 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
|
||||
import routes.shared as shared
|
||||
from flask import Flask, jsonify, request
|
||||
import paho.mqtt.client as mqtt
|
||||
import json
|
||||
import random
|
||||
import sqlite3
|
||||
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')
|
||||
|
||||
@@ -15,11 +17,6 @@ MQTT_BROKER = "localhost" # oder IP/Domain
|
||||
MQTT_PORT = 1883
|
||||
MQTT_TOPIC = "coffee/command"
|
||||
|
||||
@esp.route('/')
|
||||
def fetch_command():
|
||||
pCd = shared.pending_command
|
||||
shared.reset_command()
|
||||
return jsonify(pCd)
|
||||
|
||||
@esp.route('/online', methods=['POST'])
|
||||
def esp_online():
|
||||
@@ -27,6 +24,12 @@ def esp_online():
|
||||
sender_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
|
||||
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}")
|
||||
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
|
||||
import routes.shared as shared
|
||||
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('/')
|
||||
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')
|
||||
def send_command():
|
||||
pCd = shared.pending_command
|
||||
befehl = request.args.get('befehl')
|
||||
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'}])
|
||||
@unsecure.route('/update')
|
||||
def update():
|
||||
resend_static_data()
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
86
server.py
86
server.py
@@ -1,19 +1,26 @@
|
||||
from flask import Flask
|
||||
from flask_socketio import SocketIO
|
||||
from routes.unsecure_routes import unsecure
|
||||
from routes.esp_routes import esp
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
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_PORT = 1883
|
||||
MQTT_TOPIC_SUB = "coffee/status"
|
||||
MQTT_TOPIC_SEND = "coffee/command"
|
||||
|
||||
|
||||
app = Flask(__name__, static_url_path='/unsecure/static')
|
||||
app.config['SECRET_KEY'] = 'super-secret-key'
|
||||
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
|
||||
|
||||
socketio.init_app(app)
|
||||
|
||||
|
||||
# Blueprints registrieren
|
||||
app.register_blueprint(unsecure)
|
||||
@@ -33,7 +40,7 @@ def on_message(client, userdata, msg):
|
||||
'message': msg.payload.decode()
|
||||
})
|
||||
|
||||
# MQTT-Thread starten
|
||||
# MQTT-Thread
|
||||
def mqtt_thread():
|
||||
client = mqtt.Client()
|
||||
client.on_connect = on_connect
|
||||
@@ -41,24 +48,69 @@ def mqtt_thread():
|
||||
client.connect(MQTT_BROKER, MQTT_PORT, 60)
|
||||
client.loop_forever()
|
||||
|
||||
# Dummy-Daten-Thread
|
||||
# def send_data():
|
||||
# counter = 0
|
||||
# while True:
|
||||
# data = {
|
||||
# 'test': 'Live-Daten',
|
||||
# 'status': 'OK',
|
||||
# 'counter': counter
|
||||
# }
|
||||
# socketio.emit('update_data', data)
|
||||
# counter += 1
|
||||
# time.sleep(2)
|
||||
# DB-Cleanup-Thread
|
||||
def cleanup_old_commands():
|
||||
|
||||
# Beide Threads starten
|
||||
#threading.Thread(target=send_data, daemon=True).start()
|
||||
db_path = os.path.join(os.path.dirname(__file__), "db", "commands.db")
|
||||
|
||||
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()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#clear_commands_db()
|
||||
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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 30px;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
gap: 20px;
|
||||
max-height: 73vh;
|
||||
}
|
||||
|
||||
.grid-button {
|
||||
flex: 1 1 calc(50% - 15px);
|
||||
background: #95a5a6;
|
||||
color: white;
|
||||
height: 23vh;
|
||||
background: #818e8f;
|
||||
/* color: white; */
|
||||
height: 22vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -139,7 +138,7 @@ header {
|
||||
border: 2px solid #000;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
background: #f0f0f0;
|
||||
background: #c4c4c4;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -156,13 +155,13 @@ header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
background: #2ecc71;
|
||||
background: #5f5f5f;
|
||||
font-family: Arial, sans-serif;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgb(0, 0, 0);
|
||||
color: rgb(255, 255, 255);
|
||||
flex: none;
|
||||
height: 25vh;
|
||||
transition: background 0.3s;
|
||||
@@ -170,21 +169,24 @@ header {
|
||||
|
||||
|
||||
.clickable:hover {
|
||||
background: #27ae60;
|
||||
background: #505050;
|
||||
cursor: pointer;
|
||||
}
|
||||
.not:hover {
|
||||
background: #5f5f5f;
|
||||
}
|
||||
.clickable .top-left-text {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 12px;
|
||||
font-size: 24px;
|
||||
color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.clickable .center-number {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
color: #ffffff;
|
||||
}
|
||||
.grid-button .top-left-text {
|
||||
position: absolute;
|
||||
@@ -202,9 +204,11 @@ header {
|
||||
color: #222;
|
||||
text-align: center;
|
||||
}
|
||||
.deniePress:hover{
|
||||
cursor: not-allowed;
|
||||
.defaultGray:hover {
|
||||
background: #6a7274;
|
||||
|
||||
}
|
||||
|
||||
.blink-orange {
|
||||
background-color: orange;
|
||||
animation: pulse-orange 1.0s infinite;
|
||||
@@ -219,6 +223,34 @@ header {
|
||||
background-color: red;
|
||||
}
|
||||
.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>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
<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>
|
||||
<body>
|
||||
<header>
|
||||
@@ -16,23 +17,43 @@
|
||||
<a href="/logout" class="logout">Logout</a>
|
||||
</div>
|
||||
</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">
|
||||
|
||||
<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="grid-button deniePress">
|
||||
<div class="top-left-text">Kaffeeee</div>
|
||||
<div class="center-number">Kaffee Machen</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="center-number" id="machine-status">AUS</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
@@ -40,13 +61,13 @@
|
||||
<div class="top-left-text">Fehler</div>
|
||||
<div class="center-number">Wasser leer?</div>
|
||||
</div>
|
||||
<div class="grid-button deniePress">
|
||||
<div class="grid-button defaultGray">
|
||||
<div class="top-left-text">Wasser</div>
|
||||
<div class="center-number">Aufgefüllt</div>
|
||||
<div class="center-number">Nachgefüllt?</div>
|
||||
</div>
|
||||
<div class="grid-button deniePress">
|
||||
<div class="grid-button defaultGray">
|
||||
<div class="top-left-text">Bohnen</div>
|
||||
<div class="center-number">Nachgefüllt</div>
|
||||
<div class="center-number">Nachgefüllt?</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -59,34 +80,26 @@
|
||||
</div>
|
||||
<div class="clickable">
|
||||
<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 class="clickable">
|
||||
<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 class="clickable">
|
||||
<div class="clickable not">
|
||||
<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 class="clickable">
|
||||
<div class="clickable not">
|
||||
<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>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<script>
|
||||
function 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);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='socketio.js') }}"></script>
|
||||
</body>
|
||||
</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