diff --git a/custom_components/JackeryHome/__init__.py b/custom_components/JackeryHome/__init__.py index 0b9d9ac..fd89024 100644 --- a/custom_components/JackeryHome/__init__.py +++ b/custom_components/JackeryHome/__init__.py @@ -12,8 +12,8 @@ PLATFORMS = [Platform.SENSOR] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Energy Monitor from a config entry.""" - _LOGGER.info("Setting up Energy Monitor integration") + """Set up JackeryHome from a config entry.""" + _LOGGER.info("Setting up JackeryHome integration") # 存储配置数据 hass.data.setdefault(DOMAIN, {}) @@ -27,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - _LOGGER.info("Unloading Energy Monitor integration") + _LOGGER.info("Unloading JackeryHome integration") # 卸载传感器平台 unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/custom_components/JackeryHome/config_flow.py b/custom_components/JackeryHome/config_flow.py index 40fe87b..1ff5705 100644 --- a/custom_components/JackeryHome/config_flow.py +++ b/custom_components/JackeryHome/config_flow.py @@ -31,8 +31,8 @@ DATA_SCHEMA = vol.Schema( ) -class EnergyMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Energy Monitor.""" +class JackeryHomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for JackeryHome.""" VERSION = 1 @@ -47,10 +47,10 @@ class EnergyMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): await self.async_set_unique_id(DOMAIN) self._abort_if_unique_id_configured() - _LOGGER.info(f"Creating Energy Monitor config entry with topic_prefix: {user_input['topic_prefix']}") + _LOGGER.info(f"Creating JackeryHome config entry with topic_prefix: {user_input['topic_prefix']}") return self.async_create_entry( - title="Energy Monitor", + title="JackeryHome", data=user_input, ) diff --git a/custom_components/JackeryHome/sensor.py b/custom_components/JackeryHome/sensor.py index 47b1a8b..baebfec 100644 --- a/custom_components/JackeryHome/sensor.py +++ b/custom_components/JackeryHome/sensor.py @@ -1,11 +1,8 @@ -"""Energy Monitor Sensor Platform.""" -import asyncio +"""JackeryHome Sensor Platform.""" import json import logging from typing import Any -import paho.mqtt.client as mqtt - from homeassistant.components import mqtt as ha_mqtt from homeassistant.components.sensor import ( SensorDeviceClass, @@ -21,173 +18,52 @@ from . import DOMAIN _LOGGER = logging.getLogger(__name__) -# 全局 MQTT 客户端和数据处理 -class MQTTDataManager: - """MQTT 数据管理器,负责订阅设备数据并发送请求""" - - def __init__(self, hass: HomeAssistant, topic_prefix: str, mqtt_broker: str = "192.168.0.101", mqtt_port: int = 1883): - self.hass = hass - self.topic_prefix = topic_prefix - self.mqtt_broker = mqtt_broker - self.mqtt_port = mqtt_port - self.client = None - self.data_task = None - self.sensors = {} - - async def start(self): - """启动 MQTT 客户端和数据获取任务""" - try: - # 创建 MQTT 客户端 - self.client = mqtt.Client(client_id="energy_monitor_sensor", callback_api_version=mqtt.CallbackAPIVersion.VERSION2) - self.client.on_connect = self._on_connect - self.client.on_message = self._on_message - - # 连接到 MQTT 代理 - await self._connect_mqtt() - - # 启动数据获取任务 - self.data_task = asyncio.create_task(self._data_fetch_loop()) - - _LOGGER.info("MQTT Data Manager started successfully") - - except Exception as e: - _LOGGER.error(f"Failed to start MQTT Data Manager: {e}") - - async def _connect_mqtt(self): - """连接到 MQTT 代理""" - def _connect(): - self.client.connect(self.mqtt_broker, self.mqtt_port, 60) - self.client.loop_start() - - loop = asyncio.get_event_loop() - await loop.run_in_executor(None, _connect) - - def _on_connect(self, client, userdata, flags, rc, properties): - """MQTT 连接回调""" - if rc == 0: - _LOGGER.info("Connected to MQTT broker") - # 订阅设备数据主题 - client.subscribe("/device/data") - _LOGGER.info("Subscribed to /device/data topic") - else: - _LOGGER.error(f"Failed to connect to MQTT broker with result code {rc}") - - def _on_message(self, client, userdata, msg): - """MQTT 消息接收回调""" - try: - topic = msg.topic - payload = msg.payload.decode() - - if topic == "/device/data": - _LOGGER.debug(f"Received data from /device/data: {payload}") - # 处理接收到的数据 - self._process_device_data(payload) - - except Exception as e: - _LOGGER.error(f"Error processing MQTT message: {e}") - - def _process_device_data(self, payload: str): - """处理设备数据""" - try: - data = json.loads(payload) - _LOGGER.debug(f"Processed device data: {data}") - - # 将处理后的数据发送到相应的传感器 - for sensor_id, sensor_entity in self.sensors.items(): - if sensor_id in data: - # 在 Home Assistant 主线程中更新传感器状态 - self.hass.create_task(sensor_entity._update_state(data[sensor_id])) - - except json.JSONDecodeError as e: - _LOGGER.error(f"Invalid JSON data received: {payload}, error: {e}") - except Exception as e: - _LOGGER.error(f"Error processing device data: {e}") - - async def _data_fetch_loop(self): - """数据获取循环,每秒5次发送请求""" - while True: - try: - # 发送数据获取请求 - if self.client: - self.client.publish("/data/data-get", "get_data") - _LOGGER.debug("Sent data request to /data/data-get") - - # 等待 0.2 秒(每秒5次) - await asyncio.sleep(0.2) - - except Exception as e: - _LOGGER.error(f"Error in data fetch loop: {e}") - await asyncio.sleep(1) # 出错时等待更长时间 - - def register_sensor(self, sensor_id: str, sensor_entity): - """注册传感器实体""" - self.sensors[sensor_id] = sensor_entity - _LOGGER.info(f"Registered sensor: {sensor_id}") - - async def stop(self): - """停止 MQTT 客户端和数据获取任务""" - if self.data_task: - self.data_task.cancel() - try: - await self.data_task - except asyncio.CancelledError: - pass - - if self.client: - self.client.loop_stop() - self.client.disconnect() - - _LOGGER.info("MQTT Data Manager stopped") - -# 全局数据管理器实例 -_data_manager = None - -# 传感器配置(对应 main.py 中的传感器定义) +# 传感器配置 SENSORS = { "solar_power": { - "name": "Solar Power", + "name": "太阳能发电", "unit": UnitOfPower.WATT, "icon": "mdi:solar-power", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "home_power": { - "name": "Home Power", + "name": "家庭用电", "unit": UnitOfPower.WATT, "icon": "mdi:home-lightning-bolt", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "grid_import": { - "name": "Grid Import", + "name": "电网输入", "unit": UnitOfPower.WATT, "icon": "mdi:transmission-tower-import", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "grid_export": { - "name": "Grid Export", + "name": "电网输出", "unit": UnitOfPower.WATT, "icon": "mdi:transmission-tower-export", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "battery_charge": { - "name": "Battery Charge", + "name": "电池充电", "unit": UnitOfPower.WATT, "icon": "mdi:battery-charging", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "battery_discharge": { - "name": "Battery Discharge", + "name": "电池放电", "unit": UnitOfPower.WATT, "icon": "mdi:battery-minus", "device_class": SensorDeviceClass.POWER, "state_class": SensorStateClass.MEASUREMENT, }, "battery_soc": { - "name": "Battery State of Charge", + "name": "电池电量", "unit": PERCENTAGE, "icon": "mdi:battery-70", "device_class": SensorDeviceClass.BATTERY, @@ -201,26 +77,19 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: - """Set up Energy Monitor sensors from a config entry.""" - global _data_manager - - _LOGGER.info("Setting up Energy Monitor sensors") + """Set up JackeryHome sensors from a config entry.""" + _LOGGER.info("Setting up JackeryHome sensors") # 获取配置数据 - config = hass.data[DOMAIN][config_entry.entry_id] + config = config_entry.data topic_prefix = config.get("topic_prefix", "homeassistant/sensor") - mqtt_broker = config.get("mqtt_broker", "192.168.0.101") - mqtt_port = config.get("mqtt_port", 1883) - # 创建全局数据管理器(如果还没有创建) - if _data_manager is None: - _data_manager = MQTTDataManager(hass, topic_prefix, mqtt_broker, mqtt_port) - await _data_manager.start() + _LOGGER.info(f"Topic prefix: {topic_prefix}") # 创建所有传感器实体 entities = [] for sensor_id, sensor_config in SENSORS.items(): - entity = EnergyMonitorSensor( + entity = JackeryHomeSensor( sensor_id=sensor_id, name=sensor_config["name"], unit=sensor_config["unit"], @@ -228,18 +97,16 @@ async def async_setup_entry( device_class=sensor_config["device_class"], state_class=sensor_config["state_class"], topic_prefix=topic_prefix, + config_entry_id=config_entry.entry_id, ) entities.append(entity) - - # 将传感器注册到数据管理器 - _data_manager.register_sensor(sensor_id, entity) async_add_entities(entities, True) - _LOGGER.info(f"Added {len(entities)} Energy Monitor sensors") + _LOGGER.info(f"Added {len(entities)} JackeryHome sensors") -class EnergyMonitorSensor(SensorEntity): - """Representation of an Energy Monitor Sensor.""" +class JackeryHomeSensor(SensorEntity): + """Representation of a JackeryHome Sensor.""" def __init__( self, @@ -250,6 +117,7 @@ class EnergyMonitorSensor(SensorEntity): device_class: SensorDeviceClass, state_class: SensorStateClass, topic_prefix: str, + config_entry_id: str, ) -> None: """Initialize the sensor.""" self._sensor_id = sensor_id @@ -258,7 +126,13 @@ class EnergyMonitorSensor(SensorEntity): self._attr_icon = icon self._attr_device_class = device_class self._attr_state_class = state_class - self._attr_unique_id = f"energy_monitor_{sensor_id}" + self._attr_unique_id = f"jackery_home_{sensor_id}" + self._attr_device_info = { + "identifiers": {(DOMAIN, config_entry_id)}, + "name": "JackeryHome", + "manufacturer": "Jackery", + "model": "Energy Monitor", + } self._topic = f"{topic_prefix}/{sensor_id}/state" self._attr_native_value = None self._attr_available = False @@ -270,22 +144,52 @@ class EnergyMonitorSensor(SensorEntity): async def async_added_to_hass(self) -> None: """Set up the sensor.""" - _LOGGER.info(f"Energy Monitor sensor {self._sensor_id} added to Home Assistant") - # 注意:现在数据通过 MQTTDataManager 处理,不再直接订阅 MQTT topic - - async def _update_state(self, value: Any) -> None: - """更新传感器状态(由 MQTTDataManager 调用)""" - try: - # 确保值是数字类型 - if isinstance(value, (int, float)): + _LOGGER.info(f"JackeryHome sensor {self._sensor_id} added to Home Assistant") + + # 订阅 MQTT 主题 + @callback + def message_received(msg): + """Handle new MQTT messages.""" + try: + payload = msg.payload + if isinstance(payload, bytes): + payload = payload.decode("utf-8") + + _LOGGER.debug(f"Received MQTT message for {self._sensor_id}: {payload}") + + # 尝试解析 JSON + try: + data = json.loads(payload) + if isinstance(data, dict) and "value" in data: + value = data["value"] + else: + value = data + except json.JSONDecodeError: + # 如果不是 JSON,直接使用原始值 + try: + value = float(payload) + except ValueError: + value = payload + + # 更新传感器状态 self._attr_native_value = value self._attr_available = True self.async_write_ha_state() + _LOGGER.debug(f"Updated {self._sensor_id} with value: {value}") - else: - _LOGGER.warning(f"Invalid value type for {self._sensor_id}: {type(value)} - {value}") - except Exception as e: - _LOGGER.error(f"Error updating sensor {self._sensor_id}: {e}") + + except Exception as e: + _LOGGER.error(f"Error processing MQTT message for {self._sensor_id}: {e}") + + # 订阅 MQTT 主题 + await ha_mqtt.async_subscribe( + self.hass, + self._topic, + message_received, + 1 + ) + + _LOGGER.info(f"Subscribed to MQTT topic: {self._topic}") @property def extra_state_attributes(self) -> dict[str, Any]: @@ -293,19 +197,4 @@ class EnergyMonitorSensor(SensorEntity): return { "sensor_id": self._sensor_id, "mqtt_topic": self._topic, - } - - -async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: - """Unload the sensor platform.""" - global _data_manager - - _LOGGER.info("Unloading Energy Monitor sensors") - - # 停止数据管理器 - if _data_manager is not None: - await _data_manager.stop() - _data_manager = None - - return True - + } \ No newline at end of file diff --git a/test_mqtt.py b/test_mqtt.py new file mode 100644 index 0000000..919bf56 --- /dev/null +++ b/test_mqtt.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +测试 MQTT 数据发送脚本 +用于测试 JackeryHome 集成是否能正确接收数据 +""" +import json +import time +import paho.mqtt.client as mqtt +import random + +# MQTT 配置 +MQTT_BROKER = "192.168.0.101" # 修改为你的 MQTT Broker 地址 +MQTT_PORT = 1883 +TOPIC_PREFIX = "homeassistant/sensor" + +def on_connect(client, userdata, flags, rc): + """连接回调""" + if rc == 0: + print("✅ 成功连接到 MQTT Broker") + else: + print(f"❌ 连接失败,错误代码: {rc}") + +def on_publish(client, userdata, mid): + """发布回调""" + print(f"📤 消息已发布,ID: {mid}") + +def main(): + """主函数""" + print("🚀 启动 JackeryHome MQTT 测试脚本") + print(f"📡 MQTT Broker: {MQTT_BROKER}:{MQTT_PORT}") + print(f"📂 Topic Prefix: {TOPIC_PREFIX}") + print("-" * 50) + + # 创建 MQTT 客户端 + client = mqtt.Client(client_id="jackery_home_test", callback_api_version=mqtt.CallbackAPIVersion.VERSION2) + client.on_connect = on_connect + client.on_publish = on_publish + + try: + # 连接到 MQTT Broker + print("🔗 正在连接到 MQTT Broker...") + client.connect(MQTT_BROKER, MQTT_PORT, 60) + client.loop_start() + + # 等待连接 + time.sleep(2) + + # 模拟数据 + sensors = { + "solar_power": {"min": 200, "max": 3000, "unit": "W"}, + "home_power": {"min": 500, "max": 3500, "unit": "W"}, + "grid_import": {"min": 0, "max": 2000, "unit": "W"}, + "grid_export": {"min": 0, "max": 1500, "unit": "W"}, + "battery_charge": {"min": 0, "max": 1000, "unit": "W"}, + "battery_discharge": {"min": 0, "max": 1000, "unit": "W"}, + "battery_soc": {"min": 20, "max": 100, "unit": "%"}, + } + + print("📊 开始发送测试数据...") + print("按 Ctrl+C 停止") + print("-" * 50) + + count = 0 + while True: + count += 1 + print(f"\n🔄 第 {count} 轮数据发送:") + + for sensor_id, config in sensors.items(): + # 生成随机值 + value = random.randint(config["min"], config["max"]) + + # 构建主题 + topic = f"{TOPIC_PREFIX}/{sensor_id}/state" + + # 发送数据 + result = client.publish(topic, str(value)) + + if result.rc == mqtt.MQTT_ERR_SUCCESS: + print(f" ✅ {sensor_id}: {value} {config['unit']} -> {topic}") + else: + print(f" ❌ {sensor_id}: 发送失败") + + # 等待 5 秒 + time.sleep(5) + + except KeyboardInterrupt: + print("\n\n⏹️ 用户中断,正在停止...") + except Exception as e: + print(f"\n❌ 发生错误: {e}") + finally: + # 清理资源 + client.loop_stop() + client.disconnect() + print("🔚 测试脚本已停止") + +if __name__ == "__main__": + main()