Skip to content

Commit

Permalink
Moved Zone properties out in to a separate class. Added tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
manzanotti authored and Paul Manzotti committed Jan 7, 2024
1 parent 87b8622 commit 24579cc
Show file tree
Hide file tree
Showing 20 changed files with 549 additions and 141 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
uses: actions/checkout@v3
- name: Check code with Black
uses: psf/black@stable
with:
options: "--check --exclude *.json"
- name: Set up Python 3.10.8
uses: actions/setup-python@v3
with:
Expand Down
7 changes: 4 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

repos:
- repo: https://github.com/psf/black
rev: 22.8.0
rev: 23.12.1
hooks:
- id: black
args:
- --safe
- --quiet
- --exclude *.json
files: ^genius.*/.+\.py$|^genius.*/tests\.py$

- repo: https://github.com/pycqa/flake8
rev: 3.8.4
rev: 7.0.0
hooks:
- id: flake8
additional_dependencies:
Expand All @@ -20,7 +21,7 @@ repos:
files: ^genius.*/.+\.py$|^genius.*/tests\.py$

- repo: https://github.com/pycqa/isort
rev: 5.7.0
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
46 changes: 30 additions & 16 deletions geniushubclient/zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@
import re
from typing import Dict, List # Any, Optional, Set, Tuple

from .const import (
from geniushubclient.const import (
ATTRS_ZONE,
FOOTPRINT_MODES,
IDAY_TO_DAY,
IMODE_TO_MODE,
ITYPE_TO_TYPE,
MODE_TO_IMODE,
TYPE_TO_ITYPE,
ZONE_KIT,
ZONE_MODE,
ZONE_TYPE,
)
from .device import GeniusBase
from geniushubclient.device import GeniusBase
from geniushubclient.zoneclasses.properties import Properties

_LOGGER = logging.getLogger(__name__)

Expand All @@ -39,9 +38,20 @@ class GeniusZone(GeniusBase):
def __init__(self, zone_id, raw_json, hub) -> None:
super().__init__(zone_id, raw_json, hub, ATTRS_ZONE)

self._properties = Properties()

self.device_objs = []
self.device_by_id = {}

self._update(raw_json, self._hub.api_version)

def _update(self, json_data: Dict, api_version: int):
"""Parse the json for the zone data."""
try:
self._properties._update(json_data, api_version)
except (AttributeError, LookupError, TypeError, ValueError):
_LOGGER.exception("Failed to convert Zone %s.", self.id)

@property
def data(self) -> Dict:
"""Convert a zone's v3 JSON to the v1 schema."""
Expand All @@ -58,7 +68,8 @@ def is_occupied(node) -> bool: # from web app v5.2.4
O = occupancy detected (valid in any mode)
A = occupancy detected, sufficient to call for heat (iff in Sense/FP mode)
l = null != i.settings.experimentalFeatures && i.settings.experimentalFeatures.timerPlus,
l = null != i.settings.experimentalFeatures
&& i.settings.experimentalFeatures.timerPlus,
p = parseInt(n.iMode) === e.zoneModes.Mode_Footprint || l, # sense mode?
u = parseInt(n.iFlagExpectedKit) & e.equipmentTypes.Kit_PIR, # has a PIR
d = n.trigger.reactive && n.trigger.output,
Expand Down Expand Up @@ -138,13 +149,14 @@ def footprint_schedule(raw_json) -> Dict:

return root

self._data = result = {"id": self._raw["iID"], "name": self._raw["strName"]}
self._data = result = {}
result["id"] = self.id

raw_json = self._raw # TODO: remove raw_json, use self._raw

try: # convert zone (v1 attributes)
result["type"] = ITYPE_TO_TYPE[raw_json["iType"]]
if raw_json["iType"] == ZONE_TYPE.TPI and raw_json["zoneSubType"] == 0:
result["type"] = ITYPE_TO_TYPE[ZONE_TYPE.ControlOnOffPID]
properties_data = self._properties._get_v1_data()
result.update(properties_data)

result["mode"] = IMODE_TO_MODE[raw_json["iMode"]]

Expand All @@ -156,12 +168,11 @@ def footprint_schedule(raw_json) -> Dict:
if raw_json["iType"] == ZONE_TYPE.Manager:
if raw_json["fPV"]:
result["temperature"] = raw_json["fPV"]

elif raw_json["iType"] == ZONE_TYPE.OnOffTimer:
result["setpoint"] = bool(raw_json["fSP"])

if self._has_pir:
if TYPE_TO_ITYPE[result["type"]] == ZONE_TYPE.ControlSP:
if self._properties.has_room_sensor:
if raw_json["iType"] == ZONE_TYPE.ControlSP:
result["occupied"] = is_occupied(raw_json)
else:
result["_occupied"] = is_occupied(raw_json)
Expand Down Expand Up @@ -220,17 +231,20 @@ def footprint_schedule(raw_json) -> Dict:

return self._data

@property
def properties(self) -> Properties:
"""Return the properties of the zone."""
return self._properties

@property
def _has_pir(self) -> bool:
"""Return True if the zone has a PIR (movement sensor)."""
if self._hub.api_version == 1:
return "occupied" in self.data
return self._raw["iFlagExpectedKit"] & ZONE_KIT.PIR
return self._properties.has_room_sensor

@property
def name(self) -> str:
"""Return the name of the zone, which can change."""
return self.data["name"]
return self._properties.name

@property
def devices(self) -> List:
Expand Down
File renamed without changes.
74 changes: 74 additions & 0 deletions geniushubclient/zoneclasses/properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import Dict

from geniushubclient.const import ITYPE_TO_TYPE, TYPE_TO_ITYPE, ZONE_KIT, ZONE_TYPE


class Properties:
"""The properties of the zone.
Intended to be the properties that are relatively static for
the lifecycle of the zone"""

def __init__(self):
self._name: str = None
self._zone_type: int = None
self._subtype: int = None
self._type_name: str = None
self._has_room_sensor: bool = None

def _update(self, json_data: Dict, api_version: int) -> None:
"""Parse the json and use it to populate the properties data class."""

if api_version == 1:
self._update_from_v1_data(json_data)
elif api_version == 3:
self._update_from_v3_data(json_data)

def _update_from_v1_data(self, json_data: Dict) -> None:
self._name = json_data["name"]
self._type_name = json_data["type"]
self._zone_type = TYPE_TO_ITYPE[self.type_name]
self._has_room_sensor = "occupied" in json_data

def _update_from_v3_data(self, json_data: Dict) -> None:
self._name = json_data["strName"]
self._zone_type = iType = json_data["iType"]
self._subtype = json_data["zoneSubType"]
self._has_room_sensor = json_data["iFlagExpectedKit"] & ZONE_KIT.PIR
self._type_name = ITYPE_TO_TYPE[iType]

if iType == ZONE_TYPE.TPI and json_data["zoneSubType"] == 0:
self._zone_type = ZONE_TYPE.ControlOnOffPID
self._type_name = ITYPE_TO_TYPE[ZONE_TYPE.ControlOnOffPID]

def _get_v1_data(self) -> Dict:
result = {}
result["name"] = self._name
result["type"] = self._type_name

return result

@property
def name(self) -> str:
"""Return the name of the zone."""
return self._name

@property
def zone_type(self) -> int:
"""Return the zone type integer representation of the zone."""
return self._zone_type

@property
def subtype(self) -> int:
"""Return the zone subtype integer representation of the zone."""
return self._subtype

@property
def type_name(self) -> str:
"""Return the name of the type of zone."""
return self._type_name

@property
def has_room_sensor(self) -> bool:
"""Return whether the zone has a PIR (movement sensor)."""
return self._has_room_sensor
20 changes: 10 additions & 10 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
black==22.8.0
debugpy==1.2.1
flake8==3.8.4
pre-commit==2.11.1
black==23.12.1
debugpy==1.8.0
flake8==7.0.0
pre-commit==3.6.0
#
flake8-docstrings==1.5.0
isort==5.7.0
pycodestyle==2.6.0
pydocstyle==5.1.1
pyflakes==2.2.0
pylint==2.7.2
flake8-docstrings==1.7.0
isort==5.13.2
pycodestyle==2.11.1
pydocstyle==6.3.0
pyflakes==3.2.0
pylint==3.0.3

# check-manifest==0.41
# pyroma==2.6
Expand Down

This file was deleted.

Empty file added tests/zone/__init__.py
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def test_when_bIsActive_is_true_then_state_bIsActive_true(self):
self.assertTrue(genius_zone.data["_state"]["bIsActive"])

def test_when_bOutRequestHeat_is_false_then_output_false(self):
"Check that the bOutRequestHeat is correctly set to false"
"Check that the output is correctly set to false"

self.raw_json["bOutRequestHeat"] = 0

Expand All @@ -92,7 +92,7 @@ def test_when_bOutRequestHeat_is_false_then_output_false(self):
self.assertEqual(genius_zone.data["output"], 0)

def test_when_bOutRequestHeat_is_true_then_output_true(self):
"Check that the bOutRequestHeat is correctly set to true"
"Check that the output is correctly set to true"

self.raw_json["bOutRequestHeat"] = 1

Expand Down
Loading

0 comments on commit 24579cc

Please sign in to comment.