""" MQTT 协议适配器 负责MQTT连接管理、消息订阅/发布、消息解析等功能 """ import json import logging import paho.mqtt.client as mqtt from datetime import datetime from typing import Dict, Any, Optional, Callable, List from .models import MqttMessage from threading import Lock class MqttAdapter: """MQTT适配器""" def __init__(self, broker_host: str = "localhost", broker_port: int = 1883, username: Optional[str] = None, password: Optional[str] = None, client_id: str = "water-management-system"): """ 初始化MQTT适配器 Args: broker_host: MQTT broker地址 broker_port: MQTT broker端口 username: 用户名 password: 密码 client_id: 客户端ID """ self.broker_host = broker_host self.broker_port = broker_port self.username = username self.password = password self.client_id = client_id self.client = mqtt.Client(client_id=client_id) self.message_handlers: Dict[str, Callable] = {} self.connected = False self.lock = Lock() # 配置MQTT客户端 if username and password: self.client.username_pw_set(username, password) # 设置回调函数 self.client.on_connect = self._on_connect self.client.on_disconnect = self._on_disconnect self.client.on_message = self._on_message self.client.on_publish = self._on_publish self.client.on_subscribe = self._on_subscribe self.logger = logging.getLogger(__name__) def _on_connect(self, client, userdata, flags, rc): """连接回调""" if rc == 0: self.connected = True self.logger.info(f"Connected to MQTT broker at {self.broker_host}:{self.broker_port}") else: self.logger.error(f"Failed to connect to MQTT broker, return code {rc}") def _on_disconnect(self, client, userdata, rc): """断开连接回调""" self.connected = False self.logger.warning(f"Disconnected from MQTT broker, return code {rc}") def _on_message(self, client, userdata, msg): """消息接收回调""" try: # 解析消息 payload = json.loads(msg.payload.decode('utf-8')) if msg.payload else {} message = MqttMessage( topic=msg.topic, payload=payload, qos=msg.qos, retain=msg.retain ) self.logger.debug(f"Received message: {message.topic} - {message.payload}") # 查找对应的消息处理器 for topic_pattern, handler in self.message_handlers.items(): if self._topic_matches(msg.topic, topic_pattern): try: handler(message) except Exception as e: self.logger.error(f"Error in message handler for {msg.topic}: {e}") except json.JSONDecodeError as e: self.logger.error(f"Failed to parse JSON message from {msg.topic}: {e}") except Exception as e: self.logger.error(f"Error processing message from {msg.topic}: {e}") def _on_publish(self, client, userdata, mid): """发布消息回调""" self.logger.debug(f"Message published with mid: {mid}") def _on_subscribe(self, client, userdata, mid, granted_qos): """订阅回调""" self.logger.debug(f"Subscribed with mid: {mid}, granted_qos: {granted_qos}") def _topic_matches(self, topic: str, pattern: str) -> bool: """检查主题是否匹配模式""" # 简单的通配符匹配实现 # 支持单层通配符 + 和多层通配符 # pattern_parts = pattern.split('/') topic_parts = topic.split('/') if len(pattern_parts) != len(topic_parts): return False for p_part, t_part in zip(pattern_parts, topic_parts): if p_part == '+' or p_part == '#': continue if p_part != t_part: return False return True def connect(self) -> bool: """ 连接到MQTT broker Returns: bool: 是否连接成功 """ try: self.client.connect(self.broker_host, self.broker_port, 60) self.client.loop_start() return True except Exception as e: self.logger.error(f"Failed to connect to MQTT broker: {e}") return False def disconnect(self): """断开MQTT连接""" if self.connected: self.client.loop_stop() self.client.disconnect() def is_connected(self) -> bool: """ 检查是否已连接 Returns: bool: 是否已连接 """ return self.connected def subscribe(self, topic: str, qos: int = 0) -> bool: """ 订阅主题 Args: topic: 主题 qos: QoS等级 Returns: bool: 是否订阅成功 """ try: result = self.client.subscribe(topic, qos) if result[0] == mqtt.MQTT_ERR_SUCCESS: self.logger.info(f"Subscribed to topic: {topic}") return True else: self.logger.error(f"Failed to subscribe to topic: {topic}") return False except Exception as e: self.logger.error(f"Error subscribing to topic {topic}: {e}") return False def unsubscribe(self, topic: str) -> bool: """ 取消订阅主题 Args: topic: 主题 Returns: bool: 是否取消订阅成功 """ try: result = self.client.unsubscribe(topic) if result[0] == mqtt.MQTT_ERR_SUCCESS: self.logger.info(f"Unsubscribed from topic: {topic}") return True else: self.logger.error(f"Failed to unsubscribe from topic: {topic}") return False except Exception as e: self.logger.error(f"Error unsubscribing from topic {topic}: {e}") return False def publish(self, topic: str, payload: Any, qos: int = 0, retain: bool = False) -> bool: """ 发布消息 Args: topic: 主题 payload: 消息内容 qos: QoS等级 retain: 是否保留消息 Returns: bool: 是否发布成功 """ try: if isinstance(payload, dict): payload = json.dumps(payload) elif not isinstance(payload, str): payload = str(payload) result = self.client.publish(topic, payload, qos, retain) if result[0] == mqtt.MQTT_ERR_SUCCESS: self.logger.debug(f"Published to topic: {topic}") return True else: self.logger.error(f"Failed to publish to topic: {topic}") return False except Exception as e: self.logger.error(f"Error publishing to topic {topic}: {e}") return False def add_message_handler(self, topic_pattern: str, handler: Callable[[MqttMessage], None]): """ 添加消息处理器 Args: topic_pattern: 主题模式(支持通配符) handler: 消息处理函数 """ with self.lock: self.message_handlers[topic_pattern] = handler self.logger.info(f"Added message handler for pattern: {topic_pattern}") def remove_message_handler(self, topic_pattern: str): """ 移除消息处理器 Args: topic_pattern: 主题模式 """ with self.lock: if topic_pattern in self.message_handlers: del self.message_handlers[topic_pattern] self.logger.info(f"Removed message handler for pattern: {topic_pattern}") def subscribe_device_topics(self, device_manager): """ 订阅设备相关主题 Args: device_manager: 设备管理器实例 """ # 设备状态上报 self.add_message_handler("devices/+/status", self._handle_device_status) # 设备数据上报 self.add_message_handler("devices/+/data", self._handle_device_data) # 设备控制命令响应 self.add_message_handler("devices/+/command/response", self._handle_command_response) # 设备OTA状态 self.add_message_handler("devices/+/ota/status", self._handle_ota_status) def _handle_device_status(self, message: MqttMessage): """处理设备状态消息""" topic_parts = message.topic.split('/') if len(topic_parts) >= 2: device_sn = topic_parts[1] status = message.payload.get('status', 'unknown') # 更新设备状态 device = device_manager.get_device(device_sn) if device: from .models import DeviceStatus try: device.status = DeviceStatus(status) device.last_seen = datetime.now() device_manager.logger.info(f"Device {device_sn} status updated to {status}") except ValueError: device_manager.logger.warning(f"Unknown status: {status}") def _handle_device_data(self, message: MqttMessage): """处理设备数据消息""" topic_parts = message.topic.split('/') if len(topic_parts) >= 2: device_sn = topic_parts[1] data = message.payload # 更新设备影子 device_manager.update_device_shadow(device_sn, data) device_manager.logger.debug(f"Device {device_sn} data updated") def _handle_command_response(self, message: MqttMessage): """处理命令响应消息""" topic_parts = message.topic.split('/') if len(topic_parts) >= 2: device_sn = topic_parts[1] command_id = message.payload.get('command_id') result = message.payload.get('result') device_manager.logger.info(f"Device {device_sn} command response: {command_id} -> {result}") def _handle_ota_status(self, message: MqttMessage): """处理OTA状态消息""" topic_parts = message.topic.split('/') if len(topic_parts) >= 2: device_sn = topic_parts[1] status = message.payload.get('status') progress = message.payload.get('progress', 0) device_manager.logger.info(f"Device {device_sn} OTA status: {status}, progress: {progress}%") def send_command(self, device_sn: str, command: Dict[str, Any]) -> bool: """ 发送设备控制命令 Args: device_sn: 设备序列号 command: 命令内容 Returns: bool: 是否发送成功 """ topic = f"devices/{device_sn}/command" command['command_id'] = f"cmd_{datetime.now().timestamp()}" command['timestamp'] = datetime.now().isoformat() return self.publish(topic, command, qos=1) def get_connection_status(self) -> Dict[str, Any]: """ 获取连接状态 Returns: Dict[str, Any]: 连接状态信息 """ return { "connected": self.connected, "broker_host": self.broker_host, "broker_port": self.broker_port, "client_id": self.client_id, "message_handlers_count": len(self.message_handlers) }