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 ( from homeassistant.const import (
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS, ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv 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 homeassistant.helpers.event import async_call_later
from .config_flow import configured_instances from .config_flow import configured_instances
from .const import ( 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'] REQUIREMENTS = ['aioambient==0.1.0']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_SOCKET_MIN_RETRY = 15 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 = { SENSOR_TYPES = {
'24hourrainin': ('24 Hr Rain', 'in'), TYPE_24HOURRAININ: ('24 Hr Rain', 'in', TYPE_SENSOR, None),
'baromabsin': ('Abs Pressure', 'inHg'), TYPE_BAROMABSIN: ('Abs Pressure', 'inHg', TYPE_SENSOR, None),
'baromrelin': ('Rel Pressure', 'inHg'), TYPE_BAROMRELIN: ('Rel Pressure', 'inHg', TYPE_SENSOR, None),
'battout': ('Battery', ''), TYPE_BATTOUT: ('Battery', None, TYPE_BINARY_SENSOR, 'battery'),
'co2': ('co2', 'ppm'), TYPE_CO2: ('co2', 'ppm', TYPE_SENSOR, None),
'dailyrainin': ('Daily Rain', 'in'), TYPE_DAILYRAININ: ('Daily Rain', 'in', TYPE_SENSOR, None),
'dewPoint': ('Dew Point', '°F'), TYPE_DEWPOINT: ('Dew Point', '°F', TYPE_SENSOR, None),
'eventrainin': ('Event Rain', 'in'), TYPE_EVENTRAININ: ('Event Rain', 'in', TYPE_SENSOR, None),
'feelsLike': ('Feels Like', '°F'), TYPE_FEELSLIKE: ('Feels Like', '°F', TYPE_SENSOR, None),
'hourlyrainin': ('Hourly Rain Rate', 'in/hr'), TYPE_HOURLYRAININ: ('Hourly Rain Rate', 'in/hr', TYPE_SENSOR, None),
'humidity': ('Humidity', '%'), TYPE_HUMIDITY: ('Humidity', '%', TYPE_SENSOR, None),
'humidityin': ('Humidity In', '%'), TYPE_HUMIDITYIN: ('Humidity In', '%', TYPE_SENSOR, None),
'lastRain': ('Last Rain', ''), TYPE_LASTRAIN: ('Last Rain', None, TYPE_SENSOR, None),
'maxdailygust': ('Max Gust', 'mph'), TYPE_MAXDAILYGUST: ('Max Gust', 'mph', TYPE_SENSOR, None),
'monthlyrainin': ('Monthly Rain', 'in'), TYPE_MONTHLYRAININ: ('Monthly Rain', 'in', TYPE_SENSOR, None),
'solarradiation': ('Solar Rad', 'W/m^2'), TYPE_SOLARRADIATION: ('Solar Rad', 'W/m^2', TYPE_SENSOR, None),
'tempf': ('Temp', '°F'), TYPE_TEMPF: ('Temp', '°F', TYPE_SENSOR, None),
'tempinf': ('Inside Temp', '°F'), TYPE_TEMPINF: ('Inside Temp', '°F', TYPE_SENSOR, None),
'totalrainin': ('Lifetime Rain', 'in'), TYPE_TOTALRAININ: ('Lifetime Rain', 'in', TYPE_SENSOR, None),
'uv': ('uv', 'Index'), TYPE_UV: ('uv', 'Index', TYPE_SENSOR, None),
'weeklyrainin': ('Weekly Rain', 'in'), TYPE_WEEKLYRAININ: ('Weekly Rain', 'in', TYPE_SENSOR, None),
'winddir': ('Wind Dir', '°'), TYPE_WINDDIR: ('Wind Dir', '°', TYPE_SENSOR, None),
'winddir_avg10m': ('Wind Dir Avg 10m', '°'), TYPE_WINDDIR_AVG10M: ('Wind Dir Avg 10m', '°', TYPE_SENSOR, None),
'winddir_avg2m': ('Wind Dir Avg 2m', 'mph'), TYPE_WINDDIR_AVG2M: ('Wind Dir Avg 2m', 'mph', TYPE_SENSOR, None),
'windgustdir': ('Gust Dir', '°'), TYPE_WINDGUSTDIR: ('Gust Dir', '°', TYPE_SENSOR, None),
'windgustmph': ('Wind Gust', 'mph'), TYPE_WINDGUSTMPH: ('Wind Gust', 'mph', TYPE_SENSOR, None),
'windspdmph_avg10m': ('Wind Avg 10m', 'mph'), TYPE_WINDSPDMPH_AVG10M: ('Wind Avg 10m', 'mph', TYPE_SENSOR, None),
'windspdmph_avg2m': ('Wind Avg 2m', 'mph'), TYPE_WINDSPDMPH_AVG2M: ('Wind Avg 2m', 'mph', TYPE_SENSOR, None),
'windspeedmph': ('Wind Speed', 'mph'), TYPE_WINDSPEEDMPH: ('Wind Speed', 'mph', TYPE_SENSOR, None),
'yearlyrainin': ('Yearly Rain', 'in'), TYPE_YEARLYRAININ: ('Yearly Rain', 'in', TYPE_SENSOR, None),
} }
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
@ -102,8 +136,7 @@ async def async_setup_entry(hass, config_entry):
try: try:
ambient = AmbientStation( ambient = AmbientStation(
hass, hass, config_entry,
config_entry,
Client( Client(
config_entry.data[CONF_API_KEY], config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY], session), 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) ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
hass.async_create_task(ambient.ws_disconnect()) hass.async_create_task(ambient.ws_disconnect())
await hass.config_entries.async_forward_entry_unload( for component in ('binary_sensor', 'sensor'):
config_entry, 'sensor') await hass.config_entries.async_forward_entry_unload(
config_entry, component)
return True return True
@ -178,9 +212,10 @@ class AmbientStation:
ATTR_NAME: station['info']['name'], ATTR_NAME: station['info']['name'],
} }
for component in ('binary_sensor', 'sensor'):
self._hass.async_create_task( self._hass.async_create_task(
self._hass.config_entries.async_forward_entry_setup( self._hass.config_entries.async_forward_entry_setup(
self._config_entry, 'sensor')) self._config_entry, component))
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
@ -194,8 +229,7 @@ class AmbientStation:
except WebsocketError as err: except WebsocketError as err:
_LOGGER.error("Error with the websocket connection: %s", err) _LOGGER.error("Error with the websocket connection: %s", err)
self._ws_reconnect_delay = min( self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480)
2 * self._ws_reconnect_delay, 480)
async_call_later( async_call_later(
self._hass, self._ws_reconnect_delay, self.ws_connect) self._hass, self._ws_reconnect_delay, self.ws_connect)
@ -203,3 +237,49 @@ class AmbientStation:
async def ws_disconnect(self): async def ws_disconnect(self):
"""Disconnect from the websocket.""" """Disconnect from the websocket."""
await self.client.websocket.disconnect() 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' DATA_CLIENT = 'data_client'
TOPIC_UPDATE = 'update' 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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ambient_station/ https://home-assistant.io/components/sensor.ambient_station/
""" """
import logging import logging
from homeassistant.components.ambient_station import SENSOR_TYPES from homeassistant.components.ambient_station import (
from homeassistant.helpers.entity import Entity SENSOR_TYPES, AmbientWeatherEntity)
from homeassistant.const import ATTR_NAME 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'] DEPENDENCIES = ['ambient_station']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -20,52 +18,39 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_platform( async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None): 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 pass
async def async_setup_entry(hass, entry, async_add_entities): 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] ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
sensor_list = [] sensor_list = []
for mac_address, station in ambient.stations.items(): for mac_address, station in ambient.stations.items():
for condition in ambient.monitored_conditions: for condition in ambient.monitored_conditions:
name, unit = SENSOR_TYPES[condition] name, unit, kind, _ = SENSOR_TYPES[condition]
sensor_list.append( if kind == TYPE_SENSOR:
AmbientWeatherSensor( sensor_list.append(
ambient, mac_address, station[ATTR_NAME], condition, name, AmbientWeatherSensor(
unit)) ambient, mac_address, station[ATTR_NAME], condition,
name, unit))
async_add_entities(sensor_list, True) async_add_entities(sensor_list, True)
class AmbientWeatherSensor(Entity): class AmbientWeatherSensor(AmbientWeatherEntity):
"""Define an Ambient sensor.""" """Define an Ambient sensor."""
def __init__( def __init__(
self, ambient, mac_address, station_name, sensor_type, sensor_name, self, ambient, mac_address, station_name, sensor_type, sensor_name,
unit): unit):
"""Initialize the sensor.""" """Initialize the sensor."""
self._ambient = ambient super().__init__(
self._async_unsub_dispatcher_connect = None ambient, mac_address, station_name, sensor_type, sensor_name)
self._mac_address = mac_address
self._sensor_name = sensor_name
self._sensor_type = sensor_type
self._state = None
self._station_name = station_name
self._unit = unit 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 @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
@ -76,26 +61,6 @@ class AmbientWeatherSensor(Entity):
"""Return the unit of measurement.""" """Return the unit of measurement."""
return self._unit 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): async def async_update(self):
"""Fetch new state data for the sensor.""" """Fetch new state data for the sensor."""
self._state = self._ambient.stations[ self._state = self._ambient.stations[