diff --git a/README.md b/README.md index edb7fbd..4698afc 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,9 @@ If you have selected one entry, you only need to input the device's Friendly Nam Setting the scan interval is optional, it is only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues. -Setting the 'Manual DPS To Add' is optional, it is only needed if the device doesn't advertise the DPS correctly until the entity has been properly initiailised. This setting can often be avoided by first connecting/initialising the device with the Tuya App, then closing the app and then adding the device in the integration. +Setting the 'Manual DPS To Add' is optional, it is only needed if the device doesn't advertise the DPS correctly until the entity has been properly initiailised. This setting can often be avoided by first connecting/initialising the device with the Tuya App, then closing the app and then adding the device in the integration. **Note: Any DPS added using this option will have a -1 value during setup.** -Setting the 'DPIDs to send in RESET command' is optional. It is used when a device doesn't respond to any Tuya commands after a power cycle, but can be connected to (zombie state). The DPids will vary between devices, but typically "18,19,20" is used (and will be the default if none specified). If the wrong entries are added here, then the device may not come out of the zombie state. Typically only sensor DPIDs entered here. +Setting the 'DPIDs to send in RESET command' is optional. It is used when a device doesn't respond to any Tuya commands after a power cycle, but can be connected to (zombie state). This scenario mostly occurs when the device is blocked from accessing the internet. The DPids will vary between devices, but typically "18,19,20" is used. If the wrong entries are added here, then the device may not come out of the zombie state. Typically only sensor DPIDs entered here. Once you press "Submit", the connection is tested to check that everything works. @@ -107,8 +107,11 @@ After you have defined all the needed entities, leave the "Do not add more entit ![entity_type](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/3-entity_type.png) For each entity, the associated DP has to be selected. All the options requiring to select a DP will provide a drop-down menu showing -all the available DPs found on the device (with their current status!!) for easy identification. Each entity type has different options -to be configured. Here is an example for the "switch" entity: +all the available DPs found on the device (with their current status!!) for easy identification. + +**Note: If your device requires an LocalTuya to send an initialisation value to the entity for it to work, this can be configured (in supported entities) through the 'Passive entity' option. Optionally you can specify the initialisation value to be sent** + +Each entity type has different options to be configured. Here is an example for the "switch" entity: ![entity](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/4-entity.png) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 5eb6d81..aa8992f 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -36,6 +36,7 @@ from .const import ( ATTR_STATE, CONF_RESTORE_ON_RECONNECT, CONF_RESET_DPIDS, + CONF_PASSIVE_ENTITY, ) _LOGGER = logging.getLogger(__name__) @@ -208,20 +209,23 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): except Exception as ex: # pylint: disable=broad-except try: - self.debug( - "Initial state update failed, trying reset command " - + "for DP IDs: %s", - self._default_reset_dpids, - ) - await self._interface.reset(self._default_reset_dpids) + if (self._default_reset_dpids is not None) and ( + len(self._default_reset_dpids) > 0 + ): + self.debug( + "Initial state update failed, trying reset command " + + "for DP IDs: %s", + self._default_reset_dpids, + ) + await self._interface.reset(self._default_reset_dpids) - self.debug("Update completed, retrying initial state") - status = await self._interface.status() - if status is None or not status: - raise Exception("Failed to retrieve status") from ex + self.debug("Update completed, retrying initial state") + status = await self._interface.status() + if status is None or not status: + raise Exception("Failed to retrieve status") from ex - self._interface.start_heartbeat() - self.status_updated(status) + self._interface.start_heartbeat() + self.status_updated(status) except UnicodeDecodeError as e: # pylint: disable=broad-except self.exception( @@ -372,6 +376,9 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): # Default value is available to be provided by Platform entities if required self._default_value = self._config.get(CONF_DEFAULT_VALUE) + # Determine whether is a passive entity + self._is_passive_entity = self._config.get(CONF_PASSIVE_ENTITY) or False + """ Restore on connect setting is available to be provided by Platform entities if required""" self._restore_on_reconnect = ( @@ -552,10 +559,13 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): Which indicates a DPS that needs to be set before it starts returning status. """ - if not self.restore_on_reconnect and (str(self._dp_id) in self._status): + if (not self.restore_on_reconnect) and ( + (str(self._dp_id) in self._status) or (not self._is_passive_entity) + ): self.debug( - "Entity %s (DP %d) - Not restoring as restore on reconnect is \ - disabled for this entity and the entity has an initial status", + "Entity %s (DP %d) - Not restoring as restore on reconnect is " + + "disabled for this entity and the entity has an initial status " + + "or it is not a passive entity", self.name, self._dp_id, ) @@ -572,8 +582,12 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): # If no current or saved state, then use the default value if restore_state is None: - self.debug("No last restored state - using default") - restore_state = self.default_value() + if self._is_passive_entity: + self.debug("No last restored state - using default") + restore_state = self.default_value() + else: + self.debug("Not a passive entity and no state found - aborting restore") + return self.debug( "Entity %s (DP %d) - Restoring state: %s", diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index e70076f..1eeb3b5 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -248,24 +248,24 @@ async def validate_input(hass: core.HomeAssistant, data): data[CONF_LOCAL_KEY], float(data[CONF_PROTOCOL_VERSION]), ) - + if CONF_RESET_DPIDS in data: + reset_ids_str = data[CONF_RESET_DPIDS].split(",") + reset_ids = [] + for reset_id in reset_ids_str: + reset_ids.append(int(reset_id.strip())) + _LOGGER.debug( + "Reset DPIDs configured: %s (%s)", + data[CONF_RESET_DPIDS], + reset_ids, + ) try: detected_dps = await interface.detect_available_dps() except Exception: # pylint: disable=broad-except try: _LOGGER.debug("Initial state update failed, trying reset command") - if CONF_RESET_DPIDS in data: - reset_ids_str = data[CONF_RESET_DPIDS].split(",") - reset_ids = [] - for reset_id in reset_ids_str: - reset_ids.append(int(reset_id.strip())) - _LOGGER.debug( - "Reset DPIDs configured: %s (%s)", - data[CONF_RESET_DPIDS], - reset_ids, - ) - await interface.reset(reset_ids) - detected_dps = await interface.detect_available_dps() + if len(reset_ids) > 0: + await interface.reset(reset_ids) + detected_dps = await interface.detect_available_dps() except Exception: # pylint: disable=broad-except _LOGGER.debug("No DPS able to be detected") detected_dps = {} @@ -282,7 +282,7 @@ async def validate_input(hass: core.HomeAssistant, data): for new_dps in manual_dps_list + (reset_ids or []): # If the DPS not in the detected dps list, then add with a # default value indicating that it has been manually added - if new_dps not in detected_dps: + if str(new_dps) not in detected_dps: detected_dps[new_dps] = -1 except (ConnectionRefusedError, ConnectionResetError) as ex: diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 9b117fc..8010d18 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -44,6 +44,7 @@ CONF_NO_CLOUD = "no_cloud" CONF_MANUAL_DPS = "manual_dps_strings" CONF_DEFAULT_VALUE = "dps_default_value" CONF_RESET_DPIDS = "reset_dpids" +CONF_PASSIVE_ENTITY = "is_passive_entity" # light CONF_BRIGHTNESS_LOWER = "brightness_lower" diff --git a/custom_components/localtuya/number.py b/custom_components/localtuya/number.py index e8ab53d..23d7ea9 100644 --- a/custom_components/localtuya/number.py +++ b/custom_components/localtuya/number.py @@ -14,6 +14,7 @@ from .const import ( CONF_DEFAULT_VALUE, CONF_RESTORE_ON_RECONNECT, CONF_STEPSIZE_VALUE, + CONF_PASSIVE_ENTITY, ) _LOGGER = logging.getLogger(__name__) @@ -38,8 +39,9 @@ def flow_schema(dps): vol.Coerce(float), vol.Range(min=0.0, max=1000000.0), ), - vol.Optional(CONF_DEFAULT_VALUE): str, vol.Required(CONF_RESTORE_ON_RECONNECT): bool, + vol.Required(CONF_PASSIVE_ENTITY): bool, + vol.Optional(CONF_DEFAULT_VALUE): str, } diff --git a/custom_components/localtuya/select.py b/custom_components/localtuya/select.py index 75dd217..f643e08 100644 --- a/custom_components/localtuya/select.py +++ b/custom_components/localtuya/select.py @@ -16,6 +16,7 @@ from .const import ( CONF_OPTIONS_FRIENDLY, CONF_DEFAULT_VALUE, CONF_RESTORE_ON_RECONNECT, + CONF_PASSIVE_ENTITY, ) @@ -24,8 +25,9 @@ def flow_schema(dps): return { vol.Required(CONF_OPTIONS): str, vol.Optional(CONF_OPTIONS_FRIENDLY): str, - vol.Optional(CONF_DEFAULT_VALUE): str, vol.Required(CONF_RESTORE_ON_RECONNECT): bool, + vol.Required(CONF_PASSIVE_ENTITY): bool, + vol.Optional(CONF_DEFAULT_VALUE): str, } diff --git a/custom_components/localtuya/switch.py b/custom_components/localtuya/switch.py index 40e8ea1..bc664bf 100644 --- a/custom_components/localtuya/switch.py +++ b/custom_components/localtuya/switch.py @@ -16,6 +16,7 @@ from .const import ( CONF_VOLTAGE, CONF_DEFAULT_VALUE, CONF_RESTORE_ON_RECONNECT, + CONF_PASSIVE_ENTITY, ) _LOGGER = logging.getLogger(__name__) @@ -27,8 +28,9 @@ def flow_schema(dps): vol.Optional(CONF_CURRENT): vol.In(dps), vol.Optional(CONF_CURRENT_CONSUMPTION): vol.In(dps), vol.Optional(CONF_VOLTAGE): vol.In(dps), - vol.Optional(CONF_DEFAULT_VALUE): str, vol.Required(CONF_RESTORE_ON_RECONNECT): bool, + vol.Required(CONF_PASSIVE_ENTITY): bool, + vol.Optional(CONF_DEFAULT_VALUE): str, } @@ -82,7 +84,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity): # Default value is the "OFF" state def entity_default_value(self): - """Return False as the defaualt value for this entity type.""" + """Return False as the default value for this entity type.""" return False diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 2fa8141..4b3ddb0 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -189,7 +189,8 @@ "restore_on_reconnect": "Restore the last set value in HomeAssistant after a lost connection", "min_value": "Minimum Value", "max_value": "Maximum Value", - "step_size": "Minimum increment between numbers" + "step_size": "Minimum increment between numbers", + "is_passive_entity": "Passive entity - requires integration to send initialisation value" } } }