Merge branch 'master' into master

This commit is contained in:
rospogrigio
2022-01-02 13:25:51 +01:00
committed by GitHub
7 changed files with 88 additions and 16 deletions

View File

@@ -44,6 +44,8 @@ localtuya:
local_key: xxxxx
friendly_name: Tuya Device
protocol_version: "3.3"
scan_interval: # optional, only needed if energy monitoring values are not updating
seconds: 30 # Values less than 10 seconds may cause stability issues
entities:
- platform: binary_sensor
friendly_name: Plug Status
@@ -112,9 +114,13 @@ select one of these, or manually input all the parameters.
![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png)
If you have selected one entry, you only need to input the device's Friendly Name and the localKey.
Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues.
Once you press "Submit", the connection is tested to check that everything works.
![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png)
![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png)
Then, it's time to add the entities: this step will take place several times. First, select the entity type from the drop-down menu to set it up.
After you have defined all the needed entities, leave the "Do not add more entities" checkbox checked: this will complete the procedure.
@@ -140,6 +146,7 @@ You can obtain Energy monitoring (voltage, current) in two different ways:
Note: Voltage and Consumption usually include the first decimal. You will need to scale the parament by 0.1 to get the correct values.
1) Access the voltage/current/current_consumption attributes of a switch, and define template sensors
Note: these values are already divided by 10 for Voltage and Consumption
1) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended.
```
sensor:

View File

@@ -1,6 +1,7 @@
"""Code shared between all platforms."""
import asyncio
import logging
from datetime import timedelta
from homeassistant.const import (
CONF_DEVICE_ID,
@@ -9,8 +10,10 @@ from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
@@ -117,6 +120,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._is_closing = False
self._connect_task = None
self._disconnect_task = None
self._unsub_interval = None
self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID])
# This has to be done in case the device type is type_0d
@@ -166,6 +170,16 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._disconnect_task = async_dispatcher_connect(
self._hass, signal, _new_entity_handler
)
if (
CONF_SCAN_INTERVAL in self._config_entry
and self._config_entry[CONF_SCAN_INTERVAL] > 0
):
self._unsub_interval = async_track_time_interval(
self._hass,
self._async_refresh,
timedelta(seconds=self._config_entry[CONF_SCAN_INTERVAL]),
)
except Exception: # pylint: disable=broad-except
self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed")
if self._interface is not None:
@@ -173,6 +187,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
self._interface = None
self._connect_task = None
async def _async_refresh(self, _now):
if self._interface is not None:
await self._interface.update_dps()
async def close(self):
"""Close connection and stop re-connect loop."""
self._is_closing = True
@@ -223,7 +241,9 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
"""Device disconnected."""
signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}"
async_dispatcher_send(self._hass, signal, None)
if self._unsub_interval is not None:
self._unsub_interval()
self._unsub_interval = None
self._interface = None
self.debug("Disconnected - waiting for discovery broadcast")
@@ -253,12 +273,12 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger):
def _update_handler(status):
"""Update entity state when status was updated."""
if status is not None:
self._status = status
if status is None:
status = {}
if self._status != status:
self._status = status.copy()
if status:
self.status_updated()
else:
self._status = {}
self.schedule_update_ha_state()
signal = f"localtuya_{self._config_entry.data[CONF_DEVICE_ID]}"

View File

@@ -14,6 +14,7 @@ from homeassistant.const import (
CONF_HOST,
CONF_ID,
CONF_PLATFORM,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import callback
@@ -44,6 +45,7 @@ BASIC_INFO_SCHEMA = vol.Schema(
vol.Required(CONF_HOST): str,
vol.Required(CONF_DEVICE_ID): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
}
)
@@ -55,6 +57,7 @@ DEVICE_SCHEMA = vol.Schema(
vol.Required(CONF_LOCAL_KEY): cv.string,
vol.Required(CONF_FRIENDLY_NAME): cv.string,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
}
)
@@ -90,6 +93,7 @@ def options_schema(entities):
vol.Required(CONF_HOST): str,
vol.Required(CONF_LOCAL_KEY): str,
vol.Required(CONF_PROTOCOL_VERSION, default="3.3"): vol.In(["3.1", "3.3"]),
vol.Optional(CONF_SCAN_INTERVAL): int,
vol.Required(
CONF_ENTITIES, description={"suggested_value": entity_names}
): cv.multi_select(entity_names),

View File

@@ -21,6 +21,7 @@ Functions
json = status() # returns json payload
set_version(version) # 3.1 [default] or 3.3
detect_available_dps() # returns a list of available dps provided by the device
update_dps(dps) # sends update dps command
add_dps_to_request(dp_index) # adds dp_index to the list of dps used by the
# device (to be queried in the payload)
set_dp(on, dp_index) # Set value of any dps index.
@@ -61,6 +62,7 @@ TuyaMessage = namedtuple("TuyaMessage", "seqno cmd retcode payload crc")
SET = "set"
STATUS = "status"
HEARTBEAT = "heartbeat"
UPDATEDPS = "updatedps" # Request refresh of DPS
PROTOCOL_VERSION_BYTES_31 = b"3.1"
PROTOCOL_VERSION_BYTES_33 = b"3.3"
@@ -76,6 +78,9 @@ SUFFIX_VALUE = 0x0000AA55
HEARTBEAT_INTERVAL = 10
# DPS that are known to be safe to use with update_dps (0x12) command
UPDATE_DPS_WHITELIST = [18, 19, 20] # Socket (Wi-Fi)
# This is intended to match requests.json payload at
# https://github.com/codetheweb/tuyapi :
# type_0a devices require the 0a command as the status request
@@ -90,11 +95,13 @@ PAYLOAD_DICT = {
STATUS: {"hexByte": 0x0A, "command": {"gwId": "", "devId": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
},
"type_0d": {
STATUS: {"hexByte": 0x0D, "command": {"devId": "", "uid": "", "t": ""}},
SET: {"hexByte": 0x07, "command": {"devId": "", "uid": "", "t": ""}},
HEARTBEAT: {"hexByte": 0x09, "command": {}},
UPDATEDPS: {"hexByte": 0x12, "command": {"dpId": [18, 19, 20]}},
},
}
@@ -292,6 +299,8 @@ class MessageDispatcher(ContextualLogger):
sem = self.listeners[self.HEARTBEAT_SEQNO]
self.listeners[self.HEARTBEAT_SEQNO] = msg
sem.release()
elif msg.cmd == 0x12:
self.debug("Got normal updatedps response")
elif msg.cmd == 0x08:
self.debug("Got status update")
self.listener(msg)
@@ -478,6 +487,26 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
"""Send a heartbeat message."""
return await self.exchange(HEARTBEAT)
async def update_dps(self, dps=None):
"""
Request device to update index.
Args:
dps([int]): list of dps to update, default=detected&whitelisted
"""
if self.version == 3.3:
if dps is None:
if not self.dps_cache:
await self.detect_available_dps()
if self.dps_cache:
dps = [int(dp) for dp in self.dps_cache]
# filter non whitelisted dps
dps = list(set(dps).intersection(set(UPDATE_DPS_WHITELIST)))
self.debug("updatedps() entry (dps %s, dps_cache %s)", dps, self.dps_cache)
payload = self._generate_payload(UPDATEDPS, dps)
self.transport.write(payload)
return True
async def set_dp(self, value, dp_index):
"""
Set value (may be any type: bool, int or string) of any dps index.
@@ -582,6 +611,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
json_data["t"] = str(int(time.time()))
if data is not None:
if "dpId" in json_data:
json_data["dpId"] = data
else:
json_data["dps"] = data
elif command_hb == 0x0D:
json_data["dps"] = self.dps_to_request
@@ -591,7 +623,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger):
if self.version == 3.3:
payload = self.cipher.encrypt(payload, False)
if command_hb != 0x0A:
if command_hb not in [0x0A, 0x12]:
# add the 3.3 header
payload = PROTOCOL_33_HEADER + payload
elif command == SET:

View File

@@ -20,6 +20,7 @@
"device_id": "Device ID",
"local_key": "Local key",
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)",
"device_type": "Device type"
}
},

View File

@@ -29,7 +29,8 @@
"host": "Host",
"device_id": "Device ID",
"local_key": "Local key",
"protocol_version": "Protocol Version"
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)"
}
},
"pick_entity_type": {
@@ -94,6 +95,7 @@
"host": "Host",
"local_key": "Local key",
"protocol_version": "Protocol Version",
"scan_interval": "Scan interval (seconds, only when not updating automatically)",
"entities": "Entities (uncheck an entity to remove it)"
}
},

12
info.md
View File

@@ -43,6 +43,8 @@ localtuya:
local_key: xxxxx
friendly_name: Tuya Device
protocol_version: "3.3"
scan_interval: # optional, only needed if energy monitoring values are not updating
seconds: 30 # Values less than 10 seconds may cause stability issues
entities:
- platform: binary_sensor
friendly_name: Plug Status
@@ -109,9 +111,12 @@ select one of these, or manually input all the parameters.
![discovery](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/1-discovery.png)
If you have selected one entry, you just have to input the Friendly Name of the Device, and the localKey.
Once you press "Submit", the connection will be tested to check that everything works, in order to proceed.
![device](https://github.com/rospogrigio/localtuya-homeassistant/blob/master/img/2-device.png)
Setting the scan interval is optional, only needed if energy/power values are not updating frequently enough by default. Values less than 10 seconds may cause stability issues.
Once you press "Submit", the connection is tested to check that everything works.
![image](https://user-images.githubusercontent.com/1082213/146664103-ac40319e-f934-4933-90cf-2beaff1e6bac.png)
Then, it's time to add the entities: this step will take place several times. Select the entity type from the drop-down menu to set it up.
After you have defined all the needed entities leave the "Do not add more entities" checkbox checked: this will complete the procedure.
@@ -133,7 +138,8 @@ After all the entities have been configured, the procedure is complete, and the
Energy monitoring (voltage, current...) values can be obtained in two different ways:
1) creating individual sensors, each one with the desired name. Note: Voltage and Consumption usually include the first decimal, so 0.1 as "scaling" parameter shall be used in order to get the correct values.
2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption):
2) accessing the voltage/current/current_consumption attributes of a switch, and then defining template sensors like this (please note that in this case the values are already divided by 10 for Voltage and Consumption)
3) On some devices, you may find that the energy values are not updating frequently enough by default. If so, set the scan interval (see above) to an appropriate value. Settings below 10 seconds may cause stability issues, 30 seconds is recommended.
```
sensor: