From 9f0ab19493ef9704781a2540b2d366e0ccd8fa72 Mon Sep 17 00:00:00 2001 From: Bander <46300268+xZetsubou@users.noreply.github.com> Date: Sat, 16 Mar 2024 21:01:54 +0300 Subject: [PATCH] Refactoring codes (#170) * Attempt to fix max header length. * Refactor main config --- custom_components/localtuya/__init__.py | 8 +- custom_components/localtuya/climate.py | 4 +- custom_components/localtuya/common.py | 106 ++++++++---------- .../localtuya/core/ha_entities/numbers.py | 1 + .../localtuya/core/pytuya/__init__.py | 8 +- custom_components/localtuya/remote.py | 2 +- 6 files changed, 59 insertions(+), 70 deletions(-) diff --git a/custom_components/localtuya/__init__.py b/custom_components/localtuya/__init__.py index 2e5f60422..c815890f4 100644 --- a/custom_components/localtuya/__init__.py +++ b/custom_components/localtuya/__init__.py @@ -335,11 +335,11 @@ async def setup_entities(entry_devices: dict): if node_id := config.get(CONF_NODE_ID): # Setup sub device as gateway if there is no gateway exist. if host not in devices: - devices[host] = TuyaDevice(hass, entry, dev_id, True) + devices[host] = TuyaDevice(hass, entry, config, True) host = f"{host}_{node_id}" - devices[host] = TuyaDevice(hass, entry, dev_id) + devices[host] = TuyaDevice(hass, entry, config) hass_localtuya = HassLocalTuyaData(tuya_api, devices, []) hass.data[DOMAIN][entry.entry_id] = hass_localtuya @@ -374,7 +374,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: for dev in hass_data.devices.values(): if dev.connected: disconnect_devices.append(dev.close()) - for entity in dev._device_config[CONF_ENTITIES]: + for entity in dev._device_config.entities: platforms[entity[CONF_PLATFORM]] = True # Unload the platforms. @@ -443,7 +443,7 @@ async def _async_reconnect(now): """Try connecting to devices not already connected to.""" reconnect_devices = [] for host, dev in hass_localtuya.devices.items(): - dev_id = dev._device_config[CONF_DEVICE_ID] + dev_id = dev._device_config.id if check_if_device_disabled(hass, entry, dev_id): return if not dev.connected: diff --git a/custom_components/localtuya/climate.py b/custom_components/localtuya/climate.py index 09db72b04..76eaae12e 100644 --- a/custom_components/localtuya/climate.py +++ b/custom_components/localtuya/climate.py @@ -60,13 +60,13 @@ HVAC_OFF = {HVACMode.OFF.value: "off"} -RENAME_HVAC_MODE_SETS = { # Mirgae to 3 +RENAME_HVAC_MODE_SETS = { # Migrate to 3 ("manual", "Manual", "hot", "m", "True"): HVACMode.HEAT.value, ("auto", "0", "p", "Program"): HVACMode.AUTO.value, ("freeze", "cold", "1"): HVACMode.COOL.value, ("wet"): HVACMode.DRY.value, } -RENAME_ACTION_SETS = { # Mirgae to 3 +RENAME_ACTION_SETS = { # Migrate to 3 ("open", "opened", "heating", "Heat", "True"): HVACAction.HEATING.value, ("closed", "close", "no_heating"): HVACAction.IDLE.value, ("Warming", "warming", "False"): HVACAction.IDLE.value, diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index d00691418..6911560df 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -155,7 +155,7 @@ def __init__( self, hass: HomeAssistant, config_entry: ConfigEntry, - dev_id: str, + device_config: dict, fake_gateway=False, ): """Initialize the cache.""" @@ -163,18 +163,20 @@ def __init__( self._hass = hass self._hass_entry: HassLocalTuyaData = None self._config_entry = config_entry - self._device_config: dict = config_entry.data[CONF_DEVICES][dev_id].copy() + self._device_config = DeviceConfig(device_config.copy()) + self._interface = None self._connect_max_tries = 3 + # For SubDevices - self._node_id: str = self._device_config.get(CONF_NODE_ID) + self._node_id: str = self._device_config.node_id self._fake_gateway = fake_gateway self._gwateway: TuyaDevice = None self._sub_devices: dict[str, TuyaDevice] = {} self._status = {} # Sleep timer, a device that reports the status every x seconds then goes into sleep. - self._passive_device = self._device_config.get(CONF_DEVICE_SLEEP_TIME, 0) > 0 + self._passive_device = self._device_config.sleep_time > 0 self._last_update_time: int = int(time.time()) - 5 self._pending_status: dict[str, dict[str, Any]] = {} @@ -185,19 +187,17 @@ def __init__( self._unsub_interval: CALLBACK_TYPE[[], None] = None self._shutdown_entities_delay: CALLBACK_TYPE[[], None] = None self._entities = [] - self._local_key: str = self._device_config[CONF_LOCAL_KEY] + self._local_key: str = self._device_config.local_key self._default_reset_dpids: list | None = None - if reset_dps := self._device_config.get(CONF_RESET_DPIDS): + if reset_dps := self._device_config.reset_dps: self._default_reset_dpids = [int(id.strip()) for id in reset_dps.split(",")] self.set_logger( - _LOGGER, - self._device_config.get(CONF_DEVICE_ID), - self._device_config.get(CONF_ENABLE_DEBUG), + _LOGGER, self._device_config.id, self._device_config.enable_debug ) # This has to be done in case the device type is type_0d - for entity in self._device_config[CONF_ENTITIES]: + for entity in self._device_config.entities: self.dps_to_request[entity[CONF_ID]] = None def add_entities(self, entities): @@ -222,7 +222,7 @@ def is_subdevice(self): @property def is_sleep(self): """Return whether the device is sleep or not.""" - device_sleep = self._device_config.get(CONF_DEVICE_SLEEP_TIME, 0) + device_sleep = self._device_config.sleep_time last_update = int(time.time()) - self._last_update_time return last_update < device_sleep if device_sleep > 0 else False @@ -231,7 +231,7 @@ async def get_gateway(self): if not self._node_id: return gateway: TuyaDevice - node_host = self._device_config.get(CONF_HOST) + node_host = self._device_config.host devices: dict = self._hass_entry.devices # Sub to gateway. @@ -261,8 +261,8 @@ async def _make_connection(self): if self.is_sleep and not self._status: self.status_updated(RESTORE_STATES) - name = self._device_config.get(CONF_FRIENDLY_NAME) - host = name if self.is_subdevice else self._device_config.get(CONF_HOST) + name = self._device_config.name + host = name if self.is_subdevice else self._device_config.host retry = 0 self.debug(f"Trying to connect to {host}...", force=True) while retry < self._connect_max_tries: @@ -276,11 +276,11 @@ async def _make_connection(self): else: self._interface = await asyncio.wait_for( pytuya.connect( - self._device_config[CONF_HOST], - self._device_config[CONF_DEVICE_ID], + self._device_config.host, + self._device_config.id, self._local_key, - float(self._device_config[CONF_PROTOCOL_VERSION]), - self._device_config.get(CONF_ENABLE_DEBUG, False), + float(self._device_config.protocol_version), + self._device_config.enable_debug, self, ), 5, @@ -291,9 +291,6 @@ async def _make_connection(self): await self.abort_connect() if not retry < self._connect_max_tries and not self.is_sleep: self.warning(f"Failed to connect to {host}: {str(ex)}") - # if self.is_sleep and not self._status: - # self.status_updated(RESTORE_STATES) - # break if self._interface is not None: try: @@ -343,12 +340,12 @@ def _new_entity_handler(entity_id): self.debug(f"New entity {entity_id} was added to {host}") self._dispatch_status() - signal = f"localtuya_entity_{self._device_config[CONF_DEVICE_ID]}" + signal = f"localtuya_entity_{self._device_config.id}" self._disconnect_task = async_dispatcher_connect( self._hass, signal, _new_entity_handler ) - if (scan_inv := int(self._device_config.get(CONF_SCAN_INTERVAL, 0))) > 0: + if (scan_inv := int(self._device_config.scan_interval)) > 0: self._unsub_interval = async_track_time_interval( self._hass, self._async_refresh, timedelta(seconds=scan_inv) ) @@ -362,7 +359,7 @@ def _new_entity_handler(entity_id): ] await asyncio.gather(*connect_sub_devices) - if "0" in self._device_config.get(CONF_MANUAL_DPS, "").split(","): + if "0" in self._device_config.manual_dps.split(","): self.status_updated(BYPASS_STATUS) if self._pending_status: @@ -403,13 +400,13 @@ async def close(self): if self._disconnect_task: self._disconnect_task() self.debug( - f"Closed connection with {self._device_config[CONF_FRIENDLY_NAME]}", + f"Closed connection with {self._device_config.name}", force=True, ) async def update_local_key(self): """Retrieve updated local_key from Cloud API and update the config_entry.""" - dev_id = self._device_config[CONF_DEVICE_ID] + dev_id = self._device_config.id cloud_api = self._hass_entry.cloud_data await cloud_api.async_get_devices_list() cloud_devs = cloud_api.device_list @@ -438,9 +435,7 @@ async def set_dp(self, state, dp_index): else: if self.is_sleep: return self._pending_status.update({str(dp_index): state}) - self.error( - f"Not connected to device {self._device_config[CONF_FRIENDLY_NAME]}" - ) + self.error(f"Not connected to device {self._device_config.name}") async def set_dps(self, states): """Change value of a DPs of the Tuya device.""" @@ -453,9 +448,7 @@ async def set_dps(self, states): else: if self.is_sleep: return self._pending_status.update(states) - self.error( - f"Not connected to device {self._device_config[CONF_FRIENDLY_NAME]}" - ) + self.error(f"Not connected to device {self._device_config.name}") async def _async_refresh(self, _now): if self._interface is not None: @@ -463,16 +456,14 @@ async def _async_refresh(self, _now): await self._interface.update_dps(cid=self._node_id) def _dispatch_status(self): - signal = f"localtuya_{self._device_config[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._device_config.id}" async_dispatcher_send(self._hass, signal, self._status) def _handle_event(self, old_status: dict, new_status: dict, deviceID=None): """Handle events in HA when devices updated.""" def fire_event(event, data: dict): - event_data = { - CONF_DEVICE_ID: deviceID or self._device_config[CONF_DEVICE_ID] - } + event_data = {CONF_DEVICE_ID: deviceID or self._device_config.id} event_data.update(data) # Send an event with status, The default length of event without data is 2. if len(event_data) > 2: @@ -517,7 +508,7 @@ def status_updated(self, status: dict): @callback def disconnected(self): """Device disconnected.""" - sleep_time = self._device_config.get(CONF_DEVICE_SLEEP_TIME, 0) + sleep_time = self._device_config.sleep_time def shutdown_entities(now=None): """Shutdown device entities""" @@ -526,7 +517,7 @@ def shutdown_entities(now=None): return if not self.connected: self.debug(f"Disconnected: waiting for discovery broadcast", force=True) - signal = f"localtuya_{self._device_config[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._device_config.id}" async_dispatcher_send(self._hass, signal, None) if self._unsub_interval is not None: @@ -566,7 +557,7 @@ def __init__( """Initialize the Tuya entity.""" super().__init__() self._device = device - self._device_config = device_config + self._device_config = DeviceConfig(device_config) self._config = get_entity_config(device_config, dp_id) self._dp_id = dp_id self._status = {} @@ -579,7 +570,7 @@ def __init__( """ Restore on connect setting is available to be provided by Platform entities if required""" - self.set_logger(logger, self._device_config[CONF_DEVICE_ID]) + self.set_logger(logger, self._device_config.id) self.debug(f"Initialized {self._config.get(CONF_PLATFORM)} [{self.name}]") async def async_added_to_hass(self): @@ -599,8 +590,8 @@ def _update_handler(status): if self._status != status: if status == RESTORE_STATES: status = {} - self.debug(f"{self.name}: restoring the state: {stored_data.state}") if stored_data and stored_data.state != STATE_UNAVAILABLE: + self.debug(f"{self.name}: restore state: {stored_data.state}") status = {self._dp_id: stored_data.state} self._status = status.copy() if status: @@ -609,13 +600,13 @@ def _update_handler(status): # Update HA self.schedule_update_ha_state() - signal = f"localtuya_{self._device_config[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._device_config.id}" self.async_on_remove( async_dispatcher_connect(self.hass, signal, _update_handler) ) - signal = f"localtuya_entity_{self._device_config[CONF_DEVICE_ID]}" + signal = f"localtuya_entity_{self._device_config.id}" async_dispatcher_send(self.hass, signal, self.entity_id) @property @@ -637,15 +628,15 @@ def extra_state_attributes(self): @property def device_info(self) -> DeviceInfo: """Return device information for the device registry.""" - model = self._device_config.get(CONF_MODEL, "Tuya generic") + model = self._device_config.model return DeviceInfo( # Serial numbers are unique identifiers within a specific domain - identifiers={(DOMAIN, f"local_{self._device_config[CONF_DEVICE_ID]}")}, - name=self._device_config[CONF_FRIENDLY_NAME], + identifiers={(DOMAIN, f"local_{self._device_config.id}")}, + name=self._device_config.name, manufacturer="Tuya", - model=f"{model} ({self._device_config[CONF_DEVICE_ID]})", - sw_version=self._device_config[CONF_PROTOCOL_VERSION], + model=f"{model} ({self._device_config.id})", + sw_version=self._device_config.protocol_version, ) @property @@ -661,7 +652,7 @@ def icon(self) -> str | None: @property def unique_id(self) -> str: """Return unique device identifier.""" - return f"local_{self._device_config[CONF_DEVICE_ID]}_{self._dp_id}" + return f"local_{self._device_config.id}_{self._dp_id}" @property def available(self) -> bool: @@ -817,7 +808,7 @@ class HassLocalTuyaData(NamedTuple): @dataclass class DeviceConfig: - """Represent Main Device Config.""" + """Represent the main configuration for LocalTuya device.""" device_config: dict[str, Any] @@ -827,11 +818,12 @@ def __post_init__(self) -> None: self.local_key: str = self.device_config[CONF_LOCAL_KEY] self.entities: list = self.device_config[CONF_ENTITIES] self.protocol_version: str = self.device_config[CONF_PROTOCOL_VERSION] - self.sleep_time = self.device_config.get(CONF_DEVICE_SLEEP_TIME, 0) - self.scan_interval = self.device_config.get(CONF_SCAN_INTERVAL, 0) - self.enable_debug = self.device_config.get(CONF_ENABLE_DEBUG, False) + self.sleep_time: int = self.device_config.get(CONF_DEVICE_SLEEP_TIME, 0) + self.scan_interval: int = self.device_config.get(CONF_SCAN_INTERVAL, 0) + self.enable_debug: bool = self.device_config.get(CONF_ENABLE_DEBUG, False) self.name: str = self.device_config.get(CONF_FRIENDLY_NAME) - self.node_id = self.device_config.get(CONF_NODE_ID) - self.model = self.device_config.get(CONF_MODEL) - self.reset_dps = self.device_config.get(CONF_RESET_DPIDS) - self.dps_strings = self.device_config.get(CONF_DPS_STRINGS) + self.node_id: str | None = self.device_config.get(CONF_NODE_ID) + self.model: str = self.device_config.get(CONF_MODEL, "Tuya generic") + self.reset_dps: str = self.device_config.get(CONF_RESET_DPIDS, "") + self.manual_dps: str = self.device_config.get(CONF_MANUAL_DPS, "") + self.dps_strings: list = self.device_config.get(CONF_DPS_STRINGS, []) diff --git a/custom_components/localtuya/core/ha_entities/numbers.py b/custom_components/localtuya/core/ha_entities/numbers.py index c0704a9ec..5a4a8b573 100644 --- a/custom_components/localtuya/core/ha_entities/numbers.py +++ b/custom_components/localtuya/core/ha_entities/numbers.py @@ -851,6 +851,7 @@ def localtuya_numbers(_min, _max, _step=1, _scale=1, unit=None) -> dict: NUMBERS["cjkg"] = NUMBERS["kg"] NUMBERS["cz"] = NUMBERS["kg"] +NUMBERS["tdq"] = NUMBERS["kg"] NUMBERS["pc"] = NUMBERS["kg"] # Locker diff --git a/custom_components/localtuya/core/pytuya/__init__.py b/custom_components/localtuya/core/pytuya/__init__.py index 285971ee3..4602f54f9 100644 --- a/custom_components/localtuya/core/pytuya/__init__.py +++ b/custom_components/localtuya/core/pytuya/__init__.py @@ -621,7 +621,7 @@ def add_data(self, data): """Add new data to the buffer and try to parse messages.""" self.buffer += data - header_len_55AA = struct.calcsize(MESSAGE_RECV_HEADER_FMT) + header_len_55AA = struct.calcsize(MESSAGE_HEADER_FMT_55AA) header_len_6699 = struct.calcsize(MESSAGE_HEADER_FMT_6699) header_len = header_len_55AA @@ -1109,11 +1109,7 @@ async def detect_available_dps(self, cid=None): # in the requested range self.dps_to_request = {"1": None} self.add_dps_to_request(range(*dps_range)) - try: - data = await self.status(cid=cid) - except Exception as ex: - self.exception("Failed to get status: %s", ex) - raise + data = await self.status(cid=cid) # if "dps" in data: if cid and cid in data: self.dps_cache.update({cid: data[cid]}) diff --git a/custom_components/localtuya/remote.py b/custom_components/localtuya/remote.py index d86591f65..7425a8a06 100644 --- a/custom_components/localtuya/remote.py +++ b/custom_components/localtuya/remote.py @@ -80,7 +80,7 @@ def __init__( self._dp_send = str(self._config.get(self._dp_id, RemoteDP.DP_SEND)) self._dp_recieve = str(self._config.get(CONF_RECEIVE_DP, RemoteDP.DP_RECIEVE)) - self._device_id = self._device_config[CONF_DEVICE_ID] + self._device_id = self._device_config.id # self._attr_activity_list: list = [] # self._attr_current_activity: str | None = None