187 lines
5.1 KiB
Python
187 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""MQTT Simulator for test_lampe device.
|
|
|
|
This simulator acts as a virtual light device that:
|
|
- Subscribes to vendor/test_lampe/set
|
|
- Maintains local state
|
|
- Publishes 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"))
|
|
DEVICE_ID = "test_lampe_1"
|
|
SET_TOPIC = f"vendor/{DEVICE_ID}/set"
|
|
STATE_TOPIC = f"vendor/{DEVICE_ID}/state"
|
|
|
|
# Device state
|
|
device_state = {
|
|
"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 topic
|
|
client.subscribe(SET_TOPIC, qos=1)
|
|
logger.info(f"Subscribed to {SET_TOPIC}")
|
|
|
|
# Publish initial state (retained)
|
|
publish_state(client)
|
|
logger.info(f"Simulator started, initial state published: {device_state}")
|
|
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
|
|
"""
|
|
global device_state
|
|
|
|
try:
|
|
payload = json.loads(msg.payload.decode())
|
|
logger.info(f"Received SET command: {payload}")
|
|
|
|
# Update device state
|
|
updated = False
|
|
|
|
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"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"Brightness changed: {old_brightness} -> {device_state['brightness']}")
|
|
|
|
# Publish updated state if changed
|
|
if updated:
|
|
publish_state(client)
|
|
logger.info(f"Published new state: {device_state}")
|
|
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Invalid JSON in message: {e}")
|
|
except Exception as e:
|
|
logger.error(f"Error processing message: {e}")
|
|
|
|
|
|
def publish_state(client):
|
|
"""Publish current device state to STATE topic.
|
|
|
|
Args:
|
|
client: MQTT client instance
|
|
"""
|
|
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"Published state to {STATE_TOPIC}: {state_json}")
|
|
else:
|
|
logger.error(f"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 before disconnecting
|
|
offline_state = device_state.copy()
|
|
offline_state["power"] = "off"
|
|
client_global.publish(STATE_TOPIC, json.dumps(offline_state), qos=1, retain=True)
|
|
logger.info("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=f"simulator-{DEVICE_ID}",
|
|
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()
|