Skip to content

Commit

Permalink
Merge pull request #92 from natekspencer/fault-log
Browse files Browse the repository at this point in the history
Add helpers for fault logs, default to retrieving the last entry
  • Loading branch information
natekspencer authored Feb 8, 2025
2 parents 427fdaa + 2492da2 commit 2b90811
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 5 deletions.
23 changes: 20 additions & 3 deletions pybalboa/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(
self._filter_cycle_2_end: time | None = None

# status update
self._time_offset: timedelta = timedelta(0)
self._accessibility_type: AccessibilityType = AccessibilityType.NONE
self._filter_cycle_1_running: bool = False
self._filter_cycle_2_running: bool = False
Expand Down Expand Up @@ -353,6 +354,10 @@ def configuration_loaded(self) -> bool:
"""Return `True` if the configuration is loaded."""
return self._configuration_loaded.is_set()

def get_current_time(self) -> datetime:
"""Return the current time."""
return datetime.now() + self._time_offset

async def async_configuration_loaded(self, timeout: float = 15) -> bool:
"""Wait for configuration to complete."""
if self.configuration_loaded:
Expand Down Expand Up @@ -555,7 +560,7 @@ def _parse_fault_log(self, data: bytes) -> None:
08 | sensor A temperature
09 | sensor B temperature
"""
self._fault = FaultLog(*data)
self._fault = FaultLog(*(*data, self.get_current_time()))

def _parse_filter_cycle(self, data: bytes) -> None:
"""Parse a filter cycle message.
Expand Down Expand Up @@ -664,6 +669,10 @@ def _parse_status_update(self, data: bytes, reprocess: bool = False) -> None:
self._previous_status = data
self._time_hour = data[3]
self._time_minute = data[4]
if not reprocess:
now = datetime.now()
device_time = now.replace(hour=self._time_hour, minute=self._time_minute)
self._time_offset = device_time - now
self._is_24_hour = (flag := data[9]) & 0x02 != 0
if flag & 0x01 == 0:
self._temperature_unit = TemperatureUnit.FAHRENHEIT
Expand Down Expand Up @@ -777,8 +786,16 @@ async def request_device_configuration(self) -> None:
MessageType.REQUEST, SettingsCode.DEVICE_CONFIGURATION, 0x00, 0x01
)

async def request_fault_log(self, entry: int = 0) -> None:
"""Request the filter cycle."""
async def request_fault_log(self, entry: int = 0xFF) -> None:
"""Request a fault log entry.
entry: The fault log to retrieve, 0..23 or 0xFF (255) for the last fault
"""
if not 0 <= entry < 24 and entry != 0xFF:
raise ValueError(
f"Invalid fault log entry: {entry} (expected 0–23 or 0xFF for the last fault)"
)

await self.send_message(
MessageType.REQUEST, SettingsCode.FAULT_LOG, entry % 256, 0x00
)
Expand Down
51 changes: 49 additions & 2 deletions pybalboa/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import logging
from collections.abc import Callable
from dataclasses import dataclass
from dataclasses import InitVar, dataclass, field
from datetime import datetime, time, timedelta
from enum import IntEnum
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Final

from .enums import (
ControlType,
Expand Down Expand Up @@ -42,6 +43,28 @@

EVENT_UPDATE = "update"

FAULT_LOG_ERROR_CODES: Final[dict[int, str]] = {
15: "Sensors are out of sync",
16: "The water flow is low",
17: "The water flow has failed",
18: "The settings have been reset",
19: "Priming Mode",
20: "The clock has failed",
21: "The settings have been reset",
22: "Program memory failure",
26: "Sensors are out of sync -- Call for service",
27: "The heater is dry",
28: "The heater may be dry",
29: "The water is too hot",
30: "The heater is too hot",
31: "Sensor A Fault",
32: "Sensor B Fault",
34: "A pump may be stuck on",
35: "Hot fault",
36: "The GFCI test failed",
37: "Standby Mode (Hold Mode)",
}


class EventMixin:
"""Event mixin."""
Expand Down Expand Up @@ -208,3 +231,27 @@ class FaultLog:
target_temperature: int
sensor_a_temperature: int
sensor_b_temperature: int

current_time: InitVar[datetime | None] = None
fault_datetime: datetime = field(init=False)

def __post_init__(self, current_time: datetime | None) -> None:
"""Compute the fault datetime on initialization."""
self.fault_datetime = datetime.combine(
(current_time or datetime.now()) - timedelta(days=self.days_ago),
time(self.time_hour, self.time_minute),
)

def __str__(self) -> str:
"""Return str(self)."""
return (
f"Fault log {self.entry_number + 1}/{self.count}: {self.message} "
f"occurred on {self.fault_datetime:%Y-%m-%d at %H:%M}"
)

@property
def message(self) -> str:
"""Return the message for the error code."""
return FAULT_LOG_ERROR_CODES.get(
self.message_code, f"Unknown ({self.message_code})"
)

0 comments on commit 2b90811

Please sign in to comment.