Add component VersaSense (#24619)

* Add component VersaSense

* Updates based on review

* Changes based on review

* Fixed whitespace

* Fixed lines too long

* Fixed lines too long

* Formatted using black

* Added available property

* Set unavailable property appropriately

* Conversion to f-strings

* Load platform only once per platform

* Fixed duplicate identifiers across multiple devices

* Single call to async_add_entities during setup

* Removed unnecessary async/await syntax

* Added constants for key-value pairs

* Removed async/await syntax

* Added breaks in measurement check

* Added guard clause for discovery_info
This commit is contained in:
Steven Impens 2019-11-19 14:05:23 +01:00 committed by Martin Hjelmare
parent 4e9e9efa43
commit 475c8ebae2
8 changed files with 331 additions and 0 deletions

View File

@ -742,6 +742,7 @@ omit =
homeassistant/components/venstar/climate.py
homeassistant/components/vera/*
homeassistant/components/verisure/*
homeassistant/components/versasense/*
homeassistant/components/vesync/__init__.py
homeassistant/components/vesync/common.py
homeassistant/components/vesync/const.py

View File

@ -332,6 +332,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @cereal2nd
homeassistant/components/velux/* @Julius2342
homeassistant/components/versasense/* @flamm3blemuff1n
homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe
homeassistant/components/vicare/* @oischinger

View File

@ -0,0 +1,97 @@
"""Support for VersaSense MicroPnP devices."""
import logging
import pyversasense as pyv
import voluptuous as vol
from homeassistant.const import CONF_HOST
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from .const import (
PERIPHERAL_CLASS_SENSOR,
PERIPHERAL_CLASS_SENSOR_ACTUATOR,
KEY_IDENTIFIER,
KEY_PARENT_NAME,
KEY_PARENT_MAC,
KEY_UNIT,
KEY_MEASUREMENT,
KEY_CONSUMER,
)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "versasense"
# Validation of the user's configuration
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_HOST): cv.string})}, extra=vol.ALLOW_EXTRA
)
async def async_setup(hass, config):
"""Set up the versasense component."""
session = aiohttp_client.async_get_clientsession(hass)
consumer = pyv.Consumer(config[DOMAIN]["host"], session)
hass.data[DOMAIN] = {KEY_CONSUMER: consumer}
await _configure_entities(hass, config, consumer)
# Return boolean to indicate that initialization was successful.
return True
async def _configure_entities(hass, config, consumer):
"""Fetch all devices with their peripherals for representation."""
devices = await consumer.fetchDevices()
_LOGGER.debug(devices)
sensor_info_list = []
switch_info_list = []
for mac, device in devices.items():
_LOGGER.info("Device connected: %s %s", device.name, mac)
hass.data[DOMAIN][mac] = {}
for peripheral_id, peripheral in device.peripherals.items():
hass.data[DOMAIN][mac][peripheral_id] = peripheral
if peripheral.classification == PERIPHERAL_CLASS_SENSOR:
sensor_info_list = _add_entity_info_to_list(
peripheral, device, sensor_info_list
)
elif peripheral.classification == PERIPHERAL_CLASS_SENSOR_ACTUATOR:
switch_info_list = _add_entity_info_to_list(
peripheral, device, switch_info_list
)
if sensor_info_list:
_load_platform(hass, config, "sensor", sensor_info_list)
if switch_info_list:
_load_platform(hass, config, "switch", switch_info_list)
def _add_entity_info_to_list(peripheral, device, entity_info_list):
"""Add info from a peripheral to specified list."""
for measurement in peripheral.measurements:
entity_info = {
KEY_IDENTIFIER: peripheral.identifier,
KEY_UNIT: measurement.unit,
KEY_MEASUREMENT: measurement.name,
KEY_PARENT_NAME: device.name,
KEY_PARENT_MAC: device.mac,
}
entity_info_list.append(entity_info)
return entity_info_list
def _load_platform(hass, config, entity_type, entity_info_list):
"""Load platform with list of entity info."""
hass.async_create_task(
async_load_platform(hass, entity_type, DOMAIN, entity_info_list, config)
)

View File

@ -0,0 +1,11 @@
"""Constants for versasense."""
KEY_CONSUMER = "consumer"
KEY_IDENTIFIER = "identifier"
KEY_MEASUREMENT = "measurement"
KEY_PARENT_MAC = "parent_mac"
KEY_PARENT_NAME = "parent_name"
KEY_UNIT = "unit"
PERIPHERAL_CLASS_SENSOR = "sensor"
PERIPHERAL_CLASS_SENSOR_ACTUATOR = "sensor-actuator"
PERIPHERAL_STATE_OFF = "OFF"
PERIPHERAL_STATE_ON = "ON"

View File

@ -0,0 +1,8 @@
{
"domain": "versasense",
"name": "VersaSense",
"documentation": "https://www.home-assistant.io/components/versasense",
"dependencies": [],
"codeowners": ["@flamm3blemuff1n"],
"requirements": ["pyversasense==0.0.6"]
}

View File

@ -0,0 +1,97 @@
"""Support for VersaSense sensor peripheral."""
import logging
from homeassistant.helpers.entity import Entity
from . import DOMAIN
from .const import (
KEY_IDENTIFIER,
KEY_PARENT_NAME,
KEY_PARENT_MAC,
KEY_UNIT,
KEY_MEASUREMENT,
KEY_CONSUMER,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the sensor platform."""
if discovery_info is None:
return None
consumer = hass.data[DOMAIN][KEY_CONSUMER]
sensor_list = []
for entity_info in discovery_info:
peripheral = hass.data[DOMAIN][entity_info[KEY_PARENT_MAC]][
entity_info[KEY_IDENTIFIER]
]
parent_name = entity_info[KEY_PARENT_NAME]
unit = entity_info[KEY_UNIT]
measurement = entity_info[KEY_MEASUREMENT]
sensor_list.append(
VSensor(peripheral, parent_name, unit, measurement, consumer)
)
async_add_entities(sensor_list)
class VSensor(Entity):
"""Representation of a Sensor."""
def __init__(self, peripheral, parent_name, unit, measurement, consumer):
"""Initialize the sensor."""
self._state = None
self._available = True
self._name = f"{parent_name} {measurement}"
self._parent_mac = peripheral.parentMac
self._identifier = peripheral.identifier
self._unit = unit
self._measurement = measurement
self.consumer = consumer
@property
def unique_id(self):
"""Return the unique id of the sensor."""
return f"{self._parent_mac}/{self._identifier}/{self._measurement}"
@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
@property
def available(self):
"""Return if the sensor is available."""
return self._available
async def async_update(self):
"""Fetch new state data for the sensor."""
samples = await self.consumer.fetchPeripheralSample(
None, self._identifier, self._parent_mac
)
if samples is not None:
for sample in samples:
if sample.measurement == self._measurement:
self._available = True
self._state = sample.value
break
else:
_LOGGER.error("Sample unavailable")
self._available = False
self._state = None

View File

@ -0,0 +1,113 @@
"""Support for VersaSense actuator peripheral."""
import logging
from homeassistant.components.switch import SwitchDevice
from . import DOMAIN
from .const import (
PERIPHERAL_STATE_ON,
PERIPHERAL_STATE_OFF,
KEY_IDENTIFIER,
KEY_PARENT_NAME,
KEY_PARENT_MAC,
KEY_UNIT,
KEY_MEASUREMENT,
KEY_CONSUMER,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up actuator platform."""
if discovery_info is None:
return None
consumer = hass.data[DOMAIN][KEY_CONSUMER]
actuator_list = []
for entity_info in discovery_info:
peripheral = hass.data[DOMAIN][entity_info[KEY_PARENT_MAC]][
entity_info[KEY_IDENTIFIER]
]
parent_name = entity_info[KEY_PARENT_NAME]
unit = entity_info[KEY_UNIT]
measurement = entity_info[KEY_MEASUREMENT]
actuator_list.append(
VActuator(peripheral, parent_name, unit, measurement, consumer)
)
async_add_entities(actuator_list)
class VActuator(SwitchDevice):
"""Representation of an Actuator."""
def __init__(self, peripheral, parent_name, unit, measurement, consumer):
"""Initialize the sensor."""
self._is_on = False
self._available = True
self._name = f"{parent_name} {measurement}"
self._parent_mac = peripheral.parentMac
self._identifier = peripheral.identifier
self._unit = unit
self._measurement = measurement
self.consumer = consumer
@property
def unique_id(self):
"""Return the unique id of the actuator."""
return f"{self._parent_mac}/{self._identifier}/{self._measurement}"
@property
def name(self):
"""Return the name of the actuator."""
return self._name
@property
def is_on(self):
"""Return the state of the actuator."""
return self._is_on
@property
def available(self):
"""Return if the actuator is available."""
return self._available
async def async_turn_off(self, **kwargs):
"""Turn off the actuator."""
await self.update_state(0)
async def async_turn_on(self, **kwargs):
"""Turn on the actuator."""
await self.update_state(1)
async def update_state(self, state):
"""Update the state of the actuator."""
payload = {"id": "state-num", "value": state}
await self.consumer.actuatePeripheral(
None, self._identifier, self._parent_mac, payload
)
async def async_update(self):
"""Fetch state data from the actuator."""
samples = await self.consumer.fetchPeripheralSample(
None, self._identifier, self._parent_mac
)
if samples is not None:
for sample in samples:
if sample.measurement == self._measurement:
self._available = True
if sample.value == PERIPHERAL_STATE_OFF:
self._is_on = False
elif sample.value == PERIPHERAL_STATE_ON:
self._is_on = True
break
else:
_LOGGER.error("Sample unavailable")
self._available = False
self._is_on = None

View File

@ -1655,6 +1655,9 @@ pyuptimerobot==0.0.5
# homeassistant.components.vera
pyvera==0.3.6
# homeassistant.components.versasense
pyversasense==0.0.6
# homeassistant.components.vesync
pyvesync==1.1.0