drop obsolete simulators

This commit is contained in:
2025-11-06 11:54:48 +01:00
parent e28633cb9a
commit 723441bd19
2 changed files with 0 additions and 420 deletions

View File

@@ -1,215 +0,0 @@
#!/usr/bin/env python3
"""MQTT Simulator for multiple test_lampe devices.
This simulator acts as virtual light devices that:
- Subscribe to vendor/test_lampe_*/set
- Maintain local state for each device
- Publish state changes to vendor/test_lampe_*/state (retained)
"""
import json
import logging
import os
import signal
import sys
import time
import paho.mqtt.client as mqtt
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Configuration
BROKER_HOST = os.environ.get("MQTT_HOST", "172.16.2.16")
BROKER_PORT = int(os.environ.get("MQTT_PORT", "1883"))
# Devices to simulate
DEVICES = ["test_lampe_1", "test_lampe_2", "test_lampe_3"]
# Device states (one per device)
device_states = {
"test_lampe_1": {
"power": "off",
"brightness": 50
},
"test_lampe_2": {
"power": "off",
"brightness": 50
},
"test_lampe_3": {
"power": "off",
"brightness": 50
}
}
# Global client for signal handler
client_global = None
def on_connect(client, userdata, flags, rc, properties=None):
"""Callback when connected to MQTT broker.
Args:
client: MQTT client instance
userdata: User data
flags: Connection flags
rc: Connection result code
properties: Connection properties (MQTT v5)
"""
if rc == 0:
logger.info(f"Connected to MQTT broker {BROKER_HOST}:{BROKER_PORT}")
# Subscribe to SET topics for all devices
for device_id in DEVICES:
set_topic = f"vendor/{device_id}/set"
client.subscribe(set_topic, qos=1)
logger.info(f"Subscribed to {set_topic}")
# Publish initial states (retained)
for device_id in DEVICES:
publish_state(client, device_id)
logger.info(f"Simulator started for {device_id}, initial state: {device_states[device_id]}")
else:
logger.error(f"Connection failed with code {rc}")
def on_message(client, userdata, msg):
"""Callback when message received on subscribed topic.
Args:
client: MQTT client instance
userdata: User data
msg: MQTT message
"""
# Extract device_id from topic (vendor/test_lampe_X/set)
topic_parts = msg.topic.split('/')
if len(topic_parts) != 3 or topic_parts[0] != "vendor" or topic_parts[2] != "set":
logger.warning(f"Unexpected topic format: {msg.topic}")
return
device_id = topic_parts[1]
if device_id not in device_states:
logger.warning(f"Unknown device: {device_id}")
return
try:
payload = json.loads(msg.payload.decode())
logger.info(f"[{device_id}] Received SET command: {payload}")
# Update device state
updated = False
device_state = device_states[device_id]
if "power" in payload:
old_power = device_state["power"]
device_state["power"] = payload["power"]
if old_power != device_state["power"]:
updated = True
logger.info(f"[{device_id}] Power changed: {old_power} -> {device_state['power']}")
if "brightness" in payload:
old_brightness = device_state["brightness"]
device_state["brightness"] = int(payload["brightness"])
if old_brightness != device_state["brightness"]:
updated = True
logger.info(f"[{device_id}] Brightness changed: {old_brightness} -> {device_state['brightness']}")
# Publish updated state if changed
if updated:
publish_state(client, device_id)
logger.info(f"[{device_id}] Published new state: {device_state}")
except json.JSONDecodeError as e:
logger.error(f"[{device_id}] Invalid JSON in message: {e}")
except Exception as e:
logger.error(f"[{device_id}] Error processing message: {e}")
def publish_state(client, device_id):
"""Publish current device state to STATE topic.
Args:
client: MQTT client instance
device_id: Device identifier
"""
device_state = device_states[device_id]
state_topic = f"vendor/{device_id}/state"
state_json = json.dumps(device_state)
result = client.publish(state_topic, state_json, qos=1, retain=True)
if result.rc == mqtt.MQTT_ERR_SUCCESS:
logger.debug(f"[{device_id}] Published state to {state_topic}: {state_json}")
else:
logger.error(f"[{device_id}] Failed to publish state: {result.rc}")
def signal_handler(sig, frame):
"""Handle shutdown signals gracefully.
Args:
sig: Signal number
frame: Current stack frame
"""
logger.info(f"Received signal {sig}, shutting down...")
if client_global:
# Publish offline state for all devices before disconnecting
for device_id in DEVICES:
offline_state = device_states[device_id].copy()
offline_state["power"] = "off"
state_topic = f"vendor/{device_id}/state"
client_global.publish(state_topic, json.dumps(offline_state), qos=1, retain=True)
logger.info(f"[{device_id}] Published offline state")
client_global.disconnect()
client_global.loop_stop()
sys.exit(0)
def main():
"""Main entry point for the simulator."""
global client_global
# Setup signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Create MQTT client
client = mqtt.Client(
client_id="simulator-test-lampes",
protocol=mqtt.MQTTv5,
callback_api_version=mqtt.CallbackAPIVersion.VERSION2
)
client_global = client
# Set callbacks
client.on_connect = on_connect
client.on_message = on_message
# Connect to broker
logger.info(f"Connecting to MQTT broker {BROKER_HOST}:{BROKER_PORT}...")
try:
client.connect(BROKER_HOST, BROKER_PORT, keepalive=60)
# Start network loop
client.loop_forever()
except KeyboardInterrupt:
logger.info("Interrupted by user")
except Exception as e:
logger.error(f"Error: {e}")
finally:
if client.is_connected():
client.disconnect()
client.loop_stop()
if __name__ == "__main__":
main()

View File

