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

Move to component from platform schemas #49

Merged
merged 5 commits into from
Sep 26, 2020
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
107 changes: 64 additions & 43 deletions custom_components/localtuya/__init__.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,83 @@
"""The LocalTuya integration integration."""
"""The LocalTuya integration integration.

Sample YAML config with all supported entity types (default values
are pre-filled for optional fields):

localtuya:
- host: 192.168.1.x
device_id: xxxxx
local_key: xxxxx
friendly_name: Tuya Device
protocol_version: "3.3"
entities:
- platform: binary_sensor
friendly_name: Plug Status
id: 1
device_class: power
state_on: "true" # Optional
state_off: "false" # Optional

- platform: cover
friendly_name: Device Cover
id: 2
open_cmd: "on" # Optional
close_cmd: "off" # Optional
stop_cmd: "stop" # Optional

- platform: fan
friendly_name: Device Fan
id: 3

- platform: light
friendly_name: Device Light
id: 4

- platform: sensor
friendly_name: Plug Voltage
id: 20
scaling: 0.1 # Optional
device_class: voltage # Optional
unit_of_measurement: "V" # Optional

- platform: switch
friendly_name: Plug
id: 1
current: 18 # Optional
current_consumption: 19 # Optional
voltage: 20 # Optional
"""
import asyncio
import logging
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_ID,
CONF_ICON,
CONF_NAME,
CONF_FRIENDLY_NAME,
CONF_HOST,
CONF_PLATFORM,
CONF_ENTITIES,
)
import homeassistant.helpers.config_validation as cv

from .const import CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN


import pprint

pp = pprint.PrettyPrinter(indent=4)
from .const import DOMAIN, TUYA_DEVICE
from .config_flow import config_schema
from .common import TuyaDevice

_LOGGER = logging.getLogger(__name__)

DEFAULT_ID = "1"
DEFAULT_PROTOCOL_VERSION = 3.3

UNSUB_LISTENER = "unsub_listener"

BASE_PLATFORM_SCHEMA = {
vol.Optional(CONF_ICON): cv.icon, # Deprecated: not used
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_DEVICE_ID): cv.string,
vol.Required(CONF_LOCAL_KEY): cv.string,
vol.Optional(CONF_NAME): cv.string, # Deprecated: not used
vol.Required(CONF_FRIENDLY_NAME): cv.string,
vol.Required(CONF_PROTOCOL_VERSION, default=DEFAULT_PROTOCOL_VERSION): vol.Coerce(
float
),
vol.Optional(CONF_ID, default=DEFAULT_ID): cv.string,
}


def import_from_yaml(hass, config, platform):
"""Import configuration from YAML."""
config[CONF_PLATFORM] = platform
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)

return True
CONFIG_SCHEMA = config_schema()


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the LocalTuya integration component."""
hass.data.setdefault(DOMAIN, {})

for host_config in config.get(DOMAIN, []):
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=host_config
)
)

return True


Expand All @@ -69,12 +87,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

hass.data[DOMAIN][entry.entry_id] = {
UNSUB_LISTENER: unsub_listener,
TUYA_DEVICE: TuyaDevice(entry.data),
}

for platform in set(entity[CONF_PLATFORM] for entity in entry.data[CONF_ENTITIES]):
for entity in entry.data[CONF_ENTITIES]:
platform = entity[CONF_PLATFORM]
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)

return True


Expand Down
115 changes: 7 additions & 108 deletions custom_components/localtuya/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
"""
Platform to prsent any Tuya DP as a binary sensor.

Sample config yaml

sensor:
- platform: localtuya
host: 192.168.0.1
local_key: 1234567891234567
device_id: 12345678912345671234
friendly_name: Current
protocol_version: 3.3
id: 18
state_on: "true" (optional, default is "true")
state_off: "false" (optional, default is "false")
device_class: current
"""
"""Platform to present any Tuya DP as a binary sensor."""
import logging
from time import time, sleep
from threading import Lock

import voluptuous as vol

from homeassistant.components.binary_sensor import (
DOMAIN,
PLATFORM_SCHEMA,
DEVICE_CLASSES_SCHEMA,
BinarySensorEntity,
)
from homeassistant.const import (
CONF_ID,
CONF_DEVICE_CLASS,
CONF_FRIENDLY_NAME,
)
from homeassistant.const import CONF_ID, CONF_DEVICE_CLASS

from . import (
BASE_PLATFORM_SCHEMA,
LocalTuyaEntity,
prepare_setup_entities,
import_from_yaml,
)
from .common import LocalTuyaEntity, prepare_setup_entities

_LOGGER = logging.getLogger(__name__)

CONF_STATE_ON = "state_on"
CONF_STATE_OFF = "state_off"

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(BASE_PLATFORM_SCHEMA)


def flow_schema(dps):
"""Return schema used in config flow."""
Expand All @@ -59,15 +29,17 @@ def flow_schema(dps):

async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up a Tuya sensor based on a config entry."""
device, entities_to_setup = prepare_setup_entities(config_entry, DOMAIN)
tuyainterface, entities_to_setup = prepare_setup_entities(
hass, config_entry, DOMAIN
)
if not entities_to_setup:
return

