Add Timestamp Tracking

This commit is contained in:
Tim
2026-06-11 11:05:22 +02:00
parent 507673a8c8
commit 1096bea997
4 changed files with 159 additions and 66 deletions
+35 -21
View File
@@ -3,6 +3,7 @@ import json
import threading import threading
from flask import Flask, render_template from flask import Flask, render_template
import os import os
import datetime # Import für Datums- und Zeitstempel
# ========================================================== # ==========================================================
@@ -10,15 +11,13 @@ import os
# ========================================================== # ==========================================================
CHIP_NAMES_FILE = "data.json" CHIP_NAMES_FILE = "data.json"
STATE_SAVE_FILE = "current_status.json" # Die Datei, in die der Zustand geschrieben wird STATE_SAVE_FILE = "current_status.json"
MQTT_TOPIC = None MQTT_TOPIC = None
MQTT_BROKER = None MQTT_BROKER = None
MQTT_PORT = None MQTT_PORT = None
# Global State Management: Schlüssel ist die eindeutige ID.
RFID_STATUS = {} RFID_STATUS = {}
status_lock = threading.Lock() status_lock = threading.Lock()
app = Flask(__name__) app = Flask(__name__)
@@ -42,37 +41,49 @@ def load_config():
print(f"✅ MQTT-Einstellungen geladen: Broker={MQTT_BROKER}, Topic={MQTT_TOPIC}") print(f"✅ MQTT-Einstellungen geladen: Broker={MQTT_BROKER}, Topic={MQTT_TOPIC}")
# 2. Chip-Status initialisieren # 2. Chip-Status initialisieren (Hier wird der Time Active Wert aus der JSON gelesen)
if "chips" not in data or not isinstance(data["chips"], list):
raise ValueError("Die JSON-Struktur ist falsch. Fehlendes 'chips' Key.")
for chip_data in data["chips"]: for chip_data in data["chips"]:
chip_id = str(chip_data.get('id', 'UNKNOWN')) chip_id = str(chip_data.get('id', 'UNKNOWN'))
chip_name = str(chip_data.get('name', 'N/A')) chip_name = str(chip_data.get('name', 'N/A'))
initial_status = int(str(chip_data.get('status', '0'))) initial_status = int(str(chip_data.get('status', '0')))
time_active = chip_data.get('time_active')
if time_active:
# Wir speichern den Wert aus der JSON, um ihn beim Start zu laden
RFID_STATUS[chip_id] = { RFID_STATUS[chip_id] = {
"Name": chip_name, "Name": chip_name,
"ID": chip_id, "ID": chip_id,
"Status": initial_status "Status": initial_status,
"Time_Active": time_active
}
else:
# Fallback: Wenn kein Zeitstempel gefunden wird, setze ihn auf jetzt.
RFID_STATUS[chip_id] = {
"Name": chip_name,
"ID": chip_id,
"Status": initial_status,
"Time_Active": datetime.datetime.now().isoformat()
} }
print(f"✅ Starte mit {len(RFID_STATUS)} Chips, geladen aus {CHIP_NAMES_FILE}.") print(f"✅ Starte mit {len(RFID_STATUS)} Chips.")
return True return True
except FileNotFoundError: except FileNotFoundError:
# ... (Error Handling bleibt gleich) ...
print("❌ KRITISCHER FEHLER: Die Datei data.json wurde nicht gefunden!") print("❌ KRITISCHER FEHLER: Die Datei data.json wurde nicht gefunden!")
print("Bitte erstellen Sie diese Datei und verwenden Sie die korrekte Struktur.") print("Bitte erstellen Sie diese Datei und verwenden Sie die korrekte Struktur.")
return False return False
except json.JSONDecodeError: except json.JSONDecodeError:
# ... (Error Handling bleibt gleich) ...
print(f"❌ KRITISCHER FEHLER: Die Datei {CHIP_NAMES_FILE} ist kein gültiges JSON!") print(f"❌ KRITISCHER FEHLER: Die Datei {CHIP_NAMES_FILE} ist kein gültiges JSON!")
return False return False
except ValueError as e: except ValueError as e:
# ... (Error Handling bleibt gleich) ...
print(f"❌ KONFIGURIERUNGSFEHLER: {e}") print(f"❌ KONFIGURIERUNGSFEHLER: {e}")
return False return False
# ========================================================== # ==========================================================
# 💾 Funktion zur Zustandsspeicherung (Korrigierter Block) # 💾 Funktion zur Zustandsspeicherung (Speichert Status UND Zeit)
# ========================================================== # ==========================================================
def save_state(): def save_state():
@@ -83,24 +94,23 @@ def save_state():
{ {
"name": data['Name'], "name": data['Name'],
"id": data['ID'], "id": data['ID'],
"status": str(data['Status']) "status": str(data['Status']),
"time_active": data['Time_Active'] # Speichern des Zeitstempels
} for data in RFID_STATUS.values() } for data in RFID_STATUS.values()
] ]
} }
try: try:
with status_lock: with status_lock:
# Innerstes Level der Indentation ist hier entscheidend!
with open(STATE_SAVE_FILE, 'w', encoding='utf-8') as f: with open(STATE_SAVE_FILE, 'w', encoding='utf-8') as f:
json.dump(data_to_save, f, indent=4) json.dump(data_to_save, f, indent=4)
print(f"\n[💾 SPEICHERT] Status erfolgreich in {STATE_SAVE_FILE} gespeichert.") print(f"\n[💾 SPEICHERT] Status erfolgreich in {STATE_SAVE_FILE} gespeichert.")
except Exception as e: except Exception as e:
# Dieser Block muss korrekt unter dem 'try' stehen und einen Fehler abfangen.
print(f"[FEHLER!] Konnte den Zustand nicht speichern: {e}") print(f"[FEHLER!] Konnte den Zustand nicht speichern: {e}")
# ========================================================== # ==========================================================
# ⚙️ Angepasste Funktion (Status-Update und Logging) # ⚙️ Angepasste Funktion (Status-Update, Logging & Time Stamping)
# ========================================================== # ==========================================================
def update_status(chip_id): def update_status(chip_id):
@@ -114,9 +124,10 @@ def update_status(chip_id):
with status_lock: with status_lock:
old_status = RFID_STATUS[chip_id]['Status'] old_status = RFID_STATUS[chip_id]['Status']
new_status = 1 - old_status new_status = 1 - old_status
RFID_STATUS[chip_id]['Status'] = new_status
# Logging der Änderung und Speichern des Zustands # Überprüfung, ob sich der Status tatsächlich geändert hat
if new_status != old_status:
logging_message = ""
if old_status == 0 and new_status == 1: if old_status == 0 and new_status == 1:
logging_message = "✅ AKTIVIERUNG DETEKTIERT! (Von Inaktiv zu Aktiv)" logging_message = "✅ AKTIVIERUNG DETEKTIERT! (Von Inaktiv zu Aktiv)"
print(f"\n[!!! STATUS GEÄNDERT !!!] {logging_message} Chip ID '{chip_id}'. Neuer Status: 1.") print(f"\n[!!! STATUS GEÄNDERT !!!] {logging_message} Chip ID '{chip_id}'. Neuer Status: 1.")
@@ -124,12 +135,16 @@ def update_status(chip_id):
logging_message = "⚠️ DEAKTIVIERUNG DETEKTIERT! (Von Aktiv zu Inaktiv)" logging_message = "⚠️ DEAKTIVIERUNG DETEKTIERT! (Von Aktiv zu Inaktiv)"
print(f"\n[!!! STATUS GEÄNDERT !!!] {logging_message} Chip ID '{chip_id}'. Neuer Status: 0.") print(f"\n[!!! STATUS GEÄNDERT !!!] {logging_message} Chip ID '{chip_id}'. Neuer Status: 0.")
# Speichern des Zustandes nach jeder Änderung # Wichtig: Status und Zeitstempel aktualisieren
RFID_STATUS[chip_id]['Status'] = new_status
RFID_STATUS[chip_id]['Time_Active'] = datetime.datetime.now().isoformat()
save_state() save_state()
# ========================================================== # ==========================================================
# MQTT LOGIK (Verwendet die globalen Variablen) # MQTT LOGIK (Bleibt unverändert)
# ========================================================== # ==========================================================
def on_connect(client, userdata, flags, rc): def on_connect(client, userdata, flags, rc):
@@ -155,14 +170,14 @@ def on_message(client, userdata, msg):
def run_mqtt_client(): def run_mqtt_client():
"""Startet den MQTT-Client im Hintergrundthread mit den konfigurierten Daten.""" """Startet den MQTT-Client im Hintergrundthread."""
# ... (Code bleibt gleich) ...
client = mqtt.Client("RFID_Tracker") client = mqtt.Client("RFID_Tracker")
client.on_connect = on_connect client.on_connect = on_connect
client.on_message = on_message client.on_message = on_message
print("\n[MQTT] Starte Verbindung zum Broker...") print("\n[MQTT] Starte Verbindung zum Broker...")
try: try:
# Nutzung der globalen Variablen (die aus data.json kommen)
client.connect(MQTT_BROKER, MQTT_PORT, 60) client.connect(MQTT_BROKER, MQTT_PORT, 60)
client.loop_start() client.loop_start()
except Exception as e: except Exception as e:
@@ -198,5 +213,4 @@ if __name__ == '__main__':
print("============================================================") print("============================================================")
print(f"Statusseite läuft unter: http://0.0.0.0:{MQTT_PORT}/") print(f"Statusseite läuft unter: http://0.0.0.0:{MQTT_PORT}/")
# Start des Webservers auf allen Interfaces (für externen Zugriff)
app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False) app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False)
+8 -4
View File
@@ -3,22 +3,26 @@
{ {
"name": "1", "name": "1",
"id": "729558387180", "id": "729558387180",
"status": "0" "status": "0",
"time_active": "2026-06-10T13:07:21.722653"
}, },
{ {
"name": "2", "name": "2",
"id": "987572218311", "id": "987572218311",
"status": "0" "status": "0",
"time_active": "2026-06-10T13:07:21.722681"
}, },
{ {
"name": "3", "name": "3",
"id": "842310768930", "id": "842310768930",
"status": "0" "status": "0",
"time_active": "2026-06-10T13:07:21.722690"
}, },
{ {
"name": "4", "name": "4",
"id": "773910059391", "id": "773910059391",
"status": "0" "status": "0",
"time_active": "2026-06-10T13:07:21.722698"
} }
] ]
} }
+8 -4
View File
@@ -8,22 +8,26 @@
{ {
"name": "1", "name": "1",
"id": "729558387180", "id": "729558387180",
"status": "0" "status": "0",
"time_active": ""
}, },
{ {
"name": "2", "name": "2",
"id": "987572218311", "id": "987572218311",
"status": "0" "status": "0",
"time_active": ""
}, },
{ {
"name": "3", "name": "3",
"id": "842310768930", "id": "842310768930",
"status": "0" "status": "0",
"time_active": ""
}, },
{ {
"name": "4", "name": "4",
"id": "773910059391", "id": "773910059391",
"status": "0" "status": "0",
"time_active": ""
} }
] ]
} }
+95 -24
View File
@@ -4,17 +4,18 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RFID Status Tracker</title> <title>RFID Status Tracker</title>
<!-- (Ihr gesamter CSS Block bleibt hier unverändert) -->
<style> <style>
body { body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 20px; margin: 20px;
background-color: #f4f7fa; /* Heller Hintergrund */ background-color: #f4f7fa;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.container { .container {
width: 100%; width: 100%;
max-width: 900px; max-width: 950px;
background: white; background: white;
padding: 30px; padding: 30px;
border-radius: 12px; border-radius: 12px;
@@ -42,15 +43,13 @@
padding: 18px 20px; padding: 18px 20px;
margin-bottom: 15px; margin-bottom: 15px;
border-radius: 8px; border-radius: 8px;
background-color: #ffffff; /* Weißer Hintergrund für jeden Chip */ background-color: #ffffff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
border-left: 5px solid #007bff; /* Akzentstreifen links */ border-left: 5px solid #007bff;
} }
.chip-info { .chip-info {
flex-grow: 2; flex-basis: 35%;
font-size: 1.3em;
color: #0056b3;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -59,62 +58,134 @@
color: #888; color: #888;
} }
.time-info {
flex-basis: 25%;
display: flex;
flex-direction: column;
}
/* Statusanzeige */
.status-container { .status-container {
text-align: center; flex-basis: 30%;
margin-right: 20px; text-align: right;
} }
/* Status-Indikator (Das Quadrat) */
.status-indicator { .status-indicator {
padding: 10px 25px; padding: 10px 25px;
border-radius: 30px; border-radius: 30px;
font-weight: bold; font-weight: bold;
font-size: 1.4em; font-size: 1.4em;
min-width: 90px; /* Stellt sicher, dass die Breite konstant ist */ min-width: 90px;
text-align: center; text-align: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
} }
/* Status Styling basierend auf der Variable 'Status' (1 oder 0) */ /* Status Styling */
.status-active { .status-active {
background-color: #e6ffe9; /* Hellgrün für Hintergrund */ background-color: #e6ffe9;
color: #28a745; /* Dunkelgrüner Text */ color: #28a745;
border: 1px solid #28a745; border: 1px solid #28a745;
} }
.status-inactive { .status-inactive {
background-color: #fdecec; /* Hellrot für Hintergrund */ background-color: #fdecec;
color: #dc3545; /* Dunkelroter Text */ color: #dc3545;
border: 1px solid #dc3545; border: 1px solid #dc3545;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>RFID Chip Status Übersicht</h1> <h1>RFID Chip Status Übersicht</h1>
<p class="connection-info">Daten werden über MQTT von Topic "{{ mqtt_topic }}" empfangen.</p> <p class="connection-info">Daten werden über MQTT von Topic "{{ mqtt_topic }}" empfangen.
Status und Dauer basieren auf dem letzten Scan.</p>
{% for chip_id, data in rfid_data.items() %} {% for chip_id, data in rfid_data.items() %}
<div class="card-status"> <div class="card-status" id="chip_{{ chip_id }}">
<!-- Name und ID --> <!-- Chip Name -->
<span class="chip-info"> <span class="chip-info">
{{ data.Name }} {{ data.Name }}
<div class="chip-details"><span>(ID: {{ data.ID }})</span></div> <div class="chip-details"><span>(ID: {{ data.ID }})</span></div>
</span> </span>
<!-- Statusbereich --> <!-- Zeit-Informationen (Neu) -->
<div class="time-info">
<p style="font-size: 0.9em; color: #555;">Seit Statuswechsel:</p>
<!-- Die ID muss eindeutig sein, daher nutzen wir die Chip ID als Basis -->
<span id="duration_{{ chip_id }}" style="font-weight: bold; color: #007bff;">Lade...</span>
</div>
<!-- Statusanzeige -->
<div class="status-container"> <div class="status-container">
<p style="font-size: 0.9em; color: #555;">Status:</p> <p style="font-size: 0.9em; color: #555;">Status:</p>
<!-- Dynamisches Styling basierend auf dem Status -->
{% set status_class = 'status-active' if data.Status == 1 else 'status-inactive' %} {% set status_class = 'status-active' if data.Status == 1 else 'status-inactive' %}
<span class="status-indicator {{ status_class }}"> <span class="status-indicator {{ status_class }}">
{{ data.Status }} {{ data.Status }}
</span> </span>
</div> </div>
</div> </div >
{% endfor %} {% endfor %}
</div> </div>
<script>
// -----------------------------------------------------------
// 1. Die Funktion bleibt gleich, braucht aber jetzt nur einen Key und das Datum
function updateDuration(chipId, timeActiveString) {
if (!timeActiveString) return;
// Stelle sicher, dass wir ein gültiges Datum haben (ISO-Format ist am besten)
const startTime = new Date(timeActiveString);
// Überprüfe, ob das Startdatum gültig ist
if (isNaN(startTime.getTime())) {
document.getElementById('duration_' + chipId).innerText = 'Datum fehlt';
return;
}
const now = new Date();
const durationMs = now - startTime;
// Konvertiere Millisekunden in ein lesbares Format (Tage, Stunden, Minuten, Sekunden)
// ACHTUNG: Die Berechnung der Tage muss mit 1000*60*60*24 erfolgen.
const totalSeconds = Math.floor(durationMs / 1000);
const seconds = totalSeconds % 60;
const minutes = Math.floor(totalSeconds / 60) % 60;
// Berechnet Stunden innerhalb eines Tages (0-23)
const hours = Math.floor((totalSeconds / (3600)) % 24);
// Berechnet Gesamttage
const days = Math.floor(totalSeconds / (3600 * 60 * 60));
const formattedTime = `${days} Tage, ${hours}h, ${minutes}m, ${seconds}s`;
document.getElementById('duration_' + chipId).innerText = formattedTime;
}
// -----------------------------------------------------------
// 2. Die Initialisierung muss nach dem Laden des gesamten DOM erfolgen (window.onload)
window.onload = function() {
// Hier müssen wir alle benötigten Daten aus dem Jinja-Kontext übergeben,
// um das Problem der Stringkonkatenation zu vermeiden.
// Wir speichern die Schlüssel und Daten in einem Array von Objekten,
// damit JavaScript sie verarbeiten kann.
// Jetzt iterieren wir über das erstellte JavaScript-Array
chipDataArray.forEach(dataItem => {
updateDuration(dataItem.id, dataItem.timeActive);
});
// Optional: setInterval für den "Live"-Tick (z.B. alle 5 Sekunden)
setInterval(() => {
// Wir müssen bei jeder Aktualisierung das gesamte Array erneut verarbeiten
chipDataArray.forEach(dataItem => {
updateDuration(dataItem.id, dataItem.timeActive);
});
}, 5000); // Alle 5 Sekunden aktualisieren
};
</script>
</body> </body>
</html> </html>