feat:加入 能流图计算逻辑
https://alidocs.dingtalk.com/i/nodes/ydxXB52LJq75R0a0SpdyZ7MAWqjMp697?cid=69072404141&utm_source=im&utm_scene=team_space&iframeQuery=utm_medium%3Dim_card%26utm_source%3Dim&utm_medium=im_card&corpId=ding01d381724bff4144bc961a6cb783455b
This commit is contained in:
@@ -203,6 +203,48 @@ SENSORS = {
|
||||
"icon": "mdi:power-sleep",
|
||||
"device_class": None,
|
||||
"state_class": None, # 0-Invalid, 1-Sleep/Off, 2-On
|
||||
},
|
||||
|
||||
# Calculated Sensors
|
||||
"home_power": {
|
||||
"json_key": "calc_home_power",
|
||||
"name": "Home Power",
|
||||
"unit": UnitOfPower.WATT,
|
||||
"icon": "mdi:home-lightning-bolt",
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
"battery_net_power": {
|
||||
"json_key": "calc_batt_net_power",
|
||||
"name": "Battery Net Power",
|
||||
"unit": UnitOfPower.WATT,
|
||||
"icon": "mdi:battery-sync",
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
"calc_battery_charge_power": {
|
||||
"json_key": "calc_battery_charge_power",
|
||||
"name": "Battery Charge Power (Calc)",
|
||||
"unit": UnitOfPower.WATT,
|
||||
"icon": "mdi:battery-charging",
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
"calc_battery_discharge_power": {
|
||||
"json_key": "calc_battery_discharge_power",
|
||||
"name": "Battery Discharge Power (Calc)",
|
||||
"unit": UnitOfPower.WATT,
|
||||
"icon": "mdi:battery-minus",
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
},
|
||||
"grid_net_power": {
|
||||
"json_key": "calc_grid_net_power",
|
||||
"name": "Grid Net Power",
|
||||
"unit": UnitOfPower.WATT,
|
||||
"icon": "mdi:transmission-tower",
|
||||
"device_class": SensorDeviceClass.POWER,
|
||||
"state_class": SensorStateClass.MEASUREMENT,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,18 +342,151 @@ class JackeryDataCoordinator:
|
||||
_LOGGER.warning(f"Invalid JSON payload on {topic}")
|
||||
return
|
||||
|
||||
# Enrich data with calculations
|
||||
data = self._calculate_energy_flow(data)
|
||||
self._distribute_data(data)
|
||||
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error handling message: {e}")
|
||||
|
||||
def _calculate_energy_flow(self, data: dict) -> dict:
|
||||
"""
|
||||
根据用户需求计算能量流数据.
|
||||
|
||||
Variables Mapping:
|
||||
- PV: pvPw
|
||||
- OngridCharge: inOngridPw
|
||||
- OngridSupply: outOngridPw
|
||||
- ACIn: swEpsInPw
|
||||
- ACOut: swEpsOutPw
|
||||
- GridBuy: (Need Key, assuming 'gridBuyPw' or similar, else None)
|
||||
- GridSell: (Need Key, assuming 'gridSellPw', else None)
|
||||
"""
|
||||
try:
|
||||
# 1. PV
|
||||
# Handle dict for PV if necessary (copied from sensor logic)
|
||||
pv_val = data.get("pvPw", 0)
|
||||
if isinstance(pv_val, dict):
|
||||
pv = float(pv_val.get("pvPw", 0) or pv_val.get("w", 0) or pv_val.get("power", 0))
|
||||
else:
|
||||
pv = float(pv_val)
|
||||
|
||||
# 2. Ongrid
|
||||
ongrid_charge = float(data.get("inOngridPw", 0))
|
||||
ongrid_supply = float(data.get("outOngridPw", 0))
|
||||
p_ong = ongrid_charge - ongrid_supply # 流入主机为正
|
||||
|
||||
# 3. ACSocket (EPS)
|
||||
ac_in = float(data.get("swEpsInPw", 0))
|
||||
ac_out = float(data.get("swEpsOutPw", 0))
|
||||
p_ac = ac_in - ac_out # 流入主机为正
|
||||
|
||||
# 4. Grid (Meter)
|
||||
# 优先从 'cts' 数组中提取 CT 数据 (Smart CT Meter)
|
||||
# cts item: { ..., "TphasePw": <Import>, "TnphasePw": <Export>, "commState": 1/0, ... }
|
||||
grid_available = False
|
||||
grid_buy = 0.0
|
||||
grid_sell = 0.0
|
||||
|
||||
cts = data.get("cts")
|
||||
if cts and isinstance(cts, list) and len(cts) > 0:
|
||||
# 尝试获取第一个 CT 数据
|
||||
ct_data = cts[0]
|
||||
# 检查通讯状态 (如果 commState 存在且为 0 可能表示离线,视具体协议而定,这里暂定只要有数据即可)
|
||||
# TphasePw: 总正向有功 (Grid Buy)
|
||||
# TnphasePw: 总负向有功 (Grid Sell)
|
||||
t_phase_pw = ct_data.get("TphasePw")
|
||||
tn_phase_pw = ct_data.get("TnphasePw")
|
||||
|
||||
if t_phase_pw is not None and tn_phase_pw is not None:
|
||||
grid_buy = float(t_phase_pw)
|
||||
grid_sell = float(tn_phase_pw)
|
||||
grid_available = True
|
||||
|
||||
# 兼容旧逻辑或直接字段 (如果 cts 不存在)
|
||||
if not grid_available:
|
||||
grid_buy_raw = data.get("gridBuyPw") # Hypothetical key
|
||||
grid_sell_raw = data.get("gridSellPw") # Hypothetical key
|
||||
if grid_buy_raw is not None and grid_sell_raw is not None:
|
||||
grid_available = True
|
||||
grid_buy = float(grid_buy_raw)
|
||||
grid_sell = float(grid_sell_raw)
|
||||
|
||||
# Calculate P_grid
|
||||
p_grid = None
|
||||
if grid_available:
|
||||
p_grid = grid_buy - grid_sell
|
||||
|
||||
# 🔴异常流程(仅当电表可用且并网口处于充电态时生效)
|
||||
# GridAvailable=true 且 GridBuy < OngridCharge 且 (OngridCharge - GridBuy) <= 50W
|
||||
if grid_buy < ongrid_charge and (ongrid_charge - grid_buy) <= 50:
|
||||
p_grid = p_ong
|
||||
|
||||
# 5. Battery (Calculated)
|
||||
# P_batt = P_pv + P_ac + P_ong
|
||||
p_batt = pv + p_ac + p_ong
|
||||
|
||||
# 6. Home (Calculated)
|
||||
p_home = 0.0
|
||||
|
||||
if p_grid is not None:
|
||||
# 电表可用
|
||||
p_home = p_grid - p_ong
|
||||
|
||||
# 🔴 异常分支 1
|
||||
if grid_buy > 0 and ongrid_charge > 0 and grid_buy < ongrid_charge and (ongrid_charge - grid_buy) <= 50:
|
||||
# p_grid = p_ong # Already handled in p_grid calc above?
|
||||
# Note: User spec says "P_grid = P_ong (按异常流程先修正); P_home = 0"
|
||||
# My P_grid calc above handled P_grid. Now P_home:
|
||||
p_home = 0.0
|
||||
|
||||
# 🔴 异常分支 2
|
||||
elif grid_buy > 0 and ongrid_charge > 0 and grid_buy < ongrid_charge and (ongrid_charge - grid_buy) > 50:
|
||||
p_home = ongrid_charge - grid_buy
|
||||
|
||||
# 🔴 馈网场景分支 A
|
||||
elif grid_sell > 0 and ongrid_supply > 0:
|
||||
p_home = grid_sell - ongrid_supply
|
||||
|
||||
# 🔴 馈网场景分支 B
|
||||
elif grid_sell > 0 and ongrid_charge > 0:
|
||||
p_home = grid_sell + ongrid_charge
|
||||
|
||||
else:
|
||||
# 电表不可用 (No CT)
|
||||
if ongrid_supply > 0:
|
||||
p_home = ongrid_supply
|
||||
else:
|
||||
p_home = 0.0
|
||||
|
||||
# Store calculated values
|
||||
data["calc_home_power"] = p_home
|
||||
data["calc_batt_net_power"] = p_batt
|
||||
data["calc_battery_charge_power"] = max(0.0, p_batt)
|
||||
data["calc_battery_discharge_power"] = max(0.0, -p_batt)
|
||||
data["calc_grid_net_power"] = p_grid if p_grid is not None else 0 # Return 0 if None for sensor safety?
|
||||
# Note: If p_grid is None, the sensor might show 0 or unavailable.
|
||||
# Ideally "Grid Net Power" sensor should be unavailable if no CT.
|
||||
# But let's set it to 0 for now or handle in sensor.
|
||||
|
||||
# Additional: We might want to pass 'grid_available' to data for sensor state?
|
||||
if p_grid is None:
|
||||
# If we return None, the sensor logic below might error or show Unknown.
|
||||
# Let's leave it as None in data, and handle in sensor update.
|
||||
data["calc_grid_net_power"] = None
|
||||
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Error calculating energy flow: {e}")
|
||||
|
||||
return data
|
||||
|
||||
def _distribute_data(self, data: dict) -> None:
|
||||
"""分发数据给传感器."""
|
||||
for sensor_id, entity in self._sensors.items():
|
||||
entity._update_from_coordinator(data)
|
||||
|
||||
async def _periodic_data_request(self) -> None:
|
||||
"""定期发送 'type: 25' 指令请求全量数据."""
|
||||
"""定期发送 'type: 25' 和 'type: 100' 指令."""
|
||||
_LOGGER.info(f"Starting periodic data polling for {self._device_sn} via {self._mqtt_host}...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
@@ -324,25 +499,49 @@ class JackeryDataCoordinator:
|
||||
|
||||
# Construct Action Topic
|
||||
action_topic = f"{self._topic_root}/device/{self._device_sn}/action"
|
||||
|
||||
# Construct Payload
|
||||
payload = {
|
||||
ts = int(time.time())
|
||||
|
||||
# 1. Poll Device Status (Type 25)
|
||||
payload_25 = {
|
||||
"type": 25,
|
||||
"eventId": 0,
|
||||
"messageId": random.randint(1000, 9999),
|
||||
"ts": int(time.time()),
|
||||
"ts": ts,
|
||||
"token": self._token,
|
||||
"body": None
|
||||
}
|
||||
|
||||
|
||||
await ha_mqtt.async_publish(
|
||||
self.hass,
|
||||
action_topic,
|
||||
json.dumps(payload),
|
||||
json.dumps(payload_25),
|
||||
0,
|
||||
False
|
||||
)
|
||||
_LOGGER.debug(f"Sent poll request to {action_topic}")
|
||||
|
||||
# 2. Poll Sub-devices (Type 100) - specifically for CTs (devType: 2)
|
||||
# type=100 通知设备上报特定类型子设备全量数据
|
||||
# devType: 2 (同时获取CT&电表采集头&电表)
|
||||
payload_100 = {
|
||||
"type": 100,
|
||||
"eventId": 0,
|
||||
"messageId": random.randint(1000, 9999),
|
||||
"ts": ts,
|
||||
"token": self._token,
|
||||
"body": {
|
||||
"devType": 2
|
||||
}
|
||||
}
|
||||
|
||||
await ha_mqtt.async_publish(
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user