Skip to content

Commit 932ae6b

Browse files
authored
Merge pull request #218 from plugwise/neg-energy-4
Add support for devices with production enabled - try 4
2 parents 077a435 + 7836e01 commit 932ae6b

File tree

6 files changed

+267
-200
lines changed

6 files changed

+267
-200
lines changed

plugwise_usb/api.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,10 @@ class EnergyStatistics:
216216
hour_consumption_reset: datetime | None = None
217217
day_consumption: float | None = None
218218
day_consumption_reset: datetime | None = None
219-
week_consumption: float | None = None
220-
week_consumption_reset: datetime | None = None
221219
hour_production: float | None = None
222220
hour_production_reset: datetime | None = None
223221
day_production: float | None = None
224222
day_production_reset: datetime | None = None
225-
week_production: float | None = None
226-
week_production_reset: datetime | None = None
227223

228224

229225
class PlugwiseNode(Protocol):

plugwise_usb/nodes/circle.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -440,27 +440,25 @@ async def get_missing_energy_logs(self) -> None:
440440
self._energy_counters.update()
441441
if self._current_log_address is None:
442442
return None
443+
443444
if self._energy_counters.log_addresses_missing is None:
444445
_LOGGER.debug(
445446
"Start with initial energy request for the last 10 log addresses for node %s.",
446447
self._mac_in_str,
447448
)
448449
total_addresses = 11
449450
log_address = self._current_log_address
450-
log_update_tasks = []
451451
while total_addresses > 0:
452-
log_update_tasks.append(self.energy_log_update(log_address))
452+
await self.energy_log_update(log_address)
453453
log_address, _ = calc_log_address(log_address, 1, -4)
454454
total_addresses -= 1
455455

456-
for task in log_update_tasks:
457-
await task
458-
459456
if self._cache_enabled:
460457
await self._energy_log_records_save_to_cache()
458+
461459
return
462-
if self._energy_counters.log_addresses_missing is not None:
463-
_LOGGER.debug("Task created to get missing logs of %s", self._mac_in_str)
460+
461+
_LOGGER.debug("Task created to get missing logs of %s", self._mac_in_str)
464462
if (
465463
missing_addresses := self._energy_counters.log_addresses_missing
466464
) is not None:
@@ -472,8 +470,12 @@ async def get_missing_energy_logs(self) -> None:
472470
)
473471

474472
missing_addresses = sorted(missing_addresses, reverse=True)
475-
for address in missing_addresses:
476-
await self.energy_log_update(address)
473+
tasks = [
474+
create_task(self.energy_log_update(address))
475+
for address in missing_addresses
476+
]
477+
for task in tasks:
478+
await task
477479

478480
if self._cache_enabled:
479481
await self._energy_log_records_save_to_cache()
@@ -496,6 +498,7 @@ async def energy_log_update(self, address: int | None) -> bool:
496498
)
497499
return False
498500

501+
_LOGGER.debug("EnergyLogs data from %s, address=%s", self._mac_in_str, address)
499502
await self._available_update_state(True, response.timestamp)
500503
energy_record_update = False
501504

@@ -504,7 +507,12 @@ async def energy_log_update(self, address: int | None) -> bool:
504507
# energy pulses collected during the previous hour of given timestamp
505508
for _slot in range(4, 0, -1):
506509
log_timestamp, log_pulses = response.log_data[_slot]
507-
510+
_LOGGER.debug(
511+
"In slot=%s: pulses=%s, timestamp=%s",
512+
_slot,
513+
log_pulses,
514+
log_timestamp
515+
)
508516
if log_timestamp is None or log_pulses is None:
509517
self._energy_counters.add_empty_log(response.log_address, _slot)
510518
elif await self._energy_log_record_update_state(
@@ -515,9 +523,11 @@ async def energy_log_update(self, address: int | None) -> bool:
515523
import_only=True,
516524
):
517525
energy_record_update = True
526+
518527
self._energy_counters.update()
519528
if energy_record_update:
520529
await self.save_cache()
530+
521531
return True
522532

523533
async def _energy_log_records_load_from_cache(self) -> bool:

plugwise_usb/nodes/helpers/counter.py

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,13 @@ class EnergyType(Enum):
2121
PRODUCTION_HOUR = auto()
2222
CONSUMPTION_DAY = auto()
2323
PRODUCTION_DAY = auto()
24-
CONSUMPTION_WEEK = auto()
25-
PRODUCTION_WEEK = auto()
2624

