Skip to content

Commit

Permalink
Handle up since oscillation (#14)
Browse files Browse the repository at this point in the history
* Handle up_since oscillation on devices
* Rename private initialization variables for clarity
* Update changelog
  • Loading branch information
natekspencer committed Jun 1, 2022
1 parent 5a28d67 commit de6cf96
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 11 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# unreleased

- Handle up_since oscillation on devices
- Rename private initialization variables in device class for clarity

# 1.3.0 (2021-12-22)

- Add cloud base and freezing level calculations
Expand Down
30 changes: 19 additions & 11 deletions pyweatherflowudp/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def __init__(self, serial_number: str, data: dict[str, Any] | None = None) -> No
self._rssi: int = 0
self._timestamp: int | None = None
self._uptime: int = 0
self._initial_status: bool = False
self._initial_status_complete: bool = False

self._listeners: dict[str, list[Callable]] = {}
self._parse_message_map: dict[str, Callable | tuple[Callable, str]] = {}
Expand All @@ -95,7 +95,7 @@ def firmware_revision(self) -> str:
@property
def load_complete(self) -> bool:
"""Return `True` if the device has been completely loaded."""
return self._initial_status
return self._initial_status_complete

@property
def model(self) -> str:
Expand Down Expand Up @@ -192,8 +192,8 @@ def parse_hub_status(self, data: dict[str, Any]) -> None:

assert self._timestamp

if not self._initial_status:
self._initial_status = True
if not self._initial_status_complete:
self._initial_status_complete = True
self.emit(
EVENT_LOAD_COMPLETE,
CustomEvent(self._timestamp, EVENT_LOAD_COMPLETE),
Expand Down Expand Up @@ -234,7 +234,7 @@ def __init__(self, serial_number: str, data: dict[str, Any] | None = None) -> No
self._hub_rssi: int = 0
self._sensor_status: int = 0
self._debug: bool = False
self._initial_observation: bool = False
self._initial_observation_complete: bool = False

self.register_parse_handlers(
{
Expand All @@ -260,7 +260,7 @@ def hub_sn(self) -> str | None:
@property
def load_complete(self) -> bool:
"""Return `True` if the device has been completely loaded."""
return self._initial_status and self._initial_observation
return self._initial_status_complete and self._initial_observation_complete

@property
def sensor_status(self) -> list[str]:
Expand All @@ -278,17 +278,25 @@ def sensor_status(self) -> list[str]:

def parse_device_status(self, data: dict[str, Any]) -> None:
"""Parse the device status."""
old_up_since = (self._timestamp or 0) - self._uptime
self._timestamp = data.get(DATA_TIMESTAMP)
self._uptime = data.get(DATA_UPTIME, 0)
# Tempest devices seem to have a timestamp/uptime combo that oscillates +/- a few seconds.
# Attempt to correct this by adjusting the uptime if the difference is less than 60 seconds.
if (
self._initial_status_complete
and 0 < abs(dif := (self._timestamp - self._uptime) - old_up_since) < 60
):
self._uptime += dif
self._voltage = data.get(DATA_VOLTAGE, 0)
self._firmware_revision = data.get(DATA_FIRMWARE_REVISION)
self._rssi = data.get(DATA_RSSI, 0)
self._hub_rssi = data.get(DATA_HUB_RSSI, 0)
self._sensor_status = data.get(DATA_SENSOR_STATUS, 0)
self._debug = truebool(data.get(DATA_DEBUG))

if not self._initial_status:
self._initial_status = True
if not self._initial_status_complete:
self._initial_status_complete = True
self._send_load_complete_event()

assert self._timestamp
Expand All @@ -302,15 +310,15 @@ def parse_observation(self, data: list[list]) -> None:
for idx, field in self.OBSERVATION_VALUES_MAP.items():
setattr(self, field, observation[idx])

if not self._initial_observation:
self._initial_observation = True
if not self._initial_observation_complete:
self._initial_observation_complete = True
self._send_load_complete_event()

assert self._last_report
self.emit(EVENT_OBSERVATION, CustomEvent(self._last_report, EVENT_OBSERVATION))

def _send_load_complete_event(self) -> None:
if self._initial_status and self._initial_observation:
if self._initial_status_complete and self._initial_observation_complete:
assert self._last_report
assert self._timestamp
self.emit(
Expand Down
2 changes: 2 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Provide common pytest fixtures."""
from __future__ import annotations

import json
from typing import Any

Expand Down
12 changes: 12 additions & 0 deletions test/test_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ def load_complete(event):
device.parse_message(device_status)
assert device.rssi == -21 * UNIT_DECIBELS

# check up_since field
original_up_since = datetime.fromtimestamp(
device_status["timestamp"] - device_status["uptime"], timezone.utc
)
assert device.up_since == original_up_since
device_status.update({"uptime": device_status["uptime"] + 59})
device.parse_message(device_status)
assert device.up_since == original_up_since
device_status.update({"uptime": device_status["uptime"] + 1})
device.parse_message(device_status)
assert device.up_since < original_up_since

assert not device.last_rain_start_event
device.parse_message(evt_precip)
assert device.last_rain_start_event
Expand Down

0 comments on commit de6cf96

Please sign in to comment.