Skip to content

Commit 514888e

Browse files
DCSBLCopilot
andauthored
Add support for batteries API (#533)
* Add support for batteries API * Update homewizard_energy/models.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix type discrepancy * Fix duplicate snapshot --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4dcb461 commit 514888e

File tree

11 files changed

+436
-43
lines changed

11 files changed

+436
-43
lines changed

homewizard_energy/homewizard_energy.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from .const import LOGGER
1111
from .errors import UnsupportedError
12-
from .models import CombinedModels, Device, Measurement, State, System
12+
from .models import Batteries, CombinedModels, Device, Measurement, State, System
1313

1414

1515
class HomeWizardEnergy:
@@ -63,15 +63,20 @@ async def fetch_data(coroutine):
6363
except (UnsupportedError, NotImplementedError):
6464
return None
6565

66-
device, measurement, system, state = await asyncio.gather(
66+
device, measurement, system, state, batteries = await asyncio.gather(
6767
fetch_data(self.device()),
6868
fetch_data(self.measurement()),
6969
fetch_data(self.system()),
7070
fetch_data(self.state()),
71+
fetch_data(self.batteries()),
7172
)
7273

7374
return CombinedModels(
74-
device=device, measurement=measurement, system=system, state=state
75+
device=device,
76+
measurement=measurement,
77+
system=system,
78+
state=state,
79+
batteries=batteries,
7580
)
7681

7782
async def device(self, reset_cache: bool = False) -> Device:
@@ -104,6 +109,10 @@ async def state(
104109
"""Get/set the state."""
105110
raise UnsupportedError("State is not supported")
106111

112+
async def batteries(self, mode: Batteries.Mode | None = None) -> Batteries:
113+
"""Get the batteries."""
114+
raise UnsupportedError("Batteries are not supported")
115+
107116
async def identify(
108117
self,
109118
) -> None:

homewizard_energy/models.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AwesomeVersionSerializationStrategy(SerializationStrategy, use_annotations
2222

2323
def serialize(self, value: AwesomeVersion) -> str:
2424
"""Serialize AwesomeVersion object to string."""
25-
return str(value)
25+
return str(value) # pragma: no cover
2626

2727
def deserialize(self, value: str) -> AwesomeVersion | None:
2828
"""Deserialize string to AwesomeVersion object."""
@@ -65,18 +65,23 @@ class CombinedModels:
6565
measurement: Measurement
6666
state: State | None
6767
system: System | None
68+
batteries: Batteries | None
6869

70+
# pylint: disable=too-many-arguments
71+
# pylint: disable=too-many-positional-arguments
6972
def __init__(
7073
self,
7174
device: Device,
7275
measurement: Measurement,
7376
state: State | None,
7477
system: System | None,
78+
batteries: Batteries | None = None,
7579
):
7680
self.device = device
7781
self.measurement = measurement
7882
self.state = state
7983
self.system = system
84+
self.batteries = batteries
8085

8186
# Move things around for backwards compatibility
8287
## measurement.wifi_ssid -> system.wifi_ssid
@@ -183,6 +188,10 @@ def supports_telegram(self) -> bool:
183188
"""Return if the device supports telegram."""
184189
return self.product_type == Model.P1_METER
185190

191+
def supports_batteries(self) -> bool:
192+
"""Return if the device supports batteries."""
193+
return self.product_type == Model.P1_METER
194+
186195

187196
@dataclass(kw_only=True)
188197
class Measurement(BaseModel):
@@ -601,6 +610,36 @@ def __post_deserialize__(cls, obj: System) -> System:
601610
return obj
602611

603612

613+
@dataclass
614+
class BatteriesUpdate(UpdateBaseModel):
615+
"""Represent Batteries update config."""
616+
617+
mode: Batteries.Mode | None = field(default=None)
618+
619+
620+
@dataclass(kw_only=True)
621+
class Batteries(BaseModel):
622+
"""Represent Batteries config."""
623+
624+
class Mode(StrEnum):
625+
"""Device type allocations."""
626+
627+
ZERO = "zero"
628+
TO_FULL = "to_full"
629+
STANDBY = "standby"
630+
631+
mode: Mode | None = field(
632+
default=None,
633+
metadata={
634+
"deserialize": lambda x: Batteries.Mode.__members__.get(x.upper(), None)
635+
},
636+
)
637+
power_w: float | None = field(default=None)
638+
target_power_w: float | None = field(default=None)
639+
max_consumption_w: float | None = field(default=None)
640+
max_production_w: float | None = field(default=None)
641+
642+
604643
@dataclass(kw_only=True)
605644
class Token(BaseModel):
606645
"""Represent Token."""

homewizard_energy/v2/__init__.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,22 @@
1919
from ..errors import (
2020
DisabledError,
2121
InvalidUserNameError,
22+
NotFoundError,
2223
RequestError,
2324
ResponseError,
2425
UnauthorizedError,
26+
UnsupportedError,
2527
)
2628
from ..homewizard_energy import HomeWizardEnergy
27-
from ..models import Device, Measurement, System, SystemUpdate, Token
29+
from ..models import (
30+
Batteries,
31+
BatteriesUpdate,
32+
Device,
33+
Measurement,
34+
System,
35+
SystemUpdate,
36+
Token,
37+
)
2838
from .cacert import CACERT
2939

3040
T = TypeVar("T")
@@ -137,6 +147,36 @@ async def system(
137147
system = System.from_json(response)
138148
return system
139149

150+
@authorized_method
151+
async def batteries(
152+
self,
153+
mode: Batteries.Mode | None = None,
154+
) -> Batteries:
155+
"""Return the batteries object."""
156+
157+
if self._device is not None and self._device.supports_batteries() is False:
158+
raise UnsupportedError("Batteries is not supported")
159+
160+
try:
161+
if mode is not None:
162+
data = BatteriesUpdate(
163+
mode=mode,
164+
).to_dict()
165+
status, response = await self._request(
166+
"/api/batteries", method=METH_PUT, data=data
167+
)
168+
else:
169+
status, response = await self._request("/api/batteries")
170+
171+
if status != HTTPStatus.OK:
172+
error = json.loads(response).get("error", response)
173+
raise RequestError(f"Failed to get batteries: {error}")
174+
except NotFoundError as exception:
175+
# The batteries endpoint is not available on the device
176+
raise UnsupportedError("Batteries is not supported") from exception
177+
178+
return Batteries.from_json(response)
179+
140180
@authorized_method
141181
async def identify(
142182
self,
@@ -275,6 +315,8 @@ async def _request(
275315
case HTTPStatus.NO_CONTENT:
276316
# No content, just return
277317
return (HTTPStatus.NO_CONTENT, None)
318+
case HTTPStatus.NOT_FOUND:
319+
raise NotFoundError("Resource not found")
278320
case HTTPStatus.OK:
279321
pass
280322

tests/test_homewizard_energy.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
("state", UnsupportedError),
1919
("identify", NotImplementedError),
2020
("reboot", UnsupportedError),
21+
("batteries", UnsupportedError),
2122
],
2223
)
2324
async def test_base_class_raises_notimplementederror(

0 commit comments

Comments
 (0)