2725

2826
ENERGY_COUNTERS: Final = (
2927
EnergyType.CONSUMPTION_HOUR,
3028
EnergyType.PRODUCTION_HOUR,
3129
EnergyType.CONSUMPTION_DAY,
3230
EnergyType.PRODUCTION_DAY,
33-
EnergyType.CONSUMPTION_WEEK,
34-
EnergyType.PRODUCTION_WEEK,
3531
)
3632
ENERGY_HOUR_COUNTERS: Final = (
3733
EnergyType.CONSUMPTION_HOUR,
@@ -41,20 +37,13 @@ class EnergyType(Enum):
4137
EnergyType.CONSUMPTION_DAY,
4238
EnergyType.PRODUCTION_DAY,
4339
)
44-
ENERGY_WEEK_COUNTERS: Final = (
45-
EnergyType.CONSUMPTION_WEEK,
46-
EnergyType.PRODUCTION_WEEK,
47-
)
48-
4940
ENERGY_CONSUMPTION_COUNTERS: Final = (
5041
EnergyType.CONSUMPTION_HOUR,
5142
EnergyType.CONSUMPTION_DAY,
52-
EnergyType.CONSUMPTION_WEEK,
5343
)
5444
ENERGY_PRODUCTION_COUNTERS: Final = (
5545
EnergyType.PRODUCTION_HOUR,
5646
EnergyType.PRODUCTION_DAY,
57-
EnergyType.PRODUCTION_WEEK,
5847
)
5948

6049
_LOGGER = logging.getLogger(__name__)
@@ -105,11 +94,8 @@ def add_pulse_stats(
10594
self, pulses_consumed: int, pulses_produced: int, timestamp: datetime
10695
) -> None:
10796
"""Add pulse statistics."""
108-
_LOGGER.debug(
109-
"add_pulse_stats | consumed=%s, for %s",
110-
str(pulses_consumed),
111-
self._mac,
112-
)
97+
_LOGGER.debug("add_pulse_stats for %s with timestamp=%s", self._mac, timestamp)
98+
_LOGGER.debug("consumed=%s | produced=%s", pulses_consumed, pulses_produced)
11399
self._pulse_collection.update_pulse_counter(
114100
pulses_consumed, pulses_produced, timestamp
115101
)
@@ -160,9 +146,6 @@ def update(self) -> None:
160146
self._energy_statistics.log_interval_consumption = (
161147
self._pulse_collection.log_interval_consumption
162148
)
163-
self._energy_statistics.log_interval_production = (
164-
self._pulse_collection.log_interval_production
165-
)
166149
(
167150
self._energy_statistics.hour_consumption,
168151
self._energy_statistics.hour_consumption_reset,
@@ -171,23 +154,18 @@ def update(self) -> None:
171154
self._energy_statistics.day_consumption,
172155
self._energy_statistics.day_consumption_reset,
173156
) = self._counters[EnergyType.CONSUMPTION_DAY].update(self._pulse_collection)
174-
(
175-
self._energy_statistics.week_consumption,
176-
self._energy_statistics.week_consumption_reset,
177-
) = self._counters[EnergyType.CONSUMPTION_WEEK].update(self._pulse_collection)
178-
179-
(
180-
self._energy_statistics.hour_production,
181-
self._energy_statistics.hour_production_reset,
182-
) = self._counters[EnergyType.PRODUCTION_HOUR].update(self._pulse_collection)
183-
(
184-
self._energy_statistics.day_production,
185-
self._energy_statistics.day_production_reset,
186-
) = self._counters[EnergyType.PRODUCTION_DAY].update(self._pulse_collection)
187-
(
188-
self._energy_statistics.week_production,
189-
self._energy_statistics.week_production_reset,
190-
) = self._counters[EnergyType.PRODUCTION_WEEK].update(self._pulse_collection)
157+
if self._pulse_collection.production_logging:
158+
self._energy_statistics.log_interval_production = (
159+
self._pulse_collection.log_interval_production
160+
)
161+
(
162+
self._energy_statistics.hour_production,
163+
self._energy_statistics.hour_production_reset,
164+
) = self._counters[EnergyType.PRODUCTION_HOUR].update(self._pulse_collection)
165+
(
166+
self._energy_statistics.day_production,
167+
self._energy_statistics.day_production_reset,
168+
) = self._counters[EnergyType.PRODUCTION_DAY].update(self._pulse_collection)
191169

