diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 0991336f42a..c5ddd2734cb 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -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() diff --git a/homeassistant/components/ambient_station/binary_sensor.py b/homeassistant/components/ambient_station/binary_sensor.py new file mode 100644 index 00000000000..c9c0160cf7c --- /dev/null +++ b/homeassistant/components/ambient_station/binary_sensor.py @@ -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) diff --git a/homeassistant/components/ambient_station/const.py b/homeassistant/components/ambient_station/const.py index 75606a1c699..27ec7afefaa 100644 --- a/homeassistant/components/ambient_station/const.py +++ b/homeassistant/components/ambient_station/const.py @@ -8,3 +8,6 @@ CONF_APP_KEY = 'app_key' DATA_CLIENT = 'data_client' TOPIC_UPDATE = 'update' + +TYPE_BINARY_SENSOR = 'binary_sensor' +TYPE_SENSOR = 'sensor' diff --git a/homeassistant/components/ambient_station/sensor.py b/homeassistant/components/ambient_station/sensor.py index 9e0833e3441..2699975cfb5 100644 --- a/homeassistant/components/ambient_station/sensor.py +++ b/homeassistant/components/ambient_station/sensor.py @@ -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[