Skip to content

Commit 6114df0

Browse files
authored
Add support for River 3 devices, clean up sensor code (#6)
* Add support for River 3, clean up sensor code * Add total input/output energy sensors to River 3 * Add convenience descriptors for updatable fields * Take out common bt time commands into TimeCommands class * Update all sensor callbacks on demand * Fix black CI job
1 parent e3c2630 commit 6114df0

15 files changed

Lines changed: 856 additions & 253 deletions

File tree

.github/workflows/validate-code-style.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ jobs:
1717
- run: python3 -m pip install black
1818

1919
- name: Black style validation
20-
run: black .
20+
run: black --check .

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ number of EcoFlow devices through bluetooth and monitor their status / control p
1010
Recognized devices:
1111
* Smart Home Panel 2 (EF-HD3####, FW Version: 4.0.0.122, WiFi Version: 2.0.1.20)
1212
* Delta Pro Ultra (EF-YJ####, FW Version: 5.0.0.25, WiFi Version: 2.0.2.4)
13+
* River 3, River 3 Plus (EF-R3####, FW Version: 1.0.0.0)
1314

1415
**NOTICE**: this integration utilizes Bluetooth LE of the EF device, which is supporting just one
1516
connection at a time - so if you want to manage the device through BLE from your phone, you will

custom_components/ef_ble/binary_sensor.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
import logging
44

55
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
6-
76
from homeassistant.const import EntityCategory
87
from homeassistant.core import HomeAssistant
9-
from homeassistant.helpers.entity import Entity
108
from homeassistant.helpers.entity_platform import AddEntitiesCallback
119

1210
from . import DeviceConfigEntry
13-
from .sensor import SensorBase
14-
from .const import DOMAIN
11+
from .sensor import EcoflowSensor
1512

1613
_LOGGER = logging.getLogger(__name__)
1714

@@ -26,21 +23,21 @@ async def async_setup_entry(
2623

2724
new_sensors = []
2825
if hasattr(device, "error_happened"):
29-
new_sensors.append(ErrorDetectedSensor(device))
26+
new_sensors.append(ErrorDetectedSensor(device, "error_happened"))
3027

3128
if new_sensors:
3229
async_add_entities(new_sensors)
3330

3431

35-
class ErrorDetectedSensor(SensorBase):
32+
class ErrorDetectedSensor(EcoflowSensor):
3633
"""Represents that problem happened on device."""
3734

3835
device_class = BinarySensorDeviceClass.PROBLEM
3936
_attr_entity_category = EntityCategory.DIAGNOSTIC
4037

41-
def __init__(self, device):
38+
def __init__(self, device, sensor):
4239
"""Initialize the sensor."""
43-
super().__init__(device)
40+
super().__init__(device, sensor)
4441

4542
self._attr_unique_id = f"{self._device.name}_error"
4643
self._attr_name = "Error"

custom_components/ef_ble/config_flow.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,24 @@
33
from __future__ import annotations
44

55
import logging
6-
76
from typing import Any
87

98
import voluptuous as vol
10-
119
from homeassistant.components.bluetooth import (
1210
BluetoothServiceInfoBleak,
1311
async_discovered_service_info,
1412
)
1513
from homeassistant.config_entries import (
14+
CONN_CLASS_LOCAL_PUSH,
1615
ConfigFlow,
1716
ConfigFlowResult,
1817
OptionsFlow,
19-
CONN_CLASS_LOCAL_PUSH,
2018
)
2119
from homeassistant.const import CONF_ADDRESS
22-
from homeassistant.helpers import config_validation as cv
2320
from homeassistant.core import callback
2421

2522
from . import eflib
26-
from .const import DOMAIN, CONF_USER_ID
23+
from .const import CONF_USER_ID, DOMAIN
2724

2825
_LOGGER = logging.getLogger(__name__)
2926

@@ -78,6 +75,7 @@ async def async_step_bluetooth_confirm(
7875
data_schema=vol.Schema(
7976
{
8077
vol.Required(CONF_USER_ID): str,
78+
vol.Required(CONF_ADDRESS, default=device.address): str,
8179
}
8280
),
8381
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import asyncio
2+
from dataclasses import dataclass
3+
import logging
4+
import struct
5+
import time
6+
7+
from .devicebase import DeviceBase
8+
from .packet import Packet
9+
from .pb import utc_sys_pb2_v4 as utc_sys_pb2
10+
11+
_LOGGER = logging.getLogger(__name__)
12+
13+
14+
@dataclass
15+
class TimeCommands:
16+
device: DeviceBase
17+
18+
async def sendUtcTime(self):
19+
"""Send UTC time as unix timestamp seconds through PB"""
20+
_LOGGER.debug("%s: sendUtcTime", self.device.address)
21+
22+
utcs = utc_sys_pb2.SysUTCSync()
23+
utcs.sys_utc_time = int(time.time())
24+
payload = utcs.SerializeToString()
25+
packet = Packet(0x21, 0x0B, 0x01, 0x55, payload, 0x01, 0x01, 0x13)
26+
27+
await self.device._conn.sendPacket(packet)
28+
29+
async def sendRTCRespond(self):
30+
"""Send RTC timestamp seconds and TZ as respond to device's request"""
31+
_LOGGER.debug("%s: sendRTCRespond", self.device.address)
32+
33+
# Building payload
34+
tz_offset = (
35+
(time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
36+
/ 60
37+
/ 60
38+
* -1
39+
)
40+
tz_maj = int(tz_offset)
41+
tz_min = int((tz_offset - tz_maj) * 100)
42+
time_sec = int(time.time())
43+
payload = (
44+
struct.pack("<L", time_sec)
45+
+ struct.pack("<b", tz_maj)
46+
+ struct.pack("<b", tz_min)
47+
)
48+
49+
# Forming packet
50+
packet = Packet(
51+
0x21,
52+
0x35,
53+
0x01,
54+
Packet.NET_BLE_COMMAND_CMD_SET_RET_TIME,
55+
payload,
56+
0x01,
57+
0x01,
58+
0x03,
59+
)
60+
61+
await self.device._conn.sendPacket(packet)
62+
63+
async def sendRTCCheck(self):
64+
"""Send command to check RTC of the device"""
65+
_LOGGER.debug("%s: sendRTCCheck", self.device.address)
66+
67+
# Building payload
68+
tz_offset = (
69+
(time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
70+
/ 60
71+
/ 60
72+
* -1
73+
)
74+
tz_maj = int(tz_offset)
75+
tz_min = int((tz_offset - tz_maj) * 100)
76+
time_sec = int(time.time())
77+
payload = (
78+
struct.pack("<L", time_sec)
79+
+ struct.pack("<b", tz_maj)
80+
+ struct.pack("<b", tz_min)
81+
)
82+
83+
# Forming packet
84+
packet = Packet(
85+
0x21,
86+
0x35,
87+
0x01,
88+
Packet.NET_BLE_COMMAND_CMD_CHECK_RET_TIME,
89+
payload,
90+
0x01,
91+
0x01,
92+
0x03,
93+
)
94+
95+
await self.device._conn.sendPacket(packet)
96+
97+
def async_send_all(self):
98+
asyncio.create_task(self.sendUtcTime())
99+
asyncio.create_task(self.sendRTCRespond())
100+
asyncio.create_task(self.sendRTCCheck())

0 commit comments

Comments
 (0)