Skip to content

Commit 613c53d

Browse files
authored
Move River 2 to controls system, fix missing remaining time sensors and fix DC Charging Max Amps control (#353)
Migrates the River 2 family (River 2 / 2 Pro / 2 Max) to the @controls decorator system, fixes a field-naming bug that prevented the charge/discharge time sensors from being created and fixes DC Charging Max Amps control sending incorrect payload.
1 parent 860073a commit 613c53d

3 files changed

Lines changed: 48 additions & 22 deletions

File tree

custom_components/ef_ble/eflib/devices/river2.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from bleak.backends.scanner import AdvertisementData
33

44
from ..devicebase import DeviceBase
5+
from ..entity import controls
6+
from ..entity.base import dynamic
57
from ..model import (
68
DirectBmsMDeltaHeartbeatPack,
79
DirectEmsDeltaHeartbeatPack,
@@ -56,8 +58,8 @@ class Device(DeviceBase, RawDataProps):
5658
ac_input_power = raw_field(pb_inv.input_watts)
5759
ac_output_power = raw_field(pb_inv.output_watts)
5860

59-
remain_time_charging = raw_field(pb_ems.chg_remain_time)
60-
remain_time_discharging = raw_field(pb_ems.dsg_remain_time)
61+
remaining_time_charging = raw_field(pb_ems.chg_remain_time)
62+
remaining_time_discharging = raw_field(pb_ems.dsg_remain_time)
6163

6264
dc_mode = raw_field(pb_mppt.cfg_chg_type, DCMode.from_value)
6365

@@ -140,11 +142,13 @@ async def data_parse(self, packet: Packet) -> bool:
140142

141143
return processed
142144

145+
@controls.outlet(ac_ports)
143146
async def enable_ac_ports(self, enabled: bool):
144147
payload = bytes([1 if enabled else 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
145148
packet = Packet(0x21, 0x05, 0x20, 0x42, payload, version=2)
146149
await self._conn.sendPacket(packet)
147150

151+
@controls.switch(dc_12v_port)
148152
async def enable_dc_12v_port(self, enabled: bool):
149153
payload = bytes([0x01 if enabled else 0x00])
150154
packet = Packet(0x21, 0x05, 0x20, 0x51, payload, version=2)
@@ -160,6 +164,7 @@ async def enable_ac_always_on(self, enabled: bool):
160164
packet = Packet(0x21, 0x02, 0x20, 0x5F, payload, version=2)
161165
await self._conn.sendPacket(packet)
162166

167+
@controls.switch(energy_backup)
163168
async def enable_energy_backup(self, enabled: bool):
164169
reserve = (
165170
self.energy_backup_battery_level
@@ -172,15 +177,22 @@ async def enable_energy_backup(self, enabled: bool):
172177
packet = Packet(0x21, 0x02, 0x20, 0x5E, payload, version=2)
173178
await self._conn.sendPacket(packet)
174179

175-
async def set_energy_backup_battery_level(self, value: int) -> bool:
180+
@controls.battery(
181+
energy_backup_battery_level,
182+
min=dynamic(battery_charge_limit_min),
183+
max=dynamic(battery_charge_limit_max),
184+
availability=energy_backup,
185+
)
186+
async def set_energy_backup_battery_level(self, value: float) -> bool:
176187
percent = max(0, min(int(value), 100))
177188
payload = bytes([0x01, percent, 0x00, 0x00])
178189
packet = Packet(0x21, 0x02, 0x20, 0x5E, payload, version=2)
179190
await self._conn.sendPacket(packet)
180191
return True
181192

182-
async def set_battery_charge_limit_min(self, limit: int) -> bool:
183-
limit = max(0, min(int(limit), 30))
193+
@controls.battery(battery_charge_limit_min, max=dynamic(battery_charge_limit_max))
194+
async def set_battery_charge_limit_min(self, value: float) -> bool:
195+
limit = max(0, min(int(value), 30))
184196
if (
185197
self.battery_charge_limit_max is not None
186198
and limit > self.battery_charge_limit_max
@@ -191,28 +203,38 @@ async def set_battery_charge_limit_min(self, limit: int) -> bool:
191203
await self._conn.sendPacket(packet)
192204
return True
193205

194-
async def set_battery_charge_limit_max(self, limit: int) -> bool:
195-
if self.battery_charge_limit_min is not None and limit < int(
196-
self.battery_charge_limit_min
206+
@controls.battery(battery_charge_limit_max, min=dynamic(battery_charge_limit_min))
207+
async def set_battery_charge_limit_max(self, value: float) -> bool:
208+
limit = int(value)
209+
if (
210+
self.battery_charge_limit_min is not None
211+
and limit < self.battery_charge_limit_min
197212
):
198213
return False
199214

200215
packet = Packet(0x21, 0x03, 0x20, 0x31, limit.to_bytes(), version=2)
201216
await self._conn.sendPacket(packet)
202217
return True
203218

204-
async def set_dc_charging_amps_max(self, value: int) -> bool:
205-
packet = Packet(0x21, 0x05, 0x20, 0x47, value.to_bytes(), version=2)
219+
@controls.current(dc_charging_max_amps, max=dynamic(dc_charging_current_max))
220+
async def set_dc_charging_amps_max(self, value: float) -> bool:
221+
payload = (int(value) * 1000).to_bytes(4, "little")
222+
packet = Packet(0x21, 0x05, 0x20, 0x47, payload, version=2)
206223
await self._conn.sendPacket(packet)
207224
return True
208225

209-
async def set_dc_mode(self, value: DCMode) -> bool:
226+
@controls.select(dc_mode, options=DCMode)
227+
async def set_dc_mode(self, value: DCMode):
210228
packet = Packet(0x21, 0x05, 0x20, 0x52, value.to_bytes(), version=2)
211229
await self._conn.sendPacket(packet)
212-
return True
213230

214-
async def set_ac_charging_speed(self, value: int):
215-
payload = value.to_bytes(2, "little") + b"\xff"
231+
@controls.power(
232+
ac_charging_speed,
233+
min=dynamic(min_ac_charging_power),
234+
max=dynamic(max_ac_charging_power),
235+
)
236+
async def set_ac_charging_speed(self, value: float) -> bool:
237+
payload = int(value).to_bytes(2, "little") + b"\xff"
216238
packet = Packet(0x21, 0x05, 0x20, 0x45, payload, version=2)
217239
await self._conn.sendPacket(packet)
218240
return True

custom_components/ef_ble/entity.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,6 @@ def _register_update_callback(
5252
if prop_name is None or not hasattr(self._device, prop_name):
5353
return
5454

55-
if value := getattr(self._device, prop_name, None):
56-
setattr(self, entity_attr, get_state(value))
57-
else:
58-
setattr(self, entity_attr, None)
59-
6055
@callback
6156
def state_updated(state: Any):
6257
if (state := get_state(state)) is EcoflowEntity.SkipWrite:
@@ -65,10 +60,11 @@ def state_updated(state: Any):
6560
setattr(self, entity_attr, state)
6661
self.async_write_ha_state()
6762

68-
if (state := getattr(self._device, prop_name, None)) is not None:
69-
setattr(self, entity_attr, get_state(state))
70-
elif default_state is not None:
63+
device_state = getattr(self._device, prop_name, None)
64+
if device_state is None and default_state is not None:
7165
setattr(self, entity_attr, default_state)
66+
elif (initial := get_state(device_state)) is not EcoflowEntity.SkipWrite:
67+
setattr(self, entity_attr, initial)
7268

7369
self._update_callbacks.append((prop_name, state_updated))
7470

custom_components/ef_ble/translations/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,14 @@
922922
"dc_charging_type": {
923923
"name": "DC Charging Type"
924924
},
925+
"dc_mode": {
926+
"name": "DC Mode",
927+
"state": {
928+
"auto": "Auto",
929+
"solar": "Solar",
930+
"car": "Car"
931+
}
932+
},
925933
"performance_mode": {
926934
"name": "Performance Mode"
927935
},

0 commit comments

Comments
 (0)