diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bd4b18..9f9e644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 0.3 2019-08-01 +Tested with Hassio 0.96.5 and Bosch Indego 1000 + +### Breaking changes +Fixed a typo in service name. New name is "indego.mower_command" +Changed default names on sensors to make them shorter. + +### Changes +Uses pyIndego 0.1.6 +- Added getSerial in API +Added sensor for Runtime +Added properties on sensor **mower state** +- model +- serial +- firmware +Added properties on sensor **lawn mowed** +- Session Operation +- Session Mowing +- Session Charging +Added binary sensor **update available** +Added sensor "runtime total" with properties +- Total Operation +- Total Mowing +- Total Charging +Code refactoring and typos corrected + ## 0.2 2019-07-29 Tested with Hassio 0.96.5 diff --git a/README.md b/README.md index d6a27e8..56678a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Home Assistant Custom Component for Bosch Indego Lawn Mower. -![Entities in Home Asistant](/doc/Indego_Sensors.PNG) +![Entities in Home Asistant](/doc/1-Indego_Sensors.png) ## Installation @@ -13,7 +13,7 @@ Add the domain to your configuration.yaml ``` yaml #configuration.yaml indego: - name: MowerName + name: Indego username: !secret indego_username password: !secret indego_password id: !secret indego_id @@ -29,19 +29,28 @@ indego_id: 123456789 ## Usage ### Entities -There are four sensor entities: +There are six sensor entities: -|Sensor | Description| -|-------|------------| -|MowerName_mower_state | Current state of the mower| -|MowerName_lawn_mowed | Current percentage of the lawn that is mowed| -|MowerName_alerts | Number of alerts on the mower| -|MowerName_mowing_mode | The mowing mode set for the mower| +|Sensor | Description | +|------------------------|-------------------------------------------------| +|Indego mower state | Current state | +|Indego lawn mowed | Current percentage of the lawn that is mowed | +|Indego alerts | Number of alerts | +|Indego mowing mode | The mowing mode set | +|Indego update available | Check if there are any firmware update availabe | +|Indego runtime total | Sum the total runtime of the mover | +**mover state** has properties for model name, serial and firmware. +![Entities in Home Asistant](/doc/2-Indego_State_details.png) +**lawn moved** has properties for session total, mowing and charging time. +![Entities in Home Asistant](/doc/3-Indego_Sensors.png) + +**Indego runtime total** has properties for total, mowig and charging time. +![Entities in Home Asistant](/doc/4-Indego_Sensors.png) ### Service -There are a service exposed to HA called indego.mower_command. It sends a specified command to the mower. Accepted commands are: +There are a service exposed to HA called **indego.mower_command**. It sends a specified command to the mower. Accepted commands are: |Command |Description | |-------------|----------------------| @@ -50,7 +59,7 @@ There are a service exposed to HA called indego.mower_command. It sends a specif |returnToDock | Return mower to dock | Example creating automation in HA gui: -![Services](/doc/Indego_Call_service.PNG) +![Services](/doc/5-Indego_Call_service.png) Example for automations.yaml: @@ -65,7 +74,7 @@ Example for automations.yaml: action: - data: command: mow - service: indego.mover_command + service: indego.mower_command ``` ## Debugging diff --git a/doc/1-Indego_Sensors.png b/doc/1-Indego_Sensors.png new file mode 100644 index 0000000..81f5ccc Binary files /dev/null and b/doc/1-Indego_Sensors.png differ diff --git a/doc/2-Indego_State_details.png b/doc/2-Indego_State_details.png new file mode 100644 index 0000000..6d10198 Binary files /dev/null and b/doc/2-Indego_State_details.png differ diff --git a/doc/3-Indego_Lawn_mowed.png b/doc/3-Indego_Lawn_mowed.png new file mode 100644 index 0000000..3f836f3 Binary files /dev/null and b/doc/3-Indego_Lawn_mowed.png differ diff --git a/doc/4-Indego_Runtime_detail.png b/doc/4-Indego_Runtime_detail.png new file mode 100644 index 0000000..8033ee9 Binary files /dev/null and b/doc/4-Indego_Runtime_detail.png differ diff --git a/doc/5-Indego_Call_service.png b/doc/5-Indego_Call_service.png new file mode 100644 index 0000000..8abac3b Binary files /dev/null and b/doc/5-Indego_Call_service.png differ diff --git a/doc/Indego_Call_service.PNG b/doc/Indego_Call_service.PNG deleted file mode 100644 index a609b1d..0000000 Binary files a/doc/Indego_Call_service.PNG and /dev/null differ diff --git a/doc/Indego_Sensors.PNG b/doc/Indego_Sensors.PNG deleted file mode 100644 index b75ff3b..0000000 Binary files a/doc/Indego_Sensors.PNG and /dev/null differ diff --git a/indego/__init__.py b/indego/__init__.py index 84596a8..de9b060 100644 --- a/indego/__init__.py +++ b/indego/__init__.py @@ -8,31 +8,27 @@ import voluptuous as vol from aiohttp.hdrs import CONTENT_TYPE from datetime import timedelta -from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_ID, CONF_REGION, CONTENT_TYPE_JSON) +from homeassistant.const import (CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_ID, CONTENT_TYPE_JSON) from homeassistant.helpers import discovery from homeassistant.helpers.event import track_utc_time_change from homeassistant.util import Throttle from requests.auth import HTTPBasicAuth -#from pyIndego import * + +###### +# Working in 0.94, not working in 0.95 +# from pyIndego import * +###### _LOGGER = logging.getLogger(__name__) DOMAIN = 'indego' DATA_KEY = DOMAIN -CONF_HOST = 'api.indego.iot.bosch-si.com' -CONF_PORT = '443' -CONF_REGION = 'region' CONF_SEND_COMMAND = 'command' -ATTR_VIN = 'vin' -DEFAULT_NAME = 'Bosch Indego Mower' -DEFAULT_REGION = 'en' +DEFAULT_NAME = 'Indego' MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) -#INDEGO_COMPONENTS = ['sensor', 'binary_sensor', 'device_tracker', 'lock'] -INDEGO_COMPONENTS = ['sensor'] +INDEGO_COMPONENTS = ['sensor', 'binary_sensor'] UPDATE_INTERVAL = 5 # in minutes -SERVICE_UPDATE_STATE = 'update_state' DEFAULT_URL = 'https://api.indego.iot.bosch-si.com:443/api/v1/' - IndegoAPI_Instance = None CONFIG_SCHEMA = vol.Schema({ @@ -40,8 +36,7 @@ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_ID): cv.string, - vol.Required(CONF_REGION, default=DEFAULT_REGION): vol.Any('sv', 'en') + vol.Required(CONF_ID): cv.string }), }, extra=vol.ALLOW_EXTRA) @@ -55,24 +50,18 @@ def setup(hass, config: dict): """Set up the Indego components.""" global IndegoAPI_Instance, CONF_MOWER_NAME - host = CONF_HOST - _LOGGER.debug(f"Host = {host}") - port = CONF_PORT - _LOGGER.debug(f"Port = {port}") - mower_name = config[DOMAIN].get(CONF_NAME) + _LOGGER.debug(f"API URL = {DEFAULT_URL}") CONF_MOWER_NAME = config[DOMAIN].get(CONF_NAME) - _LOGGER.debug(f"Name = {mower_name}") + _LOGGER.debug(f"Name = {CONF_MOWER_NAME}") mower_username = config[DOMAIN].get(CONF_USERNAME) _LOGGER.debug(f"Username = {mower_username}") mower_password = config[DOMAIN].get(CONF_PASSWORD) _LOGGER.debug(f"Password = {mower_password}") mower_serial = config[DOMAIN].get(CONF_ID) _LOGGER.debug(f"ID = {mower_serial}") - url = "https://{}:{}/api/v1/".format(host, port) - _LOGGER.debug(f"Idego API Host = {url}") try: - _LOGGER.debug("Idego new pyIndego API") + _LOGGER.debug("Idego pyIndego API") IndegoAPI_Instance = IndegoAPI(username=mower_username, password=mower_password, serial=mower_serial) except (requests.exceptions.ConnectionError, @@ -83,20 +72,15 @@ def setup(hass, config: dict): for component in INDEGO_COMPONENTS: discovery.load_platform(hass, component, DOMAIN, {}, config) - ATTR_NAME = 'command' DEFAULT_NAME = None - #SERVICE_NAME = CONF_MOWER_NAME + 'mower_command' - SERVICE_NAME = 'mover_command' + SERVICE_NAME = 'mower_command' def send_command(call): """Handle the service call.""" - name = call.data.get(ATTR_NAME, DEFAULT_NAME) - #hass.states.set('hello_service.hello', name) + name = call.data.get(CONF_SEND_COMMAND, DEFAULT_NAME) _LOGGER.debug("Indego.send_command service called") _LOGGER.debug("Command: %s", name) IndegoAPI_Instance.putCommand(name) - hass.services.register(DOMAIN, SERVICE_NAME, send_command, schema=SERVICE_SCHEMA) - return True class IndegoAPI(): @@ -274,9 +258,10 @@ def getState(self): elif value == 1537: self._state = 'Stuck on lawn, help needed' elif value == 64513: - self._state = 'Waking up mover' + self._state = 'Sleeping' else: self._state = value + _LOGGER.debug(f"Value = {value}") return self._state def getMowed(self): @@ -327,6 +312,7 @@ def getAlertsDescription(self): return value def getNextPredicitiveCutting(self): + # Not working _LOGGER.debug("getNetPRedicitveCutting") complete_url = 'alms/' + self.serial + '/predictive/nextcutting?last=YYYY-MM-DDTHH:MM:SS%2BHH:MM' Runtime_temp = self.get(complete_url) @@ -381,6 +367,10 @@ def getModel(self): self._state = 'Undefined ' + value return self._state + def getSerial(self): + _LOGGER.debug("getSerial") + return self.serial + def getFirmware(self): _LOGGER.debug("getFirmware") complete_url = 'alms/' + self.serial @@ -403,11 +393,12 @@ def getPredicitiveCalendar(self): return value def getUserAdjustment(self): + # No idea what this does? _LOGGER.debug("getUserAdjustment") complete_url = 'alms/' + self.serial + '/predictive/useradjustment' Runtime_temp = self.get(complete_url) value = Runtime_temp - return value + return value['user_adjustment'] def getCalendar(self): _LOGGER.debug("getCalendar") @@ -438,14 +429,18 @@ def getUpdateAvailable(self): _LOGGER.debug("getUpdateAvailable") complete_url = 'alms/' + self.serial + '/updates' Runtime_temp = self.get(complete_url) - value = Runtime_temp + value = Runtime_temp['available'] + #if value == 'True': + # value_binary = 0 + #else: + # value_binary = 1 + #return value_binary return value def putCommand(self, command): _LOGGER.debug("postCommand: " + command) if command == "mow" or command == "pause" or command == "returnToDock": complete_url = "alms/" + self.serial + "/state" - #accepted commands = mow, pause, returnToDock temp = self.put(complete_url, command) return temp diff --git a/indego/binary_sensor.py b/indego/binary_sensor.py new file mode 100644 index 0000000..18bd3b6 --- /dev/null +++ b/indego/binary_sensor.py @@ -0,0 +1,58 @@ +from homeassistant.const import TEMP_CELSIUS +from homeassistant.helpers.entity import Entity +from . import IndegoAPI_Instance as API, CONF_MOWER_NAME, DOMAIN +import logging + +_LOGGER = logging.getLogger(__name__) + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the binary sensor platform.""" + _LOGGER.debug("Setup Binary Sensor Platform") + + update_available_sensor_name = CONF_MOWER_NAME + ' update available' + add_devices([IndegoUpdateAvailable(update_available_sensor_name)]) + + _LOGGER.debug("Finished Binary Sensor Platform setup!") + +class IndegoUpdateAvailable(Entity): + """Indego Update Available Sensor.""" + + def __init__(self, device_label): + """Initialize Update Avaliable sensor""" + self._state = None + self._device_label = device_label + + @property + def name(self): + """Return the name of the sensor.""" + return self._device_label + + @property + def state(self): + """Return the user adjustment.""" + return self._state + + @property + def is_on(self): + """Return if entity is on.""" + return self._state + + @property + def icon(self): + """Return the icon for the frontend based on the status.""" + tmp_icon = 'mdi:check-decagram' + #if self._state: + # if self._state = '1': + # tmp_icon = 'mdi:alert-decagram' + #else: + # tmp_icon = 'mdi:check-decagram' + return tmp_icon + + def update(self): + """Fetch firmware sensor.""" + _LOGGER.debug("Update Firmware Available Sensor") + tmp_mode = API.getUpdateAvailable() + _LOGGER.debug(f"Update available = {tmp_mode}") + self._state = tmp_mode + #self._state = 'True' + _LOGGER.debug("Finished update available sensor") \ No newline at end of file diff --git a/indego/manifest.json b/indego/manifest.json index d88e280..38eedcc 100644 --- a/indego/manifest.json +++ b/indego/manifest.json @@ -4,5 +4,5 @@ "documentation": "https://github.com/jm-73/Indego", "dependencies": [], "codeowners": ["@jm-73"], - "requirements": ["pyIndego==0.1.4"] + "requirements": ["pyIndego==0.1.6"] } diff --git a/indego/sensor.py b/indego/sensor.py index 9005cb2..3427d72 100644 --- a/indego/sensor.py +++ b/indego/sensor.py @@ -1,4 +1,4 @@ -from homeassistant.const import TEMP_CELSIUS +#from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity from . import IndegoAPI_Instance as API, CONF_MOWER_NAME, DOMAIN import logging @@ -13,20 +13,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the sensor platform.""" - _LOGGER.debug("Setup Sensor Platform") + _LOGGER.debug("Setup Sensor Platform with all sensors") - mower_state_sensor_name = CONF_MOWER_NAME + '_mower_state' + mower_state_sensor_name = CONF_MOWER_NAME + ' mower state' add_devices([IndegoStateSensor(mower_state_sensor_name)]) - lawn_mowed_sensor_name = CONF_MOWER_NAME + '_lawn_mowed' + lawn_mowed_sensor_name = CONF_MOWER_NAME + ' lawn mowed' add_devices([IndegoLawnMowedSensor(lawn_mowed_sensor_name)]) - mower_alert_sensor_name = CONF_MOWER_NAME + '_mower_alert' + mower_alert_sensor_name = CONF_MOWER_NAME + ' mower alert' add_devices([IndegoAlertSensor(mower_alert_sensor_name)]) - mowing_mode_sensor_name = CONF_MOWER_NAME + '_mowing_mode' + mowing_mode_sensor_name = CONF_MOWER_NAME + ' mowing mode' add_devices([IndegoMowingMode(mowing_mode_sensor_name)]) + runtime_total_sensor_name = CONF_MOWER_NAME + ' runtime total' + add_devices([IndegoRuntimeTotal(runtime_total_sensor_name)]) + _LOGGER.debug("Finished Sensor Platform setup!") class IndegoStateSensor(Entity): @@ -35,6 +38,9 @@ class IndegoStateSensor(Entity): def __init__(self, device_label): """Initialize state sensor""" self._state = None + self._model = None + self._serial = None + self._firmware = None self._device_label = device_label @property @@ -47,6 +53,21 @@ def state(self): """Return the state of the sensor.""" return self._state + @property + def model(self): + """Return the state of the sensor.""" + return self._model + + @property + def serial(self): + """Return the state of the sensor.""" + return self._serial + + @property + def firmware(self): + """Return the state of the sensor.""" + return self._firmware + @property def icon(self): """Return the icon for the frontend based on the status.""" @@ -58,15 +79,30 @@ def update(self): """ _LOGGER.debug("Update Sensor") self._state = API.getState() + self._model = API.getModel() + self._serial = API.getSerial() + self._firmware = API.getFirmware() _LOGGER.debug("Finished Sensor update") + @property + def device_state_attributes(self): + """Return the classifier attributes.""" + return { + 'Model': self._model, + 'Serial': self._serial, + 'Firmware': self._firmware + } + class IndegoLawnMowedSensor(Entity): """Indego Lawn Mowed Sensor.""" def __init__(self, device_label): """Initialize state sensor""" - self._state = None - self._device_label = device_label + self._state = None + self._session_operation = None + self._session_cut = None + self._session_charge = None + self._device_label = device_label @property def name(self): @@ -88,12 +124,41 @@ def state(self): """Return the state of the sensor.""" return self._state + @property + def session_operation(self): + """Return the session cut time.""" + return self._session_operation + + @property + def session_cut(self): + """Return the session cut time.""" + return self._session_cut + + @property + def session_charge(self): + """Return the session cut time.""" + return self._session_charge + def update(self): """Fetch new state data for the sensor.""" _LOGGER.debug("Update Lawn mowed") self._state = API.getMowed() + tmp_session = API.getRuntimeSession() + session_cut = tmp_session['operate']-tmp_session['charge'] + self._session_operation = round(tmp_session['operate']/60) + self._session_cut = round(session_cut/60) + self._session_charge = round(tmp_session['charge']/60) _LOGGER.debug("Finished Lawn mowed update") + @property + def device_state_attributes(self): + """Return the classifier attributes.""" + return { + 'Last session Operation': self._session_operation, + 'Last session Cut': self._session_cut, + 'Last session Charge': self._session_charge + } + class IndegoAlertSensor(Entity): """Indego Alert Sensor.""" @@ -160,4 +225,79 @@ def update(self): tmp_mode = API.getMowingMode() _LOGGER.debug(f"Mowing Mode State = {tmp_mode}") self._state = MOWING_MODE.get(tmp_mode) - _LOGGER.debug("Finished update mowing mode") \ No newline at end of file + _LOGGER.debug("Finished update mowing mode") + +class IndegoRuntimeTotal(Entity): + """Indego Runtime Sensor.""" + + def __init__(self, device_label): + """Initialize runtime sensor""" + self._state = None + self._total = None + self._mowing = None + self._charging = None + self._device_label = device_label + + @property + def name(self): + """Return the name of the sensor.""" + return self._device_label + + @property + def state(self): + """Return the runtime.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return 'h' + + @property + def total(self): + """Return the runtime.""" + return self._total + + @property + def mowing(self): + """Return the runtime.""" + return self._mowing + + @property + def charging(self): + """Return the runtime.""" + return self._charging + + @property + def state(self): + """Return the runtime.""" + return self._state + + @property + def icon(self): + """Return the icon.""" + tmp_icon = 'mdi:information-outline' + return tmp_icon + + def update(self): + """Fetch runtime sensor.""" + _LOGGER.debug("Update Model Sensor") + tmp = API.getRuntimeTotal() + _LOGGER.debug(f"Runtime = {tmp}") + #self._total = API.getRuntimeTotal() + self._total = round(tmp['operate']/60) + tmp_mowing = tmp['operate'] - tmp['charge'] + self._mowing = round(tmp_mowing/60) + self._charging = round(tmp['charge']/60) + self._state = round(tmp['operate']/60) + _LOGGER.debug("Finished update runtime sensor") + + @property + def device_state_attributes(self): + """Return the classifier attributes.""" + return { + 'Total operation time': self._total, + 'Total mowing time': self._mowing, + 'Total charging time': self._charging + } +