feat: 加入 插座

This commit is contained in:
不求圣剑
2026-01-26 15:45:03 +08:00
parent 1a971c975c
commit b9721d9b19

View File

@@ -265,6 +265,9 @@ class JackeryDataCoordinator:
self._data_task = None self._data_task = None
self._subscribed = False self._subscribed = False
self._known_plugs = set() # Set of known plug SNs
self.add_entities_callback = None # Callback to add new entities
# Topic patterns # Topic patterns
self._topic_status_wildcard = f"{self._topic_root}/device/+/status" self._topic_status_wildcard = f"{self._topic_root}/device/+/status"
@@ -344,11 +347,52 @@ class JackeryDataCoordinator:
# Enrich data with calculations # Enrich data with calculations
data = self._calculate_energy_flow(data) data = self._calculate_energy_flow(data)
# Check for new plugs
self._check_for_new_plugs(data)
self._distribute_data(data) self._distribute_data(data)
except Exception as e: except Exception as e:
_LOGGER.error(f"Error handling message: {e}") _LOGGER.error(f"Error handling message: {e}")
def _check_for_new_plugs(self, data: dict) -> None:
"""检查并添加新发现的插座."""
plugs = data.get("plugs")
if not plugs or not isinstance(plugs, list):
return
new_entities = []
for plug in plugs:
sn = plug.get("sn")
if sn and sn not in self._known_plugs:
_LOGGER.info(f"Discovered new plug: {sn}")
self._known_plugs.add(sn)
# Create plug sensor
# Pass entry_id if needed, but entity init can handle without it for simple cases?
# Need config_entry_id. We can store it in coordinator or pass it later.
# Actually, standard pattern is to use the config_entry passed in setup.
# We'll need to store entry_id in coordinator or make JackeryPlugSensor use it.
# Let's attach config_entry_id to coordinator in __init__?
# Or just pass it. Coordinator doesn't have it currently.
# We'll use a property or modify init.
# For now, let's assume async_add_entities callback handles the addition.
# But we need to instantiate the entity class.
# Wait, I need config_entry_id to create proper device_info identifiers.
# I'll add config_entry_id to JackeryDataCoordinator.
if hasattr(self, "config_entry_id"):
entity = JackeryPlugSensor(
plug_sn=sn,
coordinator=self,
config_entry_id=self.config_entry_id
)
new_entities.append(entity)
if new_entities and self.add_entities_callback:
self.add_entities_callback(new_entities)
def _calculate_energy_flow(self, data: dict) -> dict: def _calculate_energy_flow(self, data: dict) -> dict:
""" """
根据用户需求计算能量流数据. 根据用户需求计算能量流数据.
@@ -519,29 +563,29 @@ class JackeryDataCoordinator:
False False
) )
# 2. Poll Sub-devices (Type 100) - specifically for CTs (devType: 2) # 2. Poll Sub-devices (Type 100) - CTs (2) and Plugs (6)
# type=100 通知设备上报特定类型子设备全量数据 for dev_type in [2, 6]:
# devType: 2 (同时获取CT&电表采集头&电表) payload_100 = {
payload_100 = { "type": 100,
"type": 100, "eventId": 0,
"eventId": 0, "messageId": random.randint(1000, 9999),
"messageId": random.randint(1000, 9999), "ts": ts,
"ts": ts, "token": self._token,
"token": self._token, "body": {
"body": { "devType": dev_type
"devType": 2 }
} }
}
await ha_mqtt.async_publish(
self.hass,
action_topic,
json.dumps(payload_100),
0,
False
)
await asyncio.sleep(0.5) # Avoid spamming too fast
await ha_mqtt.async_publish( _LOGGER.debug(f"Sent poll requests (25 & 100 [2,6]) to {action_topic}")
self.hass,
action_topic,
json.dumps(payload_100),
0,
False
)
_LOGGER.debug(f"Sent poll requests (25 & 100) to {action_topic}")
await asyncio.sleep(REQUEST_INTERVAL) await asyncio.sleep(REQUEST_INTERVAL)
@@ -565,6 +609,13 @@ async def async_setup_entry(
device_sn = config.get("device_sn") device_sn = config.get("device_sn")
coordinator = JackeryDataCoordinator(hass, topic_prefix, token, mqtt_host, device_sn) coordinator = JackeryDataCoordinator(hass, topic_prefix, token, mqtt_host, device_sn)
coordinator.config_entry_id = config_entry.entry_id # Assign entry_id
# Register callback for dynamic entities
def add_entities_callback(new_entities):
async_add_entities(new_entities)
coordinator.add_entities_callback = add_entities_callback
hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator hass.data[DOMAIN][config_entry.entry_id]["coordinator"] = coordinator
entities = [] entities = []
@@ -585,7 +636,7 @@ async def async_setup_entry(
class JackerySensor(SensorEntity): class JackerySensor(SensorEntity):
"""Jackery Sensor.""" """Jackery Sensor."""
# ... (Existing JackerySensor Code) ...
def __init__( def __init__(
self, self,
sensor_id: str, sensor_id: str,
@@ -672,3 +723,79 @@ class JackerySensor(SensorEntity):
"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")
} }
class JackeryPlugSensor(SensorEntity):
"""Jackery Smart Plug Sensor."""
def __init__(
self,
plug_sn: str,
coordinator: JackeryDataCoordinator,
config_entry_id: str,
) -> None:
"""Initialize."""
self._plug_sn = plug_sn
self._coordinator = coordinator
self._attr_name = f"Plug {plug_sn} Power"
self._attr_native_unit_of_measurement = UnitOfPower.WATT
self._attr_icon = "mdi:power-socket-eu"
self._attr_device_class = SensorDeviceClass.POWER
self._attr_state_class = SensorStateClass.MEASUREMENT
self._attr_unique_id = f"jackery_plug_{plug_sn}_power"
self._attr_has_entity_name = True
self._attr_device_info = {
"identifiers": {(DOMAIN, f"plug_{plug_sn}")}, # Unique identifier for this plug device? Or attach to main?
# Ideally attach to main device if it's a sub-device, but having separate device in HA is also fine.
# Let's attach to the main Jackery device for simplicity.
"identifiers": {(DOMAIN, config_entry_id)},
"name": "Jackery",
"manufacturer": "Jackery",
"model": "Energy Monitor",
}
@property
def should_poll(self) -> bool:
return False
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
# Register with coordinator using a unique ID format
self._coordinator.register_sensor(f"plug_{self._plug_sn}", self)
async def async_will_remove_from_hass(self) -> None:
self._coordinator.unregister_sensor(f"plug_{self._plug_sn}")
await super().async_will_remove_from_hass()
def _update_from_coordinator(self, data: dict) -> None:
"""Receive data from coordinator."""
plugs = data.get("plugs")
if not plugs or not isinstance(plugs, list):
return
# Find my plug data
my_plug = next((p for p in plugs if p.get("sn") == self._plug_sn), None)
if not my_plug:
return
# Update state (outPw)
try:
self._attr_native_value = float(my_plug.get("outPw", 0))
# Update name if available and changed?
# name = my_plug.get("name")
# if name and name != self._attr_name:
# self._attr_name = name # Changing name dynamically might be tricky for entity registry.
self._attr_available = True
self.async_write_ha_state()
except (TypeError, ValueError):
pass
@property
def extra_state_attributes(self) -> dict[str, Any]:
return {
"plug_sn": self._plug_sn
}