@@ -1,205 +0,0 @@
#!/usr/bin/env python3
"""
MQTT Simulator für test_thermo_1 Thermostat.
Funktionalität:
- Subscribt auf vendor/test_thermo_1/set
- Hält internen Zustand (mode, target, current, battery, window_open)
- Reagiert auf SET-Commands (mode/target)
- Simuliert Temperatur-Drift zu target (alle 5s, max ±0.2°C)
- Publiziert vendor/test_thermo_1/state (retained, QoS 1)
Usage:
poetry run python tools/sim_thermo.py
Environment Variables:
MQTT_BROKER: MQTT broker hostname (default: 172.16.2.16)
MQTT_PORT: MQTT broker port (default: 1883)
Test Commands:
# Start simulator
poetry run python tools/sim_thermo.py &
# Test SET command
curl -X POST http://localhost:8001/devices/test_thermo_1/set \
-H "Content-Type: application/json" \
-d '{"type":"thermostat","payload":{"mode":"heat","target":22.5}}'
# Monitor state
mosquitto_sub -h 172.16.2.16 -t 'vendor/test_thermo_1/state' -v
"""
import asyncio
import json
import os
import sys
from datetime import datetime
from aiomqtt import Client, MqttError
# Configuration
BROKER_HOST = os.getenv("MQTT_BROKER", "172.16.2.16")
BROKER_PORT = int(os.getenv("MQTT_PORT", "1883"))
DEVICE_ID = "test_thermo_1"
SET_TOPIC = f"vendor/{DEVICE_ID}/set"
STATE_TOPIC = f"vendor/{DEVICE_ID}/state"
DRIFT_INTERVAL = 5 # seconds
class ThermostatSimulator:
"""Simulates a thermostat device with temperature regulation."""
def __init__(self):
self.state = {
"mode": "auto",
"target": 21.0,
"current": 20.5,
"battery": 90,
"window_open": False
}
self.client = None
self.running = True
def log(self, msg: str):
"""Log with timestamp."""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {msg}", flush=True)
async def publish_state(self):
"""Publish current state to MQTT (retained, QoS 1)."""
if not self.client:
return
payload = json.dumps(self.state)
await self.client.publish(
STATE_TOPIC,
payload=payload,
qos=1,
retain=True
)
self.log(f"📤 Published state: {payload}")
def apply_temperature_drift(self):
"""
Simulate temperature drift towards target.
Max change: ±0.2°C per interval.
"""
if self.state["mode"] == "off":
# In OFF mode, drift slowly towards ambient (assume 18°C)
ambient = 18.0
diff = ambient - self.state["current"]
else:
# In HEAT/AUTO mode, drift towards target
diff = self.state["target"] - self.state["current"]
# Apply max ±0.2°C drift
if abs(diff) < 0.1:
# Close enough, small adjustment
self.state["current"] = round(self.state["current"] + diff, 1)
elif diff > 0:
self.state["current"] = round(self.state["current"] + 0.2, 1)
else:
self.state["current"] = round(self.state["current"] - 0.2, 1)
self.log(f"🌡️ Temperature drift: current={self.state['current']}°C (target={self.state['target']}°C)")
async def handle_set_command(self, payload: dict):
"""
Handle SET command from MQTT.
Payload can contain: mode, target
"""
self.log(f"📥 Received SET: {payload}")
changed = False
if "mode" in payload:
new_mode = payload["mode"]
if new_mode in ["off", "heat", "auto"]:
self.state["mode"] = new_mode
changed = True
self.log(f" Mode changed to: {new_mode}")
else:
self.log(f" ⚠️ Invalid mode: {new_mode}")
if "target" in payload:
try:
new_target = float(payload["target"])
if 5.0 <= new_target <= 30.0:
self.state["target"] = new_target
changed = True
self.log(f" Target changed to: {new_target}°C")
else:
self.log(f" ⚠️ Target out of range: {new_target}")
except (ValueError, TypeError):
self.log(f" ⚠️ Invalid target value: {payload['target']}")
if changed:
await self.publish_state()
async def drift_loop(self):
"""Background loop for temperature drift simulation."""
while self.running:
await asyncio.sleep(DRIFT_INTERVAL)
self.apply_temperature_drift()
await self.publish_state()
async def mqtt_loop(self):
"""Main MQTT connection and message handling loop."""
try:
async with Client(
hostname=BROKER_HOST,
port=BROKER_PORT,
identifier=f"sim_{DEVICE_ID}"
) as client:
self.client = client
self.log(f"✅ Connected to MQTT broker {BROKER_HOST}:{BROKER_PORT}")
# Publish initial state
await self.publish_state()
self.log(f"📡 Thermo sim started for {DEVICE_ID}")
# Subscribe to SET topic
await client.subscribe(SET_TOPIC, qos=1)
self.log(f"👂 Subscribed to {SET_TOPIC}")
# Start drift loop in background
drift_task = asyncio.create_task(self.drift_loop())
# Listen for messages
async for message in client.messages:
try:
payload = json.loads(message.payload.decode())
await self.handle_set_command(payload)
except json.JSONDecodeError as e:
self.log(f"❌ Invalid JSON: {e}")
except Exception as e:
self.log(f"❌ Error handling message: {e}")
# Cancel drift loop on disconnect
drift_task.cancel()
except MqttError as e:
self.log(f"❌ MQTT Error: {e}")
except KeyboardInterrupt:
self.log("⚠️ Interrupted by user")
finally:
self.running = False
self.log("👋 Simulator stopped")
async def run(self):
"""Run the simulator."""
await self.mqtt_loop()
async def main():
"""Entry point."""
simulator = ThermostatSimulator()
await simulator.run()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n👋 Simulator terminated")
sys.exit(0)