diff --git a/custom_components/localtuya/__init__.py b/custom_components/localtuya/__init__.py index 5b4804f..d575198 100644 --- a/custom_components/localtuya/__init__.py +++ b/custom_components/localtuya/__init__.py @@ -57,6 +57,8 @@ localtuya: import asyncio import logging +import homeassistant.helpers.config_validation as cv +import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import ( CONF_DEVICE_ID, @@ -67,16 +69,12 @@ from homeassistant.const import ( SERVICE_RELOAD, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.reload import async_integration_yaml_config from .common import TuyaDevice from .config_flow import config_schema -from .const import ( - CONF_PRODUCT_KEY, - DATA_DISCOVERY, - DOMAIN, - TUYA_DEVICE, -) +from .const import CONF_PRODUCT_KEY, DATA_DISCOVERY, DOMAIN, TUYA_DEVICE from .discovery import TuyaDiscovery _LOGGER = logging.getLogger(__name__) @@ -85,6 +83,18 @@ UNSUB_LISTENER = "unsub_listener" CONFIG_SCHEMA = config_schema() +CONF_DP = "dp" +CONF_VALUE = "value" + +SERVICE_SET_DP = "set_dp" +SERVICE_SET_DP_SCHEMA = vol.Schema( + { + vol.Required(CONF_DEVICE_ID): cv.string, + vol.Required(CONF_DP): int, + vol.Required(CONF_VALUE): object, + } +) + @callback def _async_update_config_entry_if_from_yaml(hass, entries_by_id, conf): @@ -102,6 +112,14 @@ async def async_setup(hass: HomeAssistant, config: dict): device_cache = {} + def _entry_by_device_id(device_id): + """Look up config entry by device id.""" + current_entries = hass.config_entries.async_entries(DOMAIN) + for entry in current_entries: + if entry.data[CONF_DEVICE_ID] == device_id: + return entry + return None + async def _handle_reload(service): """Handle reload service call.""" config = await async_integration_yaml_config(hass, DOMAIN) @@ -122,13 +140,20 @@ async def async_setup(hass: HomeAssistant, config: dict): await asyncio.gather(*reload_tasks) - def _entry_by_device_id(device_id): - """Look up config entry by device id.""" - current_entries = hass.config_entries.async_entries(DOMAIN) - for entry in current_entries: - if entry.data[CONF_DEVICE_ID] == device_id: - return entry - return None + async def _handle_set_dp(event): + """Handle set_dp service call.""" + entry = _entry_by_device_id(event.data[CONF_DEVICE_ID]) + if not entry: + raise HomeAssistantError("unknown device id") + + if entry.entry_id not in hass.data[DOMAIN]: + raise HomeAssistantError("device has not been discovered") + + device = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE] + if not device.connected: + raise HomeAssistantError("not connected to device") + + await device.set_dp(event.data[CONF_VALUE], event.data[CONF_DP]) def _device_discovered(device): """Update address of device if it has changed.""" @@ -193,6 +218,10 @@ async def async_setup(hass: HomeAssistant, config: dict): _handle_reload, ) + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_SET_DP, _handle_set_dp, schema=SERVICE_SET_DP_SCHEMA + ) + for host_config in config.get(DOMAIN, []): hass.async_create_task( hass.config_entries.flow.async_init( diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 5d57220..e63bd11 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -111,6 +111,11 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): for entity in config_entry[CONF_ENTITIES]: self._dps_to_request[entity[CONF_ID]] = None + @property + def connected(self): + """Return if connected to device.""" + return self._interface is not None + def connect(self): """Connect to device if not already connected.""" if not self._is_closing and self._connect_task is None and not self._interface: diff --git a/custom_components/localtuya/services.yaml b/custom_components/localtuya/services.yaml index 9aff7d7..b276d2f 100644 --- a/custom_components/localtuya/services.yaml +++ b/custom_components/localtuya/services.yaml @@ -1,2 +1,15 @@ reload: description: Reload localtuya and re-process yaml configuration. + +set_dp: + description: Change the value of a datapoint (DP) + fields: + device_id: + description: Device ID of device to change datapoint value for + example: 11100118278aab4de001 + dp: + description: Datapoint index + example: 1 + value: + description: New value to set + example: False