Skip to content

Commit

Permalink
Merge pull request #79 from manzanotti/CopeWithUnknownIssue
Browse files Browse the repository at this point in the history
Issues with unknown devices or errors are now handled
  • Loading branch information
manzanotti committed Sep 30, 2023
2 parents 8c2e05d + 10cfb35 commit 8bfb2d5
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 71 deletions.
33 changes: 7 additions & 26 deletions geniushubclient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
from datetime import datetime as dt
from typing import Dict, List, Tuple # Any, Optional, Set

from .const import HUB_SW_VERSIONS, ISSUE_DESCRIPTION, ISSUE_TEXT, ZONE_MODE
from .const import HUB_SW_VERSIONS, ZONE_MODE
from .device import GeniusDevice
from .issue import GeniusIssue
from .session import GeniusService
from .zone import GeniusZone, natural_sort

Expand Down Expand Up @@ -148,36 +149,13 @@ def populate_objects(
entities.append(entity)
return entities, {e.id: e for e in entities}

def convert_issue(raw_json) -> Dict:
"""Convert a issues's v3 JSON to the v1 schema."""
description = ISSUE_DESCRIPTION.get(raw_json["id"], raw_json["id"])
level = ISSUE_TEXT.get(raw_json["level"], str(raw_json["level"]))

if "{zone_name}" in description:
zone_name = raw_json["data"]["location"]
if "{device_type}" in description:
# don't use nodeHash, it won't pick up (e.g. DCR - Channel 1)
# vice_type = DEVICE_HASH_TO_TYPE[raw_json["data"]["nodeHash"]]
device_type = self.device_by_id[raw_json["data"]["nodeID"]].data["type"]

if "{zone_name}" in description and "{device_type}" in description:
description = description.format(
zone_name=zone_name, device_type=device_type
)
elif "{zone_name}" in description:
description = description.format(zone_name=zone_name)
elif "{device_type}" in description:
description = description.format(device_type=device_type)

return {"description": description, "level": level}

if self.api_version == 1:
self._sense_mode = None # currently, no way to tell
else: # self.api_version == 3:
manager = [z for z in self._zones if z["iID"] == 0][0]
self._sense_mode = bool(manager["lOptions"] & ZONE_MODE.Other)

# TODO: this looks dodgy: replacing rather than updating entitys
# TODO: this looks dodgy: replacing rather than updating entities
self.zone_objs, self.zone_by_id = populate_objects(
self._zones, "iID", self.zone_by_id, GeniusZone
)
Expand All @@ -198,7 +176,10 @@ def convert_issue(raw_json) -> Dict:
self.issues = self._issues
self.version = self._version
else: # self.api_version == 3:
self.issues = [convert_issue(raw_json) for raw_json in self._issues]
self.issues = [
GeniusIssue(raw_json, self.device_by_id).data
for raw_json in self._issues
]
self.version = {
"hubSoftwareVersion": self._version,
"earliestCompatibleAPI": "https://my.geniushub.co.uk/v1",
Expand Down
25 changes: 0 additions & 25 deletions geniushubclient/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,31 +88,6 @@
TPI=2048,
) # from app.js, search for '.ZoneFlags = {'

ISSUE_TEXT = {0: "information", 1: "warning", 2: "error"}
ISSUE_DESCRIPTION = {
"manager:no_boiler_controller": "The hub does not have a boiler controller assigned",
"manager:no_boiler_comms": "The hub has lost communication with the boiler controller",
"manager:no_temp": "The hub does not have a valid temperature",
"manager:weather": "Unable to fetch the weather data", # correct
"manager:weather_data": "Weather data -",
"zone:using_weather_temp": "{zone_name} is currently using the outside temperature", # correct
"zone:using_assumed_temp": "{zone_name} is currently using the assumed temperature",
"zone:tpi_no_temp": "{zone_name} currently has no valid temperature", # correct
"node:no_comms": "The {device_type} has lost communication with the Hub",
"node:not_seen": "The {device_type} in {zone_name} can not been found by the Hub", # correct
"node:low_battery": "The battery for the {device_type} in {zone_name} is dead and needs to be replaced", # correct
"node:warn_battery": "The battery for the {device_type} is low",
"node:assignment_limit_exceeded": "{device_type} has been assigned to too many zones", # for DCR channels
} # from app.js, search for: "node:, "zone:, "manager:

# Example errors
# {'id': 'node:low_battery', 'level': 2, 'data': {'location': 'Room 2.2',
# 'nodeHash': '0x00000002A0107FFF', 'nodeID': '27', 'batteryLevel': 255}}
# {'id': 'node:not_seen', 'level': 2, 'data': {'location': 'Kitchen',
# 'nodeHash': '0x0000000000000000', 'nodeID': '4'}}
# {'id': 'zone:tpi_no_temp', 'level': 2, 'data': {'location': 'Temp'}}
# {'id': 'zone:using_weather_temp', 'level': 1, 'data': {'location': 'Test Rad'}}

IDAY_TO_DAY = {
0: "sunday",
1: "monday",
Expand Down
79 changes: 79 additions & 0 deletions geniushubclient/issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Python client library for the Genius Hub API."""

import logging
from typing import Dict

_LOGGER = logging.getLogger(__name__)


class GeniusIssue:
"""Class to hold information on any issues the hub is reporting"""

# Example errors
# {'id': 'node:low_battery', 'level': 2, 'data': {'location': 'Room 2.2',
# 'nodeHash': '0x00000002A0107FFF', 'nodeID': '27', 'batteryLevel': 255}}
# {'id': 'node:not_seen', 'level': 2, 'data': {'location': 'Kitchen',
# 'nodeHash': '0x0000000000000000', 'nodeID': '4'}}
# {'id': 'zone:tpi_no_temp', 'level': 2, 'data': {'location': 'Temp'}}
# {'id': 'zone:using_weather_temp', 'level': 1, 'data': {'location': 'Test Rad'}}

def __init__(self, raw_json, device_by_id) -> None:
self.id = id
self._raw = raw_json
self._device_by_id = device_by_id

self._data = {}
self._issue_level = {0: "information", 1: "warning", 2: "error"}
self._issue_description = {
"manager:no_boiler_controller": "The hub does not have a boiler controller assigned",
"manager:no_boiler_comms": "The hub has lost communication with the boiler controller",
"manager:no_temp": "The hub does not have a valid temperature",
"manager:weather": "Unable to fetch the weather data", # correct
"manager:weather_data": "Weather data -",
"zone:using_weather_temp": "{zone_name} is currently using the outside temperature", # correct
"zone:using_assumed_temp": "{zone_name} is currently using the assumed temperature",
"zone:tpi_no_temp": "{zone_name} currently has no valid temperature", # correct
"node:no_comms": "The {device_type} has lost communication with the Hub",
"node:not_seen": "The {device_type} in {zone_name} can not been found by the Hub", # correct
"node:low_battery": "The battery for the {device_type} in {zone_name} is dead and needs to be replaced", # correct
"node:warn_battery": "The battery for the {device_type} is low",
"node:assignment_limit_exceeded": "{device_type} has been assigned to too many zones", # for DCR channels
} # from app.js, search for: "node:, "zone:, "manager:

@property
def data(self) -> Dict:
def convert_issue(raw_json) -> Dict:
"""Convert a issues's v3 JSON to the v1 schema."""
unknown_error_message = (
"Unknown error for {device_type} in {zone_name} returned by hub: "
+ raw_json["id"]
)
description = self._issue_description.get(
raw_json["id"], unknown_error_message
)
level = self._issue_level.get(raw_json["level"], str(raw_json["level"]))

if "{zone_name}" in description:
zone_name = raw_json["data"]["location"]
if "{device_type}" in description:
# don't use nodeHash, it won't pick up (e.g. DCR - Channel 1)
# vice_type = DEVICE_HASH_TO_TYPE[raw_json["data"]["nodeHash"]]
device_id = raw_json["data"]["nodeID"]
if device_id in self._device_by_id.keys():
device = self._device_by_id[device_id]
device_type = device.data["type"]
else:
device_type = "Unknown device"

if "{zone_name}" in description and "{device_type}" in description:
description = description.format(
zone_name=zone_name, device_type=device_type
)
elif "{zone_name}" in description:
description = description.format(zone_name=zone_name)
elif "{device_type}" in description:
description = description.format(device_type=device_type)

return {"description": description, "level": level}

return convert_issue(self._raw)
40 changes: 20 additions & 20 deletions ghclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,25 +146,6 @@ def _parse_args():
async def main(loop):
"""Return the JSON as requested."""

args = _parse_args()
# print(args)
if args is None:
return

if args.debug_mode > 0:
import ptvsd

print(f"Debugging is enabled, listening on: {DEBUG_ADDR}:{DEBUG_PORT}.")
ptvsd.enable_attach(address=(DEBUG_ADDR, DEBUG_PORT))

if args.debug_mode > 1:
print("Waiting for debugger to attach...")
ptvsd.wait_for_attach()
print("Debugger is attached!")

if args.debug_mode > 2:
breakpoint()

# Option of providing test data (as list of Dicts), or leave both as None
if FILE_MODE:
with open("raw_zones.json", mode="r") as fh:
Expand All @@ -177,6 +158,25 @@ async def main(loop):
session = None
hub = GeniusTestHub(zones_json=z, device_json=d, debug=True)
else:
args = _parse_args()
# print(args)
if args is None:
return

if args.debug_mode > 0:
import ptvsd

print(f"Debugging is enabled, listening on: {DEBUG_ADDR}:{DEBUG_PORT}.")
ptvsd.enable_attach(address=(DEBUG_ADDR, DEBUG_PORT))

if args.debug_mode > 1:
print("Waiting for debugger to attach...")
ptvsd.wait_for_attach()
print("Debugger is attached!")

if args.debug_mode > 2:
breakpoint()

session = aiohttp.ClientSession()
hub = GeniusHub(
hub_id=args.hub_id,
Expand All @@ -186,7 +186,7 @@ async def main(loop):
debug=args.debug_mode,
)

hub.verbosity = args.verbosity
hub.verbosity = args.verbosity

await hub.update() # initialise: enumerate all zones, devices & issues
# ait hub.update() # for testing, do twice in a row to check for no duplicates
Expand Down
Empty file.
Loading

0 comments on commit 8bfb2d5

Please sign in to comment.