Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions blebox_uniapi/box_types.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .button import GateBoxSecondOutput, TvLift
from .cover import Gate, GateBox, GateBoxB, Shutter
from typing import Union, Any

Expand Down Expand Up @@ -50,9 +51,8 @@ def get_latest_api_level(product_type: str) -> Union[dict, int]:
"set": lambda command: ("GET", f"/s/c/{command}")
}, # dictionary with interaction methods
"buttons": [
"tvLift",
{"lift": ""},
], # key used to set platform, list elements used in cls init, e.g. [<alias>, {"path": "state_value"}]
["tvLift", {}, TvLift],
], # key used to set platform, list elements used in cls init, e.g. [<alias>, {"path": "state_value"}, <optional type class>]
}
},
# airSensor
Expand Down Expand Up @@ -135,11 +135,15 @@ def get_latest_api_level(product_type: str) -> Union[dict, int]:
{
"position": "gate.currentPos",
"gate_type": "gate.gateType",
"extra_button_type": "gate.extraButtonType",
},
"gatebox",
GateBoxB,
]
],
"buttons": [
["second_output", {}, GateBoxSecondOutput],
],
},
},
# gateController
Expand Down
102 changes: 84 additions & 18 deletions blebox_uniapi/button.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .cover import GateBoxControlType, GateBoxExtraButtonType
from .feature import Feature
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Any, Optional, Type

from enum import Enum, auto

Expand All @@ -23,33 +24,98 @@ class ControlType(Enum):
CLOSE = auto()


class Buttons:
"""Buttons defines how a product derives its buttons from extended state."""

@staticmethod
def many_from_extended_state(
button_cls: Type["Button"],
product: "Box",
alias: str,
methods: dict,
extended_state: dict,
) -> list["Button"]:
raise NotImplementedError # pragma: no cover


class TvLift(Buttons):
@staticmethod
def many_from_extended_state(
button_cls: Type["Button"],
product: "Box",
alias: str,
methods: dict,
extended_state: dict,
) -> list["Button"]:
control_type = extended_state.get("tvLift", {}).get("controlType")
endpoints = TV_LIFT_CONTROL_TYPES_API.get(control_type, {})
return [
button_cls(product, f"{alias}_{endpoint}", methods, endpoint)
for endpoint in endpoints.values()
]


class GateBoxSecondOutput(Buttons):
"""GateBoxSecondOutput exposes the gateBox extra button output as a button.

WALK_IN and OTHER share one button name because what the output does is a
device setting the user can change at any time, not a hardware trait.
"""

@staticmethod
def many_from_extended_state(
button_cls: Type["Button"],
product: "Box",
alias: str,
methods: dict,
extended_state: dict,
) -> list["Button"]:
gate = extended_state.get("gate", {})
if gate.get("extraButtonType") not in (
GateBoxExtraButtonType.WALK_IN,
GateBoxExtraButtonType.OTHER,
):
return []

# Api levels below 20230102 report no mode and cannot be wired that way.
if gate.get("openCloseMode") == GateBoxControlType.OPEN_CLOSE:
return []

return [button_cls(product, alias, methods, "second_output", "secondary")]


class Button(Feature):
def __init__(
self, product: "Box", alias: str, methods: dict, query_string: str
self,
product: "Box",
alias: str,
methods: dict,
query_string: str,
api_command: str = "set",
) -> None:
super().__init__(product, alias, methods)
self._device_class = "UPDATE"
self._query_string: str = query_string
self._api_command: str = api_command

@classmethod
def many_from_config(cls, product, box_type_config, extended_state):
object_list = list()
if len(box_type_config) > 0:
alias = box_type_config[0]
if isinstance(extended_state, dict) and extended_state is not None:
lift_mode = extended_state.get("tvLift", {}).get("controlType", None)
for row in TV_LIFT_CONTROL_TYPES_API[lift_mode].items():
indicator, endpoint = row
object_list.append(
cls(product, alias + "_" + endpoint, {}, endpoint)
)

return object_list
else:
def many_from_config(
cls, product: "Box", box_type_config: list, extended_state: Any
) -> list["Button"]:
if not isinstance(extended_state, dict):
return []

async def set(self):
await self.async_api_command("set", self.query_string)
features: list[Button] = []
for alias, methods, buttons in box_type_config:
features.extend(
buttons.many_from_extended_state(
cls, product, alias, methods, extended_state
)
)
return features

async def set(self) -> None:
await self.async_api_command(self._api_command, self.query_string)

def after_update(self) -> None:
pass
Expand Down
31 changes: 29 additions & 2 deletions blebox_uniapi/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ class GateBoxControlType(IntEnum):
OPEN_CLOSE = 2


class GateBoxExtraButtonType(IntEnum):
"""GateBoxExtraButtonType defines gateBox extra button semantics.

Only meaningful outside OPEN_CLOSE mode, which takes the extra button output
for the close action and leaves this field unused.
"""

DISABLED = 0
STOP = 1
WALK_IN = 2
OTHER = 3


class GateBoxGateType(IntEnum):
"""GateBoxGateType defines possible gate/cover types reported by gateBox"""

Expand Down Expand Up @@ -305,7 +318,7 @@ def read_has_stop(self, alias: str, raw_value: Any, product: "Box") -> bool:
if button_type is None:
return False

return button_type == 1
return button_type == GateBoxExtraButtonType.STOP


class GateBoxB(GateBox):
Expand All @@ -328,9 +341,23 @@ def read_desired(self, alias: str, raw_value: Any, product: "Box") -> Optional[i
return raw_value("position")

def read_has_stop(self, alias: str, raw_value: Any, product: "Box") -> bool:
if product.last_data is None:
return False

# note: if control type is unknown we assume it is not open/close
# and has the stop feature via secondary button command.
return self._control_type != GateBoxControlType.OPEN_CLOSE
if self._control_type == GateBoxControlType.OPEN_CLOSE:
return False

# stop_command issues the same secondary command as the second_output button.
extra_button_type = raw_value("extra_button_type")
if extra_button_type in (
GateBoxExtraButtonType.WALK_IN,
GateBoxExtraButtonType.OTHER,
):
return False

return True

def read_cover_type(
self, alias: str, raw_value: Any, product: "Box"
Expand Down
Loading
Loading