修复实体设备不显示问题

- 重写 sensor.py,使用 Home Assistant 内置 MQTT 组件
- 修复配置流程标题显示
- 添加更好的错误处理和日志记录
- 创建 MQTT 测试脚本
- 统一所有日志信息为 JackeryHome
This commit is contained in:
不求圣剑
2025-10-14 11:04:32 +08:00
parent 7f5b5492ca
commit 04821cb501
4 changed files with 173 additions and 187 deletions

View File

@@ -12,8 +12,8 @@ PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Energy Monitor from a config entry.""" """Set up JackeryHome from a config entry."""
_LOGGER.info("Setting up Energy Monitor integration") _LOGGER.info("Setting up JackeryHome integration")
# 存储配置数据 # 存储配置数据
hass.data.setdefault(DOMAIN, {}) 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: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """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) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -31,8 +31,8 @@ DATA_SCHEMA = vol.Schema(
) )
class EnergyMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class JackeryHomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Energy Monitor.""" """Handle a config flow for JackeryHome."""
VERSION = 1 VERSION = 1
@@ -47,10 +47,10 @@ class EnergyMonitorConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(DOMAIN) await self.async_set_unique_id(DOMAIN)
self._abort_if_unique_id_configured() 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( return self.async_create_entry(
title="Energy Monitor", title="JackeryHome",
data=user_input, data=user_input,
) )

View File

@@ -1,11 +1,8 @@
"""Energy Monitor Sensor Platform.""" """JackeryHome Sensor Platform."""
import asyncio
import json import json
import logging import logging
from typing import Any from typing import Any
import paho.mqtt.client as mqtt
from homeassistant.components import mqtt as ha_mqtt from homeassistant.components import mqtt as ha_mqtt
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
@@ -21,173 +18,52 @@ from . import DOMAIN
_LOGGER = logging.getLogger(__name__) _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 = { SENSORS = {
"solar_power": { "solar_power": {
"name": "Solar Power", "name": "太阳能发电",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:solar-power", "icon": "mdi:solar-power",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"home_power": { "home_power": {
"name": "Home Power", "name": "家庭用电",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:home-lightning-bolt", "icon": "mdi:home-lightning-bolt",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"grid_import": { "grid_import": {
"name": "Grid Import", "name": "电网输入",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:transmission-tower-import", "icon": "mdi:transmission-tower-import",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"grid_export": { "grid_export": {
"name": "Grid Export", "name": "电网输出",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:transmission-tower-export", "icon": "mdi:transmission-tower-export",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"battery_charge": { "battery_charge": {
"name": "Battery Charge", "name": "电池充电",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:battery-charging", "icon": "mdi:battery-charging",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"battery_discharge": { "battery_discharge": {
"name": "Battery Discharge", "name": "电池放电",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:battery-minus", "icon": "mdi:battery-minus",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"battery_soc": { "battery_soc": {
"name": "Battery State of Charge", "name": "电池电量",
"unit": PERCENTAGE, "unit": PERCENTAGE,
"icon": "mdi:battery-70", "icon": "mdi:battery-70",
"device_class": SensorDeviceClass.BATTERY, "device_class": SensorDeviceClass.BATTERY,
@@ -201,26 +77,19 @@ async def async_setup_entry(
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Energy Monitor sensors from a config entry.""" """Set up JackeryHome sensors from a config entry."""
global _data_manager _LOGGER.info("Setting up JackeryHome sensors")
_LOGGER.info("Setting up Energy Monitor sensors")
# 获取配置数据 # 获取配置数据
config = hass.data[DOMAIN][config_entry.entry_id] config = config_entry.data
topic_prefix = config.get("topic_prefix", "homeassistant/sensor") 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)
# 创建全局数据管理器(如果还没有创建) _LOGGER.info(f"Topic prefix: {topic_prefix}")
if _data_manager is None:
_data_manager = MQTTDataManager(hass, topic_prefix, mqtt_broker, mqtt_port)
await _data_manager.start()
# 创建所有传感器实体 # 创建所有传感器实体
entities = [] entities = []
for sensor_id, sensor_config in SENSORS.items(): for sensor_id, sensor_config in SENSORS.items():
entity = EnergyMonitorSensor( entity = JackeryHomeSensor(
sensor_id=sensor_id, sensor_id=sensor_id,
name=sensor_config["name"], name=sensor_config["name"],
unit=sensor_config["unit"], unit=sensor_config["unit"],
@@ -228,18 +97,16 @@ async def async_setup_entry(
device_class=sensor_config["device_class"], device_class=sensor_config["device_class"],
state_class=sensor_config["state_class"], state_class=sensor_config["state_class"],
topic_prefix=topic_prefix, topic_prefix=topic_prefix,
config_entry_id=config_entry.entry_id,
) )
entities.append(entity) entities.append(entity)
# 将传感器注册到数据管理器
_data_manager.register_sensor(sensor_id, entity)
async_add_entities(entities, True) 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): class JackeryHomeSensor(SensorEntity):
"""Representation of an Energy Monitor Sensor.""" """Representation of a JackeryHome Sensor."""
def __init__( def __init__(
self, self,
@@ -250,6 +117,7 @@ class EnergyMonitorSensor(SensorEntity):
device_class: SensorDeviceClass, device_class: SensorDeviceClass,
state_class: SensorStateClass, state_class: SensorStateClass,
topic_prefix: str, topic_prefix: str,
config_entry_id: str,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
self._sensor_id = sensor_id self._sensor_id = sensor_id
@@ -258,7 +126,13 @@ class EnergyMonitorSensor(SensorEntity):
self._attr_icon = icon self._attr_icon = icon
self._attr_device_class = device_class self._attr_device_class = device_class
self._attr_state_class = state_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._topic = f"{topic_prefix}/{sensor_id}/state"
self._attr_native_value = None self._attr_native_value = None
self._attr_available = False self._attr_available = False
@@ -270,22 +144,52 @@ class EnergyMonitorSensor(SensorEntity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Set up the sensor.""" """Set up the sensor."""
_LOGGER.info(f"Energy Monitor sensor {self._sensor_id} added to Home Assistant") _LOGGER.info(f"JackeryHome sensor {self._sensor_id} added to Home Assistant")
# 注意:现在数据通过 MQTTDataManager 处理,不再直接订阅 MQTT topic
# 订阅 MQTT 主题
async def _update_state(self, value: Any) -> None: @callback
"""更新传感器状态(由 MQTTDataManager 调用)""" def message_received(msg):
try: """Handle new MQTT messages."""
# 确保值是数字类型 try:
if isinstance(value, (int, float)): 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_native_value = value
self._attr_available = True self._attr_available = True
self.async_write_ha_state() self.async_write_ha_state()
_LOGGER.debug(f"Updated {self._sensor_id} with value: {value}") _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:
except Exception as e: _LOGGER.error(f"Error processing MQTT message for {self._sensor_id}: {e}")
_LOGGER.error(f"Error updating sensor {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 @property
def extra_state_attributes(self) -> dict[str, Any]: def extra_state_attributes(self) -> dict[str, Any]:
@@ -293,19 +197,4 @@ class EnergyMonitorSensor(SensorEntity):
return { return {
"sensor_id": self._sensor_id, "sensor_id": self._sensor_id,
"mqtt_topic": self._topic, "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

97
test_mqtt.py Normal file
View File

@@ -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()