Skip to content

Commit

Permalink
Update packgen to disambiguate accessories
Browse files Browse the repository at this point in the history
Prep for accessory support, specifically inMix
  • Loading branch information
gazoodle committed Feb 17, 2025
1 parent bde2d5e commit 3e36ae6
Show file tree
Hide file tree
Showing 25 changed files with 1,193 additions and 955 deletions.
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,28 +285,25 @@ https://www.gnu.org/licenses/gpl-3.0.html
- Error handling (ongoing)
- Pythonize where possible
- More unit tests
- Handle other device types such as Waterfall
- Handle inMix for lighting control
- Add API documentation
- Tidy up support files. One class per file
- Add switch for winterizing
- Add ability to set hours so we can implement a crude clock sync mechanism
- Move to pytest unit test framework (replace all unittest fixtures and custom asserts)
- Use snapshots to generate some specific tests
- Build some documentation
- Add coverage to GitHub package workflow
- API set_config_mode needs to be per device rather than global
- Move localizable strings so that HA can handle itself
- Add command to simulator to start a blank configuration instead of loading a snapshot
- Use MinSetpointG and MaxSetpointG if available
- One of the accessors is AuxAsBubbleGen. Investigate how this is used
- Add a watt setting to pumps, heaters and so on so that the library can produce an instantaneous
power consumption figure which ought to be able to integrate into HA's energy dashboard.
- Add ability to change the RF channel. The app sends a CHACH\xb4\x0c command, play in the simulator!
- When there are two spas loaded, the pinp frequency seems to be too high. Is this related to the
- When there are two spas loaded, the ping frequency seems to be too high. Is this related to the
shared global config?
- Expose the master timeouts as configurations and the ud timeouts as controls

## Done/Fixed in 1.0.4
- Putting foundations in for inMix support

## Done/Fixed in 1.0.3
- Support setting of the remaining duration for reminders
Expand Down
6 changes: 6 additions & 0 deletions src/geckolib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
GeckoBubbleGenerator,
GeckoButton,
GeckoErrorSensor,
GeckoHeatPump,
GeckoInGrid,
GeckoInMix,
GeckoKeypad,
GeckoLight,
GeckoPump,
Expand Down Expand Up @@ -88,7 +91,10 @@
"GeckoEnumStructAccessor",
"GeckoErrorSensor",
"GeckoGetChannelProtocolHandler",
"GeckoHeatPump",
"GeckoHelloProtocolHandler",
"GeckoInGrid",
"GeckoInMix",
"GeckoKeypad",
"GeckoLight",
"GeckoPackCommandProtocolHandler",
Expand Down
2 changes: 2 additions & 0 deletions src/geckolib/async_spa.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ async def _connect(self) -> None:

return

await self.struct.check_for_accessories()

_LOGGER.debug("Status block was completed, so spa can be connected")

