first commit
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import DOMAIN
|
||||
from .core import utils
|
||||
from .core.converters import Converter
|
||||
from .core.device import XDevice, RE_DID
|
||||
from .core.entity import XEntity
|
||||
from .core.gateway import XGateway
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
def setup(gateway: XGateway, device: XDevice, conv: Converter):
|
||||
if conv.attr in device.entities:
|
||||
entity: XEntity = device.entities[conv.attr]
|
||||
entity.gw = gateway
|
||||
elif conv.attr == "command":
|
||||
entity = CommandSelect(gateway, device, conv)
|
||||
elif conv.attr == "data":
|
||||
entity = DataSelect(gateway, device, conv)
|
||||
else:
|
||||
entity = XiaomiSelect(gateway, device, conv)
|
||||
async_add_entities([entity])
|
||||
|
||||
gw: XGateway = hass.data[DOMAIN][config_entry.entry_id]
|
||||
gw.add_setup(__name__, setup)
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class XiaomiSelectBase(XEntity, SelectEntity):
|
||||
_attr_current_option: str = None
|
||||
|
||||
def __init__(self, gateway: 'XGateway', device: XDevice, conv: Converter):
|
||||
super().__init__(gateway, device, conv)
|
||||
|
||||
if hasattr(conv, "map"):
|
||||
self._attr_options = list(conv.map.values())
|
||||
|
||||
@callback
|
||||
def async_set_state(self, data: dict):
|
||||
if self.attr in data:
|
||||
self._attr_current_option = data[self.attr]
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class XiaomiSelect(XiaomiSelectBase, RestoreEntity):
|
||||
@callback
|
||||
def async_restore_last_state(self, state: str, attrs: dict):
|
||||
self._attr_current_option = state
|
||||
|
||||
async def async_select_option(self, option: str):
|
||||
await self.device_send({self.attr: option})
|
||||
|
||||
async def async_update(self):
|
||||
await self.device_read(self.subscribed_attrs)
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class CommandSelect(XEntity, SelectEntity):
|
||||
_attr_current_option = None
|
||||
_attr_device_class = "command"
|
||||
|
||||
def __init__(self, gateway: 'XGateway', device: XDevice, conv: Converter):
|
||||
super().__init__(gateway, device, conv)
|
||||
if device.model == "lumi.gateway.mgl03":
|
||||
self._attr_options = [
|
||||
"pair", "bind", "ota", "config", "parentscan", "firmwarelock",
|
||||
"reboot", "ftp"
|
||||
]
|
||||
elif device.model.startswith("lumi.gateway.aqcn0"):
|
||||
self._attr_options = [
|
||||
"pair", "bind", "ota", "config", "parentscan", "reboot", "ftp"
|
||||
]
|
||||
elif device.model.startswith("lumi.gateway.mcn001"):
|
||||
self._attr_options = [
|
||||
"pair", "bind", "ota", "config", "parentscan", "reboot", "ftp"
|
||||
]
|
||||
|
||||
@callback
|
||||
def async_set_state(self, data: dict):
|
||||
if self.attr in data:
|
||||
self._attr_current_option = data[self.attr]
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
# clear select.data
|
||||
self.device.update({"data": None})
|
||||
|
||||
if option == "pair":
|
||||
await self.device_send({"pair": True})
|
||||
elif option in ("bind", "config", "ota"):
|
||||
self.device.update({"command": option})
|
||||
elif option == "firmwarelock":
|
||||
lock = await self.gw.gw3_read_lock()
|
||||
self.device.update({"command": option, "lock": lock})
|
||||
elif option in ("ftp", "reboot"):
|
||||
ok = await self.gw.telnet_send(option)
|
||||
self.device.update({"command": option, "ok": ok})
|
||||
elif option == "parentscan":
|
||||
await self.gw.z3_run_parent_scan()
|
||||
self.device.update({"command": option, "ok": True})
|
||||
|
||||
|
||||
OPT_ENABLED = "enabled"
|
||||
OPT_DISABLED = "disabled"
|
||||
OPT_UNKNOWN = "unknown"
|
||||
OPT_OK = "ok"
|
||||
OPT_ERROR = "error"
|
||||
OPT_JOIN = "permit_join"
|
||||
OPT_STOP_JOIN = "stop_join"
|
||||
OPT_CANCEL = "cancel"
|
||||
OPT_KEY_SECURE = "key_secure"
|
||||
OPT_KEY_LEGACY = "key_legacy"
|
||||
OPT_NO_FIRMWARE = "no_firmware"
|
||||
OPT_BIND = "bind"
|
||||
OPT_UNBIND = "unbind"
|
||||
OPT_NO_DEVICES = "no_devices"
|
||||
|
||||
|
||||
# noinspection PyAbstractClass,PyUnusedLocal
|
||||
class DataSelect(XEntity, SelectEntity):
|
||||
_attr_current_option = None
|
||||
_attr_device_class = "data"
|
||||
_attr_options = None
|
||||
step_id = None
|
||||
kwargs = None
|
||||
|
||||
def set_options(self, option: str = None, options: list = None):
|
||||
"""Change current option with options list dynamically."""
|
||||
# always change to None first time
|
||||
self._attr_current_option = None
|
||||
|
||||
if option:
|
||||
self._attr_options = options or [option]
|
||||
# important to change current option with delay after options
|
||||
self.hass.async_create_task(self.async_set_current(option))
|
||||
else:
|
||||
# better not leave options list empty
|
||||
self._attr_options = options or []
|
||||
|
||||
async def async_set_current(self, option: str):
|
||||
"""Delayed update current option."""
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
|
||||
def set_devices(self, feature: str):
|
||||
"""Set options list with devices list."""
|
||||
devices = [
|
||||
f"{d.mac}: {d.name}" for d in self.gw.filter_devices(feature)
|
||||
]
|
||||
if devices:
|
||||
self.set_options(None, devices)
|
||||
else:
|
||||
self.set_options(OPT_NO_DEVICES)
|
||||
|
||||
def set_end(self, option: str):
|
||||
self.set_options(option)
|
||||
self.device.update({"command": None})
|
||||
|
||||
###########################################################################
|
||||
|
||||
@callback
|
||||
def async_set_state(self, data: dict):
|
||||
"""Can process:
|
||||
- command step, done by user from another select
|
||||
- event step, done by receiving some data from gateway
|
||||
"""
|
||||
if "command" in data:
|
||||
self.step_id = data["command"]
|
||||
func = getattr(self, f"step_command_{self.step_id}", None)
|
||||
else:
|
||||
self.step_id = next(k for k in self.subscribed_attrs if k in data)
|
||||
data = data[self.step_id]
|
||||
func = getattr(self, f"step_event_{self.step_id}", None)
|
||||
|
||||
if func:
|
||||
func(data)
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Can process user step, done by user from this select"""
|
||||
coro = getattr(self, f"step_user_{self.step_id}", None)
|
||||
if coro:
|
||||
await coro(option)
|
||||
|
||||
###########################################################################
|
||||
|
||||
def step_event_data(self, value: str):
|
||||
# update select.data value from outside
|
||||
self.set_options(value)
|
||||
|
||||
def step_event_pair(self, value: bool):
|
||||
if value:
|
||||
self.set_options(OPT_JOIN, [OPT_JOIN, OPT_CANCEL])
|
||||
# change select.command
|
||||
self.device.update({"command": "pair"})
|
||||
else:
|
||||
# clear select.command
|
||||
self.device.update({"command": None})
|
||||
|
||||
async def step_user_pair(self, option: str):
|
||||
assert option == OPT_CANCEL
|
||||
await self.device_send({"pair": False})
|
||||
|
||||
def step_event_discovered_mac(self, value: str):
|
||||
self.set_options(f"Discovered: 0x{value:>016s}")
|
||||
|
||||
def step_event_pair_command(self, value: dict):
|
||||
secure = value["install_code"]
|
||||
option = OPT_KEY_SECURE if secure else OPT_KEY_LEGACY
|
||||
self.set_options(option, self.options + [option])
|
||||
|
||||
def step_event_added_device(self, value: dict):
|
||||
option = f"Paired: {value['model']}"
|
||||
self.set_options(option, self.options + [option])
|
||||
|
||||
def step_event_remove_did(self, value: str):
|
||||
assert RE_DID.search(value)
|
||||
device = self.gw.devices.get(value)
|
||||
utils.remove_device(self.hass, device)
|
||||
self.set_end(f"Removed: {value[5:]}")
|
||||
|
||||
def step_event_ota_progress(self, value: int):
|
||||
self.set_options(f"Update progress: {value}%")
|
||||
|
||||
def step_command_config(self, value: dict):
|
||||
self.set_devices("zigbee")
|
||||
|
||||
async def step_user_config(self, option: str):
|
||||
did = "lumi." + option[:18].lstrip("0x")
|
||||
device = self.gw.devices.get(did)
|
||||
await self.gw.silabs_rejoin(device)
|
||||
|
||||
def step_command_ota(self, value: dict):
|
||||
self.set_devices("zigbee")
|
||||
|
||||
async def step_user_ota(self, option: str):
|
||||
did = "lumi." + option[:18].lstrip("0x")
|
||||
device = self.gw.devices.get(did)
|
||||
resp = await utils.run_zigbee_ota(self.hass, self.gw, device)
|
||||
if resp is True:
|
||||
resp = OPT_OK
|
||||
elif resp is False:
|
||||
resp = OPT_NO_FIRMWARE
|
||||
else:
|
||||
resp = OPT_ERROR
|
||||
self.set_end(resp)
|
||||
|
||||
def step_command_bind(self, value: dict):
|
||||
self.set_devices(self.step_id)
|
||||
self.step_id = "bind_from"
|
||||
|
||||
async def step_user_bind_from(self, option: str):
|
||||
did = "lumi." + option[:18].lstrip("0x")
|
||||
self.kwargs = {"bind_from": self.gw.devices.get(did)}
|
||||
self.set_devices("bind_to")
|
||||
self.step_id = "bind_to"
|
||||
|
||||
async def step_user_bind_to(self, option: str):
|
||||
did = "lumi." + option[:18].lstrip("0x")
|
||||
self.kwargs["bind_to"] = self.gw.devices.get(did)
|
||||
self.set_options(None, [OPT_BIND, OPT_UNBIND])
|
||||
self.step_id = "bind_type"
|
||||
|
||||
async def step_user_bind_type(self, option: str):
|
||||
if option == OPT_BIND:
|
||||
await self.gw.silabs_bind(**self.kwargs)
|
||||
else:
|
||||
await self.gw.silabs_unbind(**self.kwargs)
|
||||
self.set_end(OPT_OK)
|
||||
|
||||
def step_command_firmwarelock(self, value: dict):
|
||||
lock = value["lock"]
|
||||
if lock is None:
|
||||
self.set_options(
|
||||
OPT_UNKNOWN, [OPT_UNKNOWN, OPT_ENABLED, OPT_DISABLED]
|
||||
)
|
||||
else:
|
||||
self.set_options(
|
||||
OPT_ENABLED if lock else OPT_DISABLED,
|
||||
[OPT_ENABLED, OPT_DISABLED]
|
||||
)
|
||||
|
||||
async def step_user_firmwarelock(self, option: str):
|
||||
ok = await self.gw.gw3_send_lock(option == OPT_ENABLED)
|
||||
self.set_end(OPT_OK if ok else OPT_ERROR)
|
||||
|
||||
def step_command_reboot(self, value: dict):
|
||||
self.set_end(OPT_OK if value["ok"] else OPT_ERROR)
|
||||
|
||||
def step_command_ftp(self, value: dict):
|
||||
self.step_command_reboot(value)
|
||||
|
||||
def step_command_parentscan(self, value: dict):
|
||||
self.step_command_reboot(value)
|
||||
Reference in New Issue
Block a user