Merge branch 'master' into light_scenes

This commit is contained in:
ultratoto14
2020-11-17 14:17:43 +01:00
committed by GitHub
11 changed files with 121 additions and 76 deletions

View File

@@ -38,7 +38,7 @@ class LocaltuyaBinarySensor(LocalTuyaEntity, BinarySensorEntity):
**kwargs, **kwargs,
): ):
"""Initialize the Tuya binary sensor.""" """Initialize the Tuya binary sensor."""
super().__init__(device, config_entry, sensorid, **kwargs) super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs)
self._is_on = False self._is_on = False
@property @property
@@ -59,7 +59,7 @@ class LocaltuyaBinarySensor(LocalTuyaEntity, BinarySensorEntity):
elif state == self._config[CONF_STATE_OFF].lower(): elif state == self._config[CONF_STATE_OFF].lower():
self._is_on = False self._is_on = False
else: else:
_LOGGER.warning( self.warning(
"State for entity %s did not match state patterns", self.entity_id "State for entity %s did not match state patterns", self.entity_id
) )

View File

@@ -90,7 +90,7 @@ def get_entity_config(config_entry, dp_id):
raise Exception(f"missing entity config for id {dp_id}") raise Exception(f"missing entity config for id {dp_id}")
class TuyaDevice(pytuya.TuyaListener): class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger):
"""Cache wrapper for pytuya.TuyaInterface.""" """Cache wrapper for pytuya.TuyaInterface."""
def __init__(self, hass, config_entry): def __init__(self, hass, config_entry):
@@ -103,6 +103,7 @@ class TuyaDevice(pytuya.TuyaListener):
self._is_closing = False self._is_closing = False
self._connect_task = None self._connect_task = None
self._connection_attempts = 0 self._connection_attempts = 0
self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID])
# This has to be done in case the device type is type_0d # This has to be done in case the device type is type_0d
for entity in config_entry[CONF_ENTITIES]: for entity in config_entry[CONF_ENTITIES]:
@@ -111,14 +112,13 @@ class TuyaDevice(pytuya.TuyaListener):
def connect(self): def connect(self):
"""Connet to device if not already connected.""" """Connet to device if not already connected."""
if not self._is_closing and self._connect_task is None and not self._interface: if not self._is_closing and self._connect_task is None and not self._interface:
_LOGGER.debug( self.debug(
"Connecting to %s (%s)", "Connecting to %s",
self._config_entry[CONF_HOST], self._config_entry[CONF_HOST],
self._config_entry[CONF_DEVICE_ID],
) )
self._connect_task = asyncio.ensure_future(self._make_connection()) self._connect_task = asyncio.ensure_future(self._make_connection())
else: else:
_LOGGER.debug( self.debug(
"Already connecting to %s (%s) - %s, %s, %s", "Already connecting to %s (%s) - %s, %s, %s",
self._config_entry[CONF_HOST], self._config_entry[CONF_HOST],
self._config_entry[CONF_DEVICE_ID], self._config_entry[CONF_DEVICE_ID],
@@ -132,11 +132,11 @@ class TuyaDevice(pytuya.TuyaListener):
randrange(2 ** self._connection_attempts), BACKOFF_TIME_UPPER_LIMIT randrange(2 ** self._connection_attempts), BACKOFF_TIME_UPPER_LIMIT
) )
_LOGGER.debug("Waiting %d seconds before connecting", backoff) self.debug("Waiting %d seconds before connecting", backoff)
await asyncio.sleep(backoff) await asyncio.sleep(backoff)
try: try:
_LOGGER.debug("Connecting to %s", self._config_entry[CONF_HOST]) self.debug("Connecting to %s", self._config_entry[CONF_HOST])
self._interface = await pytuya.connect( self._interface = await pytuya.connect(
self._config_entry[CONF_HOST], self._config_entry[CONF_HOST],
self._config_entry[CONF_DEVICE_ID], self._config_entry[CONF_DEVICE_ID],
@@ -146,7 +146,7 @@ class TuyaDevice(pytuya.TuyaListener):
) )
self._interface.add_dps_to_request(self._dps_to_request) self._interface.add_dps_to_request(self._dps_to_request)
_LOGGER.debug("Retrieving initial state") self.debug("Retrieving initial state")
status = await self._interface.status() status = await self._interface.status()
if status is None: if status is None:
raise Exception("Failed to retrieve status") raise Exception("Failed to retrieve status")
@@ -154,7 +154,7 @@ class TuyaDevice(pytuya.TuyaListener):
self.status_updated(status) self.status_updated(status)
self._connection_attempts = 0 self._connection_attempts = 0
except Exception: except Exception:
_LOGGER.exception(f"Connect to {self._config_entry[CONF_HOST]} failed") self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed")
self._connection_attempts += 1 self._connection_attempts += 1
if self._interface is not None: if self._interface is not None:
self._interface.close() self._interface.close()
@@ -176,9 +176,9 @@ class TuyaDevice(pytuya.TuyaListener):
try: try:
await self._interface.set_dp(state, dp_index) await self._interface.set_dp(state, dp_index)
except Exception: except Exception:
_LOGGER.exception("Failed to set DP %d to %d", dp_index, state) self.exception("Failed to set DP %d to %d", dp_index, state)
else: else:
_LOGGER.error( self.error(
"Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME] "Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME]
) )
@@ -188,9 +188,9 @@ class TuyaDevice(pytuya.TuyaListener):
try: try:
await self._interface.set_dps(states) await self._interface.set_dps(states)
except Exception: except Exception:
_LOGGER.exception("Failed to set DPs %r", states) self.exception("Failed to set DPs %r", states)
else: else:
_LOGGER.error( self.error(
"Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME] "Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME]
) )
@@ -205,9 +205,7 @@ class TuyaDevice(pytuya.TuyaListener):
@callback @callback
def disconnected(self, exc): def disconnected(self, exc):
"""Device disconnected.""" """Device disconnected."""
_LOGGER.debug( self.debug("Disconnected: %s", exc)
"Disconnected from %s: %s", self._config_entry[CONF_DEVICE_ID], exc
)
signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}"
async_dispatcher_send(self._hass, signal, None) async_dispatcher_send(self._hass, signal, None)
@@ -216,22 +214,23 @@ class TuyaDevice(pytuya.TuyaListener):
self.connect() self.connect()
class LocalTuyaEntity(Entity): class LocalTuyaEntity(Entity, pytuya.ContextualLogger):
"""Representation of a Tuya entity.""" """Representation of a Tuya entity."""
def __init__(self, device, config_entry, dp_id, **kwargs): def __init__(self, device, config_entry, dp_id, logger, **kwargs):
"""Initialize the Tuya entity.""" """Initialize the Tuya entity."""
self._device = device self._device = device
self._config_entry = config_entry self._config_entry = config_entry
self._config = get_entity_config(config_entry, dp_id) self._config = get_entity_config(config_entry, dp_id)
self._dp_id = dp_id self._dp_id = dp_id
self._status = {} self._status = {}
self.set_logger(logger, self._config_entry.data[CONF_DEVICE_ID])
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Subscribe localtuya events.""" """Subscribe localtuya events."""
await super().async_added_to_hass() await super().async_added_to_hass()
_LOGGER.debug("Adding %s with configuration: %s", self.entity_id, self._config) self.debug("Adding %s with configuration: %s", self.entity_id, self._config)
def _update_handler(status): def _update_handler(status):
"""Update entity state when status was updated.""" """Update entity state when status was updated."""
@@ -291,7 +290,7 @@ class LocalTuyaEntity(Entity):
"""Return cached value for DPS index.""" """Return cached value for DPS index."""
value = self._status.get(str(dp_index)) value = self._status.get(str(dp_index))
if value is None: if value is None:
_LOGGER.warning( self.warning(
"Entity %s is requesting unknown DPS index %s", "Entity %s is requesting unknown DPS index %s",
self.entity_id, self.entity_id,
dp_index, dp_index,
@@ -307,7 +306,7 @@ class LocalTuyaEntity(Entity):
""" """
dp_index = self._config.get(conf_item) dp_index = self._config.get(conf_item)
if dp_index is None: if dp_index is None:
_LOGGER.warning( self.warning(
"Entity %s is requesting unset index for option %s", "Entity %s is requesting unset index for option %s",
self.entity_id, self.entity_id,
conf_item, conf_item,

View File

@@ -1,4 +1,5 @@
"""Config flow for LocalTuya integration integration.""" """Config flow for LocalTuya integration integration."""
import errno
import logging import logging
from importlib import import_module from importlib import import_module
@@ -16,8 +17,8 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
from . import pytuya from . import pytuya
from .const import ( # pylint: disable=unused-import from .const import CONF_DPS_STRINGS # pylint: disable=unused-import
CONF_DPS_STRINGS, from .const import (
CONF_LOCAL_KEY, CONF_LOCAL_KEY,
CONF_PROTOCOL_VERSION, CONF_PROTOCOL_VERSION,
DATA_DISCOVERY, DATA_DISCOVERY,
@@ -202,7 +203,7 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.entities = [] self.entities = []
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle initial step.""" """Handle the initial step."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
if user_input[DISCOVERED_DEVICE] != CUSTOM_DEVICE: if user_input[DISCOVERED_DEVICE] != CUSTOM_DEVICE:
@@ -210,10 +211,22 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_step_basic_info() return await self.async_step_basic_info()
# Use cache if available or fallback to manual discovery # Use cache if available or fallback to manual discovery
if DOMAIN in self.hass.data: devices = {}
devices = self.hass.data[DOMAIN][DATA_DISCOVERY].devices data = self.hass.data.get(DOMAIN)
if data and DATA_DISCOVERY in data:
devices = data[DATA_DISCOVERY].devices
else: else:
devices = await discover() try:
devices = await discover()
except OSError as ex:
if ex.errno == errno.EADDRINUSE:
errors["base"] = "address_in_use"
else:
errors["base"] = "discovery_failed"
except Exception:
_LOGGER.exception("discovery failed")
errors["base"] = "discovery_failed"
self.devices = { self.devices = {
ip: dev ip: dev
for ip, dev in devices.items() for ip, dev in devices.items()

View File

@@ -68,7 +68,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
**kwargs, **kwargs,
): ):
"""Initialize a new LocaltuyaCover.""" """Initialize a new LocaltuyaCover."""
super().__init__(device, config_entry, switchid, **kwargs) super().__init__(device, config_entry, switchid, _LOGGER, **kwargs)
self._state = None self._state = None
self._current_cover_position = None self._current_cover_position = None
commands_set = DEFAULT_COMMANDS_SET commands_set = DEFAULT_COMMANDS_SET
@@ -122,7 +122,7 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
async def async_set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
_LOGGER.debug("Setting cover position: %r", kwargs[ATTR_POSITION]) self.debug("Setting cover position: %r", kwargs[ATTR_POSITION])
if self._config[CONF_POSITIONING_MODE] == COVER_MODE_FAKE: if self._config[CONF_POSITIONING_MODE] == COVER_MODE_FAKE:
newpos = float(kwargs[ATTR_POSITION]) newpos = float(kwargs[ATTR_POSITION])
@@ -130,15 +130,15 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
posdiff = abs(newpos - currpos) posdiff = abs(newpos - currpos)
mydelay = posdiff / 50.0 * self._config[CONF_SPAN_TIME] mydelay = posdiff / 50.0 * self._config[CONF_SPAN_TIME]
if newpos > currpos: if newpos > currpos:
_LOGGER.debug("Opening to %f: delay %f", newpos, mydelay) self.debug("Opening to %f: delay %f", newpos, mydelay)
await self.async_open_cover() await self.async_open_cover()
else: else:
_LOGGER.debug("Closing to %f: delay %f", newpos, mydelay) self.debug("Closing to %f: delay %f", newpos, mydelay)
await self.async_close_cover() await self.async_close_cover()
await asyncio.sleep(mydelay) await asyncio.sleep(mydelay)
await self.async_stop_cover() await self.async_stop_cover()
self._current_cover_position = 50 self._current_cover_position = 50
_LOGGER.debug("Done") self.debug("Done")
elif self._config[CONF_POSITIONING_MODE] == COVER_MODE_POSITION: elif self._config[CONF_POSITIONING_MODE] == COVER_MODE_POSITION:
converted_position = int(kwargs[ATTR_POSITION]) converted_position = int(kwargs[ATTR_POSITION])
@@ -152,17 +152,17 @@ class LocaltuyaCover(LocalTuyaEntity, CoverEntity):
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
_LOGGER.debug("Launching command %s to cover ", self._open_cmd) self.debug("Launching command %s to cover ", self._open_cmd)
await self._device.set_dp(self._open_cmd, self._dp_id) await self._device.set_dp(self._open_cmd, self._dp_id)
async def async_close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
"""Close cover.""" """Close cover."""
_LOGGER.debug("Launching command %s to cover ", self._close_cmd) self.debug("Launching command %s to cover ", self._close_cmd)
await self._device.set_dp(self._close_cmd, self._dp_id) await self._device.set_dp(self._close_cmd, self._dp_id)
async def async_stop_cover(self, **kwargs): async def async_stop_cover(self, **kwargs):
"""Stop the cover.""" """Stop the cover."""
_LOGGER.debug("Launching command %s to cover ", self._stop_cmd) self.debug("Launching command %s to cover ", self._stop_cmd)
await self._device.set_dp(self._stop_cmd, self._dp_id) await self._device.set_dp(self._stop_cmd, self._dp_id)
def status_updated(self): def status_updated(self):

View File

@@ -55,7 +55,7 @@ class TuyaDiscovery(asyncio.DatagramProtocol):
def close(self): def close(self):
"""Stop discovery.""" """Stop discovery."""
self.callback = None self.callback = None
for transport, _ in self.listeners: for transport, _ in self._listeners:
transport.close() transport.close()
def datagram_received(self, data, addr): def datagram_received(self, data, addr):
@@ -85,8 +85,6 @@ async def discover():
try: try:
await discover.start() await discover.start()
await asyncio.sleep(DEFAULT_TIMEOUT) await asyncio.sleep(DEFAULT_TIMEOUT)
except Exception:
_LOGGER.exception("failed to discover devices")
finally: finally:
discover.close() discover.close()
return discover.devices return discover.devices

View File

@@ -54,7 +54,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
**kwargs, **kwargs,
): ):
"""Initialize the entity.""" """Initialize the entity."""
super().__init__(device, config_entry, fanid, **kwargs) super().__init__(device, config_entry, fanid, _LOGGER, **kwargs)
self._is_on = False self._is_on = False
self._speed = None self._speed = None
self._oscillating = None self._oscillating = None
@@ -141,7 +141,7 @@ class LocaltuyaFan(LocalTuyaEntity, FanEntity):
if self.has_config(CONF_FAN_SPEED_CONTROL): if self.has_config(CONF_FAN_SPEED_CONTROL):
self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL)) self._speed = mappings.get(self.dps_conf(CONF_FAN_SPEED_CONTROL))
if self.speed is None: if self.speed is None:
_LOGGER.warning( self.warning(
"%s/%s: Ignoring unknown fan controller state: %s", "%s/%s: Ignoring unknown fan controller state: %s",
self.name, self.name,
self.entity_id, self.entity_id,

View File

@@ -135,7 +135,7 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity):
**kwargs, **kwargs,
): ):
"""Initialize the Tuya light.""" """Initialize the Tuya light."""
super().__init__(device, config_entry, lightid, **kwargs) super().__init__(device, config_entry, lightid, _LOGGER, **kwargs)
self._state = False self._state = False
self._brightness = None self._brightness = None
self._color_temp = None self._color_temp = None

View File

@@ -104,7 +104,40 @@ class TuyaLoggingAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs): def process(self, msg, kwargs):
"""Process log point and return output.""" """Process log point and return output."""
return f"[{self.extra['device_id']}] {msg}", kwargs dev_id = self.extra["device_id"]
return f"[{dev_id[0:3]}...{dev_id[-3:]}] {msg}", kwargs
class ContextualLogger:
"""Contextual logger adding device id to log points."""
def __init__(self):
"""Initialize a new ContextualLogger."""
self._logger = None
def set_logger(self, logger, device_id):
"""Set base logger to use."""
self._logger = TuyaLoggingAdapter(logger, {"device_id": device_id})
def debug(self, msg, *args):
"""Debug level log."""
return self._logger.log(logging.DEBUG, msg, *args)
def info(self, msg, *args):
"""Info level log."""
return self._logger.log(logging.INFO, msg, *args)
def warning(self, msg, *args):
"""Warning method log."""
return self._logger.log(logging.WARNING, msg, *args)
def error(self, msg, *args):
"""Error level log."""
return self._logger.log(logging.ERROR, msg, *args)
def exception(self, msg, *args):
"""Exception level log."""
return self._logger.log(logging.EXCEPTION, msg, *args)
def pack_message(msg): def pack_message(msg):
@@ -171,19 +204,19 @@ class AESCipher:
return s[: -ord(s[len(s) - 1 :])] return s[: -ord(s[len(s) - 1 :])]
class MessageDispatcher: class MessageDispatcher(ContextualLogger):
"""Buffer and dispatcher for Tuya messages.""" """Buffer and dispatcher for Tuya messages."""
# Heartbeats always respond with sequence number 0, so they can't be waited for like # Heartbeats always respond with sequence number 0, so they can't be waited for like
# other messages. This is a hack to allow waiting for heartbeats. # other messages. This is a hack to allow waiting for heartbeats.
HEARTBEAT_SEQNO = -100 HEARTBEAT_SEQNO = -100
def __init__(self, log, listener): def __init__(self, dev_id, listener):
"""Initialize a new MessageBuffer.""" """Initialize a new MessageBuffer."""
self.log = log
self.buffer = b"" self.buffer = b""
self.listeners = {} self.listeners = {}
self.listener = listener self.listener = listener
self.set_logger(_LOGGER, dev_id)
def abort(self): def abort(self):
"""Abort all waiting clients.""" """Abort all waiting clients."""
@@ -198,9 +231,9 @@ class MessageDispatcher:
async def wait_for(self, seqno, timeout=5): async def wait_for(self, seqno, timeout=5):
"""Wait for response to a sequence number to be received and return it.""" """Wait for response to a sequence number to be received and return it."""
if seqno in self.listeners: if seqno in self.listeners:
raise Exception(f"listener exists for {seqno} (id: {self.id})") raise Exception(f"listener exists for {seqno}")
self.log.debug("Waiting for sequence number %d", seqno) self.debug("Waiting for sequence number %d", seqno)
self.listeners[seqno] = asyncio.Semaphore(0) self.listeners[seqno] = asyncio.Semaphore(0)
try: try:
await asyncio.wait_for(self.listeners[seqno].acquire(), timeout=timeout) await asyncio.wait_for(self.listeners[seqno].acquire(), timeout=timeout)
@@ -246,23 +279,23 @@ class MessageDispatcher:
def _dispatch(self, msg): def _dispatch(self, msg):
"""Dispatch a message to someone that is listening.""" """Dispatch a message to someone that is listening."""
_LOGGER.debug("Dispatching message %s", msg) self.debug("Dispatching message %s", msg)
if msg.seqno in self.listeners: if msg.seqno in self.listeners:
self.log.debug("Dispatching sequence number %d", msg.seqno) self.debug("Dispatching sequence number %d", msg.seqno)
sem = self.listeners[msg.seqno] sem = self.listeners[msg.seqno]
self.listeners[msg.seqno] = msg self.listeners[msg.seqno] = msg
sem.release() sem.release()
elif msg.cmd == 0x09: elif msg.cmd == 0x09:
self.log.debug("Got heartbeat response") self.debug("Got heartbeat response")
if self.HEARTBEAT_SEQNO in self.listeners: if self.HEARTBEAT_SEQNO in self.listeners:
sem = self.listeners[self.HEARTBEAT_SEQNO] sem = self.listeners[self.HEARTBEAT_SEQNO]
self.listeners[self.HEARTBEAT_SEQNO] = msg self.listeners[self.HEARTBEAT_SEQNO] = msg
sem.release() sem.release()
elif msg.cmd == 0x08: elif msg.cmd == 0x08:
self.log.debug("Got status update") self.debug("Got status update")
self.listener(msg) self.listener(msg)
else: else:
self.log.debug( self.debug(
"Got message type %d for unknown listener %d: %s", "Got message type %d for unknown listener %d: %s",
msg.cmd, msg.cmd,
msg.seqno, msg.seqno,
@@ -292,7 +325,7 @@ class EmptyListener(TuyaListener):
"""Device disconnected.""" """Device disconnected."""
class TuyaProtocol(asyncio.Protocol): class TuyaProtocol(asyncio.Protocol, ContextualLogger):
"""Implementation of the Tuya protocol.""" """Implementation of the Tuya protocol."""
def __init__(self, dev_id, local_key, protocol_version, on_connected, listener): def __init__(self, dev_id, local_key, protocol_version, on_connected, listener):
@@ -308,7 +341,7 @@ class TuyaProtocol(asyncio.Protocol):
port (int): The port to connect to. port (int): The port to connect to.
""" """
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
self.log = TuyaLoggingAdapter(_LOGGER, {"device_id": dev_id}) self.set_logger(_LOGGER, dev_id)
self.id = dev_id self.id = dev_id
self.local_key = local_key.encode("latin1") self.local_key = local_key.encode("latin1")
self.version = protocol_version self.version = protocol_version
@@ -333,22 +366,22 @@ class TuyaProtocol(asyncio.Protocol):
if listener is not None: if listener is not None:
listener.status_updated(self.dps_cache) listener.status_updated(self.dps_cache)
return MessageDispatcher(self.log, _status_update) return MessageDispatcher(self.id, _status_update)
def connection_made(self, transport): def connection_made(self, transport):
"""Did connect to the device.""" """Did connect to the device."""
async def heartbeat_loop(): async def heartbeat_loop():
"""Continuously send heart beat updates.""" """Continuously send heart beat updates."""
self.log.debug("Started heartbeat loop") self.debug("Started heartbeat loop")
while True: while True:
try: try:
await self.heartbeat() await self.heartbeat()
except Exception as ex: except Exception as ex:
self.log.exception("Heartbeat failed (%s), disconnecting", ex) self.exception("Heartbeat failed (%s), disconnecting", ex)
break break
await asyncio.sleep(HEARTBEAT_INTERVAL) await asyncio.sleep(HEARTBEAT_INTERVAL)
self.log.debug("Stopped heartbeat loop") self.debug("Stopped heartbeat loop")
self.close() self.close()
self.transport = transport self.transport = transport
@@ -361,22 +394,22 @@ class TuyaProtocol(asyncio.Protocol):
def connection_lost(self, exc): def connection_lost(self, exc):
"""Disconnected from device.""" """Disconnected from device."""
self.log.debug("Connection lost: %s", exc) self.debug("Connection lost: %s", exc)
try: try:
self.close() self.close()
except Exception: except Exception:
self.log.exception("Failed to close connection") self.exception("Failed to close connection")
finally: finally:
try: try:
listener = self.listener() listener = self.listener()
if listener is not None: if listener is not None:
listener.disconnected(exc) listener.disconnected(exc)
except Exception: except Exception:
self.log.exception("Failed to call disconnected callback") self.exception("Failed to call disconnected callback")
def close(self): def close(self):
"""Close connection and abort all outstanding listeners.""" """Close connection and abort all outstanding listeners."""
self.log.debug("Closing connection") self.debug("Closing connection")
if self.heartbeater is not None: if self.heartbeater is not None:
self.heartbeater.cancel() self.heartbeater.cancel()
if self.dispatcher is not None: if self.dispatcher is not None:
@@ -388,7 +421,7 @@ class TuyaProtocol(asyncio.Protocol):
async def exchange(self, command, dps=None): async def exchange(self, command, dps=None):
"""Send and receive a message, returning response from device.""" """Send and receive a message, returning response from device."""
self.log.debug( self.debug(
"Sending command %s (device type: %s)", "Sending command %s (device type: %s)",
command, command,
self.dev_type, self.dev_type,
@@ -406,7 +439,7 @@ class TuyaProtocol(asyncio.Protocol):
self.transport.write(payload) self.transport.write(payload)
msg = await self.dispatcher.wait_for(seqno) msg = await self.dispatcher.wait_for(seqno)
if msg is None: if msg is None:
self.log.debug("Wait was aborted for seqno %d", seqno) self.debug("Wait was aborted for seqno %d", seqno)
return None return None
# TODO: Verify stuff, e.g. CRC sequence number? # TODO: Verify stuff, e.g. CRC sequence number?
@@ -414,7 +447,7 @@ class TuyaProtocol(asyncio.Protocol):
# Perform a new exchange (once) if we switched device type # Perform a new exchange (once) if we switched device type
if dev_type != self.dev_type: if dev_type != self.dev_type:
self.log.debug( self.debug(
"Re-send %s due to device type change (%s -> %s)", "Re-send %s due to device type change (%s -> %s)",
command, command,
dev_type, dev_type,
@@ -465,14 +498,14 @@ class TuyaProtocol(asyncio.Protocol):
try: try:
data = await self.status() data = await self.status()
except Exception as e: except Exception as e:
self.log.exception("Failed to get status: %s", e) self.exception("Failed to get status: %s", e)
raise raise
if "dps" in data: if "dps" in data:
self.dps_cache.update(data["dps"]) self.dps_cache.update(data["dps"])
if self.dev_type == "type_0a": if self.dev_type == "type_0a":
return self.dps_cache return self.dps_cache
self.log.debug("Detected dps: %s", self.dps_cache) self.debug("Detected dps: %s", self.dps_cache)
return self.dps_cache return self.dps_cache
def add_dps_to_request(self, dp_indicies): def add_dps_to_request(self, dp_indicies):
@@ -501,17 +534,17 @@ class TuyaProtocol(asyncio.Protocol):
if "data unvalid" in payload: if "data unvalid" in payload:
self.dev_type = "type_0d" self.dev_type = "type_0d"
self.log.debug( self.debug(
"switching to dev_type %s", "switching to dev_type %s",
self.dev_type, self.dev_type,
) )
return None return None
else: else:
raise Exception(f"Unexpected payload={payload} (id: {self.id})") raise Exception(f"Unexpected payload={payload}")
if not isinstance(payload, str): if not isinstance(payload, str):
payload = payload.decode() payload = payload.decode()
self.log.debug("Decrypted payload: %s", payload) self.debug("Decrypted payload: %s", payload)
return json.loads(payload) return json.loads(payload)
def _generate_payload(self, command, data=None): def _generate_payload(self, command, data=None):
@@ -543,7 +576,7 @@ class TuyaProtocol(asyncio.Protocol):
json_data["dps"] = self.dps_to_request json_data["dps"] = self.dps_to_request
payload = json.dumps(json_data).replace(" ", "").encode("utf-8") payload = json.dumps(json_data).replace(" ", "").encode("utf-8")
self.log.debug("Send payload: %s", payload) self.debug("Send payload: %s", payload)
if self.version == 3.3: if self.version == 3.3:
payload = self.cipher.encrypt(payload, False) payload = self.cipher.encrypt(payload, False)

View File

@@ -40,7 +40,7 @@ class LocaltuyaSensor(LocalTuyaEntity):
**kwargs, **kwargs,
): ):
"""Initialize the Tuya sensor.""" """Initialize the Tuya sensor."""
super().__init__(device, config_entry, sensorid, **kwargs) super().__init__(device, config_entry, sensorid, _LOGGER, **kwargs)
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
@property @property

View File

@@ -38,7 +38,7 @@ class LocaltuyaSwitch(LocalTuyaEntity, SwitchEntity):
**kwargs, **kwargs,
): ):
"""Initialize the Tuya switch.""" """Initialize the Tuya switch."""
super().__init__(device, config_entry, switchid, **kwargs) super().__init__(device, config_entry, switchid, _LOGGER, **kwargs)
self._state = None self._state = None
print("Initialized switch [{}]".format(self.name)) print("Initialized switch [{}]".format(self.name))

View File

@@ -7,7 +7,9 @@
"cannot_connect": "Cannot connect to device. Verify that address is correct and try again.", "cannot_connect": "Cannot connect to device. Verify that address is correct and try again.",
"invalid_auth": "Failed to authenticate with device. Verify that device id and local key are correct.", "invalid_auth": "Failed to authenticate with device. Verify that device id and local key are correct.",
"unknown": "An unknown error occurred. See log for details.", "unknown": "An unknown error occurred. See log for details.",
"entity_already_configured": "Entity with this ID has already been configured." "entity_already_configured": "Entity with this ID has already been configured.",
"address_in_use": "Address used for discovery is already in use. Make sure no other application is using it (TCP port 6668).",
"discovery_failed": "Something failed when discovering devices. See log for details."
}, },
"step": { "step": {
"user": { "user": {