mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
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/vera/*
|
||||
homeassistant/components/verisure/*
|
||||
homeassistant/components/versasense/*
|
||||
homeassistant/components/vesync/__init__.py
|
||||
homeassistant/components/vesync/common.py
|
||||
homeassistant/components/vesync/const.py
|
||||
|
@ -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
|
||||
|
97
homeassistant/components/versasense/__init__.py
Normal file
97
homeassistant/components/versasense/__init__.py
Normal 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)
|
||||
)
|
11
homeassistant/components/versasense/const.py
Normal file
11
homeassistant/components/versasense/const.py
Normal 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"
|
8
homeassistant/components/versasense/manifest.json
Normal file
8
homeassistant/components/versasense/manifest.json
Normal 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"]
|
||||
}
|
97
homeassistant/components/versasense/sensor.py
Normal file
97
homeassistant/components/versasense/sensor.py
Normal 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
|
113
homeassistant/components/versasense/switch.py
Normal file
113
homeassistant/components/versasense/switch.py
Normal 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
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user