self.struct.build_accessors()
Expand Down
6 changes: 6 additions & 0 deletions src/geckolib/automation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
from .bubblegen import GeckoBubbleGenerator
from .button import GeckoButton
from .heater import GeckoWaterHeater
from .heatpump import GeckoHeatPump
from .ingrid import GeckoInGrid
from .inmix import GeckoInMix
from .keypad import GeckoKeypad
from .keypad_backlight import GeckoKeypadBacklight
from .light import GeckoLight
Expand All @@ -25,6 +28,9 @@
"GeckoBubbleGenerator",
"GeckoButton",
"GeckoErrorSensor",
"GeckoHeatPump",
"GeckoInGrid",
"GeckoInMix",
"GeckoKeypad",
"GeckoKeypadBacklight",
"GeckoLight",
Expand Down
35 changes: 22 additions & 13 deletions src/geckolib/automation/async_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from geckolib.automation.bubblegen import GeckoBubbleGenerator
from geckolib.automation.heatpump import GeckoHeatPump
from geckolib.automation.ingrid import GeckoInGrid
from geckolib.automation.inmix import GeckoInMix
from geckolib.automation.lockmode import GeckoLockMode
from geckolib.automation.waterfall import GeckoWaterfall
from geckolib.config import GeckoConfig, config_sleep, set_config_mode
Expand Down Expand Up @@ -97,18 +98,24 @@ def __init__(

# Declare all the items that a spa might have. If they are
# available for this configuration, they will be marked as such.
self.pump_1 = GeckoPump(self, "Pump 1", "P1")
self.pump_2 = GeckoPump(self, "Pump 2", "P2")
self.pump_3 = GeckoPump(self, "Pump 3", "P3")
self.pump_4 = GeckoPump(self, "Pump 4", "P4")
self.pump_5 = GeckoPump(self, "Pump 5", "P5")
self.pump_1: GeckoPump = GeckoPump(self, "Pump 1", "P1")
self.pump_2: GeckoPump = GeckoPump(self, "Pump 2", "P2")
self.pump_3: GeckoPump = GeckoPump(self, "Pump 3", "P3")
self.pump_4: GeckoPump = GeckoPump(self, "Pump 4", "P4")
self.pump_5: GeckoPump = GeckoPump(self, "Pump 5", "P5")

self.blower = GeckoBlower(self)
self.waterfall = GeckoWaterfall(self)
self.bubblegenerator = GeckoBubbleGenerator(self)
self.blower: GeckoBlower = GeckoBlower(self)
self.waterfall: GeckoWaterfall = GeckoWaterfall(self)
self.bubblegenerator: GeckoBubbleGenerator = GeckoBubbleGenerator(self)

self.light = GeckoLightLi(self)
self.light2 = GeckoLightL120(self)
self.light: GeckoLightLi = GeckoLightLi(self)
self.light2: GeckoLightL120 = GeckoLightL120(self)

self._heatpump: GeckoHeatPump = GeckoHeatPump(self)
self._ingrid: GeckoInGrid = GeckoInGrid(self)
self._lockmode: GeckoLockMode = GeckoLockMode(self)

self._inmix: GeckoInMix = GeckoInMix(self)

#################

Expand All @@ -119,9 +126,6 @@ def __init__(
self._blowers: list[GeckoBlower] = []
self._lights: list[GeckoLight] = []
self._ecomode: GeckoSwitch | None = None
self._heatpump: GeckoHeatPump = GeckoHeatPump(self)
self._ingrid: GeckoInGrid = GeckoInGrid(self)
self._lockmode: GeckoLockMode = GeckoLockMode(self)
self._standby: GeckoStandby | None = None

# Build the automation items
Expand Down Expand Up @@ -337,6 +341,11 @@ def standby(self) -> GeckoStandby | None:
"""Get the standby switch if available."""
return self._standby

@property
def inmix(self) -> GeckoInMix:
"""Get the inMix handler."""
return self._inmix

@property
def spa_in_use_sensor(self) -> GeckoAsyncFacade.SpaInUseSensor:
"""Get the spa in use sensor."""
Expand Down
23 changes: 23 additions & 0 deletions src/geckolib/automation/inmix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Gecko inMix accessory class."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from geckolib.automation.power import GeckoPower

if TYPE_CHECKING:
from geckolib.automation.async_facade import GeckoAsyncFacade

_LOGGER = logging.getLogger(__name__)


class GeckoInMix(GeckoPower):
"""Gecko inMix support class."""

def __init__(self, facade: GeckoAsyncFacade) -> None:
"""Initialize the inMix class."""
super().__init__(facade, "inMix", "INMIX")
if "InMix-PackType" in facade.spa.accessors:
_LOGGER.info("Spa has an inMix accessory")
50 changes: 49 additions & 1 deletion src/geckolib/driver/async_spastruct.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

from geckolib._version import VERSION
from geckolib.const import GeckoConstants
from geckolib.driver.accessor import (
GeckoByteStructAccessor,
)

INMIX_PACKTYPE = 14

if TYPE_CHECKING:
from collections.abc import Callable
Expand Down Expand Up @@ -53,7 +58,12 @@ def set_status_block(self, block: bytes) -> None:

def build_accessors(self) -> None:
"""Build the accessors."""
self.accessors = dict(self.config_class.accessors, **self.log_class.accessors)
self.accessors = dict(
self.config_class.accessors,
**self.log_class.accessors,
**self.inmix_config_accessors,
**self.inmix_log_accessors,
)
# Get all outputs
self.all_outputs = [*self.config_class.output_keys, *self.log_class.output_keys]
# Get collection of possible devices
Expand All @@ -80,6 +90,11 @@ def reset(self) -> None:
self.log_version: int = 0
self.log_class = None

self.inmix_config_class = None
self.inmix_config_accessors = {}
self.inmix_log_class = None
self.inmix_log_accessors = {}

async def get(
self,
protocol: GeckoAsyncUdpProtocol,
Expand Down Expand Up @@ -242,3 +257,36 @@ def build_connections(self) -> None:
]

_LOGGER.debug("Connections are %s", self.connections)

############################################################################
#
# Accessory support

async def check_for_accessories(self) -> None:
"""After the initial pack has been loaded, check for accessories too."""
inmix_packtype = GeckoByteStructAccessor(self, "inMix-PackType", 628, None)
if inmix_packtype.value == INMIX_PACKTYPE:
inmix_configlib = GeckoByteStructAccessor(
self, "inMix-ConfigLib", 634, None
)
inmix_statuslib = GeckoByteStructAccessor(
self, "inMix-StatusLib", 635, None
)
await self.load_inmix_config_module(inmix_configlib.value)
await self.load_inmix_log_module(inmix_statuslib.value)

async def load_inmix_config_module(self, config_version: int) -> None:
"""Load the config module for the inmix system."""
config_module_name = f"geckolib.driver.packs.inmix-cfg-{config_version}"
config_class = (
await self._async_import_module(config_module_name)
).GeckoConfigStruct
self.inmix_config_class = config_class(self)
self.inmix_config_accessors = self.inmix_config_class.accessors

async def load_inmix_log_module(self, log_version: int) -> None:
"""Load the config module for the inmix system."""
log_module_name = f"geckolib.driver.packs.inmix-log-{log_version}"
log_class = (await self._async_import_module(log_module_name)).GeckoLogStruct
self.inmix_log_class = log_class(self)
self.inmix_log_accessors = self.inmix_log_class.accessors
99 changes: 69 additions & 30 deletions src/geckolib/driver/packs/inclear-32k-cfg-2.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,56 +30,95 @@ def output_keys(self) -> list[str]:
def accessors(self) -> dict[str, GeckoStructAccessor]:
"""The structure accessors."""
return {
"Mode": GeckoEnumStructAccessor(
"inClear-32K-Mode": GeckoEnumStructAccessor(
self.struct,
"ConfigStructure/All/Mode",
"ConfigStructure/All/inClear-32K-Mode",
512,
None,
["OFF", "ON"],
None,
None,
"ALL",
),
"MaintenanceLevel": GeckoByteStructAccessor(
self.struct, "ConfigStructure/All/MaintenanceLevel", 513, "ALL"
"inClear-32K-MaintenanceLevel": GeckoByteStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-MaintenanceLevel",
513,
"ALL",
),
"BoostLevel": GeckoByteStructAccessor(
self.struct, "ConfigStructure/All/BoostLevel", 514, "ALL"
"inClear-32K-BoostLevel": GeckoByteStructAccessor(
self.struct, "ConfigStructure/All/inClear-32K-BoostLevel", 514, "ALL"
),
"MaxCellCurrent": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/MaxCellCurrent", 515, "ALL"
"inClear-32K-MaxCellCurrent": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-MaxCellCurrent",
515,
"ALL",
),
"MaxMaintenanceLevel": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/MaxMaintenanceLevel", 517, "ALL"
"inClear-32K-MaxMaintenanceLevel": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-MaxMaintenanceLevel",
517,
"ALL",
),
"ErrDelayAfterReset": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/ErrDelayAfterReset", 519, "ALL"
"inClear-32K-ErrDelayAfterReset": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-ErrDelayAfterReset",
519,
"ALL",
),
"Boost1Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost1Durx6Minutes", 521, "ALL"
"inClear-32K-Boost1Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost1Durx6Minutes",
521,
"ALL",
),
"Boost2Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost2Durx6Minutes", 523, "ALL"
"inClear-32K-Boost2Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost2Durx6Minutes",
523,
"ALL",
),
"Boost3Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost3Durx6Minutes", 525, "ALL"
"inClear-32K-Boost3Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost3Durx6Minutes",
525,
"ALL",
),
"Boost4Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost4Durx6Minutes", 527, "ALL"
"inClear-32K-Boost4Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost4Durx6Minutes",
527,
"ALL",
),
"Boost5Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost5Durx6Minutes", 529, "ALL"
"inClear-32K-Boost5Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost5Durx6Minutes",
529,
"ALL",
),
"Boost6Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost6Durx6Minutes", 531, "ALL"
"inClear-32K-Boost6Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost6Durx6Minutes",
531,
"ALL",
),
"Boost7Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost7Durx6Minutes", 533, "ALL"
"inClear-32K-Boost7Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost7Durx6Minutes",
533,
"ALL",
),
"Boost8Durx6Minutes": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/Boost8Durx6Minutes", 535, "ALL"
"inClear-32K-Boost8Durx6Minutes": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-Boost8Durx6Minutes",
535,
"ALL",
),
"ValidRemoteFlow": GeckoWordStructAccessor(
self.struct, "ConfigStructure/All/ValidRemoteFlow", 537, "ALL"
"inClear-32K-ValidRemoteFlow": GeckoWordStructAccessor(
self.struct,
"ConfigStructure/All/inClear-32K-ValidRemoteFlow",
537,
"ALL",
),
}
Loading

0 comments on commit 3e36ae6

Please sign in to comment.