增加SN字段

This commit is contained in:
不求圣剑
2026-01-23 10:22:51 +08:00
parent c09b005ce0
commit 805af8fafd
4 changed files with 47 additions and 40 deletions

View File

@@ -16,6 +16,8 @@ _LOGGER = logging.getLogger(__name__)
# 配置数据模式 # 配置数据模式
DATA_SCHEMA = vol.Schema( DATA_SCHEMA = vol.Schema(
{ {
vol.Required("mqtt_host"): str,
vol.Required("device_sn"): str,
vol.Required("token"): str, vol.Required("token"): str,
vol.Optional( vol.Optional(
"topic_prefix", "topic_prefix",
@@ -45,8 +47,9 @@ class JackeryConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors["base"] = "mqtt_not_configured" errors["base"] = "mqtt_not_configured"
else: else:
_LOGGER.info( _LOGGER.info(
f"Creating Jackery config entry with topic_prefix: " f"Creating Jackery config entry with mqtt_host: {user_input.get('mqtt_host')}, "
f"{user_input.get('topic_prefix', 'hb')}" f"device_sn: {user_input.get('device_sn')}, "
f"topic_prefix: {user_input.get('topic_prefix', 'hb')}"
) )
return self.async_create_entry( return self.async_create_entry(

View File

@@ -2,9 +2,9 @@
import asyncio import asyncio
import json import json
import logging import logging
import time
import random import random
import re import re
import time
from typing import Any, Callable from typing import Any, Callable
from homeassistant.components import mqtt as ha_mqtt from homeassistant.components import mqtt as ha_mqtt
@@ -14,9 +14,9 @@ from homeassistant.components.sensor import (
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.const import UnitOfPower, UnitOfEnergy, PERCENTAGE, UnitOfTemperature
from . import DOMAIN from . import DOMAIN
@@ -68,7 +68,7 @@ SENSORS = {
"device_class": None, "device_class": None,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
# 太阳能 # 太阳能
"solar_power": { "solar_power": {
"json_key": "pvPw", "json_key": "pvPw",
@@ -136,18 +136,18 @@ SENSORS = {
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
# EPS (离网输出) # EPS (离网输出)
"eps_output_power": { "eps_output_power": {
"json_key": "swEpsOutPw", "json_key": "swEpsOutPw",
"name": "EPS Output Power", "name": "EPS Output Power",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:power-plug", "icon": "mdi:power-plug",
"device_class": SensorDeviceClass.POWER, "device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT, "state_class": SensorStateClass.MEASUREMENT,
}, },
"eps_input_power": { "eps_input_power": {
"json_key": "swEpsInPw", "json_key": "swEpsInPw",
"name": "EPS Input Power", "name": "EPS Input Power",
"unit": UnitOfPower.WATT, "unit": UnitOfPower.WATT,
"icon": "mdi:power-plug", "icon": "mdi:power-plug",
@@ -170,7 +170,7 @@ SENSORS = {
"device_class": None, "device_class": None,
"state_class": None, # 1-On, 0-Off "state_class": None, # 1-On, 0-Off
}, },
# Limits & Settings & Status # Limits & Settings & Status
"soc_charge_limit": { "soc_charge_limit": {
"json_key": "socChgLimit", "json_key": "socChgLimit",
@@ -215,16 +215,16 @@ class JackeryDataCoordinator:
self.hass = hass self.hass = hass
self._topic_prefix = topic_prefix self._topic_prefix = topic_prefix
self._token = token self._token = token
self._topic_root = "hb" self._topic_root = "hb"
self._device_sn = "" # 设备序列号 self._device_sn = "T02601220110001" # 设备序列号
self._sensors = {} # {sensor_id: entity} self._sensors = {} # {sensor_id: entity}
self._data_task = None self._data_task = None
self._subscribed = False self._subscribed = False
# Topic patterns # Topic patterns
self._topic_status_wildcard = f"{self._topic_root}/device/+/status" self._topic_status_wildcard = f"{self._topic_root}/device/+/status"
def register_sensor(self, sensor_id: str, entity: "JackerySensor") -> None: def register_sensor(self, sensor_id: str, entity: "JackerySensor") -> None:
"""注册传感器实体.""" """注册传感器实体."""
self._sensors[sensor_id] = entity self._sensors[sensor_id] = entity
@@ -238,13 +238,13 @@ class JackeryDataCoordinator:
"""启动协调器.""" """启动协调器."""
if self._subscribed: if self._subscribed:
return return
try: try:
# 订阅状态主题 (Wildcard) 以发现设备和接收数据 # 订阅状态主题 (Wildcard) 以发现设备和接收数据
@callback @callback
def message_received(msg): def message_received(msg):
self._handle_message(msg) self._handle_message(msg)
await ha_mqtt.async_subscribe( await ha_mqtt.async_subscribe(
self.hass, self.hass,
self._topic_status_wildcard, self._topic_status_wildcard,
@@ -252,12 +252,12 @@ class JackeryDataCoordinator:
1 1
) )
_LOGGER.info(f"Coordinator subscribed to: {self._topic_status_wildcard}") _LOGGER.info(f"Coordinator subscribed to: {self._topic_status_wildcard}")
self._subscribed = True self._subscribed = True
# 启动定时轮询 # 启动定时轮询
self._data_task = asyncio.create_task(self._periodic_data_request()) self._data_task = asyncio.create_task(self._periodic_data_request())
except Exception as e: except Exception as e:
_LOGGER.error(f"Failed to start coordinator: {e}") _LOGGER.error(f"Failed to start coordinator: {e}")
@@ -278,7 +278,7 @@ class JackeryDataCoordinator:
payload = msg.payload payload = msg.payload
if isinstance(payload, bytes): if isinstance(payload, bytes):
payload = payload.decode("utf-8") payload = payload.decode("utf-8")
# Extract device SN from topic: hb/device/{sn}/status # Extract device SN from topic: hb/device/{sn}/status
match = re.search(r"hb/device/([^/]+)/status", topic) match = re.search(r"hb/device/([^/]+)/status", topic)
if match: if match:
@@ -312,18 +312,18 @@ class JackeryDataCoordinator:
async def _periodic_data_request(self) -> None: async def _periodic_data_request(self) -> None:
"""定期发送 'type: 25' 指令请求全量数据.""" """定期发送 'type: 25' 指令请求全量数据."""
_LOGGER.info("Starting periodic data polling...") _LOGGER.info("Starting periodic data polling...")
await asyncio.sleep(2) await asyncio.sleep(2)
while True: while True:
try: try:
if not self._device_sn: if not self._device_sn:
_LOGGER.debug("Waiting for device SN discovery...") _LOGGER.debug("Waiting for device SN discovery...")
await asyncio.sleep(5) await asyncio.sleep(5)
continue continue
# Construct Action Topic # Construct Action Topic
action_topic = f"{self._topic_root}/device/{self._device_sn}/action" action_topic = f"{self._topic_root}/device/{self._device_sn}/action"
# Construct Payload # Construct Payload
payload = { payload = {
"type": 25, "type": 25,
@@ -333,7 +333,7 @@ class JackeryDataCoordinator:
"token": self._token, "token": self._token,
"body": None "body": None
} }
await ha_mqtt.async_publish( await ha_mqtt.async_publish(
self.hass, self.hass,
action_topic, action_topic,
@@ -342,9 +342,9 @@ class JackeryDataCoordinator:
False False
) )
_LOGGER.debug(f"Sent poll request to {action_topic}") _LOGGER.debug(f"Sent poll request to {action_topic}")
await asyncio.sleep(REQUEST_INTERVAL) await asyncio.sleep(REQUEST_INTERVAL)
except asyncio.CancelledError: except asyncio.CancelledError:
break break
except Exception as e: except Exception as e:
@@ -361,22 +361,22 @@ async def async_setup_entry(
config = config_entry.data config = config_entry.data
topic_prefix = config.get("topic_prefix", "hb") topic_prefix = config.get("topic_prefix", "hb")
token = config.get("token") token = config.get("token")
coordinator = JackeryDataCoordinator(hass, topic_prefix, token) coordinator = JackeryDataCoordinator(hass, topic_prefix, token)
hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator
entities = [] entities = []
for sensor_id, sensor_config in SENSORS.items(): for sensor_id, sensor_config in SENSORS.items():
if sensor_config.get("json_key") is None: if sensor_config.get("json_key") is None:
continue continue
entity = JackerySensor( entity = JackerySensor(
sensor_id=sensor_id, sensor_id=sensor_id,
coordinator=coordinator, coordinator=coordinator,
config_entry_id=config_entry.entry_id, config_entry_id=config_entry.entry_id,
) )
entities.append(entity) entities.append(entity)
async_add_entities(entities) async_add_entities(entities)
await coordinator.async_start() await coordinator.async_start()
@@ -394,7 +394,7 @@ class JackerySensor(SensorEntity):
self._sensor_id = sensor_id self._sensor_id = sensor_id
self._coordinator = coordinator self._coordinator = coordinator
self._config = SENSORS[sensor_id] self._config = SENSORS[sensor_id]
self._attr_name = self._config["name"] self._attr_name = self._config["name"]
self._attr_native_unit_of_measurement = self._config["unit"] self._attr_native_unit_of_measurement = self._config["unit"]
self._attr_icon = self._config["icon"] self._attr_icon = self._config["icon"]
@@ -402,12 +402,12 @@ class JackerySensor(SensorEntity):
self._attr_state_class = self._config["state_class"] self._attr_state_class = self._config["state_class"]
self._attr_unique_id = f"jackery_{sensor_id}" self._attr_unique_id = f"jackery_{sensor_id}"
self._attr_has_entity_name = True self._attr_has_entity_name = True
self._attr_device_info = { self._attr_device_info = {
"identifiers": {(DOMAIN, config_entry_id)}, "identifiers": {(DOMAIN, config_entry_id)},
"name": "Jackery", "name": "Jackery",
"manufacturer": "Jackery", "manufacturer": "Jackery",
"model": "Energy Monitor", "model": "Energy Monitor",
} }
@property @property
@@ -446,7 +446,7 @@ class JackerySensor(SensorEntity):
self._attr_native_value = str(value) self._attr_native_value = str(value)
else: else:
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()
@@ -455,4 +455,4 @@ class JackerySensor(SensorEntity):
return { return {
"device_sn": self._coordinator._device_sn, "device_sn": self._coordinator._device_sn,
"raw_key": self._config.get("json_key") "raw_key": self._config.get("json_key")
} }

View File

@@ -5,6 +5,8 @@
"title": "配置 Jackery", "title": "配置 Jackery",
"description": "设置您的 Jackery 能源监控集成。注意:必须先配置 MQTT 集成。", "description": "设置您的 Jackery 能源监控集成。注意:必须先配置 MQTT 集成。",
"data": { "data": {
"mqtt_host": "MQTT 地址",
"device_sn": "设备 SN",
"token": "Token", "token": "Token",
"topic_prefix": "MQTT 主题前缀" "topic_prefix": "MQTT 主题前缀"
} }

View File

@@ -5,6 +5,8 @@
"title": "配置 Jackery", "title": "配置 Jackery",
"description": "设置您的 Jackery 能源监控集成。注意:必须先配置 MQTT 集成。", "description": "设置您的 Jackery 能源监控集成。注意:必须先配置 MQTT 集成。",
"data": { "data": {
"mqtt_host": "MQTT 地址",
"device_sn": "设备 SN",
"token": "Token", "token": "Token",
"topic_prefix": "MQTT 主题前缀" "topic_prefix": "MQTT 主题前缀"
} }