Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing safe default for devices not requiring RESET/Passive functionality - Fix for bug in 4.1.0 #1022

Merged
merged 5 commits into from
Oct 11, 2022
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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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)

Expand Down
52 changes: 33 additions & 19 deletions custom_components/localtuya/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
ATTR_STATE,
CONF_RESTORE_ON_RECONNECT,
CONF_RESET_DPIDS,
CONF_PASSIVE_ENTITY,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -208,20 +209,23 @@ async def _make_connection(self):

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)

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)
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._interface.start_heartbeat()
self.status_updated(status)

except UnicodeDecodeError as e: # pylint: disable=broad-except
self.exception(
Expand Down Expand Up @@ -372,6 +376,9 @@ def __init__(self, device, config_entry, dp_id, logger, **kwargs):
# 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 = (
Expand Down Expand Up @@ -552,10 +559,13 @@ async def restore_state_when_connected(self):
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,
)
Expand All @@ -572,8 +582,12 @@ async def restore_state_when_connected(self):

# 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",
Expand Down
28 changes: 14 additions & 14 deletions custom_components/localtuya/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions custom_components/localtuya/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
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"
Expand Down
4 changes: 3 additions & 1 deletion custom_components/localtuya/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
CONF_STEPSIZE_VALUE,
CONF_PASSIVE_ENTITY,
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -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,
}


Expand Down
4 changes: 3 additions & 1 deletion custom_components/localtuya/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
CONF_OPTIONS_FRIENDLY,
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
CONF_PASSIVE_ENTITY,
)


Expand All @@ -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,
}


Expand Down
6 changes: 4 additions & 2 deletions custom_components/localtuya/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
CONF_VOLTAGE,
CONF_DEFAULT_VALUE,
CONF_RESTORE_ON_RECONNECT,
CONF_PASSIVE_ENTITY,
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -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,
}


Expand Down Expand Up @@ -82,7 +84,7 @@ async def async_turn_off(self, **kwargs):

# 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


Expand Down
3 changes: 2 additions & 1 deletion custom_components/localtuya/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Expand Down