192170
@property
193171
def timestamp(self) -> datetime | None:
@@ -211,14 +189,13 @@ def __init__(
211189
) -> None:
212190
"""Initialize energy counter based on energy id."""
213191
self._mac = mac
192+
self._midnight_reset_passed = False
214193
if energy_id not in ENERGY_COUNTERS:
215194
raise EnergyError(f"Invalid energy id '{energy_id}' for Energy counter")
216195
self._calibration: EnergyCalibration | None = None
217196
self._duration = "hour"
218197
if energy_id in ENERGY_DAY_COUNTERS:
219198
self._duration = "day"
220-
elif energy_id in ENERGY_WEEK_COUNTERS:
221-
self._duration = "week"
222199
self._energy_id: EnergyType = energy_id
223200
self._is_consumption = True
224201
self._direction = "consumption"
@@ -259,9 +236,16 @@ def energy(self) -> float | None:
259236
"""Total energy (in kWh) since last reset."""
260237
if self._pulses is None or self._calibration is None:
261238
return None
239+
262240
if self._pulses == 0:
263241
return 0.0
264-
pulses_per_s = self._pulses / float(HOUR_IN_SECONDS)
242+
243+
# Handle both positive and negative pulses values
244+
negative = False
245+
if self._pulses < 0:
246+
negative = True
247+
248+
pulses_per_s = abs(self._pulses) / float(HOUR_IN_SECONDS)
265249
corrected_pulses = HOUR_IN_SECONDS * (
266250
(
267251
(
@@ -276,8 +260,9 @@ def energy(self) -> float | None:
276260
+ self._calibration.off_tot
277261
)
278262
calc_value = corrected_pulses / PULSES_PER_KW_SECOND / HOUR_IN_SECONDS
279-
# Guard for minor negative miscalculations
280-
calc_value = max(calc_value, 0.0)
263+
if negative:
264+
calc_value = -calc_value
265+
281266
return calc_value
282267

283268
@property
@@ -297,16 +282,21 @@ def update(
297282
last_reset = datetime.now(tz=LOCAL_TIMEZONE)
298283
if self._energy_id in ENERGY_HOUR_COUNTERS:
299284
last_reset = last_reset.replace(minute=0, second=0, microsecond=0)
300-
elif self._energy_id in ENERGY_DAY_COUNTERS:
301-
last_reset = last_reset.replace(hour=0, minute=0, second=0, microsecond=0)
302-
elif self._energy_id in ENERGY_WEEK_COUNTERS:
303-
last_reset = last_reset - timedelta(days=last_reset.weekday())
304-
last_reset = last_reset.replace(
305-
hour=0,
306-
minute=0,
307-
second=0,
308-
microsecond=0,
309-
)
285+
if self._energy_id in ENERGY_DAY_COUNTERS:
286+
# Postpone the last_reset time-changes at day-end until a device pulsecounter resets
287+
if last_reset.hour == 0 and (
288+
not pulse_collection.pulse_counter_reset
289+
and not self._midnight_reset_passed
290+
):
291+
last_reset = (last_reset - timedelta(days=1)).replace(
292+
hour=0, minute=0, second=0, microsecond=0
293+
)
294+
else:
295+
if last_reset.hour == 0 and pulse_collection.pulse_counter_reset:
296+
self._midnight_reset_passed = True
297+
if last_reset.hour == 1 and self._midnight_reset_passed:
298+
self._midnight_reset_passed = False
299+
last_reset = last_reset.replace(hour=0, minute=0, second=0, microsecond=0)
310300

311301
pulses, last_update = pulse_collection.collected_pulses(
312302
last_reset, self._is_consumption
@@ -324,5 +314,6 @@ def update(
324314
self._pulses = pulses
325315

326316
energy = self.energy
327-
_LOGGER.debug("energy=%s or last_update=%s", energy, last_update)
317+
_LOGGER.debug("energy=%s on last_update=%s", energy, last_update)
328318
return (energy, last_reset)
319+

0 commit comments

Comments
 (0)