Add support for DHT and DS18B20 sensors via Konnected firmware (#21189)
* mvp basic temperature sensor support * support for DHT temperature & humidity * add support for ds18b20 sensors * improve resolution of device settings * update requirements_all.txt * re-organize new file * don't use filter(lambda: syntax * set unique_id on entities to allow renaming in the UI * leverage base Entity module to do C to F conversion * add option for setting poll_interval * use handler pattern to handle updates from Konnected device * cleanups from code review
This commit is contained in:
parent
158e25562b
commit
f62eb22ef8
7 changed files with 358 additions and 108 deletions
|
@ -14,34 +14,25 @@ from homeassistant.components.discovery import SERVICE_KONNECTED
|
|||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, HTTP_BAD_REQUEST, HTTP_NOT_FOUND,
|
||||
HTTP_UNAUTHORIZED, CONF_DEVICES, CONF_BINARY_SENSORS, CONF_SWITCHES,
|
||||
CONF_HOST, CONF_PORT, CONF_ID, CONF_NAME, CONF_TYPE, CONF_PIN, CONF_ZONE,
|
||||
CONF_ACCESS_TOKEN, ATTR_ENTITY_ID, ATTR_STATE, STATE_ON)
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_send, dispatcher_send)
|
||||
HTTP_UNAUTHORIZED, CONF_DEVICES, CONF_BINARY_SENSORS, CONF_SENSORS,
|
||||
CONF_SWITCHES, CONF_HOST, CONF_PORT, CONF_ID, CONF_NAME, CONF_TYPE,
|
||||
CONF_PIN, CONF_ZONE, CONF_ACCESS_TOKEN, ATTR_ENTITY_ID, ATTR_STATE,
|
||||
STATE_ON)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import (
|
||||
CONF_ACTIVATION, CONF_API_HOST,
|
||||
CONF_MOMENTARY, CONF_PAUSE, CONF_POLL_INTERVAL, CONF_REPEAT,
|
||||
CONF_INVERSE, CONF_BLINK, CONF_DISCOVERY, CONF_DHT_SENSORS,
|
||||
CONF_DS18B20_SENSORS, DOMAIN, STATE_LOW, STATE_HIGH, PIN_TO_ZONE,
|
||||
ZONE_TO_PIN, ENDPOINT_ROOT, UPDATE_ENDPOINT, SIGNAL_SENSOR_UPDATE)
|
||||
from .handlers import HANDLERS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['konnected==0.1.4']
|
||||
|
||||
DOMAIN = 'konnected'
|
||||
|
||||
CONF_ACTIVATION = 'activation'
|
||||
CONF_API_HOST = 'api_host'
|
||||
CONF_MOMENTARY = 'momentary'
|
||||
CONF_PAUSE = 'pause'
|
||||
CONF_REPEAT = 'repeat'
|
||||
CONF_INVERSE = 'inverse'
|
||||
CONF_BLINK = 'blink'
|
||||
CONF_DISCOVERY = 'discovery'
|
||||
|
||||
STATE_LOW = 'low'
|
||||
STATE_HIGH = 'high'
|
||||
|
||||
PIN_TO_ZONE = {1: 1, 2: 2, 5: 3, 6: 4, 7: 5, 8: 'out', 9: 6}
|
||||
ZONE_TO_PIN = {zone: pin for pin, zone in PIN_TO_ZONE.items()}
|
||||
REQUIREMENTS = ['konnected==0.1.5']
|
||||
|
||||
_BINARY_SENSOR_SCHEMA = vol.All(
|
||||
vol.Schema({
|
||||
|
@ -53,6 +44,18 @@ _BINARY_SENSOR_SCHEMA = vol.All(
|
|||
}), cv.has_at_least_one_key(CONF_PIN, CONF_ZONE)
|
||||
)
|
||||
|
||||
_SENSOR_SCHEMA = vol.All(
|
||||
vol.Schema({
|
||||
vol.Exclusive(CONF_PIN, 's_pin'): vol.Any(*PIN_TO_ZONE),
|
||||
vol.Exclusive(CONF_ZONE, 's_pin'): vol.Any(*ZONE_TO_PIN),
|
||||
vol.Required(CONF_TYPE):
|
||||
vol.All(vol.Lower, vol.In(['dht', 'ds18b20'])),
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_POLL_INTERVAL):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
}), cv.has_at_least_one_key(CONF_PIN, CONF_ZONE)
|
||||
)
|
||||
|
||||
_SWITCH_SCHEMA = vol.All(
|
||||
vol.Schema({
|
||||
vol.Exclusive(CONF_PIN, 'a_pin'): vol.Any(*PIN_TO_ZONE),
|
||||
|
@ -79,6 +82,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
vol.Required(CONF_ID): cv.matches_regex("[0-9a-f]{12}"),
|
||||
vol.Optional(CONF_BINARY_SENSORS): vol.All(
|
||||
cv.ensure_list, [_BINARY_SENSOR_SCHEMA]),
|
||||
vol.Optional(CONF_SENSORS): vol.All(
|
||||
cv.ensure_list, [_SENSOR_SCHEMA]),
|
||||
vol.Optional(CONF_SWITCHES): vol.All(
|
||||
cv.ensure_list, [_SWITCH_SCHEMA]),
|
||||
vol.Optional(CONF_HOST): cv.string,
|
||||
|
@ -93,10 +98,6 @@ CONFIG_SCHEMA = vol.Schema(
|
|||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
ENDPOINT_ROOT = '/api/konnected'
|
||||
UPDATE_ENDPOINT = (ENDPOINT_ROOT + r'/device/{device_id:[a-zA-Z0-9]+}')
|
||||
SIGNAL_SENSOR_UPDATE = 'konnected.{}.update'
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Konnected platform."""
|
||||
|
@ -180,30 +181,30 @@ class ConfiguredDevice:
|
|||
|
||||
def save_data(self):
|
||||
"""Save the device configuration to `hass.data`."""
|
||||
sensors = {}
|
||||
binary_sensors = {}
|
||||
for entity in self.config.get(CONF_BINARY_SENSORS) or []:
|
||||
if CONF_ZONE in entity:
|
||||
pin = ZONE_TO_PIN[entity[CONF_ZONE]]
|
||||
else:
|
||||
pin = entity[CONF_PIN]
|
||||
|
||||
sensors[pin] = {
|
||||
binary_sensors[pin] = {
|
||||
CONF_TYPE: entity[CONF_TYPE],
|
||||
CONF_NAME: entity.get(CONF_NAME, 'Konnected {} Zone {}'.format(
|
||||
self.device_id[6:], PIN_TO_ZONE[pin])),
|
||||
CONF_INVERSE: entity.get(CONF_INVERSE),
|
||||
ATTR_STATE: None
|
||||
}
|
||||
_LOGGER.debug('Set up sensor %s (initial state: %s)',
|
||||
sensors[pin].get('name'),
|
||||
sensors[pin].get(ATTR_STATE))
|
||||
_LOGGER.debug('Set up binary_sensor %s (initial state: %s)',
|
||||
binary_sensors[pin].get('name'),
|
||||
binary_sensors[pin].get(ATTR_STATE))
|
||||
|
||||
actuators = []
|
||||
for entity in self.config.get(CONF_SWITCHES) or []:
|
||||
if 'zone' in entity:
|
||||
pin = ZONE_TO_PIN[entity['zone']]
|
||||
if CONF_ZONE in entity:
|
||||
pin = ZONE_TO_PIN[entity[CONF_ZONE]]
|
||||
else:
|
||||
pin = entity['pin']
|
||||
pin = entity[CONF_PIN]
|
||||
|
||||
act = {
|
||||
CONF_PIN: pin,
|
||||
|
@ -216,10 +217,32 @@ class ConfiguredDevice:
|
|||
CONF_PAUSE: entity.get(CONF_PAUSE),
|
||||
CONF_REPEAT: entity.get(CONF_REPEAT)}
|
||||
actuators.append(act)
|
||||
_LOGGER.debug('Set up actuator %s', act)
|
||||
_LOGGER.debug('Set up switch %s', act)
|
||||
|
||||
sensors = []
|
||||
for entity in self.config.get(CONF_SENSORS) or []:
|
||||
if CONF_ZONE in entity:
|
||||
pin = ZONE_TO_PIN[entity[CONF_ZONE]]
|
||||
else:
|
||||
pin = entity[CONF_PIN]
|
||||
|
||||
sensor = {
|
||||
CONF_PIN: pin,
|
||||
CONF_NAME: entity.get(
|
||||
CONF_NAME, 'Konnected {} Sensor {}'.format(
|
||||
self.device_id[6:], PIN_TO_ZONE[pin])),
|
||||
CONF_TYPE: entity[CONF_TYPE],
|
||||
CONF_POLL_INTERVAL: entity.get(CONF_POLL_INTERVAL)
|
||||
}
|
||||
sensors.append(sensor)
|
||||
_LOGGER.debug('Set up %s sensor %s (initial state: %s)',
|
||||
sensor.get(CONF_TYPE),
|
||||
sensor.get(CONF_NAME),
|
||||
sensor.get(ATTR_STATE))
|
||||
|
||||
device_data = {
|
||||
CONF_BINARY_SENSORS: sensors,
|
||||
CONF_BINARY_SENSORS: binary_sensors,
|
||||
CONF_SENSORS: sensors,
|
||||
CONF_SWITCHES: actuators,
|
||||
CONF_BLINK: self.config.get(CONF_BLINK),
|
||||
CONF_DISCOVERY: self.config.get(CONF_DISCOVERY)
|
||||
|
@ -232,12 +255,10 @@ class ConfiguredDevice:
|
|||
DOMAIN, CONF_DEVICES, self.device_id, device_data)
|
||||
self.hass.data[DOMAIN][CONF_DEVICES][self.device_id] = device_data
|
||||
|
||||
discovery.load_platform(
|
||||
self.hass, 'binary_sensor', DOMAIN,
|
||||
{'device_id': self.device_id}, self.hass_config)
|
||||
discovery.load_platform(
|
||||
self.hass, 'switch', DOMAIN,
|
||||
{'device_id': self.device_id}, self.hass_config)
|
||||
for platform in ['binary_sensor', 'sensor', 'switch']:
|
||||
discovery.load_platform(
|
||||
self.hass, platform, DOMAIN,
|
||||
{'device_id': self.device_id}, self.hass_config)
|
||||
|
||||
|
||||
class DiscoveredDevice:
|
||||
|
@ -283,8 +304,8 @@ class DiscoveredDevice:
|
|||
"""Return the configuration stored in `hass.data` for this device."""
|
||||
return self.hass.data[DOMAIN][CONF_DEVICES].get(self.device_id)
|
||||
|
||||
def sensor_configuration(self):
|
||||
"""Return the configuration map for syncing sensors."""
|
||||
def binary_sensor_configuration(self):
|
||||
"""Return the configuration map for syncing binary sensors."""
|
||||
return [{'pin': p} for p in
|
||||
self.stored_configuration[CONF_BINARY_SENSORS]]
|
||||
|
||||
|
@ -295,6 +316,19 @@ class DiscoveredDevice:
|
|||
else 1)}
|
||||
for data in self.stored_configuration[CONF_SWITCHES]]
|
||||
|
||||
def dht_sensor_configuration(self):
|
||||
"""Return the configuration map for syncing DHT sensors."""
|
||||
return [{CONF_PIN: sensor[CONF_PIN],
|
||||
CONF_POLL_INTERVAL: sensor[CONF_POLL_INTERVAL]} for sensor
|
||||
in self.stored_configuration[CONF_SENSORS]
|
||||
if sensor[CONF_TYPE] == 'dht']
|
||||
|
||||
def ds18b20_sensor_configuration(self):
|
||||
"""Return the configuration map for syncing DS18B20 sensors."""
|
||||
return [{'pin': sensor[CONF_PIN]} for sensor
|
||||
in self.stored_configuration[CONF_SENSORS]
|
||||
if sensor[CONF_TYPE] == 'ds18b20']
|
||||
|
||||
def update_initial_states(self):
|
||||
"""Update the initial state of each sensor from status poll."""
|
||||
for sensor_data in self.status.get('sensors'):
|
||||
|
@ -311,57 +345,55 @@ class DiscoveredDevice:
|
|||
SIGNAL_SENSOR_UPDATE.format(entity_id),
|
||||
state)
|
||||
|
||||
def sync_device_config(self):
|
||||
"""Sync the new pin configuration to the Konnected device."""
|
||||
desired_sensor_configuration = self.sensor_configuration()
|
||||
current_sensor_configuration = [
|
||||
{'pin': s[CONF_PIN]} for s in self.status.get('sensors')]
|
||||
_LOGGER.debug('%s: desired sensor config: %s', self.device_id,
|
||||
desired_sensor_configuration)
|
||||
_LOGGER.debug('%s: current sensor config: %s', self.device_id,
|
||||
current_sensor_configuration)
|
||||
|
||||
desired_actuator_config = self.actuator_configuration()
|
||||
current_actuator_config = self.status.get('actuators')
|
||||
_LOGGER.debug('%s: desired actuator config: %s', self.device_id,
|
||||
desired_actuator_config)
|
||||
_LOGGER.debug('%s: current actuator config: %s', self.device_id,
|
||||
current_actuator_config)
|
||||
|
||||
def desired_settings_payload(self):
|
||||
"""Return a dict representing the desired device configuration."""
|
||||
desired_api_host = \
|
||||
self.hass.data[DOMAIN].get(CONF_API_HOST) or \
|
||||
self.hass.config.api.base_url
|
||||
desired_api_endpoint = desired_api_host + ENDPOINT_ROOT
|
||||
current_api_endpoint = self.status.get('endpoint')
|
||||
|
||||
_LOGGER.debug('%s: desired api endpoint: %s', self.device_id,
|
||||
desired_api_endpoint)
|
||||
_LOGGER.debug('%s: current api endpoint: %s', self.device_id,
|
||||
current_api_endpoint)
|
||||
return {
|
||||
'sensors': self.binary_sensor_configuration(),
|
||||
'actuators': self.actuator_configuration(),
|
||||
'dht_sensors': self.dht_sensor_configuration(),
|
||||
'ds18b20_sensors': self.ds18b20_sensor_configuration(),
|
||||
'auth_token': self.hass.data[DOMAIN].get(CONF_ACCESS_TOKEN),
|
||||
'endpoint': desired_api_endpoint,
|
||||
'blink': self.stored_configuration.get(CONF_BLINK),
|
||||
'discovery': self.stored_configuration.get(CONF_DISCOVERY)
|
||||
}
|
||||
|
||||
if (desired_sensor_configuration != current_sensor_configuration) or \
|
||||
(current_actuator_config != desired_actuator_config) or \
|
||||
(current_api_endpoint != desired_api_endpoint) or \
|
||||
(self.status.get(CONF_BLINK) !=
|
||||
self.stored_configuration.get(CONF_BLINK)) or \
|
||||
(self.status.get(CONF_DISCOVERY) !=
|
||||
self.stored_configuration.get(CONF_DISCOVERY)):
|
||||
def current_settings_payload(self):
|
||||
"""Return a dict of configuration currently stored on the device."""
|
||||
settings = self.status['settings']
|
||||
if not settings:
|
||||
settings = {}
|
||||
|
||||
return {
|
||||
'sensors': [
|
||||
{'pin': s[CONF_PIN]} for s in self.status.get('sensors')],
|
||||
'actuators': self.status.get('actuators'),
|
||||
'dht_sensors': self.status.get(CONF_DHT_SENSORS),
|
||||
'ds18b20_sensors': self.status.get(CONF_DS18B20_SENSORS),
|
||||
'auth_token': settings.get('token'),
|
||||
'endpoint': settings.get('apiUrl'),
|
||||
'blink': settings.get(CONF_BLINK),
|
||||
'discovery': settings.get(CONF_DISCOVERY)
|
||||
}
|
||||
|
||||
def sync_device_config(self):
|
||||
"""Sync the new pin configuration to the Konnected device if needed."""
|
||||
_LOGGER.debug('Device %s settings payload: %s', self.device_id,
|
||||
self.desired_settings_payload())
|
||||
if self.desired_settings_payload() != self.current_settings_payload():
|
||||
_LOGGER.info('pushing settings to device %s', self.device_id)
|
||||
self.client.put_settings(
|
||||
desired_sensor_configuration,
|
||||
desired_actuator_config,
|
||||
self.hass.data[DOMAIN].get(CONF_ACCESS_TOKEN),
|
||||
desired_api_endpoint,
|
||||
blink=self.stored_configuration.get(CONF_BLINK),
|
||||
discovery=self.stored_configuration.get(CONF_DISCOVERY)
|
||||
)
|
||||
self.client.put_settings(**self.desired_settings_payload())
|
||||
|
||||
|
||||
class KonnectedView(HomeAssistantView):
|
||||
"""View creates an endpoint to receive push updates from the device."""
|
||||
|
||||
url = UPDATE_ENDPOINT
|
||||
extra_urls = [UPDATE_ENDPOINT + '/{pin_num}/{state}']
|
||||
name = 'api:konnected'
|
||||
requires_auth = False # Uses access token from configuration
|
||||
|
||||
|
@ -406,8 +438,7 @@ class KonnectedView(HomeAssistantView):
|
|||
hass.states.get(pin[ATTR_ENTITY_ID]).state,
|
||||
pin[CONF_ACTIVATION])})
|
||||
|
||||
async def put(self, request: Request, device_id,
|
||||
pin_num=None, state=None) -> Response:
|
||||
async def put(self, request: Request, device_id) -> Response:
|
||||
"""Receive a sensor update via PUT request and async set state."""
|
||||
hass = request.app['hass']
|
||||
data = hass.data[DOMAIN]
|
||||
|
@ -415,11 +446,10 @@ class KonnectedView(HomeAssistantView):
|
|||
try: # Konnected 2.2.0 and above supports JSON payloads
|
||||
payload = await request.json()
|
||||
pin_num = payload['pin']
|
||||
state = payload['state']
|
||||
except json.decoder.JSONDecodeError:
|
||||
_LOGGER.warning(("Your Konnected device software may be out of "
|
||||
"date. Visit https://help.konnected.io for "
|
||||
"updating instructions."))
|
||||
_LOGGER.error(("Your Konnected device software may be out of "
|
||||
"date. Visit https://help.konnected.io for "
|
||||
"updating instructions."))
|
||||
|
||||
auth = request.headers.get(AUTHORIZATION, None)
|
||||
if not hmac.compare_digest('Bearer {}'.format(self.auth_token), auth):
|
||||
|
@ -430,20 +460,20 @@ class KonnectedView(HomeAssistantView):
|
|||
if device is None:
|
||||
return self.json_message('unregistered device',
|
||||
status_code=HTTP_BAD_REQUEST)
|
||||
pin_data = device[CONF_BINARY_SENSORS].get(pin_num)
|
||||
pin_data = device[CONF_BINARY_SENSORS].get(pin_num) or \
|
||||
next((s for s in device[CONF_SENSORS] if s[CONF_PIN] == pin_num),
|
||||
None)
|
||||
|
||||
if pin_data is None:
|
||||
return self.json_message('unregistered sensor/actuator',
|
||||
status_code=HTTP_BAD_REQUEST)
|
||||
|
||||
entity_id = pin_data.get(ATTR_ENTITY_ID)
|
||||
if entity_id is None:
|
||||
return self.json_message('uninitialized sensor/actuator',
|
||||
status_code=HTTP_NOT_FOUND)
|
||||
state = bool(int(state))
|
||||
if pin_data.get(CONF_INVERSE):
|
||||
state = not state
|
||||
pin_data['device_id'] = device_id
|
||||
|
||||
for attr in ['state', 'temp', 'humi', 'addr']:
|
||||
value = payload.get(attr)
|
||||
handler = HANDLERS.get(attr)
|
||||
if value is not None and handler:
|
||||
hass.async_create_task(handler(hass, pin_data, payload))
|
||||
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), state)
|
||||
return self.json_message('ok')
|
||||
|
|
|
@ -39,9 +39,13 @@ class KonnectedBinarySensor(BinarySensorDevice):
|
|||
self._pin_num = pin_num
|
||||
self._state = self._data.get(ATTR_STATE)
|
||||
self._device_class = self._data.get(CONF_TYPE)
|
||||
self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format(
|
||||
device_id, PIN_TO_ZONE[pin_num]))
|
||||
_LOGGER.debug("Created new Konnected sensor: %s", self._name)
|
||||
self._unique_id = '{}-{}'.format(device_id, PIN_TO_ZONE[pin_num])
|
||||
self._name = self._data.get(CONF_NAME)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
27
homeassistant/components/konnected/const.py
Normal file
27
homeassistant/components/konnected/const.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""Konnected constants."""
|
||||
|
||||
DOMAIN = 'konnected'
|
||||
|
||||
CONF_ACTIVATION = 'activation'
|
||||
CONF_API_HOST = 'api_host'
|
||||
CONF_MOMENTARY = 'momentary'
|
||||
CONF_PAUSE = 'pause'
|
||||
CONF_POLL_INTERVAL = 'poll_interval'
|
||||
CONF_PRECISION = 'precision'
|
||||
CONF_REPEAT = 'repeat'
|
||||
CONF_INVERSE = 'inverse'
|
||||
CONF_BLINK = 'blink'
|
||||
CONF_DISCOVERY = 'discovery'
|
||||
CONF_DHT_SENSORS = 'dht_sensors'
|
||||
CONF_DS18B20_SENSORS = 'ds18b20_sensors'
|
||||
|
||||
STATE_LOW = 'low'
|
||||
STATE_HIGH = 'high'
|
||||
|
||||
PIN_TO_ZONE = {1: 1, 2: 2, 5: 3, 6: 4, 7: 5, 8: 'out', 9: 6}
|
||||
ZONE_TO_PIN = {zone: pin for pin, zone in PIN_TO_ZONE.items()}
|
||||
|
||||
ENDPOINT_ROOT = '/api/konnected'
|
||||
UPDATE_ENDPOINT = (ENDPOINT_ROOT + r'/device/{device_id:[a-zA-Z0-9]+}')
|
||||
SIGNAL_SENSOR_UPDATE = 'konnected.{}.update'
|
||||
SIGNAL_DS18B20_NEW = 'konnected.ds18b20.new'
|
62
homeassistant/components/konnected/handlers.py
Normal file
62
homeassistant/components/konnected/handlers.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
"""Handle Konnected messages."""
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.util import decorator
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_STATE,
|
||||
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY)
|
||||
|
||||
from .const import (CONF_INVERSE, SIGNAL_SENSOR_UPDATE, SIGNAL_DS18B20_NEW)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
HANDLERS = decorator.Registry()
|
||||
|
||||
|
||||
@HANDLERS.register('state')
|
||||
async def async_handle_state_update(hass, context, msg):
|
||||
"""Handle a binary sensor state update."""
|
||||
_LOGGER.debug("[state handler] context: %s msg: %s", context, msg)
|
||||
entity_id = context.get(ATTR_ENTITY_ID)
|
||||
state = bool(int(msg.get(ATTR_STATE)))
|
||||
if msg.get(CONF_INVERSE):
|
||||
state = not state
|
||||
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), state)
|
||||
|
||||
|
||||
@HANDLERS.register('temp')
|
||||
async def async_handle_temp_update(hass, context, msg):
|
||||
"""Handle a temperature sensor state update."""
|
||||
_LOGGER.debug("[temp handler] context: %s msg: %s", context, msg)
|
||||
entity_id, temp = context.get(DEVICE_CLASS_TEMPERATURE), msg.get('temp')
|
||||
if entity_id:
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), temp)
|
||||
|
||||
|
||||
@HANDLERS.register('humi')
|
||||
async def async_handle_humi_update(hass, context, msg):
|
||||
"""Handle a humidity sensor state update."""
|
||||
_LOGGER.debug("[humi handler] context: %s msg: %s", context, msg)
|
||||
entity_id, humi = context.get(DEVICE_CLASS_HUMIDITY), msg.get('humi')
|
||||
if entity_id:
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), humi)
|
||||
|
||||
|
||||
@HANDLERS.register('addr')
|
||||
async def async_handle_addr_update(hass, context, msg):
|
||||
"""Handle an addressable sensor update."""
|
||||
_LOGGER.debug("[addr handler] context: %s msg: %s", context, msg)
|
||||
addr, temp = msg.get('addr'), msg.get('temp')
|
||||
entity_id = context.get(addr)
|
||||
if entity_id:
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_SENSOR_UPDATE.format(entity_id), temp)
|
||||
else:
|
||||
msg['device_id'] = context.get('device_id')
|
||||
msg['temperature'] = temp
|
||||
msg['addr'] = addr
|
||||
async_dispatcher_send(hass, SIGNAL_DS18B20_NEW, msg)
|
124
homeassistant/components/konnected/sensor.py
Normal file
124
homeassistant/components/konnected/sensor.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
"""Support for DHT and DS18B20 sensors attached to a Konnected device."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.konnected.const import (
|
||||
DOMAIN as KONNECTED_DOMAIN, SIGNAL_DS18B20_NEW, SIGNAL_SENSOR_UPDATE)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICES, CONF_PIN, CONF_TYPE, CONF_NAME, CONF_SENSORS,
|
||||
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['konnected']
|
||||
|
||||
SENSOR_TYPES = {
|
||||
DEVICE_CLASS_TEMPERATURE: ['Temperature', TEMP_CELSIUS],
|
||||
DEVICE_CLASS_HUMIDITY: ['Humidity', '%']
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up sensors attached to a Konnected device."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
data = hass.data[KONNECTED_DOMAIN]
|
||||
device_id = discovery_info['device_id']
|
||||
sensors = []
|
||||
|
||||
# Initialize all DHT sensors.
|
||||
dht_sensors = [sensor for sensor
|
||||
in data[CONF_DEVICES][device_id][CONF_SENSORS]
|
||||
if sensor[CONF_TYPE] == 'dht']
|
||||
for sensor in dht_sensors:
|
||||
sensors.append(
|
||||
KonnectedSensor(device_id, sensor, DEVICE_CLASS_TEMPERATURE))
|
||||
sensors.append(
|
||||
KonnectedSensor(device_id, sensor, DEVICE_CLASS_HUMIDITY))
|
||||
|
||||
async_add_entities(sensors)
|
||||
|
||||
@callback
|
||||
def async_add_ds18b20(attrs):
|
||||
"""Add new KonnectedSensor representing a ds18b20 sensor."""
|
||||
sensor_config = next((s for s
|
||||
in data[CONF_DEVICES][device_id][CONF_SENSORS]
|
||||
if s[CONF_TYPE] == 'ds18b20'
|
||||
and s[CONF_PIN] == attrs.get(CONF_PIN)), None)
|
||||
|
||||
async_add_entities([
|
||||
KonnectedSensor(device_id, sensor_config, DEVICE_CLASS_TEMPERATURE,
|
||||
addr=attrs.get('addr'),
|
||||
initial_state=attrs.get('temp'))
|
||||
], True)
|
||||
|
||||
# DS18B20 sensors entities are initialized when they report for the first
|
||||
# time. Set up a listener for that signal from the Konnected component.
|
||||
async_dispatcher_connect(hass, SIGNAL_DS18B20_NEW, async_add_ds18b20)
|
||||
|
||||
|
||||
class KonnectedSensor(Entity):
|
||||
"""Represents a Konnected DHT Sensor."""
|
||||
|
||||
def __init__(self, device_id, data, sensor_type, addr=None,
|
||||
initial_state=None):
|
||||
"""Initialize the entity for a single sensor_type."""
|
||||
self._addr = addr
|
||||
self._data = data
|
||||
self._device_id = device_id
|
||||
self._type = sensor_type
|
||||
self._pin_num = self._data.get(CONF_PIN)
|
||||
self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
|
||||
self._unique_id = addr or '{}-{}-{}'.format(
|
||||
device_id, self._pin_num, sensor_type)
|
||||
|
||||
# set initial state if known at initialization
|
||||
self._state = initial_state
|
||||
if self._state:
|
||||
self._state = round(float(self._state), 1)
|
||||
|
||||
# set entity name if given
|
||||
self._name = self._data.get(CONF_NAME)
|
||||
if self._name:
|
||||
self._name += ' ' + SENSOR_TYPES[sensor_type][0]
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Store entity_id and register state change callback."""
|
||||
entity_id_key = self._addr or self._type
|
||||
self._data[entity_id_key] = self.entity_id
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_SENSOR_UPDATE.format(self.entity_id),
|
||||
self.async_set_state)
|
||||
|
||||
@callback
|
||||
def async_set_state(self, state):
|
||||
"""Update the sensor's state."""
|
||||
if self._type == DEVICE_CLASS_HUMIDITY:
|
||||
self._state = int(float(state))
|
||||
else:
|
||||
self._state = round(float(state), 1)
|
||||
self.async_schedule_update_ha_state()
|
|
@ -6,7 +6,7 @@ from homeassistant.components.konnected import (
|
|||
CONF_PAUSE, CONF_REPEAT, STATE_LOW, STATE_HIGH)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICES, CONF_SWITCHES, CONF_PIN, ATTR_STATE)
|
||||
ATTR_STATE, CONF_DEVICES, CONF_NAME, CONF_PIN, CONF_SWITCHES)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -40,10 +40,13 @@ class KonnectedSwitch(ToggleEntity):
|
|||
self._pause = self._data.get(CONF_PAUSE)
|
||||
self._repeat = self._data.get(CONF_REPEAT)
|
||||
self._state = self._boolean_state(self._data.get(ATTR_STATE))
|
||||
self._name = self._data.get(
|
||||
'name', 'Konnected {} Actuator {}'.format(
|
||||
device_id, PIN_TO_ZONE[pin_num]))
|
||||
_LOGGER.debug("Created new switch: %s", self._name)
|
||||
self._unique_id = '{}-{}'.format(device_id, PIN_TO_ZONE[pin_num])
|
||||
self._name = self._data.get(CONF_NAME)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
|
|
@ -607,7 +607,7 @@ keyrings.alt==3.1.1
|
|||
kiwiki-client==0.1.1
|
||||
|
||||
# homeassistant.components.konnected
|
||||
konnected==0.1.4
|
||||
konnected==0.1.5
|
||||
|
||||
# homeassistant.components.eufy
|
||||
lakeside==0.12
|
||||
|
|
Loading…
Add table
Reference in a new issue