From 392588e51979ad8bc1a1f6f177354626500289cf Mon Sep 17 00:00:00 2001 From: nilzen Date: Mon, 18 Sep 2017 17:47:23 +0200 Subject: [PATCH] Worx Landroid sensor (#9416) * Worx Landroid sensor * Move component into sensor folder * Update .coveragerc * Remove incorrect file * Code cosmetics * Code cosmetics * Trailing whitespace * Add docstrings and update module name * Remove hyphen in component file name * Fix redefined-builtin and no-self-use * Update filename in .coveragerc * Fixed pvizelis requested changes * Update worxlandroid.py --- .coveragerc | 1 + .../components/sensor/worxlandroid.py | 165 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 homeassistant/components/sensor/worxlandroid.py diff --git a/.coveragerc b/.coveragerc index 239c155d7ac..60375fbb97e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -548,6 +548,7 @@ omit = homeassistant/components/sensor/vasttrafik.py homeassistant/components/sensor/waqi.py homeassistant/components/sensor/worldtidesinfo.py + homeassistant/components/sensor/worxlandroid.py homeassistant/components/sensor/xbox_live.py homeassistant/components/sensor/yweather.py homeassistant/components/sensor/zamg.py diff --git a/homeassistant/components/sensor/worxlandroid.py b/homeassistant/components/sensor/worxlandroid.py new file mode 100644 index 00000000000..324771c163c --- /dev/null +++ b/homeassistant/components/sensor/worxlandroid.py @@ -0,0 +1,165 @@ +""" +Support for Worx Landroid mower. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.worxlandroid/ +""" +import logging +import asyncio + +import aiohttp +import async_timeout + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv + +from homeassistant.helpers.entity import Entity +from homeassistant.components.switch import (PLATFORM_SCHEMA) +from homeassistant.const import (CONF_HOST, CONF_PIN, CONF_TIMEOUT) +from homeassistant.helpers.aiohttp_client import (async_get_clientsession) + +_LOGGER = logging.getLogger(__name__) + +CONF_ALLOW_UNREACHABLE = 'allow_unreachable' + +DEFAULT_TIMEOUT = 5 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PIN): + vol.All(vol.Coerce(int), vol.Range(min=1000, max=9999)), + vol.Optional(CONF_ALLOW_UNREACHABLE, default=True): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, +}) + +ERROR_STATE = [ + 'blade-blocked', + 'repositioning-error', + 'wire-bounced', + 'blade-blocked', + 'outside-wire', + 'mower-lifted', + 'alarm-6', + 'upside-down', + 'alarm-8', + 'collision-sensor-blocked', + 'mower-tilted', + 'charge-error', + 'battery-error' +] + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Set up the Worx Landroid sensors.""" + for typ in ('battery', 'state'): + async_add_entities([WorxLandroidSensor(typ, config)]) + + +class WorxLandroidSensor(Entity): + """Implementation of a Worx Landroid sensor.""" + + def __init__(self, sensor, config): + """Initialize a Worx Landroid sensor.""" + self._state = None + self.sensor = sensor + self.host = config.get(CONF_HOST) + self.pin = config.get(CONF_PIN) + self.timeout = config.get(CONF_TIMEOUT) + self.allow_unreachable = config.get(CONF_ALLOW_UNREACHABLE) + self.url = 'http://{}/jsondata.cgi'.format(self.host) + + @property + def name(self): + """Return the name of the sensor.""" + return 'worxlandroid-{}'.format(self.sensor) + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement of the sensor.""" + if self.sensor == 'battery': + return '%' + else: + return None + + @asyncio.coroutine + def async_update(self): + """Update the sensor data from the mower.""" + connection_error = False + + try: + session = async_get_clientsession(self.hass) + with async_timeout.timeout(self.timeout, loop=self.hass.loop): + auth = aiohttp.helpers.BasicAuth('admin', self.pin) + mower_response = yield from session.get(self.url, auth=auth) + except (asyncio.TimeoutError, aiohttp.ClientError): + if self.allow_unreachable is False: + _LOGGER.error("Error connecting to mower at %s", self.url) + + connection_error = True + + # connection error + if connection_error is True and self.allow_unreachable is False: + if self.sensor == 'error': + self._state = 'yes' + elif self.sensor == 'state': + self._state = 'connection-error' + + # connection success + elif connection_error is False: + # set the expected content type to be text/html + # since the mover incorrectly returns it... + data = yield from mower_response.json(content_type='text/html') + + # sensor battery + if self.sensor == 'battery': + self._state = data['perc_batt'] + + # sensor error + elif self.sensor == 'error': + self._state = 'no' if self.get_error(data) is None else 'yes' + + # sensor state + elif self.sensor == 'state': + self._state = self.get_state(data) + + else: + if self.sensor == 'error': + self._state = 'no' + + @staticmethod + def get_error(obj): + """Get the mower error.""" + for i, err in enumerate(obj['allarmi']): + if i != 2: # ignore wire bounce errors + if err == 1: + return ERROR_STATE[i] + + return None + + def get_state(self, obj): + """Get the state of the mower.""" + state = self.get_error(obj) + + if state is None: + state_obj = obj['settaggi'] + + if state_obj[14] == 1: + return 'manual-stop' + elif state_obj[5] == 1 and state_obj[13] == 0: + return 'charging' + elif state_obj[5] == 1 and state_obj[13] == 1: + return 'charging-complete' + elif state_obj[15] == 1: + return 'going-home' + else: + return 'mowing' + + return state