# simclient/simclient.py import time import uuid import json import socket import hashlib import paho.mqtt.client as mqtt import os import re import platform import logging from dotenv import load_dotenv # ENV laden load_dotenv("/workspace/simclient/.env") # Konfiguration aus ENV ENV = os.getenv("ENV", "development") HEARTBEAT_INTERVAL = int( os.getenv("HEARTBEAT_INTERVAL", 5 if ENV == "development" else 60)) LOG_LEVEL = os.getenv("LOG_LEVEL", "DEBUG" if ENV == "development" else "INFO") MQTT_BROKER = os.getenv("MQTT_BROKER", "mqtt") MQTT_PORT = int(os.getenv("MQTT_PORT", 1883)) DEBUG_MODE = os.getenv("DEBUG_MODE", "1" if ENV == "development" else "0") in ("1", "true", "True") # Logging-Konfiguration LOG_PATH = "/tmp/simclient.log" log_handlers = [logging.FileHandler(LOG_PATH, encoding="utf-8")] if DEBUG_MODE: log_handlers.append(logging.StreamHandler()) logging.basicConfig( level=getattr(logging, LOG_LEVEL.upper(), logging.INFO), format="%(asctime)s [%(levelname)s] %(message)s", handlers=log_handlers ) discovered = False def on_message(client, userdata, msg): global discovered logging.info(f"Empfangen: {msg.topic} {msg.payload.decode()}") # ACK-Quittung empfangen? if msg.topic.endswith("/discovery_ack"): discovered = True logging.info("Discovery-ACK empfangen. Starte Heartbeat.") def get_mac_addresses(): macs = set() try: for root, dirs, files in os.walk('/sys/class/net/'): for iface in dirs: try: with open(f'/sys/class/net/{iface}/address') as f: mac = f.read().strip() if mac and mac != '00:00:00:00:00:00': macs.add(mac) except Exception: continue break except Exception: pass return sorted(macs) def get_board_serial(): # Raspberry Pi: /proc/cpuinfo, andere: /sys/class/dmi/id/product_serial serial = None try: with open('/proc/cpuinfo') as f: for line in f: if line.lower().startswith('serial'): serial = line.split(':')[1].strip() break except Exception: pass if not serial: try: with open('/sys/class/dmi/id/product_serial') as f: serial = f.read().strip() except Exception: pass return serial or "unknown" def get_ip(): # Versucht, die lokale IP zu ermitteln (nicht 127.0.0.1) try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] s.close() return ip except Exception: return "unknown" def get_hardware_token(): serial = get_board_serial() macs = get_mac_addresses() token_raw = serial + "_" + "_".join(macs) # Hashen für Datenschutz token_hash = hashlib.sha256(token_raw.encode()).hexdigest() return token_hash def get_model(): # Versucht, das Modell auszulesen (z.B. Raspberry Pi, PC, etc.) try: if os.path.exists('/proc/device-tree/model'): with open('/proc/device-tree/model') as f: return f.read().strip() elif os.path.exists('/sys/class/dmi/id/product_name'): with open('/sys/class/dmi/id/product_name') as f: return f.read().strip() except Exception: pass return "unknown" SOFTWARE_VERSION = "1.0.0" # Optional: Anpassen bei neuen Releases def send_discovery(client, client_id, hardware_token, ip_addr): macs = get_mac_addresses() discovery_msg = { "uuid": client_id, "hardware_token": hardware_token, "ip": ip_addr, "type": "infoscreen", "hostname": socket.gethostname(), "os_version": platform.platform(), "software_version": SOFTWARE_VERSION, "macs": macs, "model": get_model(), } client.publish("infoscreen/discovery", json.dumps(discovery_msg)) logging.info(f"Discovery-Nachricht gesendet: {discovery_msg}") def get_persistent_uuid(uuid_path="/data/client_uuid.txt"): # Prüfe, ob die Datei existiert if os.path.exists(uuid_path): with open(uuid_path, "r") as f: return f.read().strip() # Generiere neue UUID und speichere sie new_uuid = str(uuid.uuid4()) os.makedirs(os.path.dirname(uuid_path), exist_ok=True) with open(uuid_path, "w") as f: f.write(new_uuid) return new_uuid def main(): global discovered client_id = get_persistent_uuid() hardware_token = get_hardware_token() ip_addr = get_ip() client = mqtt.Client(protocol=mqtt.MQTTv311, callback_api_version=2) client.on_message = on_message client.connect(MQTT_BROKER, MQTT_PORT) # Discovery-ACK-Topic abonnieren ack_topic = f"infoscreen/{client_id}/discovery_ack" client.subscribe(ack_topic) client.subscribe(f"infoscreen/{client_id}/config") # Discovery-Phase: Sende Discovery bis ACK empfangen while not discovered: send_discovery(client, client_id, hardware_token, ip_addr) client.loop(timeout=1.0) time.sleep(HEARTBEAT_INTERVAL) # Heartbeat-Phase while True: client.publish(f"infoscreen/{client_id}/heartbeat", "alive") logging.debug("Heartbeat gesendet.") client.loop(timeout=1.0) time.sleep(HEARTBEAT_INTERVAL) if __name__ == "__main__": main()