hass-core/homeassistant/components/plant.py
dominikandreas 56c66a19f0 Update plant for dealing with float values ()
Value parsing in plant component throws an ValueError if values are given as floats. This commit changes int(value) to int(float(value)) to avoid this error.
2017-11-02 09:17:26 +01:00

247 lines
8.1 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_OK, STATE_PROBLEM, STATE_UNKNOWN, TEMP_CELSIUS, ATTR_TEMPERATURE,
CONF_SENSORS, ATTR_UNIT_OF_MEASUREMENT, ATTR_ICON)
from homeassistant.components import group
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'
DEPENDENCIES = ['zone', 'group']
GROUP_NAME_ALL_PLANTS = 'all plants'
ENTITY_ID_ALL_PLANTS = group.ENTITY_ID_FORMAT.format('all_plants')
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,
group_name=GROUP_NAME_ALL_PLANTS)
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(float(value))
elif reading == READING_BATTERY:
self._battery = int(float(value))
elif reading == READING_TEMPERATURE:
self._temperature = float(value)
elif reading == READING_CONDUCTIVITY:
self._conductivity = int(float(value))
elif reading == READING_BRIGHTNESS:
self._brightness = int(float(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 result:
self._state = STATE_PROBLEM
self._problems = ','.join(result)
else:
self._state = STATE_OK
self._icon = 'mdi:thumb-up'
self._problems = PROBLEM_NONE
_LOGGER.debug("New data processed")
self.async_schedule_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