Compare commits

..

12 Commits
v1.2.1 ... main

Author SHA1 Message Date
Luke Bonaccorsi f397319070 fix: log eufy device info when device not found on tuya 2024-03-22 23:45:02 +00:00
Luke Morrigan 7e60ecd2b4
Revert "Fix the missing error key" (#69)
* Revert "Fix set fan speed"

This reverts commit 8676cb3fad.

* Revert "Add debug"

This reverts commit 94ae2b55ea.

* Revert "Add debug infos"

This reverts commit 4f1bdb3bac.

* Revert "Fix the missing error key"

This reverts commit 8e6e311bfc.

* fix: re-add consumables code
2024-03-22 00:36:38 +00:00
Luke Bonaccorsi c687f111eb fix: fix reference to incorrect key 2024-03-20 01:17:01 +00:00
Luke Bonaccorsi b180896a9c chore: fix releaserc 2024-03-08 14:30:54 +00:00
Luke Bonaccorsi 2968d722f8 fix: add more routes for trying to find the tuya region 2024-03-08 14:28:03 +00:00
Luke Bonaccorsi cdcad837b7 chore: add ability to create beta releases [skip ci] 2024-03-06 12:35:55 +00:00
Luke Bonaccorsi 2c741fe32e fix: print stack trace on connection reset 2024-03-04 01:07:44 +00:00
Luke Bonaccorsi 3dd4a7b0e0 fix: add extra logging 2024-03-03 04:18:14 +00:00
Luke Bonaccorsi c251501aed fix: force disconnect on connection reset 2024-03-01 16:53:48 +00:00
Luke Bonaccorsi 0b18494fb1 fix: send eof to reader when disconnecting 2024-03-01 14:28:23 +00:00
Luke Bonaccorsi ac0dbdd11a fix: missed underscores 🤦‍♂️ 2024-02-28 18:32:21 +00:00
Luke Bonaccorsi 96a155d378 fix: adjust times for refresh, ping and timeout 2024-02-27 14:39:01 +00:00
8 changed files with 331 additions and 80 deletions

View File

@ -2,6 +2,7 @@ on:
push: push:
branches: branches:
- main - main
workflow_dispatch:
jobs: jobs:
create-release: create-release:

View File

@ -1,6 +1,11 @@
{ {
"branches": [ "branches": [
"main" "main",
{
"name": "*",
"channel": "beta",
"prerelease": true
}
], ],
"plugins": [ "plugins": [
[ [

View File

@ -14,6 +14,7 @@
"""Config flow for Eufy Robovac integration.""" """Config flow for Eufy Robovac integration."""
from __future__ import annotations from __future__ import annotations
import json
import logging import logging
from typing import Any, Optional from typing import Any, Optional
@ -39,10 +40,17 @@ from homeassistant.const import (
CONF_IP_ADDRESS, CONF_IP_ADDRESS,
CONF_DESCRIPTION, CONF_DESCRIPTION,
CONF_MAC, CONF_MAC,
CONF_LOCATION,
CONF_CLIENT_ID, CONF_CLIENT_ID,
CONF_REGION, CONF_REGION,
CONF_TIME_ZONE, CONF_TIME_ZONE,
CONF_COUNTRY_CODE,
)
from .countries import (
get_phone_code_by_country_code,
get_phone_code_by_region,
get_region_by_country_code,
get_region_by_phone_code,
) )
from .const import CONF_AUTODISCOVERY, DOMAIN, CONF_VACS from .const import CONF_AUTODISCOVERY, DOMAIN, CONF_VACS
@ -88,15 +96,41 @@ def get_eufy_vacuums(self):
settings_response = response.json() settings_response = response.json()
self[CONF_CLIENT_ID] = user_response["user_info"]["id"] self[CONF_CLIENT_ID] = user_response["user_info"]["id"]
self[CONF_REGION] = settings_response["setting"]["home_setting"]["tuya_home"][ if (
"tuya_region_code" "tuya_home" in settings_response["setting"]["home_setting"]
] and "tuya_region_code"
in settings_response["setting"]["home_setting"]["tuya_home"]
):
self[CONF_REGION] = settings_response["setting"]["home_setting"]["tuya_home"][
"tuya_region_code"
]
if user_response["user_info"]["phone_code"]:
self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"]
else:
self[CONF_COUNTRY_CODE] = get_phone_code_by_region(self[CONF_REGION])
elif user_response["user_info"]["phone_code"]:
self[CONF_REGION] = get_region_by_phone_code(
user_response["user_info"]["phone_code"]
)
self[CONF_COUNTRY_CODE] = user_response["user_info"]["phone_code"]
elif user_response["user_info"]["country"]:
self[CONF_REGION] = get_region_by_country_code(
user_response["user_info"]["country"]
)
self[CONF_COUNTRY_CODE] = get_phone_code_by_country_code(
user_response["user_info"]["country"]
)
else:
self[CONF_REGION] = "EU"
self[CONF_COUNTRY_CODE] = "44"
self[CONF_TIME_ZONE] = user_response["user_info"]["timezone"] self[CONF_TIME_ZONE] = user_response["user_info"]["timezone"]
tuya_client = TuyaAPISession( tuya_client = TuyaAPISession(
username="eh-" + self[CONF_CLIENT_ID], username="eh-" + self[CONF_CLIENT_ID],
region=self[CONF_REGION], region=self[CONF_REGION],
timezone=self[CONF_TIME_ZONE], timezone=self[CONF_TIME_ZONE],
phone_code=self[CONF_COUNTRY_CODE],
) )
items = device_response["items"] items = device_response["items"]
@ -105,7 +139,6 @@ def get_eufy_vacuums(self):
if item["device"]["product"]["appliance"] == "Cleaning": if item["device"]["product"]["appliance"] == "Cleaning":
try: try:
device = tuya_client.get_device(item["device"]["id"]) device = tuya_client.get_device(item["device"]["id"])
_LOGGER.debug("Robovac schema: {}".format(device["schema"]))
vac_details = { vac_details = {
CONF_ID: item["device"]["id"], CONF_ID: item["device"]["id"],
@ -120,10 +153,11 @@ def get_eufy_vacuums(self):
self[CONF_VACS][item["device"]["id"]] = vac_details self[CONF_VACS][item["device"]["id"]] = vac_details
except: except:
_LOGGER.debug( _LOGGER.debug(
"Vacuum {} found on Eufy, but not on Tuya. Skipping.".format( "Skipping vacuum {}: found on Eufy but not on Tuya. Eufy details:".format(
item["device"]["id"] item["device"]["id"]
) )
) )
_LOGGER.debug(json.dumps(item["device"], indent=2))
return response return response

View File

@ -3,4 +3,6 @@
DOMAIN = "robovac" DOMAIN = "robovac"
CONF_VACS = "vacuums" CONF_VACS = "vacuums"
CONF_AUTODISCOVERY = "autodiscovery" CONF_AUTODISCOVERY = "autodiscovery"
REFRESH_RATE = 20 REFRESH_RATE = 60
PING_RATE = 10
TIMEOUT = 5

View File

@ -0,0 +1,228 @@
COUNTRIES = [
{"country_code": "AF", "phone_code": "93", "tuya_region": "EU"},
{"country_code": "AL", "phone_code": "355", "tuya_region": "EU"},
{"country_code": "DZ", "phone_code": "213", "tuya_region": "EU"},
{"country_code": "AO", "phone_code": "244", "tuya_region": "EU"},
{"country_code": "AR", "phone_code": "54", "tuya_region": "AZ"},
{"country_code": "AM", "phone_code": "374", "tuya_region": "EU"},
{"country_code": "AU", "phone_code": "61", "tuya_region": "AZ"},
{"country_code": "AT", "phone_code": "43", "tuya_region": "EU"},
{"country_code": "AZ", "phone_code": "994", "tuya_region": "EU"},
{"country_code": "BH", "phone_code": "973", "tuya_region": "EU"},
{"country_code": "BD", "phone_code": "880", "tuya_region": "EU"},
{"country_code": "BY", "phone_code": "375", "tuya_region": "EU"},
{"country_code": "BE", "phone_code": "32", "tuya_region": "EU"},
{"country_code": "BZ", "phone_code": "501", "tuya_region": "EU"},
{"country_code": "BJ", "phone_code": "229", "tuya_region": "EU"},
{"country_code": "BT", "phone_code": "975", "tuya_region": "EU"},
{"country_code": "BO", "phone_code": "591", "tuya_region": "AZ"},
{"country_code": "BA", "phone_code": "387", "tuya_region": "EU"},
{"country_code": "BW", "phone_code": "267", "tuya_region": "EU"},
{"country_code": "BR", "phone_code": "55", "tuya_region": "AZ"},
{"country_code": "VG", "phone_code": "1284", "tuya_region": "EU"},
{"country_code": "BN", "phone_code": "673", "tuya_region": "EU"},
{"country_code": "BG", "phone_code": "359", "tuya_region": "EU"},
{"country_code": "BF", "phone_code": "226", "tuya_region": "EU"},
{"country_code": "BI", "phone_code": "257", "tuya_region": "EU"},
{"country_code": "KH", "phone_code": "855", "tuya_region": "EU"},
{"country_code": "CM", "phone_code": "237", "tuya_region": "EU"},
{"country_code": "US", "phone_code": "1", "tuya_region": "AZ"},
{"country_code": "CA", "phone_code": "1", "tuya_region": "AZ"},
{"country_code": "CV", "phone_code": "238", "tuya_region": "EU"},
{"country_code": "KY", "phone_code": "1345", "tuya_region": "EU"},
{"country_code": "CF", "phone_code": "236", "tuya_region": "EU"},
{"country_code": "TD", "phone_code": "235", "tuya_region": "EU"},
{"country_code": "CL", "phone_code": "56", "tuya_region": "AZ"},
{"country_code": "CN", "phone_code": "86", "tuya_region": "AY"},
{"country_code": "CO", "phone_code": "57", "tuya_region": "AZ"},
{"country_code": "KM", "phone_code": "269", "tuya_region": "EU"},
{"country_code": "CG", "phone_code": "242", "tuya_region": "EU"},
{"country_code": "CD", "phone_code": "243", "tuya_region": "EU"},
{"country_code": "CR", "phone_code": "506", "tuya_region": "EU"},
{"country_code": "HR", "phone_code": "385", "tuya_region": "EU"},
{"country_code": "CY", "phone_code": "357", "tuya_region": "EU"},
{"country_code": "CZ", "phone_code": "420", "tuya_region": "EU"},
{"country_code": "DK", "phone_code": "45", "tuya_region": "EU"},
{"country_code": "DJ", "phone_code": "253", "tuya_region": "EU"},
{"country_code": "DO", "phone_code": "1809", "tuya_region": "EU"},
{"country_code": "DO", "phone_code": "1829", "tuya_region": "EU"},
{"country_code": "DO", "phone_code": "1849", "tuya_region": "EU"},
{"country_code": "EC", "phone_code": "593", "tuya_region": "AZ"},
{"country_code": "EG", "phone_code": "20", "tuya_region": "EU"},
{"country_code": "SV", "phone_code": "503", "tuya_region": "EU"},
{"country_code": "GQ", "phone_code": "240", "tuya_region": "EU"},
{"country_code": "ER", "phone_code": "291", "tuya_region": "EU"},
{"country_code": "EE", "phone_code": "372", "tuya_region": "EU"},
{"country_code": "ET", "phone_code": "251", "tuya_region": "EU"},
{"country_code": "FJ", "phone_code": "679", "tuya_region": "EU"},
{"country_code": "FI", "phone_code": "358", "tuya_region": "EU"},
{"country_code": "FR", "phone_code": "33", "tuya_region": "EU"},
{"country_code": "GA", "phone_code": "241", "tuya_region": "EU"},
{"country_code": "GM", "phone_code": "220", "tuya_region": "EU"},
{"country_code": "GE", "phone_code": "995", "tuya_region": "EU"},
{"country_code": "DE", "phone_code": "49", "tuya_region": "EU"},
{"country_code": "GH", "phone_code": "233", "tuya_region": "EU"},
{"country_code": "GR", "phone_code": "30", "tuya_region": "EU"},
{"country_code": "GL", "phone_code": "299", "tuya_region": "EU"},
{"country_code": "GT", "phone_code": "502", "tuya_region": "AZ"},
{"country_code": "GN", "phone_code": "224", "tuya_region": "EU"},
{"country_code": "GY", "phone_code": "592", "tuya_region": "EU"},
{"country_code": "HT", "phone_code": "509", "tuya_region": "EU"},
{"country_code": "HN", "phone_code": "504", "tuya_region": "EU"},
{"country_code": "HK", "phone_code": "852", "tuya_region": "AZ"},
{"country_code": "HU", "phone_code": "36", "tuya_region": "EU"},
{"country_code": "IS", "phone_code": "354", "tuya_region": "EU"},
{"country_code": "IN", "phone_code": "91", "tuya_region": "IN"},
{"country_code": "ID", "phone_code": "62", "tuya_region": "AZ"},
{"country_code": "IR", "phone_code": "98", "tuya_region": "EU"},
{"country_code": "IQ", "phone_code": "964", "tuya_region": "EU"},
{"country_code": "IE", "phone_code": "353", "tuya_region": "EU"},
{"country_code": "IM", "phone_code": "44", "tuya_region": "EU"},
{"country_code": "IL", "phone_code": "972", "tuya_region": "EU"},
{"country_code": "IT", "phone_code": "39", "tuya_region": "AZ"},
{"country_code": "CI", "phone_code": "225", "tuya_region": "EU"},
{"country_code": "JM", "phone_code": "1876", "tuya_region": "EU"},
{"country_code": "JP", "phone_code": "81", "tuya_region": "AZ"},
{"country_code": "JO", "phone_code": "962", "tuya_region": "EU"},
{"country_code": "KZ", "phone_code": "7", "tuya_region": "EU"},
{"country_code": "KE", "phone_code": "254", "tuya_region": "EU"},
{"country_code": "KR", "phone_code": "82", "tuya_region": "AZ"},
{"country_code": "KW", "phone_code": "965", "tuya_region": "EU"},
{"country_code": "KG", "phone_code": "996", "tuya_region": "EU"},
{"country_code": "LA", "phone_code": "856", "tuya_region": "EU"},
{"country_code": "LV", "phone_code": "371", "tuya_region": "EU"},
{"country_code": "LB", "phone_code": "961", "tuya_region": "EU"},
{"country_code": "LS", "phone_code": "266", "tuya_region": "EU"},
{"country_code": "LR", "phone_code": "231", "tuya_region": "EU"},
{"country_code": "LY", "phone_code": "218", "tuya_region": "EU"},
{"country_code": "LT", "phone_code": "370", "tuya_region": "EU"},
{"country_code": "LU", "phone_code": "352", "tuya_region": "EU"},
{"country_code": "MO", "phone_code": "853", "tuya_region": "AZ"},
{"country_code": "MK", "phone_code": "389", "tuya_region": "EU"},
{"country_code": "MG", "phone_code": "261", "tuya_region": "EU"},
{"country_code": "MW", "phone_code": "265", "tuya_region": "EU"},
{"country_code": "MY", "phone_code": "60", "tuya_region": "AZ"},
{"country_code": "MV", "phone_code": "960", "tuya_region": "EU"},
{"country_code": "ML", "phone_code": "223", "tuya_region": "EU"},
{"country_code": "MT", "phone_code": "356", "tuya_region": "EU"},
{"country_code": "MR", "phone_code": "222", "tuya_region": "EU"},
{"country_code": "MU", "phone_code": "230", "tuya_region": "EU"},
{"country_code": "MX", "phone_code": "52", "tuya_region": "AZ"},
{"country_code": "MD", "phone_code": "373", "tuya_region": "EU"},
{"country_code": "MC", "phone_code": "377", "tuya_region": "EU"},
{"country_code": "MN", "phone_code": "976", "tuya_region": "EU"},
{"country_code": "ME", "phone_code": "382", "tuya_region": "EU"},
{"country_code": "MA", "phone_code": "212", "tuya_region": "EU"},
{"country_code": "MZ", "phone_code": "258", "tuya_region": "EU"},
{"country_code": "MM", "phone_code": "95", "tuya_region": "AZ"},
{"country_code": "NA", "phone_code": "264", "tuya_region": "EU"},
{"country_code": "NP", "phone_code": "977", "tuya_region": "EU"},
{"country_code": "NL", "phone_code": "31", "tuya_region": "EU"},
{"country_code": "NZ", "phone_code": "64", "tuya_region": "AZ"},
{"country_code": "NI", "phone_code": "505", "tuya_region": "AZ"},
{"country_code": "NE", "phone_code": "227", "tuya_region": "EU"},
{"country_code": "NG", "phone_code": "234", "tuya_region": "EU"},
{"country_code": "KP", "phone_code": "850", "tuya_region": "EU"},
{"country_code": "NO", "phone_code": "47", "tuya_region": "EU"},
{"country_code": "OM", "phone_code": "968", "tuya_region": "EU"},
{"country_code": "PK", "phone_code": "92", "tuya_region": "EU"},
{"country_code": "PA", "phone_code": "507", "tuya_region": "EU"},
{"country_code": "PY", "phone_code": "595", "tuya_region": "AZ"},
{"country_code": "PE", "phone_code": "51", "tuya_region": "AZ"},
{"country_code": "PH", "phone_code": "63", "tuya_region": "AZ"},
{"country_code": "PL", "phone_code": "48", "tuya_region": "EU"},
{"country_code": "PF", "phone_code": "689", "tuya_region": "EU"},
{"country_code": "PT", "phone_code": "351", "tuya_region": "EU"},
{"country_code": "PR", "phone_code": "1787", "tuya_region": "AZ"},
{"country_code": "QA", "phone_code": "974", "tuya_region": "EU"},
{"country_code": "RE", "phone_code": "262", "tuya_region": "EU"},
{"country_code": "RO", "phone_code": "40", "tuya_region": "EU"},
{"country_code": "RU", "phone_code": "7", "tuya_region": "EU"},
{"country_code": "RW", "phone_code": "250", "tuya_region": "EU"},
{"country_code": "SM", "phone_code": "378", "tuya_region": "EU"},
{"country_code": "SA", "phone_code": "966", "tuya_region": "EU"},
{"country_code": "SN", "phone_code": "221", "tuya_region": "EU"},
{"country_code": "RS", "phone_code": "381", "tuya_region": "EU"},
{"country_code": "SL", "phone_code": "232", "tuya_region": "EU"},
{"country_code": "SG", "phone_code": "65", "tuya_region": "EU"},
{"country_code": "SK", "phone_code": "421", "tuya_region": "EU"},
{"country_code": "SI", "phone_code": "386", "tuya_region": "EU"},
{"country_code": "SO", "phone_code": "252", "tuya_region": "EU"},
{"country_code": "ZA", "phone_code": "27", "tuya_region": "EU"},
{"country_code": "ES", "phone_code": "34", "tuya_region": "EU"},
{"country_code": "LK", "phone_code": "94", "tuya_region": "EU"},
{"country_code": "SD", "phone_code": "249", "tuya_region": "EU"},
{"country_code": "SR", "phone_code": "597", "tuya_region": "AZ"},
{"country_code": "SZ", "phone_code": "268", "tuya_region": "EU"},
{"country_code": "SE", "phone_code": "46", "tuya_region": "EU"},
{"country_code": "CH", "phone_code": "41", "tuya_region": "EU"},
{"country_code": "SY", "phone_code": "963", "tuya_region": "EU"},
{"country_code": "TW", "phone_code": "886", "tuya_region": "AZ"},
{"country_code": "TJ", "phone_code": "992", "tuya_region": "EU"},
{"country_code": "TZ", "phone_code": "255", "tuya_region": "EU"},
{"country_code": "TH", "phone_code": "66", "tuya_region": "AZ"},
{"country_code": "TG", "phone_code": "228", "tuya_region": "EU"},
{"country_code": "TO", "phone_code": "676", "tuya_region": "EU"},
{"country_code": "TT", "phone_code": "1868", "tuya_region": "EU"},
{"country_code": "TN", "phone_code": "216", "tuya_region": "EU"},
{"country_code": "TR", "phone_code": "90", "tuya_region": "EU"},
{"country_code": "TM", "phone_code": "993", "tuya_region": "EU"},
{"country_code": "VI", "phone_code": "1340", "tuya_region": "EU"},
{"country_code": "UG", "phone_code": "256", "tuya_region": "EU"},
{"country_code": "UA", "phone_code": "380", "tuya_region": "EU"},
{"country_code": "AE", "phone_code": "971", "tuya_region": "EU"},
{"country_code": "GB", "phone_code": "44", "tuya_region": "EU"},
{"country_code": "UY", "phone_code": "598", "tuya_region": "AZ"},
{"country_code": "UZ", "phone_code": "998", "tuya_region": "EU"},
{"country_code": "VA", "phone_code": "379", "tuya_region": "EU"},
{"country_code": "VE", "phone_code": "58", "tuya_region": "AZ"},
{"country_code": "VN", "phone_code": "84", "tuya_region": "AZ"},
{"country_code": "YE", "phone_code": "967", "tuya_region": "EU"},
{"country_code": "ZR", "phone_code": "243", "tuya_region": "EU"},
{"country_code": "ZM", "phone_code": "260", "tuya_region": "EU"},
{"country_code": "ZW", "phone_code": "263", "tuya_region": "EU"},
{"country_code": "NCL", "phone_code": "687", "tuya_region": "EU"},
{"country_code": "MQ", "phone_code": "596", "tuya_region": "EU"},
]
def get_region_by_country_code(country_code):
country = next(
(item for item in COUNTRIES if item["country_code"] == country_code), None
)
if country is None:
return "EU"
return country["tuya_region"]
def get_region_by_phone_code(phone_code):
country = next(
(item for item in COUNTRIES if item["phone_code"] == phone_code), None
)
if country is None:
return "EU"
return country["tuya_region"]
def get_phone_code_by_region(region):
country = next((item for item in COUNTRIES if item["tuya_region"] == region), None)
if country is None:
return "44"
return country["phone_code"]
def get_phone_code_by_country_code(country_code):
country = next(
(item for item in COUNTRIES if item["country_code"] == country_code), None
)
if country is None:
return "44"
return country["phone_code"]

View File

@ -46,6 +46,7 @@ import socket
import struct import struct
import sys import sys
import time import time
import traceback
from typing import Callable, Coroutine from typing import Callable, Coroutine
from cryptography.hazmat.backends.openssl import backend as openssl_backend from cryptography.hazmat.backends.openssl import backend as openssl_backend
@ -527,7 +528,7 @@ class Message:
await self.device._async_send(self) await self.device._async_send(self)
@classmethod @classmethod
def from_bytes(cls, data, cipher=None): def from_bytes(cls, device, data, cipher=None):
try: try:
prefix, sequence, command, payload_size = struct.unpack_from( prefix, sequence, command, payload_size = struct.unpack_from(
MESSAGE_PREFIX_FORMAT, data MESSAGE_PREFIX_FORMAT, data
@ -584,15 +585,15 @@ class Message:
try: try:
payload_text = payload_data.decode("utf8") payload_text = payload_data.decode("utf8")
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
_LOGGER.debug(payload_data.hex()) device._LOGGER.debug(payload_data.hex())
_LOGGER.error(e) device._LOGGER.error(e)
raise MessageDecodeFailed() from e raise MessageDecodeFailed() from e
try: try:
payload = json.loads(payload_text) payload = json.loads(payload_text)
except json.decoder.JSONDecodeError as e: except json.decoder.JSONDecodeError as e:
# data may be encrypted # data may be encrypted
_LOGGER.debug(payload_data.hex()) device._LOGGER.debug(payload_data.hex())
_LOGGER.error(e) device._LOGGER.error(e)
raise MessageDecodeFailed() from e raise MessageDecodeFailed() from e
return cls(command, payload, sequence) return cls(command, payload, sequence)
@ -614,6 +615,7 @@ class TuyaDevice:
version=(3, 3), version=(3, 3),
): ):
"""Initialize the device.""" """Initialize the device."""
self._LOGGER = _LOGGER.getChild(device_id)
self.device_id = device_id self.device_id = device_id
self.host = host self.host = host
self.port = port self.port = port
@ -668,7 +670,7 @@ class TuyaDevice:
self.clean_queue() self.clean_queue()
if len(self._queue) > 0: if len(self._queue) > 0:
_LOGGER.debug( self._LOGGER.debug(
"Processing queue. Current length: {}".format(len(self._queue)) "Processing queue. Current length: {}".format(len(self._queue))
) )
try: try:
@ -679,14 +681,16 @@ class TuyaDevice:
self._backoff = False self._backoff = False
except Exception as e: except Exception as e:
self._failures += 1 self._failures += 1
_LOGGER.debug("{} failures. Most recent: {}".format(self._failures, e)) self._LOGGER.debug(
"{} failures. Most recent: {}".format(self._failures, e)
)
if self._failures > 3: if self._failures > 3:
self._backoff = True self._backoff = True
self._queue_interval = min( self._queue_interval = min(
INITIAL_BACKOFF * (BACKOFF_MULTIPLIER ** (self._failures - 4)), INITIAL_BACKOFF * (BACKOFF_MULTIPLIER ** (self._failures - 4)),
600, 600,
) )
_LOGGER.warn( self._LOGGER.warn(
"{} failures, backing off for {} seconds".format( "{} failures, backing off for {} seconds".format(
self._failures, self._queue_interval self._failures, self._queue_interval
) )
@ -709,7 +713,7 @@ class TuyaDevice:
sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
sock.settimeout(self.timeout) sock.settimeout(self.timeout)
_LOGGER.debug("Connecting to {}".format(self)) self._LOGGER.debug("Connecting to {}".format(self))
try: try:
sock.connect((self.host, self.port)) sock.connect((self.host, self.port))
except (socket.timeout, TimeoutError) as e: except (socket.timeout, TimeoutError) as e:
@ -721,7 +725,7 @@ class TuyaDevice:
self._connected = True self._connected = True
if self._ping_task is None: if self._ping_task is None:
self.ping_task = asyncio.create_task(self.async_ping(self.ping_interval)) self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval))
asyncio.create_task(self._async_handle_message()) asyncio.create_task(self._async_handle_message())
@ -734,13 +738,16 @@ class TuyaDevice:
if self._connected is False: if self._connected is False:
return return
_LOGGER.debug("Disconnected from {}".format(self)) self._LOGGER.debug("Disconnected from {}".format(self))
self._connected = False self._connected = False
self.last_pong = 0 self.last_pong = 0
if self.writer is not None: if self.writer is not None:
self.writer.close() self.writer.close()
if self.reader is not None and not self.reader.at_eof():
self.reader.feed_eof()
async def async_get(self): async def async_get(self):
payload = {"gwId": self.gateway_id, "devId": self.device_id} payload = {"gwId": self.gateway_id, "devId": self.device_id}
encrypt = False if self.version < (3, 3) else True encrypt = False if self.version < (3, 3) else True
@ -766,7 +773,7 @@ class TuyaDevice:
return return
if self._backoff is True: if self._backoff is True:
_LOGGER.debug("Currently in backoff, not adding ping to queue") self._LOGGER.debug("Currently in backoff, not adding ping to queue")
else: else:
self.last_ping = time.time() self.last_ping = time.time()
encrypt = False if self.version < (3, 3) else True encrypt = False if self.version < (3, 3) else True
@ -780,7 +787,7 @@ class TuyaDevice:
self._queue.append(message) self._queue.append(message)
await asyncio.sleep(ping_interval) await asyncio.sleep(ping_interval)
self.ping_task = asyncio.create_task(self.async_ping(self.ping_interval)) self._ping_task = asyncio.create_task(self.async_ping(self.ping_interval))
if self.last_pong < self.last_ping: if self.last_pong < self.last_ping:
await self.async_disconnect() await self.async_disconnect()
@ -798,7 +805,7 @@ class TuyaDevice:
and state_message.payload["dps"] and state_message.payload["dps"]
): ):
self._dps.update(state_message.payload["dps"]) self._dps.update(state_message.payload["dps"])
_LOGGER.debug("Received updated state {}: {}".format(self, self._dps)) self._LOGGER.debug("Received updated state {}: {}".format(self, self._dps))
@property @property
def state(self): def state(self):
@ -818,20 +825,23 @@ class TuyaDevice:
) )
await self._response_task await self._response_task
response_data = self._response_task.result() response_data = self._response_task.result()
message = Message.from_bytes(response_data, self.cipher) message = Message.from_bytes(self, response_data, self.cipher)
except Exception as e: except Exception as e:
if isinstance(e, InvalidMessage): if isinstance(e, InvalidMessage):
_LOGGER.debug("Invalid message from {}: {}".format(self, e)) self._LOGGER.debug("Invalid message from {}: {}".format(self, e))
elif isinstance(e, MessageDecodeFailed): elif isinstance(e, MessageDecodeFailed):
_LOGGER.debug("Failed to decrypt message from {}".format(self)) self._LOGGER.debug("Failed to decrypt message from {}".format(self))
elif isinstance(e, asyncio.IncompleteReadError): elif isinstance(e, asyncio.IncompleteReadError):
if self._connected: if self._connected:
_LOGGER.debug("Incomplete read") self._LOGGER.debug("Incomplete read")
elif isinstance(e, ConnectionResetError): elif isinstance(e, ConnectionResetError):
_LOGGER.debug("Connection reset") self._LOGGER.debug(
"Connection reset: {}\n{}".format(e, traceback.format_exc())
)
await self.async_disconnect()
else: else:
_LOGGER.debug("Received message from {}: {}".format(self, message)) self._LOGGER.debug("Received message from {}: {}".format(self, message))
if message.sequence in self._listeners: if message.sequence in self._listeners:
sem = self._listeners[message.sequence] sem = self._listeners[message.sequence]
if isinstance(sem, asyncio.Semaphore): if isinstance(sem, asyncio.Semaphore):
@ -846,7 +856,7 @@ class TuyaDevice:
asyncio.create_task(self._async_handle_message()) asyncio.create_task(self._async_handle_message())
async def _async_send(self, message, retries=2): async def _async_send(self, message, retries=2):
_LOGGER.debug("Sending to {}: {}".format(self, message)) self._LOGGER.debug("Sending to {}: {}".format(self, message))
try: try:
await self.async_connect() await self.async_connect()
self.writer.write(message.bytes()) self.writer.write(message.bytes())
@ -867,19 +877,19 @@ class TuyaDevice:
raise TuyaException("Failed to send data to {}".format(self)) raise TuyaException("Failed to send data to {}".format(self))
if isinstance(e, socket.error): if isinstance(e, socket.error):
_LOGGER.debug( self._LOGGER.debug(
"Retrying send due to error. Connection to {} failed: {}".format( "Retrying send due to error. Connection to {} failed: {}".format(
self, e self, e
) )
) )
elif isinstance(e, asyncio.IncompleteReadError): elif isinstance(e, asyncio.IncompleteReadError):
_LOGGER.debug( self._LOGGER.debug(
"Retrying send due to error. Incomplete read from: {} : {}. Partial data recieved: {}".format( "Retrying send due to error. Incomplete read from: {} : {}. Partial data recieved: {}".format(
self, e, e.partial self, e, e.partial
) )
) )
else: else:
_LOGGER.debug( self._LOGGER.debug(
"Retrying send due to error. Failed to send data to {}".format(self) "Retrying send due to error. Failed to send data to {}".format(self)
) )
await asyncio.sleep(0.25) await asyncio.sleep(0.25)

View File

@ -90,17 +90,20 @@ class TuyaAPISession:
country_code = None country_code = None
session_id = None session_id = None
def __init__(self, username, region, timezone): def __init__(self, username, region, timezone, phone_code):
self.session = requests.session() self.session = requests.session()
self.session.headers = DEFAULT_TUYA_HEADERS.copy() self.session.headers = DEFAULT_TUYA_HEADERS.copy()
self.default_query_params = DEFAULT_TUYA_QUERY_PARAMS.copy() self.default_query_params = DEFAULT_TUYA_QUERY_PARAMS.copy()
self.default_query_params["deviceId"] = self.generate_new_device_id() self.default_query_params["deviceId"] = self.generate_new_device_id()
self.username = username self.username = username
self.country_code = self.getCountryCode(region) self.country_code = phone_code
self.base_url = { self.base_url = {
"EU": "https://a1.tuyaeu.com", "AZ": "https://a1.tuyaus.com",
"AY": "https://a1.tuyacn.com", "AY": "https://a1.tuyacn.com",
}.get(region, "https://a1.tuyaus.com") "IN": "https://a1.tuyain.com",
"EU": "https://a1.tuyaeu.com",
}.get(region, "https://a1.tuyaeu.com")
DEFAULT_TUYA_QUERY_PARAMS["timeZoneId"] = timezone DEFAULT_TUYA_QUERY_PARAMS["timeZoneId"] = timezone
@staticmethod @staticmethod
@ -235,10 +238,5 @@ class TuyaAPISession:
def get_device(self, devId): def get_device(self, devId):
return self._request( return self._request(
action="tuya.m.device.get", action="tuya.m.device.get", version="1.0", data={"devId": devId}
version="1.0",
data={"devId": devId}
) )
def getCountryCode(self, region_code):
return {"EU": "44", "AY": "86"}.get(region_code, "1")

View File

@ -56,7 +56,7 @@ from homeassistant.const import (
) )
from .tuyalocalapi import TuyaException from .tuyalocalapi import TuyaException
from .const import CONF_VACS, DOMAIN, REFRESH_RATE from .const import CONF_VACS, DOMAIN, REFRESH_RATE, PING_RATE, TIMEOUT
from .errors import getErrorMessage from .errors import getErrorMessage
from .robovac import ( from .robovac import (
@ -89,28 +89,16 @@ UPDATE_RETRIES = 3
class TUYA_CODES(StrEnum): class TUYA_CODES(StrEnum):
MODE = "5"
STATE = "15"
# FAN_SPEED = "102"
FAN_SPEED = "130"
BATTERY_LEVEL = "104" BATTERY_LEVEL = "104"
STATE = "15"
ERROR_CODE = "106" ERROR_CODE = "106"
CLEANING_TIME = "109" MODE = "5"
FAN_SPEED = "102"
CLEANING_AREA = "110" CLEANING_AREA = "110"
DO_NOT_DISTURB = "107" CLEANING_TIME = "109"
DO_NOT_DISTURB2 = "139"
BOOST_IQ = "118"
AUTO_RETURN = "135" AUTO_RETURN = "135"
RETURN_HOME = "101" # boolean DO_NOT_DISTURB = "107"
A_111 = "111" # 65? BOOST_IQ = "118"
A_122 = "122" # continue
A_131 = "131" # false
A_137 = "137" # 0
HARDWARE_CODE = "115" # decoded
A_112 = "112" # decoded clean record?
A_113 = "113" # decoded
CLEAN_STATISTIC = "114" # decoded
MULTI_MAPS = "117" # decoded
TUYA_CONSUMABLES_CODES = ["142", "116"] TUYA_CONSUMABLES_CODES = ["142", "116"]
@ -285,8 +273,8 @@ class RoboVacEntity(StateVacuumEntity):
device_id=self.unique_id, device_id=self.unique_id,
host=self.ip_address, host=self.ip_address,
local_key=self.access_token, local_key=self.access_token,
timeout=2, timeout=TIMEOUT,
ping_interval=REFRESH_RATE / 2, ping_interval=PING_RATE,
model_code=self.model_code[0:5], model_code=self.model_code[0:5],
update_entity_state=self.pushed_update_handler, update_entity_state=self.pushed_update_handler,
) )
@ -364,14 +352,7 @@ class RoboVacEntity(StateVacuumEntity):
# self.map_data = self.tuyastatus.get("121") # self.map_data = self.tuyastatus.get("121")
# self.erro_msg? = self.tuyastatus.get("124") # self.erro_msg? = self.tuyastatus.get("124")
if self.robovac_supported & RoboVacEntityFeature.CONSUMABLES: if self.robovac_supported & RoboVacEntityFeature.CONSUMABLES:
_LOGGER.debug("Support Consumables")
for CONSUMABLE_CODE in TUYA_CONSUMABLES_CODES: for CONSUMABLE_CODE in TUYA_CONSUMABLES_CODES:
_LOGGER.debug("Consumable code is: {}".format(CONSUMABLE_CODE))
_LOGGER.debug(
"Consumables value is: {}".format(
self.tuyastatus.get(CONSUMABLE_CODE)
)
)
if ( if (
CONSUMABLE_CODE in self.tuyastatus CONSUMABLE_CODE in self.tuyastatus
and self.tuyastatus.get(CONSUMABLE_CODE) is not None and self.tuyastatus.get(CONSUMABLE_CODE) is not None
@ -381,18 +362,10 @@ class RoboVacEntity(StateVacuumEntity):
"ascii" "ascii"
) )
) )
_LOGGER.debug(
"Consumables decoded value is: {}".format(consumables)
)
if ( if (
"consumable" in consumables "consumable" in consumables
and "duration" in consumables["consumable"] and "duration" in consumables["consumable"]
): ):
_LOGGER.debug(
"Consumables encoded value is: {}".format(
consumables["consumable"]["duration"]
)
)
self._attr_consumables = consumables["consumable"]["duration"] self._attr_consumables = consumables["consumable"]["duration"]
async def async_locate(self, **kwargs): async def async_locate(self, **kwargs):
@ -432,7 +405,7 @@ class RoboVacEntity(StateVacuumEntity):
fan_speed = "Boost_IQ" fan_speed = "Boost_IQ"
elif fan_speed == "Pure": elif fan_speed == "Pure":
fan_speed = "Quiet" fan_speed = "Quiet"
await self.vacuum.async_set({"130": fan_speed}) await self.vacuum.async_set({"102": fan_speed})
async def async_send_command( async def async_send_command(
self, command: str, params: dict | list | None = None, **kwargs self, command: str, params: dict | list | None = None, **kwargs