diff --git a/.gitignore b/.gitignore index 970c0df..270f44b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ !*.skel !alexa_*.sh !/config/ +!/custom_components/ +!/packages/ !/scripts/ !/tileboard/ !/www/ @@ -17,6 +19,7 @@ # "Force" Disallow .storage/ android/ +/custom_components/fcm-android/ ssh-key/ ip_bans.yaml diff --git a/config/views/landroid.yaml b/config/views/landroid.yaml new file mode 100644 index 0000000..e152ed2 --- /dev/null +++ b/config/views/landroid.yaml @@ -0,0 +1,302 @@ +title: Mäheroboter +path: mower +badges: [] +icon: 'mdi:robot-vacuum-variant' +cards: + - type: vertical-stack + cards: + - elements: + - entity: sensor.landroid_wifi + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + left: 0% + opacity: 1 + top: 0% + transform: none + title: WLAN Qualität + type: state-icon + - entity: sensor.landroid_wifi + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + left: 9% + opacity: 1 + top: 1% + transform: none + title: WLAN Qualität + type: state-label + - entity: sensor.landroid_bat + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + opacity: 1 + right: 0% + top: 0% + transform: none + title: Akku Status + type: state-icon + - entity: sensor.landroid_hans_dieter_battery + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + opacity: 1 + right: 8% + top: 1% + transform: none + title: Akkuladung + type: state-label + - entity: sensor.landroid_hans_dieter_status + style: + color: 'rgb(3, 169, 244)' + font-size: 150% + font-weight: bold + left: 0% + opacity: 1 + top: 7% + transform: none + title: Status + type: state-label + - entity: sensor.landroid_hans_dieter_error + style: + color: 'rgb(3, 169, 244)' + font-size: 150% + font-weight: bold + left: 0% + opacity: 1 + top: 13% + transform: none + title: Status + type: state-label + - entity: sensor.landroid_pitch + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + opacity: 1 + right: 0% + top: 9% + transform: none + title: Nicken/Тангаж + type: state-icon + - entity: sensor.landroid_pitch + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + opacity: 1 + right: 8% + top: 10% + transform: none + title: Nicken/Тангаж + type: state-label + - entity: sensor.landroid_roll + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + opacity: 1 + right: 0% + top: 18% + transform: none + title: Rollen/Крен + type: state-icon + - entity: sensor.landroid_roll + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + opacity: 1 + right: 8% + top: 19% + transform: none + title: Rollen/Крен + type: state-label + - entity: sensor.landroid_yaw + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + opacity: 1 + right: 0% + top: 26% + transform: none + title: Gieren/Рысканье + type: state-icon + - entity: sensor.landroid_yaw + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + opacity: 1 + right: 8% + top: 27% + transform: none + title: Gieren/Рысканье + type: state-label + - entity: sensor.landroid_dist_km + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + left: 31% + opacity: 1 + top: 26% + transform: none + title: Gesamte Distanz + type: state-icon + - entity: sensor.landroid_dist_km + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + left: 40% + opacity: 1 + top: 27% + transform: none + title: Gesamte Distanz + type: state-label + - entity: sensor.landroid_totaltime + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + left: 31% + opacity: 1 + top: 41% + transform: none + title: Gesamte Arbeitszeit + type: state-icon + - entity: sensor.landroid_totaltime + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + left: 40% + opacity: 1 + top: 40% + transform: none + title: Gesamte Arbeitszeit + type: state-label + - entity: sensor.landroid_totaltime_h + style: + color: 'rgb(3, 169, 244)' + font-size: 10px + font-style: italic + font-weight: bold + left: 40% + opacity: 1 + top: 45% + transform: none + title: Gesamte Arbeitszeit + type: state-label + - entity: sensor.landroid_bladetime_h + style: + '--iron-icon-fill-color': 'rgb(3, 169, 244)' + left: 31% + opacity: 1 + top: 56% + transform: none + title: Klingenarbeitszeit + type: state-icon + - entity: sensor.landroid_bladetime + style: + color: 'rgb(3, 169, 244)' + font-weight: bold + left: 40% + opacity: 1 + top: 55% + transform: none + title: Klingenarbeitszeit + type: state-label + - entity: sensor.landroid_bladetime_h + style: + color: 'rgb(3, 169, 244)' + font-size: 10px + font-style: italic + font-weight: bold + left: 40% + opacity: 1 + top: 60% + transform: none + title: Klingenarbeitszeit + type: state-label + - entity: input_boolean.landroid_sched_settings + image: /local/mower/timer-off.png + state_filter: + 'off': brightness(100%) saturate(1) + 'on': brightness(90%) saturate(1) + state_image: + 'on': /local/mower/timer.png + style: + bottom: 4% + left: 45% + transform: 'translate(-280%, 25%) scale(0.7, 0.7)' + tap_action: + action: toggle + title: Mähplan + type: image + - entity: input_boolean.landroid_raindelay_settings + image: /local/mower/rainy-off.png + state_filter: + 'off': brightness(100%) saturate(1) + 'on': brightness(90%) saturate(1) + state_image: + 'on': /local/mower/rainy.png + style: + bottom: 4% + left: 45% + transform: 'translate(-280%, -115%) scale(0.7, 0.7)' + tap_action: + action: toggle + title: Regenverzögerung + type: image + - icon: 'mdi:play' + style: + '--iron-icon-fill-color': 'rgb(250, 250, 250)' + bottom: 4% + left: 45% + transform: 'translate(-260%, 0%) scale(1.6, 1.6)' + tap_action: + action: call-service + service: script.landroid_start + title: Mähen + type: icon + - icon: 'mdi:pause' + style: + '--iron-icon-fill-color': 'rgb(250, 250, 250)' + bottom: 4% + left: 45% + transform: 'translate(0%, 0%) scale(1.6, 1.6)' + tap_action: + action: call-service + service: script.landroid_pause + title: Pause + type: icon + - icon: 'mdi:home' + style: + '--iron-icon-fill-color': 'rgb(250, 250, 250)' + bottom: 4% + left: 45% + transform: 'translate(260%, 0%) scale(1.6, 1.6)' + tap_action: + action: call-service + service: script.landroid_stop + title: Stop & Nach Hause fahren + type: icon + - entity: input_boolean.landroid_info_toggle + image: /local/mower/info-off.png + state_filter: + 'off': brightness(100%) saturate(1) + 'on': brightness(90%) saturate(1) + state_image: + 'on': /local/mower/information.png + style: + bottom: 4% + left: 45% + transform: 'translate(240%, 25%) scale(0.7, 0.7)' + tap_action: + action: toggle + title: Information + type: image + image: /local/mower/landroid_flower.png + type: picture-elements + - card: + entities: + - entity: sensor.landroid_ip + - entity: sensor.landroid_sn + - entity: sensor.landroid_mac + - entity: sensor.landroid_lastupdate + - type: divider + - entity: automation.landroid_error_notification + - entity: automation.landroid_status_notification + show_header_toggle: false + title: Information + type: entities + conditions: + - entity: input_boolean.landroid_info_toggle + state: 'on' + type: conditional \ No newline at end of file diff --git a/configuration.yaml b/configuration.yaml index ff566d9..6c7f535 100644 --- a/configuration.yaml +++ b/configuration.yaml @@ -14,6 +14,8 @@ homeassistant: - ::1 - 192.168.0.0/16 - fd00::/8 + # landroid + packages: !include_dir_named packages config: conversation: diff --git a/custom_components/landroid_cloud/__init__.py b/custom_components/landroid_cloud/__init__.py new file mode 100644 index 0000000..1e0bac9 --- /dev/null +++ b/custom_components/landroid_cloud/__init__.py @@ -0,0 +1,247 @@ +"""Support for Worx Landroid Cloud based lawn mowers.""" +from datetime import timedelta +import json +import logging + +import voluptuous as vol + +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.dispatcher import dispatcher_send +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.util import slugify as util_slugify + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_VERIFY_SSL = True +DEFAULT_NAME = "landroid" +DOMAIN = "landroid_cloud" +LANDROID_API = "landroid_cloud_api" +SCAN_INTERVAL = timedelta(seconds=30) +FORCED_UPDATE = timedelta(minutes=30) +UPDATE_SIGNAL = "landroid_cloud_update_signal" + + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) + +SERVICE_START = "start" +SERVICE_PAUSE = "pause" +SERVICE_HOME = "home" +SERVICE_CONFIG = "config" + + +API_WORX_SENSORS = { + "battery": { + "state": { + "battery_percent": "state", + "battery_voltage": "battery_voltage", + "battery_temperature": "battery_temperature", + "battery_charge_cycles": "charge_cycles", + "battery_charging": "charging", + }, + "icon": "mdi:battery", + "unit": "%", + "device_class": None, + }, + "error": { + "state": {"error_description": "state", "error": "error_id"}, + "icon": None, + "unit": None, + "device_class": None, + }, + "status": { + "state": { + "id": "id", + "status_description": "state", + "blade_time": "blade_time", + "work_time": "work_time", + "distance": "distance", + "status": "status_id", + "updated": "last_update", + "rssi": "rssi", + "yaw": "yaw", + "roll": "roll", + "pitch": "pitch", + "gps_latitude": "latitude", + "gps_longitude": "longitude", + "rain_delay": "raindelay", + "schedule_variation": "timeextension", + "firmware": "firmware_version", + }, + "icon": None, + "unit": None, + "device_class": None, + }, +} + +client = [] + + +async def async_setup(hass, config): + """Set up the Worx Landroid Cloud component.""" + import pyworxcloud + + hass.data[LANDROID_API] = {} + dev = 0 + + for cloud in config[DOMAIN]: + cloud_email = cloud[CONF_EMAIL] + cloud_password = cloud[CONF_PASSWORD] + + master = pyworxcloud.WorxCloud() + auth = await master.initialize(cloud_email, cloud_password) + + if not auth: + _LOGGER.warning("Error in authentication!") + return False + + num_dev = await hass.async_add_executor_job(master.enumerate) + + for device in range(num_dev): + client.append(dev) + _LOGGER.debug("Connecting to device ID %s (%s)", device, cloud_email) + client[dev] = pyworxcloud.WorxCloud() + await client[dev].initialize(cloud_email, cloud_password) + await hass.async_add_executor_job(client[dev].connect, device, False) + + api = WorxLandroidAPI(hass, dev, client[dev], config) + await api.async_force_update() + async_track_time_interval(hass, api.async_update, SCAN_INTERVAL) + async_track_time_interval(hass, api.async_force_update, FORCED_UPDATE) + hass.data[LANDROID_API][dev] = api + dev += 1 + + async def handle_start(call): + """Handle start service call.""" + if "id" in call.data: + ID = call.data["id"] + + for cli in client: + attrs = vars(cli) + if attrs["id"] == ID: + cli.start() + else: + client[0].start() + + hass.services.async_register(DOMAIN, SERVICE_START, handle_start) + + async def handle_pause(call): + """Handle pause service call.""" + if "id" in call.data: + ID = call.data["id"] + + for cli in client: + attrs = vars(cli) + if attrs["id"] == ID: + cli.pause() + else: + client[0].pause() + + hass.services.async_register(DOMAIN, SERVICE_PAUSE, handle_pause) + + async def handle_home(call): + """Handle pause service call.""" + if "id" in call.data: + ID = call.data["id"] + + for cli in client: + attrs = vars(cli) + if attrs["id"] == ID: + cli.stop() + else: + client[0].stop() + + hass.services.async_register(DOMAIN, SERVICE_HOME, handle_home) + + async def handle_config(call): + """Handle config service call.""" + id = 0 + sendData = False + tmpdata = {} + + if "id" in call.data: + _LOGGER.debug("Data from Home Assistant: %s", call.data["id"]) + + for cli in client: + attrs = vars(cli) + if (attrs["id"] == call.data["id"]): + break + else: + id += 1 + + if "raindelay" in call.data: + tmpdata["rd"] = call.data["raindelay"] + _LOGGER.debug("Setting rain_delay for %s to %s", client[id].name, call.data["raindelay"]) + sendData = True + + if "timeextension" in call.data: + tmpdata["sc"] = {} + tmpdata["sc"]["p"] = call.data["timeextension"] + data = json.dumps(tmpdata) + _LOGGER.debug("Setting time_extension for %s to %s", client[id].name, call.data["timeextension"]) + sendData = True + + if sendData: + data = json.dumps(tmpdata) + _LOGGER.debug("Sending: %s", data) + client[id].sendData(data) + + hass.services.async_register(DOMAIN, SERVICE_CONFIG, handle_config) + + return True + + +class WorxLandroidAPI: + """Handle the API calls.""" + + def __init__(self, hass, device, client, config): + """Set up instance.""" + self._hass = hass + self._client = client + self._device = device + self.config = config + + sensor_info = [] + info = {} + info["name"] = util_slugify(f"{DEFAULT_NAME}_{self._client.name}") + info["friendly"] = self._client.name + info["id"] = self._device + sensor_info.append(info) + + load_platform(self._hass, "sensor", DOMAIN, sensor_info, self.config) + + def get_data(self, sensor_type): + """Get data from state cache.""" + methods = API_WORX_SENSORS[sensor_type] + data = {} + for prop, attr in methods["state"].items(): + if hasattr(self._client, prop): + prop_data = getattr(self._client, prop) + data[attr] = prop_data + return data + + async def async_update(self, now=None): + """Update the state cache from Landroid API.""" + #await self._hass.async_add_executor_job(self._client.getStatus) + dispatcher_send(self._hass, UPDATE_SIGNAL) + + async def async_force_update(self, now=None): + """Try forcing update.""" + _LOGGER.debug("Forcing update for %s", self._client.name) + await self._hass.async_add_executor_job(self._client.getStatus) diff --git a/custom_components/landroid_cloud/__pycache__/__init__.cpython-37.pyc b/custom_components/landroid_cloud/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..c08f1cd Binary files /dev/null and b/custom_components/landroid_cloud/__pycache__/__init__.cpython-37.pyc differ diff --git a/custom_components/landroid_cloud/__pycache__/sensor.cpython-37.pyc b/custom_components/landroid_cloud/__pycache__/sensor.cpython-37.pyc new file mode 100644 index 0000000..a58e162 Binary files /dev/null and b/custom_components/landroid_cloud/__pycache__/sensor.cpython-37.pyc differ diff --git a/custom_components/landroid_cloud/manifest.json b/custom_components/landroid_cloud/manifest.json new file mode 100644 index 0000000..779ed0b --- /dev/null +++ b/custom_components/landroid_cloud/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "landroid_cloud", + "name": "Worx Landroid Cloud", + "documentation": "https://www.home-assistant.io/integrations/landroid_cloud/", + "requirements": ["pyworxcloud==1.2.17"], + "codeowners": ["@MTrab"] +} diff --git a/custom_components/landroid_cloud/sensor.py b/custom_components/landroid_cloud/sensor.py new file mode 100644 index 0000000..3eeb6d5 --- /dev/null +++ b/custom_components/landroid_cloud/sensor.py @@ -0,0 +1,127 @@ +"""Support for monitoring Worx Landroid Sensors.""" +import async_timeout +import asyncio +import logging + +from homeassistant.components import sensor +from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity + +from . import API_WORX_SENSORS, LANDROID_API, UPDATE_SIGNAL + +_LOGGER = logging.getLogger(__name__) + +STATE_INITIALIZING = "Initializing" +STATE_OFFLINE = "Offline" + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the available sensors for Worx Landroid.""" + if discovery_info is None: + return + + entities = [] + + info = discovery_info[0] + for tSensor in API_WORX_SENSORS: + name = "{}_{}".format(info["name"].lower(), tSensor.lower()) + friendly_name = "{} {}".format(info["friendly"], tSensor) + dev_id = info["id"] + api = hass.data[LANDROID_API][dev_id] + sensor_type = tSensor + _LOGGER.debug("Init Landroid %s sensor for %s", sensor_type, info["friendly"]) + entity = LandroidSensor(api, name, sensor_type, friendly_name, dev_id) + entities.append(entity) + + async_add_entities(entities, True) + + +class LandroidSensor(Entity): + """Class to create and populate a Landroid Sensor.""" + + def __init__(self, api, name, sensor_type, friendly_name, dev_id): + """Init new sensor.""" + + self._api = api + self._attributes = {} + self._available = False + self._name = friendly_name + self._state = STATE_INITIALIZING + self._sensor_type = sensor_type + self._dev_id = dev_id + self.entity_id = sensor.ENTITY_ID_FORMAT.format(name) + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available + + @property + def device_state_attributes(self): + """Return sensor attributes.""" + return self._attributes + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return API_WORX_SENSORS[self._sensor_type]["unit"] + + @property + def icon(self): + """Icon to use in the frontend.""" + return API_WORX_SENSORS[self._sensor_type]["icon"] + + @property + def should_poll(self): + """Return False as entity is updated from the component.""" + return False + + @property + def state(self): + """Return sensor state.""" + return self._state + + @callback + def update_callback(self): + """Get new data and update state.""" + self.async_schedule_update_ha_state(True) + + async def async_added_to_hass(self): + """Connect update callbacks.""" + async_dispatcher_connect(self.hass, UPDATE_SIGNAL, self.update_callback) + + def _get_data(self): + """Return new data from the api cache.""" + data = self._api.get_data(self._sensor_type) + self._available = True + return data + + async def async_update(self): + """Update the sensor.""" + _LOGGER.debug("Updating %s", self.entity_id) + data = self._get_data() + if "state" in data: + _LOGGER.debug(data) + state = data.pop("state") + _LOGGER.debug("Mower %s State %s", self._name, state) + self._attributes.update(data) + self._state = state + else: + _LOGGER.debug("No data received for %s", self.entity_id) + reachable = self._api._client.online + if not reachable: + if "_battery" in self.entity_id: + self._state = "Unknown" + else: + self._state = STATE_OFFLINE + #else: + # attrs = vars(self._api._client) + # for item in attrs: + # _LOGGER.debug("%s : %s", item, attrs[item]) \ No newline at end of file diff --git a/custom_components/landroid_cloud/services.yaml b/custom_components/landroid_cloud/services.yaml new file mode 100644 index 0000000..2061315 --- /dev/null +++ b/custom_components/landroid_cloud/services.yaml @@ -0,0 +1,30 @@ +start: + description: Start mowing + fields: + id: + description: Landroid ID. Found as attribute on the Landroid status sensor + example: 123435 +pause: + description: Pause mowing + fields: + id: + description: Landroid ID. Found as attribute on the Landroid status sensor + example: 123435 +home: + description: Send Landroid home + fields: + id: + description: Landroid ID. Found as attribute on the Landroid status sensor + example: 123435 +config: + description: Set config parameters + fields: + id: + description: Landroid ID. Found as attribute on the Landroid status sensor + example: 123435 + raindelay: + description: Set rain delay. Time in minutes ranging from 0 to 300. 0 = Disabled + example: 30 + timeextension: + description: Set time extension. Extension in % ranging from -100 to 100 + example: -23 diff --git a/packages/landroid.yaml b/packages/landroid.yaml new file mode 100644 index 0000000..1a7b637 --- /dev/null +++ b/packages/landroid.yaml @@ -0,0 +1,355 @@ +# Worx Landroid (M500 WR141E) package +# https://github.com/Barma-lej/halandroid +# Based on Landroid Bridge @MTrab virtualzone +# https://github.com/MTrab/landroid_cloud + +# Cloud ############################################################# +landroid_cloud: + email: !secret landroid_mail + password: !secret landroid_pass + +# Switch ############################################################ +switch: + - platform: template + switches: + landroid_mowing: + value_template: "{{ is_state_attr('sensor.landroid_hans_dieter_status', 'status_id', 7) }}" + turn_on: + service: script.landroid_start + turn_off: + service: script.landroid_stop + +# Sensor ############################################################ +# sensor.landroid_hans_dieter_battery +# sensor.landroid_hans_dieter_error +# sensor.landroid_hans_dieter_status + +sensor: + - platform: template + sensors: + +# Info ############ + landroid_ip: + friendly_name: "IP Address" + value_template: !secret landroid_ip + icon_template: "mdi:ip-network" + + landroid_sn: + friendly_name: "Landroid SN" + value_template: !secret landroid_sn + icon_template: "mdi:numeric" + + landroid_mac: + friendly_name: "Landroid MAC" + value_template: !secret landroid_mac + icon_template: "mdi:barcode" + +# Battery ######### +# landroid_batvoltage: +# friendly_name: "Landroid BatVoltage" +# value_template: "{{ state_attr('sensor.landroid_hans_dieter_battery', 'battery_voltage') }}" +# icon_template: "mdi:battery-charging-100" +# unit_of_measurement: "V" + +# landroid_battemp: +# friendly_name: "Landroid BatTemp" +# value_template: "{{ state_attr('sensor.landroid_hans_dieter_battery', 'battery_temperature') }}" +# unit_of_measurement: "°C" +# device_class: "temperature" + +# landroid_batchargecycles: +# friendly_name: "Landroid batChargeCycles" +# value_template: "{{ state_attr('sensor.landroid_hans_dieter_battery', 'charge_cycles') }}" +# icon_template: "mdi:power-plug" + +# landroid_batcharging: +# friendly_name: "Landroid BatCharging" +# value_template: "{{ state_attr('sensor.landroid_hans_dieter_battery', 'charging') }}" + + landroid_bat: + friendly_name: "Battery level" + value_template: >- + {% if is_state_attr('sensor.landroid_hans_dieter_battery', 'charging', 1) -%}Lädt + {%- else -%}Entlädt + {%- endif %} + icon_template: >- + {% if is_state_attr('sensor.landroid_hans_dieter_battery', 'charging', 1) -%} + {%- if states('sensor.landroid_hans_dieter_battery')|float > 99 -%}mdi:battery-charging-100 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 89 -%}mdi:battery-charging-90 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 79 -%}mdi:battery-charging-80 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 69 -%}mdi:battery-charging-70 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 59 -%}mdi:battery-charging-60 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 49 -%}mdi:battery-charging-50 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 39 -%}mdi:battery-charging-40 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 29 -%}mdi:battery-charging-30 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 19 -%}mdi:battery-charging-20 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 9 -%}mdi:battery-charging-10 + {%- else -%}mdi:battery-charging-outline + {%- endif %} + {%- else -%} + {%- if states('sensor.landroid_hans_dieter_battery')|float > 99 -%}mdi:battery + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 89 -%}mdi:battery-90 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 79 -%}mdi:battery-80 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 69 -%}mdi:battery-70 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 59 -%}mdi:battery-60 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 49 -%}mdi:battery-50 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 39 -%}mdi:battery-40 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 29 -%}mdi:battery-30 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 19 -%}mdi:battery-20 + {%- elif states('sensor.landroid_hans_dieter_battery')|float > 9 -%}mdi:battery-10 + {%- else -%}mdi:battery-outline + {%- endif %} + {%- endif %} + + +# Status ########## + landroid_bladetime: + friendly_name: "Landroid BladeTime" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'blade_time') }}" + icon_template: "mdi:fan" + unit_of_measurement: "min" + + landroid_bladetime_h: + friendly_name: "Landroid BladeTime" + value_template: > + {% set t = states('sensor.landroid_bladetime') | int %} + {% if t == 0 %} + Unavailable + {% elif t > 60 %} + {{ t // 1440 }} Tg. {{ (t % 1440) // 60 }} Std. {{ t % 60 }} Min. + {% endif %} + icon_template: "mdi:fan" + unit_of_measurement: "" + + landroid_totaltime: + friendly_name: "Landroid TotalTime" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'work_time') }}" + icon_template: "mdi:bus-clock" + unit_of_measurement: "min" + + landroid_totaltime_h: + friendly_name: "Landroid TotalTime" + value_template: > + {% set t = states('sensor.landroid_totaltime') | int %} + {% if t == 0 %} + Unavailable + {% elif t > 60 %} + {{ t // 1440 }} Tg. {{ (t % 1440) // 60 }} Std. {{ t % 60 }} Min. + {% endif %} + icon_template: "mdi:bus-clock" + unit_of_measurement: "" + + landroid_dist: + friendly_name: "Landroid Dist" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'distance') }}" + icon_template: "mdi:map-marker-distance" + unit_of_measurement: "m" + + landroid_dist_km: + friendly_name: "Landroid Dist" + value_template: "{{ (states('sensor.landroid_dist') | float) / 1000 }}" + icon_template: "mdi:map-marker-distance" + unit_of_measurement: "km" + + landroid_lastupdate: + friendly_name: "Last update" + value_template: "{{ as_timestamp(strptime( state_attr('sensor.landroid_hans_dieter_status', 'last_update'), '%H:%M:%S %d/%m/%Y')) | timestamp_custom('%d.%m.%Y %H:%M:%S') }}" + icon_template: "mdi:clock" + +# -55 or higher: 4 bars +# -56 to -66: 3 bars +# -67 to -77: 2 bars +# -78 to -88: 1 bar +# -89 or lower: 0 bars + landroid_wifi: + friendly_name: "Wifi quality" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'rssi') }}" + icon_template: >- + {% if state_attr('sensor.landroid_hans_dieter_status', 'rssi')|float > -56 -%}mdi:wifi-strength-4 + {% elif state_attr('sensor.landroid_hans_dieter_status', 'rssi')|float > -67 -%}mdi:wifi-strength-3 + {% elif state_attr('sensor.landroid_hans_dieter_status', 'rssi')|float > -78 -%}mdi:wifi-strength-2 + {% elif state_attr('sensor.landroid_hans_dieter_status', 'rssi')|float > -89 -%}mdi:wifi-strength-1 + {%- else -%}mdi:wifi-strength-outline + {%- endif %} + unit_of_measurement: "dBm" + + landroid_yaw: + friendly_name: "Landroid Yaw" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'yaw') }}" + icon_template: "mdi:axis-z-rotate-clockwise" + unit_of_measurement: "°" + + landroid_roll: + friendly_name: "Landroid Roll" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'roll') }}" + icon_template: "mdi:axis-x-rotate-clockwise" + unit_of_measurement: "°" + + landroid_pitch: + friendly_name: "Landroid Pitch" + value_template: "{{ state_attr('sensor.landroid_hans_dieter_status', 'pitch') }}" + icon_template: "mdi:seat-flat-angled" + unit_of_measurement: "°" + +# Input Boolean ##################################################### +input_boolean: + landroid_raindelay_settings: + name: Raindelay Settings + icon: "mdi:file-hidden" + initial: false + landroid_timext_settings: + name: TimeExt Settings + icon: "mdi:file-hidden" + initial: false + landroid_sched_settings: + name: Scheduler Settings + icon: "mdi:file-hidden" + initial: false + landroid_info_toggle: + name: Info toggle + icon: "mdi:file-hidden" + initial: false + +# Automations ####################################################### +automation: + - id: "landroid_status_notify" + alias: "Landroid Status Notification" + initial_state: true + trigger: + - platform: state + entity_id: sensor.landroid_hans_dieter_status + condition: + - condition: template + value_template: "{{ trigger.to_state.state != trigger.from_state.state }}" + action: + - service: persistent_notification.create + data_template: + title: Lanroid Status + message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}" + + - id: "landroid_error_notify" + alias: "Landroid Error Notification" + initial_state: true + trigger: + - platform: state + entity_id: sensor.landroid_hans_dieter_error + condition: + - condition: template + value_template: "{{ trigger.to_state.state != trigger.from_state.state }}" + action: + - service: persistent_notification.create + data_template: + title: Lanroid Status + message: "{{ trigger.from_state.state }} -> {{ trigger.to_state.state }} - {{ states('sensor.date_time') }}" + +# Scripts ########################################################### +script: +# landroid_cloud.start +# landroid_cloud.stop +# landroid_cloud.pause + +# Starts the mower + landroid_start: + alias: "Start the mower" + sequence: + service: landroid_cloud.start + data: + id: 473913 + +# Stops the mower and sends it home + landroid_stop: + alias: "Stop the mower" + sequence: + service: landroid_cloud.stop + data: + id: 473913 + +# Pause the mower + landroid_pause: + alias: "Pause the mower" + sequence: + service: landroid_cloud.pause + data: + id: 473913 + +# Customize ######################################################### +homeassistant: + customize: +# Sensor ########## + sensor.landroid_hans_dieter_battery: + friendly_name: "Akkuladung" + sensor.landroid_hans_dieter_error: + friendly_name: "Fehler" + sensor.landroid_hans_dieter_status: + friendly_name: "Status" + + sensor.landroid_ip: + friendly_name: "IP-Adresse" + sensor.landroid_sn: + friendly_name: "Seriennummer" + sensor.landroid_mac: + friendly_name: "MAC-Adresse" + +# sensor.landroid_batvoltage: +# friendly_name: "Akkuspannung" +# sensor.landroid_battemp: +# friendly_name: "Akkutemperatur" +# sensor.landroid_batchargecycles: +# friendly_name: "Akkuladungen" +# sensor.landroid_batcharging: +# friendly_name: "Akkustatus" + sensor.landroid_bat: + friendly_name: "Akkuladung" + + sensor.landroid_bladetime: + friendly_name: "Klingenarbeitszeit" + sensor.landroid_bladetime_h: + friendly_name: "Klingenarbeitszeit" + sensor.landroid_totaltime: + friendly_name: "Arbeitszeit" + sensor.landroid_totaltime_h: + friendly_name: "Arbeitszeit" + sensor.landroid_dist: + friendly_name: "Ges. Distanz" + sensor.landroid_dist_km: + friendly_name: "Ges. Distanz" + sensor.landroid_lastupdate: + friendly_name: "Aktualisiert seit" + icon: "mdi:update" + sensor.landroid_wifi: + friendly_name: "WLAN Qualität" + sensor.landroid_pitch: + friendly_name: "Nicken/Тангаж" + sensor.landroid_roll: + friendly_name: "Rollen/Крен" + sensor.landroid_yaw: + friendly_name: "Gieren/Рысканье" + +# Input Boolean ### + input_boolean.landroid_raindelay_settings: + friendly_name: "Regenverzögerung" + input_boolean.landroid_timext_settings: + friendly_name: "Zeiterhöhung" + input_boolean.landroid_sched_settings: + friendly_name: "Mähplan" + input_boolean.landroid_info_toggle: + friendly_name: "Information" + +# Automation ###### + automation.landroid_status_notification: + friendly_name: "Landroid Status Benachrichtigung" + icon: "mdi:bell" + automation.landroid_error_notification: + friendly_name: "Landroid Fehler Benachrichtigung" + icon: "mdi:bell" + +# Scripts ######### + script.landroid_start: + friendly_name: "Mähen" + icon: "mdi:play" + script.landroid_pause: + friendly_name: "Mähen pausieren" + icon: "mdi:home" + script.landroid_stop: + friendly_name: "Mähen beenden" + icon: "mdi:home" diff --git a/ui-lovelace.yaml b/ui-lovelace.yaml index 67d9d3a..4412591 100644 --- a/ui-lovelace.yaml +++ b/ui-lovelace.yaml @@ -10,6 +10,7 @@ views: - !include config/views/spotify.yaml - !include config/views/instagram.yaml - !include config/views/devices.yaml + - !include config/views/landroid.yaml - !include config/views/cctv.yaml - !include config/views/alerts.yaml diff --git a/www/mower/clock-off.png b/www/mower/clock-off.png new file mode 100644 index 0000000..5e0a9fb Binary files /dev/null and b/www/mower/clock-off.png differ diff --git a/www/mower/clock-start.png b/www/mower/clock-start.png new file mode 100644 index 0000000..f918234 Binary files /dev/null and b/www/mower/clock-start.png differ diff --git a/www/mower/halandroid.png b/www/mower/halandroid.png new file mode 100644 index 0000000..d81db53 Binary files /dev/null and b/www/mower/halandroid.png differ diff --git a/www/mower/info-off.png b/www/mower/info-off.png new file mode 100644 index 0000000..4e7ddf8 Binary files /dev/null and b/www/mower/info-off.png differ diff --git a/www/mower/information.png b/www/mower/information.png new file mode 100644 index 0000000..65f3b6c Binary files /dev/null and b/www/mower/information.png differ diff --git a/www/mower/landroid_flower.png b/www/mower/landroid_flower.png new file mode 100644 index 0000000..9ace299 Binary files /dev/null and b/www/mower/landroid_flower.png differ diff --git a/www/mower/landroid_flower.webp b/www/mower/landroid_flower.webp new file mode 100644 index 0000000..2da9aec Binary files /dev/null and b/www/mower/landroid_flower.webp differ diff --git a/www/mower/rainy-off.png b/www/mower/rainy-off.png new file mode 100644 index 0000000..7d621fd Binary files /dev/null and b/www/mower/rainy-off.png differ diff --git a/www/mower/rainy.png b/www/mower/rainy.png new file mode 100644 index 0000000..d328016 Binary files /dev/null and b/www/mower/rainy.png differ diff --git a/www/mower/side_k_lader.jpg b/www/mower/side_k_lader.jpg new file mode 100644 index 0000000..5049c7e Binary files /dev/null and b/www/mower/side_k_lader.jpg differ diff --git a/www/mower/side_k_lader.webp b/www/mower/side_k_lader.webp new file mode 100644 index 0000000..a19e47c Binary files /dev/null and b/www/mower/side_k_lader.webp differ diff --git a/www/mower/side_m_lader.jpg b/www/mower/side_m_lader.jpg new file mode 100644 index 0000000..4a25732 Binary files /dev/null and b/www/mower/side_m_lader.jpg differ diff --git a/www/mower/timer-off.png b/www/mower/timer-off.png new file mode 100644 index 0000000..bd8aae5 Binary files /dev/null and b/www/mower/timer-off.png differ diff --git a/www/mower/timer.png b/www/mower/timer.png new file mode 100644 index 0000000..b6e57d5 Binary files /dev/null and b/www/mower/timer.png differ