Skip to content

Commit 0c1ead9

Browse files
authored
Merge pull request #281 from plugwise/p1-v1
P1 legacy: collect data from /core/modules
2 parents b5c8b5a + e2e0f5d commit 0c1ead9

File tree

6 files changed

+104
-54
lines changed

6 files changed

+104
-54
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Changelog
22

3-
## Ongoing
3+
## v0.27.9: P1 legacy: collect data from /core/modules
44

5+
- Collect P1 legacy data from /core/modules - fix for [#368](https://github.com/plugwise/plugwise-beta/issues/368)
56
- `Dependencies`: Default to python 3.11
67
- `Development`
78
- Improved markdown (i.e. markup and contents), added linter for markdown & added code owners
@@ -41,7 +42,7 @@
4142

4243
## v0.25.14: Improve, bugfix
4344

44-
- Anna+Elga: final solution for [#312](https://github.com/plugwise/plugwise-beta/issues/320)
45+
- Anna+Elga: final solution for [#320](https://github.com/plugwise/plugwise-beta/issues/320)
4546
- Related to [Core Issue 83068](https://github.com/home-assistant/core/issues/83068): handle actuator_functionality or sensor depending on which one is present
4647

4748
## v0.25.13: Anna+Elga, OnOff device: base heating_state, cooling_state on central_heating_state key only

plugwise/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@
456456
"voltage_phase_two": UOM(ELECTRIC_POTENTIAL_VOLT),
457457
"voltage_phase_three": UOM(ELECTRIC_POTENTIAL_VOLT),
458458
}
459+
P1_LEGACY_MEASUREMENTS: Final[dict[str, UOM]] = {
460+
"electricity_consumed": UOM(POWER_WATT),
461+
"electricity_produced": UOM(POWER_WATT),
462+
"gas_consumed": UOM(VOLUME_CUBIC_METERS),
463+
}
459464
# Thermostat and Plug/Stretch related measurements
460465
# Excluded:
461466
# zone_thermosstat: 'temperature_offset'

plugwise/helper.py

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
LOCATIONS,
4040
LOGGER,
4141
NONE,
42+
P1_LEGACY_MEASUREMENTS,
4243
P1_MEASUREMENTS,
4344
POWER_WATT,
4445
SENSORS,
@@ -1182,35 +1183,50 @@ def _heating_valves(self) -> int | None:
11821183
return None if loc_found == 0 else open_valve_count
11831184

11841185
def _power_data_peak_value(self, direct_data: DeviceData, loc: Munch) -> Munch:
1185-
"""Helper-function for _power_data_from_location()."""
1186+
"""Helper-function for _power_data_from_location() and _power_data_from_modules()."""
11861187
loc.found = True
1187-
no_tariffs = False
1188+
# If locator not found look for P1 gas_consumed or phase data (without tariff)
1189+
# or for P1 legacy electricity_point_meter or gas_*_meter data
1190+
if loc.logs.find(loc.locator) is None:
1191+
if "log" in loc.log_type and (
1192+
"gas" in loc.measurement or "phase" in loc.measurement
1193+
):
1194+
# Avoid double processing by skipping one peak-list option
1195+
if loc.peak_select == "nl_offpeak":
1196+
loc.found = False
1197+
return loc
11881198

1189-
# Only once try to find P1 Legacy values
1190-
if loc.logs.find(loc.locator) is None and self.smile_type == "power":
1191-
no_tariffs = True
1192-
# P1 Legacy: avoid doubling the net_electricity_..._point value by skipping one peak-list option
1193-
if loc.peak_select == "nl_offpeak":
1199+
loc.locator = (
1200+
f'./{loc.log_type}[type="{loc.measurement}"]/period/measurement'
1201+
)
1202+
if loc.logs.find(loc.locator) is None:
1203+
loc.found = False
1204+
return loc
1205+
# P1 legacy point_meter has no tariff_indicator
1206+
elif "meter" in loc.log_type and (
1207+
"point" in loc.log_type or "gas" in loc.measurement
1208+
):
1209+
# Avoid double processing by skipping one peak-list option
1210+
if loc.peak_select == "nl_offpeak":
1211+
loc.found = False
1212+
return loc
1213+
1214+
loc.locator = (
1215+
f"./{loc.meas_list[0]}_{loc.log_type}/"
1216+
f'measurement[@directionality="{loc.meas_list[1]}"]'
1217+
)
1218+
if loc.logs.find(loc.locator) is None:
1219+
loc.found = False
1220+
return loc
1221+
else:
11941222
loc.found = False
11951223
return loc
11961224

1197-
loc.locator = (
1198-
f'./{loc.log_type}[type="{loc.measurement}"]/period/measurement'
1199-
)
1200-
1201-
# Locator not found
1202-
if loc.logs.find(loc.locator) is None:
1203-
loc.found = False
1204-
return loc
1205-
12061225
if (peak := loc.peak_select.split("_")[1]) == "offpeak":
12071226
peak = "off_peak"
12081227
log_found = loc.log_type.split("_")[0]
12091228
loc.key_string = f"{loc.measurement}_{peak}_{log_found}"
1210-
# P1 with fw 2.x does not have tariff indicators for point_log values
1211-
if no_tariffs:
1212-
loc.key_string = f"{loc.measurement}_{log_found}"
1213-
if "gas" in loc.measurement:
1229+
if "gas" in loc.measurement or loc.log_type == "point_meter":
12141230
loc.key_string = f"{loc.measurement}_{log_found}"
12151231
if "phase" in loc.measurement:
12161232
loc.key_string = f"{loc.measurement}"
@@ -1227,19 +1243,16 @@ def _power_data_from_location(self, loc_id: str) -> DeviceData:
12271243
"""
12281244
direct_data: DeviceData = {}
12291245
loc = Munch()
1230-
1231-
search = self._locations
12321246
log_list: list[str] = ["point_log", "cumulative_log", "interval_log"]
12331247
peak_list: list[str] = ["nl_peak", "nl_offpeak"]
12341248
t_string = "tariff"
1235-
if self._smile_legacy:
1236-
t_string = "tariff_indicator"
12371249

1250+
search = self._locations
12381251
loc.logs = search.find(f'./location[@id="{loc_id}"]/logs')
1239-
# meter_string = ".//{}[type='{}']/"
12401252
for loc.measurement, loc.attrs in P1_MEASUREMENTS.items():
12411253
for loc.log_type in log_list:
12421254
for loc.peak_select in peak_list:
1255+
# meter_string = ".//{}[type='{}']/"
12431256
loc.locator = (
12441257
f'./{loc.log_type}[type="{loc.measurement}"]/period/'
12451258
f'measurement[@{t_string}="{loc.peak_select}"]'
@@ -1255,6 +1268,39 @@ def _power_data_from_location(self, loc_id: str) -> DeviceData:
12551268

12561269
return direct_data
12571270

1271+
def _power_data_from_modules(self) -> DeviceData:
1272+
"""Helper-function for smile.py: _get_device_data().
1273+
1274+
Collect the power-data from MODULES (P1 legacy only).
1275+
"""
1276+
direct_data: DeviceData = {}
1277+
loc = Munch()
1278+
mod_list: list[str] = ["interval_meter", "cumulative_meter", "point_meter"]
1279+
peak_list: list[str] = ["nl_peak", "nl_offpeak"]
1280+
t_string = "tariff_indicator"
1281+
1282+
search = self._modules
1283+
mod_logs = search.findall("./module/services")
1284+
for loc.measurement, loc.attrs in P1_LEGACY_MEASUREMENTS.items():
1285+
loc.meas_list = loc.measurement.split("_")
1286+
for loc.logs in mod_logs:
1287+
for loc.log_type in mod_list:
1288+
for loc.peak_select in peak_list:
1289+
loc.locator = (
1290+
f"./{loc.meas_list[0]}_{loc.log_type}/measurement"
1291+
f'[@directionality="{loc.meas_list[1]}"][@{t_string}="{loc.peak_select}"]'
1292+
)
1293+
loc = self._power_data_peak_value(direct_data, loc)
1294+
if not loc.found:
1295+
continue
1296+
1297+
direct_data = power_data_energy_diff(
1298+
loc.measurement, loc.net_string, loc.f_val, direct_data
1299+
)
1300+
direct_data[loc.key_string] = loc.f_val # type: ignore [literal-required]
1301+
1302+
return direct_data
1303+
12581304
def _preset(self, loc_id: str) -> str | None:
12591305
"""Helper-function for smile.py: device_data_climate().
12601306

plugwise/smile.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,12 @@ def _get_device_data(self, dev_id: str) -> DeviceData:
287287
if details["dev_class"] == "heater_central" and self._dhw_allowed_modes:
288288
device_data["dhw_modes"] = self._dhw_allowed_modes
289289

290-
# Get P1 smartmeter data from LOCATIONS
290+
# Get P1 smartmeter data from LOCATIONS or MODULES
291291
if details["dev_class"] == "smartmeter":
292-
device_data.update(self._power_data_from_location(details["location"]))
292+
if not self._smile_legacy:
293+
device_data.update(self._power_data_from_location(details["location"]))
294+
else:
295+
device_data.update(self._power_data_from_modules())
293296

294297
# Check availability of non-legacy wired-connected devices
295298
if not self._smile_legacy:
@@ -484,11 +487,6 @@ async def _full_update_device(self) -> None:
484487
self._appliances = await self._request(APPLIANCES)
485488
await self._update_domain_objects()
486489

487-
if self.smile_type != "power":
488-
await self._update_domain_objects()
489-
if not self._smile_legacy:
490-
self._appliances = await self._request(APPLIANCES)
491-
492490
async def _update_domain_objects(self) -> None:
493491
"""Helper-function for smile.py: full_update_device() and async_update().
494492
@@ -515,8 +513,10 @@ async def async_update(self) -> tuple[GatewayData, dict[str, DeviceData]]:
515513
"""Perform an incremental update for updating the various device states."""
516514
if self.smile_type != "power":
517515
await self._update_domain_objects()
518-
else:
516+
elif not self._smile_legacy:
519517
self._locations = await self._request(LOCATIONS)
518+
else:
519+
self._modules = await self._request(MODULES)
520520

521521
# P1 legacy has no appliances
522522
if not (self.smile_type == "power" and self._smile_legacy):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "plugwise"
7-
version = "0.27.8"
7+
version = "0.27.9"
88
license = {file = "LICENSE"}
99
description = "Plugwise Smile (Adam/Anna/P1), Stretch and USB (Stick) module for Python 3."
1010
readme = "README.md"

tests/test_smile.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -876,20 +876,20 @@ async def test_connect_smile_p1_v2(self):
876876
"name": "P1",
877877
"vendor": "Ene5\\T210-DESMR5.0",
878878
"sensors": {
879-
"net_electricity_point": 456,
880-
"electricity_consumed_point": 456,
881-
"net_electricity_cumulative": 1019.161,
882-
"electricity_consumed_peak_cumulative": 1155.155,
879+
"net_electricity_point": 458,
880+
"electricity_consumed_point": 458,
881+
"net_electricity_cumulative": 1019.201,
882+
"electricity_consumed_peak_cumulative": 1155.195,
883883
"electricity_consumed_off_peak_cumulative": 1642.74,
884-
"electricity_consumed_peak_interval": 210,
884+
"electricity_consumed_peak_interval": 250,
885885
"electricity_consumed_off_peak_interval": 0,
886886
"electricity_produced_point": 0,
887887
"electricity_produced_peak_cumulative": 1296.136,
888888
"electricity_produced_off_peak_cumulative": 482.598,
889889
"electricity_produced_peak_interval": 0,
890890
"electricity_produced_off_peak_interval": 0,
891-
"gas_consumed_cumulative": 584.431,
892-
"gas_consumed_interval": 0.014,
891+
"gas_consumed_cumulative": 584.433,
892+
"gas_consumed_interval": 0.016,
893893
},
894894
},
895895
}
@@ -934,20 +934,20 @@ async def test_connect_smile_p1_v2_2(self):
934934
"name": "P1",
935935
"vendor": "Ene5\\T210-DESMR5.0",
936936
"sensors": {
937-
"net_electricity_point": 456,
938-
"electricity_consumed_point": 456,
939-
"net_electricity_cumulative": 1019.161,
940-
"electricity_consumed_peak_cumulative": 1155.155,
937+
"net_electricity_point": 458,
938+
"electricity_consumed_point": 458,
939+
"net_electricity_cumulative": 1019.201,
940+
"electricity_consumed_peak_cumulative": 1155.195,
941941
"electricity_consumed_off_peak_cumulative": 1642.74,
942-
"electricity_consumed_peak_interval": 210,
942+
"electricity_consumed_peak_interval": 250,
943943
"electricity_consumed_off_peak_interval": 0,
944944
"electricity_produced_point": 0,
945945
"electricity_produced_peak_cumulative": 1296.136,
946946
"electricity_produced_off_peak_cumulative": 482.598,
947947
"electricity_produced_peak_interval": 0,
948948
"electricity_produced_off_peak_interval": 0,
949-
"gas_consumed_cumulative": 584.431,
950-
"gas_consumed_interval": 0.014,
949+
"gas_consumed_cumulative": 584.433,
950+
"gas_consumed_interval": 0.016,
951951
},
952952
},
953953
}
@@ -3554,7 +3554,6 @@ async def test_connect_p1v3(self):
35543554
"electricity_consumed_peak_cumulative": 7702.167,
35553555
"electricity_consumed_off_peak_cumulative": 10263.159,
35563556
"electricity_consumed_peak_interval": 179,
3557-
"electricity_produced_point": 0,
35583557
"electricity_produced_off_peak_point": 0,
35593558
"electricity_produced_peak_cumulative": 0.0,
35603559
"electricity_produced_off_peak_cumulative": 0.0,
@@ -3577,7 +3576,7 @@ async def test_connect_p1v3(self):
35773576

35783577
await self.device_test(smile, testdata)
35793578
assert smile.gateway_id == "a455b61e52394b2db5081ce025a430f3"
3580-
assert self.device_items == 28
3579+
assert self.device_items == 27
35813580
assert not self.notifications
35823581

35833582
await smile.close_connection()
@@ -3613,7 +3612,6 @@ async def test_connect_p1v3solarfake(self):
36133612
"electricity_consumed_peak_cumulative": 7702.167,
36143613
"electricity_consumed_off_peak_cumulative": 10263.159,
36153614
"electricity_consumed_peak_interval": 179,
3616-
"electricity_produced_point": 0,
36173615
"electricity_produced_off_peak_point": 0,
36183616
"electricity_produced_peak_cumulative": 20.0,
36193617
"electricity_produced_off_peak_cumulative": 3.0,
@@ -3636,7 +3634,7 @@ async def test_connect_p1v3solarfake(self):
36363634
assert not smile._smile_legacy
36373635

36383636
await self.device_test(smile, testdata)
3639-
assert self.device_items == 28
3637+
assert self.device_items == 27
36403638
assert not self.notifications
36413639

36423640
await smile.close_connection()

0 commit comments

Comments
 (0)