2019-04-03 15:40:03 +00:00
|
|
|
"""Support for collecting data from the ARWN project."""
|
2016-10-14 07:06:04 +00:00
|
|
|
import json
|
|
|
|
import logging
|
2016-12-16 06:12:33 +00:00
|
|
|
|
2018-07-18 09:54:27 +00:00
|
|
|
from homeassistant.components import mqtt
|
2020-04-21 17:45:53 +00:00
|
|
|
from homeassistant.const import DEGREE, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
2017-03-03 11:09:10 +00:00
|
|
|
from homeassistant.core import callback
|
2016-12-16 06:12:33 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2016-10-14 07:06:04 +00:00
|
|
|
from homeassistant.util import slugify
|
|
|
|
|
2016-12-16 06:12:33 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DOMAIN = "arwn"
|
2016-10-14 07:06:04 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
DATA_ARWN = "arwn"
|
|
|
|
TOPIC = "arwn/#"
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def discover_sensors(topic, payload):
|
2017-03-03 11:09:10 +00:00
|
|
|
"""Given a topic, dynamically create the right sensor type.
|
|
|
|
|
|
|
|
Async friendly.
|
|
|
|
"""
|
2019-07-31 19:25:30 +00:00
|
|
|
parts = topic.split("/")
|
|
|
|
unit = payload.get("units", "")
|
2016-10-14 07:06:04 +00:00
|
|
|
domain = parts[1]
|
2019-07-31 19:25:30 +00:00
|
|
|
if domain == "temperature":
|
2016-10-14 07:06:04 +00:00
|
|
|
name = parts[2]
|
2019-07-31 19:25:30 +00:00
|
|
|
if unit == "F":
|
2016-10-14 07:06:04 +00:00
|
|
|
unit = TEMP_FAHRENHEIT
|
|
|
|
else:
|
|
|
|
unit = TEMP_CELSIUS
|
2019-07-31 19:25:30 +00:00
|
|
|
return ArwnSensor(name, "temp", unit)
|
2017-07-11 21:09:05 +00:00
|
|
|
if domain == "moisture":
|
2020-04-04 23:32:58 +00:00
|
|
|
name = f"{parts[2]} Moisture"
|
2019-07-31 19:25:30 +00:00
|
|
|
return ArwnSensor(name, "moisture", unit, "mdi:water-percent")
|
2017-07-11 21:09:05 +00:00
|
|
|
if domain == "rain":
|
2017-10-09 13:41:18 +00:00
|
|
|
if len(parts) >= 3 and parts[2] == "today":
|
2019-07-31 19:25:30 +00:00
|
|
|
return ArwnSensor(
|
|
|
|
"Rain Since Midnight", "since_midnight", "in", "mdi:water"
|
|
|
|
)
|
2020-05-17 21:17:08 +00:00
|
|
|
return (
|
|
|
|
ArwnSensor("Total Rainfall", "total", unit, "mdi:water"),
|
|
|
|
ArwnSensor("Rainfall Rate", "rate", unit, "mdi:water"),
|
|
|
|
)
|
2019-07-31 19:25:30 +00:00
|
|
|
if domain == "barometer":
|
|
|
|
return ArwnSensor("Barometer", "pressure", unit, "mdi:thermometer-lines")
|
|
|
|
if domain == "wind":
|
|
|
|
return (
|
|
|
|
ArwnSensor("Wind Speed", "speed", unit, "mdi:speedometer"),
|
|
|
|
ArwnSensor("Wind Gust", "gust", unit, "mdi:speedometer"),
|
2020-04-21 17:45:53 +00:00
|
|
|
ArwnSensor("Wind Direction", "direction", DEGREE, "mdi:compass"),
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
def _slug(name):
|
2020-02-23 21:38:05 +00:00
|
|
|
return f"sensor.arwn_{slugify(name)}"
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
2016-10-14 07:06:04 +00:00
|
|
|
"""Set up the ARWN platform."""
|
2019-07-31 19:25:30 +00:00
|
|
|
|
2017-03-03 11:09:10 +00:00
|
|
|
@callback
|
2019-03-14 17:58:32 +00:00
|
|
|
def async_sensor_event_received(msg):
|
2016-10-14 07:06:04 +00:00
|
|
|
"""Process events as sensors.
|
|
|
|
|
|
|
|
When a new event on our topic (arwn/#) is received we map it
|
|
|
|
into a known kind of sensor based on topic name. If we've
|
|
|
|
never seen this before, we keep this sensor around in a global
|
|
|
|
cache. If we have seen it before, we update the values of the
|
|
|
|
existing sensor. Either way, we push an ha state update at the
|
|
|
|
end for the new event we've seen.
|
|
|
|
|
|
|
|
This lets us dynamically incorporate sensors without any
|
|
|
|
configuration on our side.
|
|
|
|
"""
|
2019-03-14 17:58:32 +00:00
|
|
|
event = json.loads(msg.payload)
|
|
|
|
sensors = discover_sensors(msg.topic, event)
|
2016-10-14 07:06:04 +00:00
|
|
|
if not sensors:
|
|
|
|
return
|
|
|
|
|
2017-03-03 11:09:10 +00:00
|
|
|
store = hass.data.get(DATA_ARWN)
|
|
|
|
if store is None:
|
|
|
|
store = hass.data[DATA_ARWN] = {}
|
|
|
|
|
2017-02-09 03:56:44 +00:00
|
|
|
if isinstance(sensors, ArwnSensor):
|
2019-07-31 19:25:30 +00:00
|
|
|
sensors = (sensors,)
|
2017-02-09 03:56:44 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if "timestamp" in event:
|
|
|
|
del event["timestamp"]
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
for sensor in sensors:
|
2017-03-03 11:09:10 +00:00
|
|
|
if sensor.name not in store:
|
2016-10-14 07:06:04 +00:00
|
|
|
sensor.hass = hass
|
|
|
|
sensor.set_event(event)
|
2017-03-03 11:09:10 +00:00
|
|
|
store[sensor.name] = sensor
|
2019-07-31 19:25:30 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Registering new sensor %(name)s => %(event)s",
|
|
|
|
dict(name=sensor.name, event=event),
|
|
|
|
)
|
2018-08-24 14:37:30 +00:00
|
|
|
async_add_entities((sensor,), True)
|
2016-10-14 07:06:04 +00:00
|
|
|
else:
|
2017-03-03 11:09:10 +00:00
|
|
|
store[sensor.name].set_event(event)
|
2016-10-14 07:06:04 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
await mqtt.async_subscribe(hass, TOPIC, async_sensor_event_received, 0)
|
2016-10-14 07:06:04 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class ArwnSensor(Entity):
|
2016-12-16 06:12:33 +00:00
|
|
|
"""Representation of an ARWN sensor."""
|
2016-10-14 07:06:04 +00:00
|
|
|
|
2017-02-09 03:56:44 +00:00
|
|
|
def __init__(self, name, state_key, units, icon=None):
|
2016-10-14 07:06:04 +00:00
|
|
|
"""Initialize the sensor."""
|
|
|
|
self.hass = None
|
|
|
|
self.entity_id = _slug(name)
|
|
|
|
self._name = name
|
|
|
|
self._state_key = state_key
|
|
|
|
self.event = {}
|
|
|
|
self._unit_of_measurement = units
|
2017-02-09 03:56:44 +00:00
|
|
|
self._icon = icon
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
def set_event(self, event):
|
|
|
|
"""Update the sensor with the most recent event."""
|
|
|
|
self.event = {}
|
|
|
|
self.event.update(event)
|
2020-04-01 21:19:51 +00:00
|
|
|
self.async_write_ha_state()
|
2016-10-14 07:06:04 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return the state of the device."""
|
|
|
|
return self.event.get(self._state_key, None)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Get the name of the sensor."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state_attributes(self):
|
|
|
|
"""Return all the state attributes."""
|
|
|
|
return self.event
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Return the unit of measurement the state is expressed in."""
|
2016-10-14 07:06:04 +00:00
|
|
|
return self._unit_of_measurement
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Return the polling state."""
|
2016-10-14 07:06:04 +00:00
|
|
|
return False
|
2017-02-09 03:56:44 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def icon(self):
|
2017-05-02 16:18:47 +00:00
|
|
|
"""Return the icon of device based on its type."""
|
2017-07-06 06:30:01 +00:00
|
|
|
return self._icon
|