core/homeassistant/components/plant.py

242 lines
7.9 KiB
Python

"""
Component to monitor plants.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/plant/
"""
import logging
import asyncio
import voluptuous as vol
from homeassistant.const import (
STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_SENSORS,
ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'plant'
READING_BATTERY = 'battery'
READING_TEMPERATURE = ATTR_TEMPERATURE
READING_MOISTURE = 'moisture'
READING_CONDUCTIVITY = 'conductivity'
READING_BRIGHTNESS = 'brightness'
ATTR_PROBLEM = 'problem'
PROBLEM_NONE = 'none'
CONF_MIN_BATTERY_LEVEL = 'min_' + READING_BATTERY
CONF_MIN_TEMPERATURE = 'min_' + READING_TEMPERATURE
CONF_MAX_TEMPERATURE = 'max_' + READING_TEMPERATURE
CONF_MIN_MOISTURE = 'min_' + READING_MOISTURE
CONF_MAX_MOISTURE = 'max_' + READING_MOISTURE
CONF_MIN_CONDUCTIVITY = 'min_' + READING_CONDUCTIVITY
CONF_MAX_CONDUCTIVITY = 'max_' + READING_CONDUCTIVITY
CONF_MIN_BRIGHTNESS = 'min_' + READING_BRIGHTNESS
CONF_MAX_BRIGHTNESS = 'max_' + READING_BRIGHTNESS
CONF_SENSOR_BATTERY_LEVEL = READING_BATTERY
CONF_SENSOR_MOISTURE = READING_MOISTURE
CONF_SENSOR_CONDUCTIVITY = READING_CONDUCTIVITY
CONF_SENSOR_TEMPERATURE = READING_TEMPERATURE
CONF_SENSOR_BRIGHTNESS = READING_BRIGHTNESS
SCHEMA_SENSORS = vol.Schema({
vol.Optional(CONF_SENSOR_BATTERY_LEVEL): cv.entity_id,
vol.Optional(CONF_SENSOR_MOISTURE): cv.entity_id,
vol.Optional(CONF_SENSOR_CONDUCTIVITY): cv.entity_id,
vol.Optional(CONF_SENSOR_TEMPERATURE): cv.entity_id,
vol.Optional(CONF_SENSOR_BRIGHTNESS): cv.entity_id,
})
PLANT_SCHEMA = vol.Schema({
vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS),
vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int,
vol.Optional(CONF_MIN_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MIN_MOISTURE): cv.positive_int,
vol.Optional(CONF_MAX_MOISTURE): cv.positive_int,
vol.Optional(CONF_MIN_CONDUCTIVITY): cv.positive_int,
vol.Optional(CONF_MAX_CONDUCTIVITY): cv.positive_int,
vol.Optional(CONF_MIN_BRIGHTNESS): cv.positive_int,
vol.Optional(CONF_MAX_BRIGHTNESS): cv.positive_int,
})
DOMAIN = 'plant'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
cv.string: PLANT_SCHEMA
},
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the Plant component."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = []
for plant_name, plant_config in config[DOMAIN].items():
_LOGGER.info("Added plant %s", plant_name)
entity = Plant(plant_name, plant_config)
sensor_entity_ids = list(plant_config[CONF_SENSORS].values())
_LOGGER.debug("Subscribing to entity_ids %s", sensor_entity_ids)
async_track_state_change(hass, sensor_entity_ids, entity.state_changed)
entities.append(entity)
yield from component.async_add_entities(entities)
return True
class Plant(Entity):
"""Plant monitors the well-being of a plant.
It also checks the measurements against
configurable min and max values.
"""
READINGS = {
READING_BATTERY: {
ATTR_UNIT_OF_MEASUREMENT: '%',
'min': CONF_MIN_BATTERY_LEVEL,
'icon': 'mdi:battery-outline'
},
READING_TEMPERATURE: {
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
'min': CONF_MIN_TEMPERATURE,
'max': CONF_MAX_TEMPERATURE,
'icon': 'mdi:thermometer'
},
READING_MOISTURE: {
ATTR_UNIT_OF_MEASUREMENT: '%',
'min': CONF_MIN_MOISTURE,
'max': CONF_MAX_MOISTURE,
'icon': 'mdi:water'
},
READING_CONDUCTIVITY: {
ATTR_UNIT_OF_MEASUREMENT: 'µS/cm',
'min': CONF_MIN_CONDUCTIVITY,
'max': CONF_MAX_CONDUCTIVITY,
'icon': 'mdi:emoticon-poop'
},
READING_BRIGHTNESS: {
ATTR_UNIT_OF_MEASUREMENT: 'lux',
'min': CONF_MIN_BRIGHTNESS,
'max': CONF_MAX_BRIGHTNESS,
'icon': 'mdi:white-balance-sunny'
}
}
def __init__(self, name, config):
"""Initialize the Plant component."""
self._config = config
self._sensormap = dict()
for reading, entity_id in config['sensors'].items():
self._sensormap[entity_id] = reading
self._state = STATE_UNKNOWN
self._name = name
self._battery = None
self._moisture = None
self._conductivity = None
self._temperature = None
self._brightness = None
self._icon = 'mdi:help-circle'
self._problems = PROBLEM_NONE
@callback
def state_changed(self, entity_id, _, new_state):
"""Update the sensor status.
This callback is triggered, when the sensor state changes.
"""
value = new_state.state
_LOGGER.debug("Received callback from %s with value %s",
entity_id, value)
if value == STATE_UNKNOWN:
return
reading = self._sensormap[entity_id]
if reading == READING_MOISTURE:
self._moisture = int(value)
elif reading == READING_BATTERY:
self._battery = int(value)
elif reading == READING_TEMPERATURE:
self._temperature = float(value)
elif reading == READING_CONDUCTIVITY:
self._conductivity = int(value)
elif reading == READING_BRIGHTNESS:
self._brightness = int(value)
else:
raise _LOGGER.error("Unknown reading from sensor %s: %s",
entity_id, value)
self._update_state()
def _update_state(self):
"""Update the state of the class based sensor data."""
result = []
for sensor_name in self._sensormap.values():
params = self.READINGS[sensor_name]
value = getattr(self, '_{}'.format(sensor_name))
if value is not None:
if 'min' in params and params['min'] in self._config:
min_value = self._config[params['min']]
if value < min_value:
result.append('{} low'.format(sensor_name))
self._icon = params['icon']
if 'max' in params and params['max'] in self._config:
max_value = self._config[params['max']]
if value > max_value:
result.append('{} high'.format(sensor_name))
self._icon = params['icon']
if len(result) == 0:
self._state = 'ok'
self._icon = 'mdi:thumb-up'
self._problems = PROBLEM_NONE
else:
self._state = 'problem'
self._problems = ','.join(result)
_LOGGER.debug("New data processed")
self.hass.async_add_job(self.async_update_ha_state())
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the entity."""
return self._state
@property
def state_attributes(self):
"""Return the attributes of the entity.
Provide the individual measurements from the
sensor in the attributes of the device.
"""
attrib = {
ATTR_ICON: self._icon,
ATTR_PROBLEM: self._problems,
}
for reading in self._sensormap.values():
attrib[reading] = getattr(self, '_{}'.format(reading))
return attrib