sensors = []
for device_config in entities_to_setup:
sensors.append(
LocaltuyaBinarySensor(
TuyaCache(device, config_entry.data[CONF_FRIENDLY_NAME]),
tuyainterface,
config_entry,
device_config[CONF_ID],
)
Expand All @@ -76,79 +48,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async_add_entities(sensors, True)


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up of the Tuya sensor."""
return import_from_yaml(hass, config, DOMAIN)


class TuyaCache:
"""Cache wrapper for pytuya.TuyaDevice."""

def __init__(self, device, friendly_name):
"""Initialize the cache."""
self._cached_status = ""
self._cached_status_time = 0
self._device = device
self._friendly_name = friendly_name
self._lock = Lock()

@property
def unique_id(self):
"""Return unique device identifier."""
return self._device.id

def __get_status(self):
for i in range(5):
try:
status = self._device.status()
return status
except Exception:
print(
"Failed to update status of device [{}]".format(
self._device.address
)
)
sleep(1.0)
if i + 1 == 3:
_LOGGER.error(
"Failed to update status of device %s", self._device.address
)
# return None
raise ConnectionError("Failed to update status .")

def set_dps(self, state, dps_index):
"""Change the Tuya sensor status and clear the cache."""
self._cached_status = ""
self._cached_status_time = 0
for i in range(5):
try:
return self._device.set_dps(state, dps_index)
except Exception:
print(
"Failed to set status of device [{}]".format(self._device.address)
)
if i + 1 == 3:
_LOGGER.error(
"Failed to set status of device %s", self._device.address
)
return

# raise ConnectionError("Failed to set status.")

def status(self):
"""Get state of Tuya sensor and cache the results."""
self._lock.acquire()
try:
now = time()
if not self._cached_status or now - self._cached_status_time > 15:
sleep(0.5)
self._cached_status = self.__get_status()
self._cached_status_time = time()
return self._cached_status
finally:
self._lock.release()


class LocaltuyaBinarySensor(LocalTuyaEntity, BinarySensorEntity):
"""Representation of a Tuya binary sensor."""

Expand Down
33 changes: 16 additions & 17 deletions custom_components/localtuya/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
)

from . import pytuya
from .const import CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN
from .const import CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN, TUYA_DEVICE

_LOGGER = logging.getLogger(__name__)


def prepare_setup_entities(config_entry, platform):
def prepare_setup_entities(hass, config_entry, platform):
"""Prepare ro setup entities for a platform."""
entities_to_setup = [
entity
Expand All @@ -30,16 +30,7 @@ def prepare_setup_entities(config_entry, platform):
if not entities_to_setup:
return None, None

tuyainterface = pytuya.TuyaInterface(
config_entry.data[CONF_DEVICE_ID],
config_entry.data[CONF_HOST],
config_entry.data[CONF_LOCAL_KEY],
float(config_entry.data[CONF_PROTOCOL_VERSION]),
)

for entity_config in entities_to_setup:
# this has to be done in case the device type is type_0d
tuyainterface.add_dps_to_request(entity_config[CONF_ID])
tuyainterface = hass.data[DOMAIN][config_entry.entry_id][TUYA_DEVICE]

return tuyainterface, entities_to_setup

Expand All @@ -55,12 +46,20 @@ def get_entity_config(config_entry, dps_id):
class TuyaDevice:
"""Cache wrapper for pytuya.TuyaInterface."""

def __init__(self, interface, friendly_name):
def __init__(self, config_entry):
"""Initialize the cache."""
self._cached_status = ""
self._cached_status_time = 0
self._interface = interface
self._friendly_name = friendly_name
self._interface = pytuya.TuyaInterface(
config_entry[CONF_DEVICE_ID],
config_entry[CONF_HOST],
config_entry[CONF_LOCAL_KEY],
float(config_entry[CONF_PROTOCOL_VERSION]),
)
for entity in config_entry[CONF_ENTITIES]:
# this has to be done in case the device type is type_0d
self._interface.add_dps_to_request(entity[CONF_ID])
self._friendly_name = config_entry[CONF_FRIENDLY_NAME]
self._lock = Lock()

@property
Expand Down Expand Up @@ -90,7 +89,7 @@ def __get_status(self):

def set_dps(self, state, dps_index):
# _LOGGER.info("running def set_dps from cover")
"""Change the Tuya device status and clear the cache."""
"""Change the Tuya switch status and clear the cache."""
self._cached_status = ""
self._cached_status_time = 0
for i in range(5):
Expand All @@ -111,7 +110,7 @@ def set_dps(self, state, dps_index):
# raise ConnectionError("Failed to set status.")

def status(self):
"""Get state of Tuya device and cache the results."""
"""Get state of Tuya switch and cache the results."""
_LOGGER.debug("running def status(self) from TuyaDevice")
self._lock.acquire()
try:
Expand Down
Loading