mirror of https://github.com/home-assistant/core
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:
parent
4e9e9efa43
commit
475c8ebae2
|
@ -742,6 +742,7 @@ omit =
|
||||||
homeassistant/components/venstar/climate.py
|
homeassistant/components/venstar/climate.py
|
||||||
homeassistant/components/vera/*
|
homeassistant/components/vera/*
|
||||||
homeassistant/components/verisure/*
|
homeassistant/components/verisure/*
|
||||||
|
homeassistant/components/versasense/*
|
||||||
homeassistant/components/vesync/__init__.py
|
homeassistant/components/vesync/__init__.py
|
||||||
homeassistant/components/vesync/common.py
|
homeassistant/components/vesync/common.py
|
||||||
homeassistant/components/vesync/const.py
|
homeassistant/components/vesync/const.py
|
||||||
|
|
|
@ -332,6 +332,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||||
homeassistant/components/utility_meter/* @dgomes
|
homeassistant/components/utility_meter/* @dgomes
|
||||||
homeassistant/components/velbus/* @cereal2nd
|
homeassistant/components/velbus/* @cereal2nd
|
||||||
homeassistant/components/velux/* @Julius2342
|
homeassistant/components/velux/* @Julius2342
|
||||||
|
homeassistant/components/versasense/* @flamm3blemuff1n
|
||||||
homeassistant/components/version/* @fabaff
|
homeassistant/components/version/* @fabaff
|
||||||
homeassistant/components/vesync/* @markperdue @webdjoe
|
homeassistant/components/vesync/* @markperdue @webdjoe
|
||||||
homeassistant/components/vicare/* @oischinger
|
homeassistant/components/vicare/* @oischinger
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
|
@ -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"
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"domain": "versasense",
|
||||||
|
"name": "VersaSense",
|
||||||
|
"documentation": "https://www.home-assistant.io/components/versasense",
|
||||||
|
"dependencies": [],
|
||||||
|
"codeowners": ["@flamm3blemuff1n"],
|
||||||
|
"requirements": ["pyversasense==0.0.6"]
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
|
@ -1655,6 +1655,9 @@ pyuptimerobot==0.0.5
|
||||||
# homeassistant.components.vera
|
# homeassistant.components.vera
|
||||||
pyvera==0.3.6
|
pyvera==0.3.6
|
||||||
|
|
||||||
|
# homeassistant.components.versasense
|
||||||
|
pyversasense==0.0.6
|
||||||
|
|
||||||
# homeassistant.components.vesync
|
# homeassistant.components.vesync
|
||||||
pyvesync==1.1.0
|
pyvesync==1.1.0
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue