Fix binary sensor in Ambient PWS (#20801)

* Fix binary sensor in Ambient PWS

* Correctly load entities

* Corrected what on and off means for existing sensor

* Make sure to return a boolean

* Member comments

* Binary sensor doesn't need state property
This commit is contained in:
Aaron Bach 2019-02-07 14:35:23 -07:00 committed by GitHub
parent d45f25ce2c
commit d24ccbd1e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 209 additions and 90 deletions

View file

@ -12,51 +12,85 @@ from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_call_later
from .config_flow import configured_instances
from .const import (
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE)
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE,
TYPE_BINARY_SENSOR, TYPE_SENSOR)
REQUIREMENTS = ['aioambient==0.1.0']
_LOGGER = logging.getLogger(__name__)
DEFAULT_SOCKET_MIN_RETRY = 15
TYPE_24HOURRAININ = '24hourrainin'
TYPE_BAROMABSIN = 'baromabsin'
TYPE_BAROMRELIN = 'baromrelin'
TYPE_BATTOUT = 'battout'
TYPE_CO2 = 'co2'
TYPE_DAILYRAININ = 'dailyrainin'
TYPE_DEWPOINT = 'dewPoint'
TYPE_EVENTRAININ = 'eventrainin'
TYPE_FEELSLIKE = 'feelsLike'
TYPE_HOURLYRAININ = 'hourlyrainin'
TYPE_HUMIDITY = 'humidity'
TYPE_HUMIDITYIN = 'humidityin'
TYPE_LASTRAIN = 'lastRain'
TYPE_MAXDAILYGUST = 'maxdailygust'
TYPE_MONTHLYRAININ = 'monthlyrainin'
TYPE_SOLARRADIATION = 'solarradiation'
TYPE_TEMPF = 'tempf'
TYPE_TEMPINF = 'tempinf'
TYPE_TOTALRAININ = 'totalrainin'
TYPE_UV = 'uv'
TYPE_WEEKLYRAININ = 'weeklyrainin'
TYPE_WINDDIR = 'winddir'
TYPE_WINDDIR_AVG10M = 'winddir_avg10m'
TYPE_WINDDIR_AVG2M = 'winddir_avg2m'
TYPE_WINDGUSTDIR = 'windgustdir'
TYPE_WINDGUSTMPH = 'windgustmph'
TYPE_WINDSPDMPH_AVG10M = 'windspdmph_avg10m'
TYPE_WINDSPDMPH_AVG2M = 'windspdmph_avg2m'
TYPE_WINDSPEEDMPH = 'windspeedmph'
TYPE_YEARLYRAININ = 'yearlyrainin'
SENSOR_TYPES = {
'24hourrainin': ('24 Hr Rain', 'in'),
'baromabsin': ('Abs Pressure', 'inHg'),
'baromrelin': ('Rel Pressure', 'inHg'),
'battout': ('Battery', ''),
'co2': ('co2', 'ppm'),
'dailyrainin': ('Daily Rain', 'in'),
'dewPoint': ('Dew Point', '°F'),
'eventrainin': ('Event Rain', 'in'),
'feelsLike': ('Feels Like', '°F'),
'hourlyrainin': ('Hourly Rain Rate', 'in/hr'),
'humidity': ('Humidity', '%'),
'humidityin': ('Humidity In', '%'),
'lastRain': ('Last Rain', ''),
'maxdailygust': ('Max Gust', 'mph'),
'monthlyrainin': ('Monthly Rain', 'in'),
'solarradiation': ('Solar Rad', 'W/m^2'),
'tempf': ('Temp', '°F'),
'tempinf': ('Inside Temp', '°F'),
'totalrainin': ('Lifetime Rain', 'in'),
'uv': ('uv', 'Index'),
'weeklyrainin': ('Weekly Rain', 'in'),
'winddir': ('Wind Dir', '°'),
'winddir_avg10m': ('Wind Dir Avg 10m', '°'),
'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'),
'windgustdir': ('Gust Dir', '°'),
'windgustmph': ('Wind Gust', 'mph'),
'windspdmph_avg10m': ('Wind Avg 10m', 'mph'),
'windspdmph_avg2m': ('Wind Avg 2m', 'mph'),
'windspeedmph': ('Wind Speed', 'mph'),
'yearlyrainin': ('Yearly Rain', 'in'),
TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None),
TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, None),
TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, None),
TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'),
TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None),
TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None),
TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, None),
TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None),
TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, None),
TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None),
TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, None),
TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, None),
TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, None),
TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None),
TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None),
TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None),
TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, None),
TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, None),
TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None),
TYPE_UV: ('uv', 'Index', TYPE_SENSOR, None),
TYPE_WEEKLYRAININ: ('Weekly Rain', 'in', TYPE_SENSOR, None),
TYPE_WINDDIR: ('Wind Dir', '°', TYPE_SENSOR, None),
TYPE_WINDDIR_AVG10M: ('Wind Dir Avg 10m', '°', TYPE_SENSOR, None),
TYPE_WINDDIR_AVG2M: ('Wind Dir Avg 2m', 'mph', TYPE_SENSOR, None),
TYPE_WINDGUSTDIR: ('Gust Dir', '°', TYPE_SENSOR, None),
TYPE_WINDGUSTMPH: ('Wind Gust', 'mph', TYPE_SENSOR, None),
TYPE_WINDSPDMPH_AVG10M: ('Wind Avg 10m', 'mph', TYPE_SENSOR, None),
TYPE_WINDSPDMPH_AVG2M: ('Wind Avg 2m', 'mph', TYPE_SENSOR, None),
TYPE_WINDSPEEDMPH: ('Wind Speed', 'mph', TYPE_SENSOR, None),
TYPE_YEARLYRAININ: ('Yearly Rain', 'in', TYPE_SENSOR, None),
}
CONFIG_SCHEMA = vol.Schema({
@ -102,8 +136,7 @@ async def async_setup_entry(hass, config_entry):
try:
ambient = AmbientStation(
hass,
config_entry,
hass, config_entry,
Client(
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY], session),
@ -126,8 +159,9 @@ async def async_unload_entry(hass, config_entry):
ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
hass.async_create_task(ambient.ws_disconnect())
await hass.config_entries.async_forward_entry_unload(
config_entry, 'sensor')
for component in ('binary_sensor', 'sensor'):
await hass.config_entries.async_forward_entry_unload(
config_entry, component)
return True
@ -178,9 +212,10 @@ class AmbientStation:
ATTR_NAME: station['info']['name'],
}
for component in ('binary_sensor', 'sensor'):
self._hass.async_create_task(
self._hass.config_entries.async_forward_entry_setup(
self._config_entry, 'sensor'))
self._config_entry, component))
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
@ -194,8 +229,7 @@ class AmbientStation:
except WebsocketError as err:
_LOGGER.error("Error with the websocket connection: %s", err)
self._ws_reconnect_delay = min(
2 * self._ws_reconnect_delay, 480)
self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480)
async_call_later(
self._hass, self._ws_reconnect_delay, self.ws_connect)
@ -203,3 +237,49 @@ class AmbientStation:
async def ws_disconnect(self):
"""Disconnect from the websocket."""
await self.client.websocket.disconnect()
class AmbientWeatherEntity(Entity):
"""Define a base Ambient PWS entity."""
def __init__(
self, ambient, mac_address, station_name, sensor_type,
sensor_name):
"""Initialize the sensor."""
self._ambient = ambient
self._async_unsub_dispatcher_connect = None
self._mac_address = mac_address
self._sensor_name = sensor_name
self._sensor_type = sensor_type
self._state = None
self._station_name = station_name
@property
def name(self):
"""Return the name of the sensor."""
return '{0}_{1}'.format(self._station_name, self._sensor_name)
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def unique_id(self):
"""Return a unique, unchanging string that represents this sensor."""
return '{0}_{1}'.format(self._mac_address, self._sensor_name)
async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()

