diff --git a/.coveragerc b/.coveragerc index e13e9fe0002..fae3ebebbe7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -28,6 +28,9 @@ omit = homeassistant/components/apple_tv.py homeassistant/components/*/apple_tv.py + homeassistant/components/aqualogic.py + homeassistant/components/*/aqualogic.py + homeassistant/components/arduino.py homeassistant/components/*/arduino.py diff --git a/homeassistant/components/aqualogic.py b/homeassistant/components/aqualogic.py new file mode 100644 index 00000000000..abb61d42ca3 --- /dev/null +++ b/homeassistant/components/aqualogic.py @@ -0,0 +1,95 @@ +""" +Support for AquaLogic component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/aqualogic/ +""" +from datetime import timedelta +import logging +import time +import threading + +import voluptuous as vol + +from homeassistant.const import (CONF_HOST, CONF_PORT, + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import config_validation as cv + +REQUIREMENTS = ["aqualogic==1.0"] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = "aqualogic" +UPDATE_TOPIC = DOMAIN + "_update" +CONF_UNIT = "unit" +RECONNECT_INTERVAL = timedelta(seconds=10) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up AquaLogic platform.""" + host = config[DOMAIN][CONF_HOST] + port = config[DOMAIN][CONF_PORT] + processor = AquaLogicProcessor(hass, host, port) + hass.data[DOMAIN] = processor + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, + processor.start_listen) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + processor.shutdown) + _LOGGER.debug("AquaLogicProcessor %s:%i initialized", host, port) + return True + + +class AquaLogicProcessor(threading.Thread): + """AquaLogic event processor thread.""" + + def __init__(self, hass, host, port): + """Initialize the data object.""" + super().__init__(daemon=True) + self._hass = hass + self._host = host + self._port = port + self._shutdown = False + self._panel = None + + def start_listen(self, event): + """Start event-processing thread.""" + _LOGGER.debug("Event processing thread started") + self.start() + + def shutdown(self, event): + """Signal shutdown of processing event.""" + _LOGGER.debug("Event processing signaled exit") + self._shutdown = True + + def data_changed(self, panel): + """Aqualogic data changed callback.""" + self._hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC) + + def run(self): + """Event thread.""" + from aqualogic.core import AquaLogic + + while True: + self._panel = AquaLogic() + self._panel.connect(self._host, self._port) + self._panel.process(self.data_changed) + + if self._shutdown: + return + + _LOGGER.error("Connection to %s:%d lost", + self._host, self._port) + time.sleep(RECONNECT_INTERVAL.seconds) + + @property + def panel(self): + """Retrieve the AquaLogic object.""" + return self._panel diff --git a/homeassistant/components/sensor/aqualogic.py b/homeassistant/components/sensor/aqualogic.py new file mode 100644 index 00000000000..f10fd05b83f --- /dev/null +++ b/homeassistant/components/sensor/aqualogic.py @@ -0,0 +1,111 @@ +""" +Support for AquaLogic sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.aqualogic/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_MONITORED_CONDITIONS, + TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.core import callback +from homeassistant.helpers.entity import Entity +import homeassistant.components.aqualogic as aq +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['aqualogic'] + +TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT] +PERCENT_UNITS = ['%', '%'] +SALT_UNITS = ['g/L', 'PPM'] +WATT_UNITS = ['W', 'W'] +NO_UNITS = [None, None] + +# sensor_type [ description, unit, icon ] +# sensor_type corresponds to property names in aqualogic.core.AquaLogic +SENSOR_TYPES = { + 'air_temp': ['Air Temperature', TEMP_UNITS, 'mdi:thermometer'], + 'pool_temp': ['Pool Temperature', TEMP_UNITS, 'mdi:oil-temperature'], + 'spa_temp': ['Spa Temperature', TEMP_UNITS, 'mdi:oil-temperature'], + 'pool_chlorinator': ['Pool Chlorinator', PERCENT_UNITS, 'mdi:gauge'], + 'spa_chlorinator': ['Spa Chlorinator', PERCENT_UNITS, 'mdi:gauge'], + 'salt_level': ['Salt Level', SALT_UNITS, 'mdi:gauge'], + 'pump_speed': ['Pump Speed', PERCENT_UNITS, 'mdi:speedometer'], + 'pump_power': ['Pump Power', WATT_UNITS, 'mdi:gauge'], + 'status': ['Status', NO_UNITS, 'mdi:alert'] +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]) +}) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the sensor platform.""" + sensors = [] + + processor = hass.data[aq.DOMAIN] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + sensors.append(AquaLogicSensor(processor, sensor_type)) + + async_add_entities(sensors) + + +class AquaLogicSensor(Entity): + """Sensor implementation for the AquaLogic component.""" + + def __init__(self, processor, sensor_type): + """Initialize sensor.""" + self._processor = processor + self._type = sensor_type + self._state = None + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def name(self): + """Return the name of the sensor.""" + return "AquaLogic {}".format(SENSOR_TYPES[self._type][0]) + + @property + def unit_of_measurement(self): + """Return the unit of measurement the value is expressed in.""" + panel = self._processor.panel + if panel is None: + return None + if panel.is_metric: + return SENSOR_TYPES[self._type][1][0] + return SENSOR_TYPES[self._type][1][1] + + @property + def should_poll(self): + """Return the polling state.""" + return False + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return SENSOR_TYPES[self._type][2] + + async def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + aq.UPDATE_TOPIC, self.async_update_callback) + + @callback + def async_update_callback(self): + """Update callback.""" + panel = self._processor.panel + if panel is not None: + self._state = getattr(panel, self._type) + self.async_schedule_update_ha_state() diff --git a/homeassistant/components/switch/aqualogic.py b/homeassistant/components/switch/aqualogic.py new file mode 100644 index 00000000000..48c4702aca0 --- /dev/null +++ b/homeassistant/components/switch/aqualogic.py @@ -0,0 +1,114 @@ +""" +Support for AquaLogic switches. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.aqualogic/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.core import callback +import homeassistant.components.aqualogic as aq +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import (CONF_MONITORED_CONDITIONS) + +DEPENDENCIES = ['aqualogic'] + +_LOGGER = logging.getLogger(__name__) + +SWITCH_TYPES = { + 'lights': 'Lights', + 'filter': 'Filter', + 'filter_low_speed': 'Filter Low Speed', + 'aux_1': 'Aux 1', + 'aux_2': 'Aux 2', + 'aux_3': 'Aux 3', + 'aux_4': 'Aux 4', + 'aux_5': 'Aux 5', + 'aux_6': 'Aux 6', + 'aux_7': 'Aux 7', +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SWITCH_TYPES)): + vol.All(cv.ensure_list, [vol.In(SWITCH_TYPES)]), +}) + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the switch platform.""" + switches = [] + + processor = hass.data[aq.DOMAIN] + for switch_type in config.get(CONF_MONITORED_CONDITIONS): + switches.append(AquaLogicSwitch(processor, switch_type)) + + async_add_entities(switches) + + +class AquaLogicSwitch(SwitchDevice): + """Switch implementation for the AquaLogic component.""" + + def __init__(self, processor, switch_type): + """Initialize switch.""" + from aqualogic.core import States + self._processor = processor + self._type = switch_type + self._state_name = { + 'lights': States.LIGHTS, + 'filter': States.FILTER, + 'filter_low_speed': States.FILTER_LOW_SPEED, + 'aux_1': States.AUX_1, + 'aux_2': States.AUX_2, + 'aux_3': States.AUX_3, + 'aux_4': States.AUX_4, + 'aux_5': States.AUX_5, + 'aux_6': States.AUX_6, + 'aux_7': States.AUX_7 + }[switch_type] + + @property + def name(self): + """Return the name of the switch.""" + return "AquaLogic {}".format(SWITCH_TYPES[self._type]) + + @property + def should_poll(self): + """Return the polling state.""" + return False + + @property + def is_on(self): + """Return true if device is on.""" + panel = self._processor.panel + if panel is None: + return False + state = panel.get_state(self._state_name) + return state + + def turn_on(self, **kwargs): + """Turn the device on.""" + panel = self._processor.panel + if panel is None: + return + panel.set_state(self._state_name, True) + + def turn_off(self, **kwargs): + """Turn the device off.""" + panel = self._processor.panel + if panel is None: + return + panel.set_state(self._state_name, False) + + async def async_added_to_hass(self): + """Register callbacks.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + aq.UPDATE_TOPIC, self.async_update_callback) + + @callback + def async_update_callback(self): + """Update callback.""" + self.async_schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index b1d8f5dc4f3..0225e78ab9b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -139,6 +139,9 @@ apcaccess==0.0.13 # homeassistant.components.notify.apns apns2==0.3.0 +# homeassistant.components.aqualogic +aqualogic==1.0 + # homeassistant.components.asterisk_mbox asterisk_mbox==0.5.0