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.
247 lines
8.1 KiB
Python
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
|