View file

@ -0,0 +1,71 @@
"""
Support for Ambient Weather Station binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ambient_station/
"""
import logging
from homeassistant.components.ambient_station import (
SENSOR_TYPES, TYPE_BATTOUT, AmbientWeatherEntity)
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import ATTR_NAME
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_BINARY_SENSOR
DEPENDENCIES = ['ambient_station']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up Ambient PWS binary sensors based on the old way."""
pass
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
binary_sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions:
name, _, kind, device_class = SENSOR_TYPES[condition]
if kind == TYPE_BINARY_SENSOR:
binary_sensor_list.append(
AmbientWeatherBinarySensor(
ambient, mac_address, station[ATTR_NAME], condition,
name, device_class))
async_add_entities(binary_sensor_list, True)
class AmbientWeatherBinarySensor(AmbientWeatherEntity, BinarySensorDevice):
"""Define an Ambient binary sensor."""
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
device_class):
"""Initialize the sensor."""
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name)
self._device_class = device_class
@property
def device_class(self):
"""Return the device class."""
return self._device_class
@property
def is_on(self):
"""Return the status of the sensor."""
if self._sensor_type == TYPE_BATTOUT:
return self._state == 0
return self._state == 1
async def async_update(self):
"""Fetch new state data for the entity."""
self._state = self._ambient.stations[
self._mac_address][ATTR_LAST_DATA].get(self._sensor_type)

View file

@ -8,3 +8,6 @@ CONF_APP_KEY = 'app_key'
DATA_CLIENT = 'data_client'
TOPIC_UPDATE = 'update'
TYPE_BINARY_SENSOR = 'binary_sensor'
TYPE_SENSOR = 'sensor'

View file

@ -1,18 +1,16 @@
"""
Support for Ambient Weather Station Service.
Support for Ambient Weather Station sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ambient_station/
"""
import logging
from homeassistant.components.ambient_station import SENSOR_TYPES
from homeassistant.helpers.entity import Entity
from homeassistant.components.ambient_station import (
SENSOR_TYPES, AmbientWeatherEntity)
from homeassistant.const import ATTR_NAME
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TYPE_SENSOR
DEPENDENCIES = ['ambient_station']
_LOGGER = logging.getLogger(__name__)
@ -20,52 +18,39 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up an Ambient PWS sensor based on existing config."""
"""Set up Ambient PWS sensors based on existing config."""
pass
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up an Ambient PWS sensor based on a config entry."""
"""Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
sensor_list = []
for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions:
name, unit = SENSOR_TYPES[condition]
sensor_list.append(
AmbientWeatherSensor(
ambient, mac_address, station[ATTR_NAME], condition, name,
unit))
name, unit, kind, _ = SENSOR_TYPES[condition]
if kind == TYPE_SENSOR:
sensor_list.append(
AmbientWeatherSensor(
ambient, mac_address, station[ATTR_NAME], condition,
name, unit))
async_add_entities(sensor_list, True)
class AmbientWeatherSensor(Entity):
class AmbientWeatherSensor(AmbientWeatherEntity):
"""Define an Ambient sensor."""
def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name,
unit):
"""Initialize the sensor."""
self._ambient = ambient
self._async_unsub_dispatcher_connect = None
self._mac_address = mac_address
self._sensor_name = sensor_name
self._sensor_type = sensor_type
self._state = None
self._station_name = station_name
super().__init__(
ambient, mac_address, station_name, sensor_type, sensor_name)
self._unit = unit
@property
def name(self):
"""Return the name of the sensor."""
return '{0}_{1}'.format(self._station_name, self._sensor_name)
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def state(self):
"""Return the state of the sensor."""
@ -76,26 +61,6 @@ class AmbientWeatherSensor(Entity):
"""Return the unit of measurement."""
return self._unit
@property
def unique_id(self):
"""Return a unique, unchanging string that represents this sensor."""
return '{0}_{1}'.format(self._mac_address, self._sensor_name)
async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()
async def async_update(self):
"""Fetch new state data for the sensor."""
self._state = self._ambient.stations[