diff --git a/README.md b/README.md index 2cc8bc8..4009271 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,14 @@ There is support for the following device type within Home Assistant: - Light - Switch - Curtain Motor +- Wireless Switch - Smart Plug +- Smart Dial - Motion Sensor - Door Sensor - Temperature Sensor - Humidity Sensor +- Illuminance Sensor ## Installation @@ -70,7 +73,7 @@ default_config: logger: default: info logs: - homeassistant.components.terncy: info + homeassistant.components.terncy: debug ``` diff --git a/README.zh-cn.md b/README.zh-cn.md index 73e4626..859721b 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -17,11 +17,14 @@ - 灯 - 开关 - 窗帘电机 +- 无线开关 - 智能插座 +- 旋钮开关 - 人体传感器 - 门磁传感器 - 温度传感器 - 湿度传感器 +- 照度传感器 ## 安装方法 @@ -70,7 +73,7 @@ default_config: logger: default: info logs: - homeassistant.components.terncy: info + homeassistant.components.terncy: debug ``` diff --git a/terncy/__init__.py b/terncy/__init__.py index 30d5c63..8b26b3b 100644 --- a/terncy/__init__.py +++ b/terncy/__init__.py @@ -22,6 +22,7 @@ HA_CLIENT_ID, PROFILE_COLOR_DIMMABLE_LIGHT, PROFILE_YAN_BUTTON, + PROFILE_SMART_DIAL, PROFILE_COLOR_LIGHT, PROFILE_COLOR_TEMPERATURE_LIGHT, PROFILE_DIMMABLE_COLOR_TEMPERATURE_LIGHT, @@ -45,6 +46,11 @@ ACTION_TRIPLE_PRESS, ACTION_LONG_PRESS, TerncyHassPlatformData, + PLATFORM_LIGHT, + PLATFORM_COVER, + PLATFORM_SWITCH, + PLATFORM_BINARY_SENSOR, + PLATFORM_SENSOR, ) from .hub_monitor import TerncyHubManager from .light import ( @@ -69,14 +75,20 @@ ) from .sensor import ( TerncyTemperatureSensor, + TerncyHumiditySensor, + TerncyIlluminanceSensor, ) EVENT_DATA_CLICK_TIMES = "click_times" DEVID_EXT_TEMP = "_temp" +DEVID_EXT_HUMIDITY = "_himidity" +DEVID_EXT_ILLU = "_illu" +DEVID_EXT_MOTIONL = "_motionl" +DEVID_EXT_MOTIONR = "_motionr" PLATFORM_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) -PLATFORMS = ["light", "cover", "switch", "binary_sensor", "sensor"] +PLATFORMS = [PLATFORM_LIGHT, PLATFORM_COVER, PLATFORM_SWITCH, PLATFORM_BINARY_SENSOR, PLATFORM_SENSOR] _LOGGER = logging.getLogger(__name__) @@ -117,9 +129,18 @@ def terncy_event_handler(tern, ev): continue devid = ent["id"] temperature = get_attr_value(ent["attributes"], "temperature") - if temperature is not None: + if get_attr_value(ent["attributes"], "temperature") is not None: _LOGGER.info("got temperature") devid = devid + DEVID_EXT_TEMP + if get_attr_value(ent["attributes"], "luminance") is not None: + _LOGGER.info("got luminance") + devid = devid + DEVID_EXT_ILLU + if get_attr_value(ent["attributes"], "motionL") is not None: + _LOGGER.info("got motionl") + devid = devid + DEVID_EXT_MOTIONL + if get_attr_value(ent["attributes"], "motionR") is not None: + _LOGGER.info("got motionr") + devid = devid + DEVID_EXT_MOTIONR if devid in parsed_devices: dev = parsed_devices[devid] @@ -230,40 +251,45 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Terncy component.""" return True +async def add_entity_to_platform(tern, devid, device, attrs, domain): + if not devid in tern.hass_platform_data.parsed_devices: + for platform in async_get_platforms(tern.hass_platform_data.hass, DOMAIN): + if platform.config_entry.unique_id == tern.dev_id: + if platform.domain == domain: + _LOGGER.info("add device %s of %s to %s", device.name, domain, platform.domain) + await platform.async_add_entities([device]) + break + tern.hass_platform_data.parsed_devices[devid] = device + device.update_state(attrs) + if device.hass: + device.schedule_update_ha_state() + + async def update_or_create_entity_inner(svc, tern, model, version, available): _LOGGER.info("Updating service %s, available=%s", svc, available) - isLight = False - isSwitch = False profile = svc["profile"] features = -1 if profile == PROFILE_ONOFF_LIGHT: features = SUPPORT_TERNCY_ON_OFF - isLight = True + elif profile == PROFILE_SMART_DIAL: + features = SUPPORT_TERNCY_ON_OFF elif profile == PROFILE_COLOR_LIGHT: features = SUPPORT_TERNCY_COLOR - isLight = True elif profile == PROFILE_EXTENDED_COLOR_LIGHT: features = SUPPORT_TERNCY_EXTENDED - isLight = True elif profile == PROFILE_COLOR_TEMPERATURE_LIGHT: features = SUPPORT_TERNCY_CT - isLight = True elif profile == PROFILE_DIMMABLE_COLOR_TEMPERATURE_LIGHT: features = SUPPORT_TERNCY_CT - isLight = True elif profile == PROFILE_DIMMABLE_LIGHT: features = SUPPORT_TERNCY_DIMMABLE - isLight = True elif profile == PROFILE_DIMMABLE_LIGHT2: features = SUPPORT_TERNCY_DIMMABLE - isLight = True elif profile == PROFILE_COLOR_DIMMABLE_LIGHT: features = SUPPORT_TERNCY_EXTENDED - isLight = True elif profile == PROFILE_EXTENDED_COLOR_LIGHT2: features = SUPPORT_TERNCY_EXTENDED - isLight = True elif profile == PROFILE_PLUG: features = SUPPORT_TERNCY_ON_OFF elif profile == PROFILE_CURTAIN: @@ -281,92 +307,70 @@ async def update_or_create_entity_inner(svc, tern, model, version, available): return devid = svc["id"] - devidTemp = devid + DEVID_EXT_TEMP - if profile == PROFILE_HA_TEMPERATURE_HUMIDITY: - devid = devidTemp - - disableRelay = get_attr_value(svc["attributes"], "disableRelay") - temperature = get_attr_value(svc["attributes"], "temperature") name = devid if "name" in svc and svc["name"] != "": name = svc["name"] device = None - deviceTemp = None - if devid in tern.hass_platform_data.parsed_devices: - device = tern.hass_platform_data.parsed_devices[devid] - if temperature is not None: - deviceTemp = tern.hass_platform_data.parsed_devices[devidTemp] - deviceTemp.update_state(svc["attributes"]) - deviceTemp.is_available = available - else: + attrs = svc["attributes"] + + disableRelay = get_attr_value(attrs, "disableRelay") + temperature = get_attr_value(attrs, "temperature") + if not devid in tern.hass_platform_data.parsed_devices: + _LOGGER.info("need to add dev %s %d %s to platform", name, profile, devid) if profile == PROFILE_YAN_BUTTON or disableRelay == 1: - isLight = False - isSwitch = True device = TerncyButton(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_SWITCH) elif model.find("TERNCY-WS") >= 0 or model.find("TERNCY-LF") >= 0: - isLight = False - isSwitch = True device = TerncySwitch(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_SWITCH) + elif profile == PROFILE_SMART_DIAL: + device = TerncyButton(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_SWITCH) elif profile == PROFILE_PLUG: device = TerncySmartPlug(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_SWITCH) elif profile == PROFILE_CURTAIN: device = TerncyCurtain(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_COVER) elif profile == PROFILE_DOOR_SENSOR: device = TerncyDoorSensor(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_BINARY_SENSOR) + if not temperature is None: + device = TerncyTemperatureSensor(tern, devid + DEVID_EXT_TEMP, name + "-T", model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid + DEVID_EXT_TEMP, device, attrs, PLATFORM_SENSOR) elif profile == PROFILE_HA_TEMPERATURE_HUMIDITY: - device = TerncyTemperatureSensor(tern, devid, name + " temperature", model, version, features) + device = TerncyTemperatureSensor(tern, devid + DEVID_EXT_TEMP, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid + DEVID_EXT_TEMP, device, attrs, PLATFORM_SENSOR) elif profile == PROFILE_PIR: - device = TerncyMotionSensor(tern, devid, name, model, version, features) + device = TerncyButton(tern, devid, name, model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_SWITCH) + device = TerncyMotionSensor(tern, devid+DEVID_EXT_MOTIONL, name + "-L", model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid+DEVID_EXT_MOTIONL, device, attrs, PLATFORM_BINARY_SENSOR) + device = TerncyMotionSensor(tern, devid+DEVID_EXT_MOTIONR, name+"-R", model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid+DEVID_EXT_MOTIONR, device, attrs, PLATFORM_BINARY_SENSOR) + device = TerncyIlluminanceSensor(tern, devid + DEVID_EXT_ILLU, name + "-I", model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid + DEVID_EXT_ILLU, device, attrs, PLATFORM_SENSOR) + device = TerncyTemperatureSensor(tern, devid + DEVID_EXT_TEMP, name + "-T", model, version, features) + device.is_available = available + await add_entity_to_platform(tern, devid + DEVID_EXT_TEMP, device, attrs, PLATFORM_SENSOR) else: device = TerncyLight(tern, devid, name, model, version, features) - - if profile != PROFILE_HA_TEMPERATURE_HUMIDITY and temperature is not None: - _LOGGER.info("create temperature sensor") - deviceTemp = TerncyTemperatureSensor(tern, devidTemp, name + " temperature", model, version, features) - deviceTemp.update_state(svc["attributes"]) - deviceTemp.is_available = available - tern.hass_platform_data.parsed_devices[devidTemp] = deviceTemp - device.update_state(svc["attributes"]) - device.is_available = available - if devid in tern.hass_platform_data.parsed_devices: - if device.hass: - device.schedule_update_ha_state() - else: - for platform in async_get_platforms(tern.hass_platform_data.hass, DOMAIN): - if platform.config_entry.unique_id == tern.dev_id: - if profile == PROFILE_PLUG and platform.domain == "switch": - await platform.async_add_entities([device]) - break - if profile == PROFILE_YAN_BUTTON and platform.domain == "switch": - await platform.async_add_entities([device]) - break - elif profile == PROFILE_CURTAIN and platform.domain == "cover": - await platform.async_add_entities([device]) - break - elif ( - profile == PROFILE_DOOR_SENSOR - and platform.domain == "binary_sensor" - ): - await platform.async_add_entities([device]) - break - elif profile == PROFILE_PIR and platform.domain == "binary_sensor": - await platform.async_add_entities([device]) - break - elif deviceTemp is not None and platform.domain == "sensor": - await platform.async_add_entities([deviceTemp]) - continue - elif profile == PROFILE_HA_TEMPERATURE_HUMIDITY and platform.domain == "sensor": - await platform.async_add_entities([device]) - break - elif isLight and platform.domain == "light": - await platform.async_add_entities([device]) - break - elif isSwitch and platform.domain == "switch": - await platform.async_add_entities([device]) - break - tern.hass_platform_data.parsed_devices[devid] = device + device.is_available = available + await add_entity_to_platform(tern, devid, device, attrs, PLATFORM_LIGHT) async def update_or_create_entity(dev, tern): diff --git a/terncy/binary_sensor.py b/terncy/binary_sensor.py index 06a3ffd..953796e 100644 --- a/terncy/binary_sensor.py +++ b/terncy/binary_sensor.py @@ -141,10 +141,16 @@ def __init__(self, api, devid, name, model, version, features): def update_state(self, attrs): """Updateterncy state.""" - _LOGGER.info("update state event to %s", attrs) + _LOGGER.info("update %s state event to %s", self.unique_id, attrs) on = get_attr_value(attrs, "motion") if on is not None: self._on = on + on = get_attr_value(attrs, "motionL") + if on is not None: + self._on = on + on = get_attr_value(attrs, "motionR") + if on is not None: + self._on = on @property def unique_id(self): @@ -195,23 +201,4 @@ def device_info(self): } def get_trigger(self, id): - return [ - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: ACTION_SINGLE_PRESS, - }, - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: ACTION_DOUBLE_PRESS, - }, - { - CONF_PLATFORM: "device", - CONF_DEVICE_ID: id, - CONF_DOMAIN: DOMAIN, - CONF_TYPE: ACTION_TRIPLE_PRESS, - }, - ] + return [] diff --git a/terncy/const.py b/terncy/const.py index 8b9cf5a..f12af82 100644 --- a/terncy/const.py +++ b/terncy/const.py @@ -38,6 +38,12 @@ CONF_IP = "ip" CONF_PORT = "port" +PLATFORM_LIGHT = "light" +PLATFORM_COVER = "cover" +PLATFORM_SWITCH = "switch" +PLATFORM_BINARY_SENSOR = "binary_sensor" +PLATFORM_SENSOR = "sensor" + ACTION_SINGLE_PRESS = "single_press" ACTION_DOUBLE_PRESS = "double_press" ACTION_TRIPLE_PRESS = "triple_press" diff --git a/terncy/device_trigger.py b/terncy/device_trigger.py index 2be8bee..7a65bd1 100644 --- a/terncy/device_trigger.py +++ b/terncy/device_trigger.py @@ -2,6 +2,7 @@ import logging _LOGGER = logging.getLogger(__name__) from typing import List +from homeassistant.helpers import device_registry as dr import voluptuous as vol from homeassistant.components.automation import AutomationActionType @@ -62,7 +63,7 @@ async def async_validate_trigger_config(hass: HomeAssistant, config: ConfigType) async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: """List device triggers for Terncy devices.""" triggers = [] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(device_id) devid = min(device.identifiers)[1] for tern in hass.data[DOMAIN].values(): @@ -81,7 +82,7 @@ async def async_attach_trigger( ) -> CALLBACK_TYPE: """Attach a trigger.""" device_id = config[CONF_DEVICE_ID] - device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry = dr.async_get(hass) device = device_registry.async_get(device_id) _LOGGER.info(device.identifiers) devid = min(device.identifiers)[1] diff --git a/terncy/sensor.py b/terncy/sensor.py index e04e4c0..f47d62e 100644 --- a/terncy/sensor.py +++ b/terncy/sensor.py @@ -3,16 +3,14 @@ from typing import Optional from homeassistant.helpers import device_registry as dr -from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_MOISTURE, - DEVICE_CLASS_MOTION, - DEVICE_CLASS_SAFETY, - DEVICE_CLASS_OPENING, +from homeassistant.components.sensor import ( + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASSES, - BinarySensorEntity, + SensorEntity, ) from homeassistant.const import ( - DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, ) from homeassistant.const import ( @@ -52,7 +50,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): -class TerncyTemperatureSensor(BinarySensorEntity): +class TerncyTemperatureSensor(SensorEntity): """Representation of a Terncy temp sensor.""" def __init__(self, api, devid, name, model, version, features): @@ -77,7 +75,7 @@ def update_state(self, attrs): @property def unique_id(self): """Return terncy unique id.""" - return self._device_id + return self._device_id + "temp" @property def device_id(self): @@ -106,7 +104,7 @@ def available(self): @property def device_class(self): - """Return if terncy device is available.""" + """Return terncy device class.""" return DEVICE_CLASS_TEMPERATURE @property @@ -126,3 +124,159 @@ def device_info(self): "sw_version": self.version, "via_device": (DOMAIN, self.hub_id), } + + def get_trigger(self, id): + return [] + + +class TerncyHumiditySensor(SensorEntity): + """Representation of a Terncy humidity sensor.""" + + def __init__(self, api, devid, name, model, version, features): + """Initialize the curtain.""" + self._device_id = devid + self.hub_id = api.dev_id + self._name = name + self.model = model + self.version = version + self.api = api + self.is_available = False + self._features = features + self._value = 0 + + def update_state(self, attrs): + """Update terncy state.""" + _LOGGER.info("update state event to %s", attrs) + humidity = get_attr_value(attrs, "humidity") + if temp is not None: + self._value = humidity + + @property + def unique_id(self): + """Return terncy unique id.""" + return self._device_id + "humidity" + + @property + def device_id(self): + """Return terncy device id.""" + return self._device_id + + @property + def name(self): + """Return terncy device name.""" + return self._name + + @property + def state(self): + """Return the current humidity.""" + return round(self._value, 1) + + @property + def available(self): + """Return if terncy device is available.""" + return self.is_available + + @property + def device_class(self): + """Return terncy device class.""" + return DEVICE_CLASS_HUMIDITY + + @property + def supported_features(self): + """Return the terncy device feature.""" + return 0 + + @property + def device_info(self): + """Return the terncy device info.""" + return { + "identifiers": {(DOMAIN, self.device_id)}, + "connections": {(dr.CONNECTION_ZIGBEE, self.device_id)}, + "name": self.name, + "manufacturer": TERNCY_MANU_NAME, + "model": self.model, + "sw_version": self.version, + "via_device": (DOMAIN, self.hub_id), + } + + def get_trigger(self, id): + return [] + + +class TerncyIlluminanceSensor(SensorEntity): + """Representation of a Terncy illuminance sensor.""" + + def __init__(self, api, devid, name, model, version, features): + """Initialize the curtain.""" + self._device_id = devid + self.hub_id = api.dev_id + self._name = name + self.model = model + self.version = version + self.api = api + self.is_available = False + self._features = features + self._value = 0 + + def update_state(self, attrs): + """Update terncy state.""" + _LOGGER.info("update state event to %s", attrs) + illuminance = get_attr_value(attrs, "luminance") + if illuminance is not None: + self._value = illuminance + + @property + def unique_id(self): + """Return terncy unique id.""" + return self._device_id + "-illumin" + + @property + def device_id(self): + """Return terncy device id.""" + return self._device_id + + @property + def name(self): + """Return terncy device name.""" + return self._name + + @property + def state(self): + """Return the current illuminance.""" + return round(self._value, 1) + + @property + def unit_of_measurement(self) -> str: + """Return gallons as the unit measurement for water.""" + return "Lux" + + @property + def available(self): + """Return if terncy device is available.""" + return self.is_available + + @property + def device_class(self): + """Return terncy device class.""" + return DEVICE_CLASS_ILLUMINANCE + + @property + def supported_features(self): + """Return the terncy device feature.""" + return 0 + + @property + def device_info(self): + """Return the terncy device info.""" + return { + "identifiers": {(DOMAIN, self.device_id)}, + "connections": {(dr.CONNECTION_ZIGBEE, self.device_id)}, + "name": self.name, + "manufacturer": TERNCY_MANU_NAME, + "model": self.model, + "sw_version": self.version, + "via_device": (DOMAIN, self.hub_id), + } + + def get_trigger(self, id): + return []