[Worxlandroid] Initial contribution (#16893)

* Worxlandroid new binding

Signed-off-by: Gael L'hopital <gael@lhopital.org>
Co-authored-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
Gaël L'hopital
2026-04-21 12:47:18 +02:00
committed by GitHub
co-authored by Leo Siepel
parent 14aa846903
commit d21e43cd8d
69 changed files with 5487 additions and 0 deletions
+1
View File
@@ -454,6 +454,7 @@
/bundles/org.openhab.binding.wlanthermo/ @CSchlipp
/bundles/org.openhab.binding.wled/ @Skinah
/bundles/org.openhab.binding.wolfsmartset/ @BoBiene
/bundles/org.openhab.binding.worxlandroid/ @clinique
/bundles/org.openhab.binding.wundergroundupdatereceiver/ @danieldemus
/bundles/org.openhab.binding.x/ @computergeek1507
/bundles/org.openhab.binding.xmltv/ @clinique
+5
View File
@@ -2251,6 +2251,11 @@
<artifactId>org.openhab.binding.wolfsmartset</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.worxlandroid</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.wundergroundupdatereceiver</artifactId>
@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
@@ -0,0 +1,539 @@
# WorxLandroid Binding
This is the binding for Worx Landroid robotic lawn mowers.
It connects openHAB with your WorxLandroid mower using the API and MQTT.
This binding allows you to integrate, view and control supported Worx lawn mowers with openHAB.
## Supported Things
Currently following Things are supported:
- `bridge`: **Bridge Worx Landroid API** Thing representing the handler for Worx API
- `mower`: One or many Things for supported **Landroid Mower**'s
## Discovery
A Bridge is required to connect to the Worx API.
Here you can provide your credentials for your WorxLandroid account.
Once the Bridge has been added Worx Landroid mowers will be discovered automatically.
## Things Configuration
The following options can be set for the `bridge`:
| Property | Description |
|----------|------------------------------------------|
| username | Username to access the WorxLandroid API. |
| password | Password to access the WorxLandroid API. |
The following options can be set for the `mower`:
| Property | Description | Default | Advanced |
|-----------------------|----------------------------------------------------------------------------------------------|---------|----------|
| serialNumber | Serial number of the mower | - | No |
| refreshStatusInterval | Interval for refreshing mower status (ONLINE/OFFLINE) and channel 'common#online' in seconds | 3600 | Yes |
| pollingInterval | Interval for polling in seconds (min="30" max="7200"). | 1200 | Yes |
Default values for `refreshStatusInterval` and `pollingInterval` are the recommended settings in order to prevent a 24h ban from Worx.
Lower polling and refresh values will likely result in a 24h ban for your account.
## Channels
Currently following **Channels** are supported on the **Landroid Mower**:
### Common
| Channel | Type | ChannelName | Values |
|------------------|----------|-------------------------|-------------------|
| status | String | common#status | *1 (see below) |
| error | String | common#error | *2 (see below) |
| online | Switch | common#online | |
| online-timestamp | DateTime | common#online-timestamp | |
| action | String | common#action | START, STOP, HOME |
| enable | Switch | common#enable | |
| lock | Switch | common#lock | |
*1: Values for **error** Channel:
UNKNOWN, NO_ERR, TRAPPED, LIFTED, WIRE_MISSING, OUTSIDE_WIRE, RAINING, CLOSE_DOOR_TO_MOW, CLOSE_DOOR_TO_GO_HOME, BLADE_MOTOR_BLOCKED, WHEEL_MOTOR_BLOCKED, TRAPPED_TIMEOUT, UPSIDE_DOWN, BATTERY_LOW, REVERSE_WIRE, CHARGE_ERROR, TIMEOUT_FINDING_HOME, MOWER_LOCKED, BATTERY_OVER_TEMPERATURE, MOWER_OUTSIDE_WIRE
*2: Values for **status** Channel:
UNKNOWN, IDLE, HOME, START_SEQUENCE, LEAVING_HOME, FOLLOW_WIRE, SEARCHING_HOME, SEARCHING_WIRE, MOWING, LIFTED, TRAPPED, BLADE_BLOCKED, DEBUG, REMOTE_CONTROL, GOING_HOME, ZONE_TRAINING, BORDER_CUT, SEARCHING_ZONE, PAUSE, MANUAL_STOP
### Config
| Channel | Type | ChannelName |
|-----------|----------|------------------|
| timestamp | DateTime | config#timestamp |
| command | Number | config#command |
### Multi-Zones
If Multi Zones are supported, you are able to define 4 separate zones and split working times by 10 to those.
To ease zone configuration, you are able to set distance in meters where a specific zone starts. Bearing in mind that you roughly shall know how many meters of cable have been used (without buffer).
As second step you are able to set time in percent and split in parts of 10 between allocation zones.
| Channel | Type | ChannelName |
|--------------|---------------|--------------------------|
| enable | Switch | multi-zones#enable |
| last-zone | Number | multi-zones#last-zone |
| zone-1 | Number:Length | multi-zones#zone-1 |
| zone-2 | Number:Length | multi-zones#zone-2 |
| zone-3 | Number:Length | multi-zones#zone-3 |
| zone-4 | Number:Length | multi-zones#zone-4 |
| allocation-0 | Number | multi-zones#allocation-0 |
| allocation-1 | Number | multi-zones#allocation-1 |
| allocation-2 | Number | multi-zones#allocation-2 |
| allocation-3 | Number | multi-zones#allocation-3 |
| allocation-4 | Number | multi-zones#allocation-4 |
| allocation-5 | Number | multi-zones#allocation-5 |
| allocation-6 | Number | multi-zones#allocation-6 |
| allocation-7 | Number | multi-zones#allocation-7 |
| allocation-8 | Number | multi-zones#allocation-8 |
| allocation-9 | Number | multi-zones#allocation-9 |
### Schedule
| Channel | Type | ChannelName | |
|----------------|----------|-------------------------|-------------------|
| mode | String | schedule#mode | ONLY IF SUPPORTED |
| time-extension | Number | schedule#time-extension | |
| next-start | DateTime | schedule#next-start | |
| next-stop | DateTime | schedule#next-stop | |
### Aws
| Channel | Type | ChannelName |
|-----------|--------|---------------|
| poll | Switch | aws#poll |
| connected | Switch | aws#connected |
### Sunday (Slot 1)
| Channel | Type | ChannelName |
|----------|-------------|-----------------|
| enable | Switch | sunday#enable |
| time | DateTime | sunday#time |
| duration | Number:Time | sunday#duration |
| edgecut | Switch | sunday#edgecut |
### Sunday2 (Slot 2, ONLY IF SUPPORTED)
| Channel | Type | ChannelName |
|----------|-------------|------------------|
| enable | Switch | sunday2#enable |
| time | DateTime | sunday2#time |
| duration | Number:Time | sunday2#duration |
| edgecut | Switch | sunday2#edgecut |
And so on for each day of the week along with the Slot 2 when supported.
### One-Time
| Channel | Type | ChannelName |
|----------|--------|-------------------|
| edgecut | Switch | one-time#edgecut |
| duration | Switch | one-time#duration |
### Battery
| Channel | Type | ChannelName |
|---------------------|--------------------------|-----------------------------|
| temperature | Number:Temperature | battery#temperature |
| voltage | Number:ElectricPotential | battery#voltage |
| level | Number | battery#level |
| charge-cycles | Number | battery#charge-cycles |
| charge-cycles-total | Number | battery#charge-cycles-total |
| charging | Switch | battery#charging |
### Orientation
| Channel | Type | ChannelName |
|---------|--------------|-------------------|
| pitch | Number:Angle | orientation#pitch |
| roll | Number:Angle | orientation#roll |
| yaw | Number:Angle | orientation#yaw |
### Metrics
| Channel | Type | ChannelName |
|------------------|---------------|--------------------------|
| blade-time | Number:Time | metrics#blade-time |
| blade-time-total | Number:Time | metrics#blade-time-total |
| distance | Number:Length | metrics#distance |
| total-time | Number:Time | metrics#total-time |
### Rain (if supported)
| Channel | Type | ChannelName |
|---------|-------------|--------------|
| state | Switch | rain#state |
| counter | Number:Time | rain#counter |
| delay | Number:Time | rain#delay |
### Wifi
| Channel | Type | ChannelName |
|--------------|--------------|-------------------|
| rssi | Number:Power | wifi#rssi |
| wifi-quality | Number | wifi#wifi-quality |
## Provided icon set
This binding has its own IconProvider and makes available the following list of icons
| Icon Name | Dynamic | Illustration |
|---------------------------|---------|-----------------------------------------|
| oh:worxlandroid:charging | Yes | ![Charging](doc/images/charging.svg) |
| oh:worxlandroid:counter | No | ![Counter](doc/images/counter.svg) |
| oh:worxlandroid:distance | Yes | ![Distance](doc/images/distance.svg) |
| oh:worxlandroid:lawnmower | No | ![Lawn Mower](doc/images/lawnmower.svg) |
| oh:worxlandroid:lock | Yes | ![Lock](doc/images/lock.svg) |
| oh:worxlandroid:rain | Yes | ![Rain](doc/images/rain.svg) |
| oh:worxlandroid:refresh | No | ![Refresh](doc/images/refresh.svg) |
| oh:worxlandroid:zones | Yes | ![Zones](doc/images/zones.svg) |
## Examples
### $OPENHAB_CONF/items/landroid.things
```java
Bridge worxlandroid:bridge:api "Worx Api" [ username="xxxxYYYxxxx", password="dldkssdjldj" ] {
Thing mower lanmower "Worx M600" [ serialNumber="sdmldksmdskmlsd" ]
}
```
### $OPENHAB_CONF/items/landroid.items
```java
String MyMower "MyMower [%s]"
String LandroidMowerCommonStatus "Status code" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#status"}
String LandroidMowerCommonError "Error code" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#error"}
Switch LandroidMowerCommonOnline "Online" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#online"}
DateTime LandroidMowerCommonOnlineTimestamp "Online status timestamp" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#online-timestamp"}
String LandroidMowerCommonAction "Action" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#action"}
Switch LandroidMowerCommonEnable "Mowing enabled" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#enable"}
Switch LandroidMowerCommonLock "Lock mower wifi" {channel="worxlandroid:mower:MyWorxBridge:mymower:common#lock"}
DateTime LandroidMowerConfigTimestamp "Last update" {channel="worxlandroid:mower:MyWorxBridge:mymower:config#timestamp"}
Number LandroidMowerConfigCommand "Command" {channel="worxlandroid:mower:MyWorxBridge:mymower:config#command"}
Switch LandroidMowerMultiZonesEnable "Multizone enabled" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#enable"}
Number LandroidMowerMultiZonesLastZone "Last zone" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#last-zone"}
Number:Length LandroidMowerMultiZonesZone1 "Meters zone 1" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#zone-1"}
Number:Length LandroidMowerMultiZonesZone2 "Meters zone 2" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#zone-2"}
Number:Length LandroidMowerMultiZonesZone3 "Meters zone 3" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#zone-3"}
Number:Length LandroidMowerMultiZonesZone4 "Meters zone 4" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#zone-4"}
Number LandroidMowerMultiZonesAllocation0 "Zone allocation 1" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-0"}
Number LandroidMowerMultiZonesAllocation1 "Zone allocation 2" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-1"}
Number LandroidMowerMultiZonesAllocation2 "Zone allocation 3" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-2"}
Number LandroidMowerMultiZonesAllocation3 "Zone allocation 4" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-3"}
Number LandroidMowerMultiZonesAllocation4 "Zone allocation 5" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-4"}
Number LandroidMowerMultiZonesAllocation5 "Zone allocation 6" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-5"}
Number LandroidMowerMultiZonesAllocation6 "Zone allocation 7" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-6"}
Number LandroidMowerMultiZonesAllocation7 "Zone allocation 8" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-7"}
Number LandroidMowerMultiZonesAllocation8 "Zone allocation 9" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-8"}
Number LandroidMowerMultiZonesAllocation9 "Zone allocation 10" {channel="worxlandroid:mower:MyWorxBridge:mymower:multi-zones#allocation-9"}
String LandroidMowerScheduleMode "Schedule mode" {channel="worxlandroid:mower:MyWorxBridge:mymower:schedule#mode"}
Number:Dimensionless LandroidMowerScheduleTimeExtension "Schedule time extension" {channel="worxlandroid:mower:MyWorxBridge:mymower:schedule#time-extension"}
DateTime LandroidMowerScheduleNextStart "Next start" {channel="worxlandroid:mower:MyWorxBridge:mymower:schedule#next-start"}
DateTime LandroidMowerScheduleNextStop "Next stop" {channel="worxlandroid:mower:MyWorxBridge:mymower:schedule#next-stop"}
Switch LandroidMowerAwsPoll "Poll AWS" {channel="worxlandroid:mower:MyWorxBridge:mymower:aws#poll"}
Switch LandroidMowerAwsConnected "AWS connected" {channel="worxlandroid:mower:MyWorxBridge:mymower:aws#connected"}
Switch LandroidMowerSundayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday#enable"}
DateTime LandroidMowerSundayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday#time"}
Number:Time LandroidMowerSundayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday#duration", unit="min"}
Switch LandroidMowerSundayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday#edgecut"}
Switch LandroidMowerSunday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday2#enable"}
DateTime LandroidMowerSunday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday2#time"}
Number:Time LandroidMowerSunday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday2#duration", unit="min"}
Switch LandroidMowerSunday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:sunday2#edgecut"}
Switch LandroidMowerMondayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday#enable"}
DateTime LandroidMowerMondayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday#time"}
Number:Time LandroidMowerMondayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday#duration", unit="min"}
Switch LandroidMowerMondayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday#edgecut"}
Switch LandroidMowerMonday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday2#enable"}
DateTime LandroidMowerMonday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday2#time"}
Number:Time LandroidMowerMonday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday2#duration", unit="min"}
Switch LandroidMowerMonday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:monday2#edgecut"}
Switch LandroidMowerTuesdayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday#enable"}
DateTime LandroidMowerTuesdayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday#time"}
Number:Time LandroidMowerTuesdayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday#duration", unit="min"}
Switch LandroidMowerTuesdayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday#edgecut"}
Switch LandroidMowerTuesday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday2#enable"}
DateTime LandroidMowerTuesday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday2#time"}
Number:Time LandroidMowerTuesday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday2#duration", unit="min"}
Switch LandroidMowerTuesday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:tuesday2#edgecut"}
Switch LandroidMowerWednesdayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday#enable"}
DateTime LandroidMowerWednesdayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday#time"}
Number:Time LandroidMowerWednesdayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday#duration", unit="min"}
Switch LandroidMowerWednesdayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday#edgecut"}
Switch LandroidMowerWednesday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday2#enable"}
DateTime LandroidMowerWednesday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday2#time"}
Number:Time LandroidMowerWednesday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday2#duration", unit="min"}
Switch LandroidMowerWednesday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:wednesday2#edgecut"}
Switch LandroidMowerThursdayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday#enable"}
DateTime LandroidMowerThursdayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday#time"}
Number:Time LandroidMowerThursdayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday#duration", unit="min"}
Switch LandroidMowerThursdayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday#edgecut"}
Switch LandroidMowerThursday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday2#enable"}
DateTime LandroidMowerThursday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday2#time"}
Number:Time LandroidMowerThursday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday2#duration", unit="min"}
Switch LandroidMowerThursday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:thursday2#edgecut"}
Switch LandroidMowerFridayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday#enable"}
DateTime LandroidMowerFridayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday#time"}
Number:Time LandroidMowerFridayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday#duration", unit="min"}
Switch LandroidMowerFridayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday#edgecut"}
Switch LandroidMowerFriday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday2#enable"}
DateTime LandroidMowerFriday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday2#time"}
Number:Time LandroidMowerFriday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday2#duration", unit="min"}
Switch LandroidMowerFriday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:friday2#edgecut"}
Switch LandroidMowerSaturdayEnable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday#enable"}
DateTime LandroidMowerSaturdayTime "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday#time"}
Number:Time LandroidMowerSaturdayDuration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday#duration", unit="min"}
Switch LandroidMowerSaturdayEdgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday#edgecut"}
Switch LandroidMowerSaturday2Enable "Active" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday2#enable"}
DateTime LandroidMowerSaturday2Time "Start time" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday2#time"}
Number:Time LandroidMowerSaturday2Duration "Duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday2#duration", unit="min"}
Switch LandroidMowerSaturday2Edgecut "Edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:saturday2#edgecut"}
Switch LandroidMowerOneTimeEdgecut "Schedule edgecut" {channel="worxlandroid:mower:MyWorxBridge:mymower:one-time#edgecut"}
Number:Time LandroidMowerOneTimeDuration "Edgecut duration" {channel="worxlandroid:mower:MyWorxBridge:mymower:one-time#duration", unit="min"}
Number:Temperature LandroidMowerBatteryTemperature "Battery temperature" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#temperature"}
Number:ElectricPotential LandroidMowerBatteryVoltage "Battery voltage" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#voltage"}
Number LandroidMowerBatteryLevel "Battery level" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#level"}
Number LandroidMowerBatteryChargeCycles "Current charge cycles" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#charge-cycles"}
Number LandroidMowerBatteryChargeCyclesTotal "Total charge cycles" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#charge-cycles-total"}
Switch LandroidMowerBatteryCharging "Battery charging" {channel="worxlandroid:mower:MyWorxBridge:mymower:battery#charging"}
Number:Angle LandroidMowerOrientationPitch "Pitch" {channel="worxlandroid:mower:MyWorxBridge:mymower:orientation#pitch"}
Number:Angle LandroidMowerOrientationRoll "Roll" {channel="worxlandroid:mower:MyWorxBridge:mymower:orientation#roll"}
Number:Angle LandroidMowerOrientationYaw "Yaw" {channel="worxlandroid:mower:MyWorxBridge:mymower:orientation#yaw"}
Number:Time LandroidMowerMetricsBladeTime "Current blade time" {channel="worxlandroid:mower:MyWorxBridge:mymower:metrics#blade-time", unit="h"}
Number:Time LandroidMowerMetricsBladeTimeTotal "Total blade time" {channel="worxlandroid:mower:MyWorxBridge:mymower:metrics#blade-time-total", unit="h"}
Number:Length LandroidMowerMetricsDistance "Total distance" {channel="worxlandroid:mower:MyWorxBridge:mymower:metrics#distance", unit="km"}
Number:Time LandroidMowerMetricsTotalTime "Total time" {channel="worxlandroid:mower:MyWorxBridge:mymower:metrics#total-time", unit="h"}
Switch LandroidMowerRainState "State" {channel="worxlandroid:mower:MyWorxBridge:mymower:rain#state"}
Number:Time LandroidMowerRainCounter "Counter" {channel="worxlandroid:mower:MyWorxBridge:mymower:rain#counter", unit="min"}
Number:Time LandroidMowerRainDelay "Delay" {channel="worxlandroid:mower:MyWorxBridge:mymower:rain#delay", unit="min"}
Number:Power LandroidMowerWifiRssi "Rssi" {channel="worxlandroid:mower:MyWorxBridge:mymower:wifi#rssi", unit="dBm"}
Number LandroidMowerWifiWifiQuality "Wifi quality" {channel="worxlandroid:mower:MyWorxBridge:mymower:wifi#wifi-quality"}
```
### $OPENHAB_CONF/sitemaps/landroid.sitemap
```perl
sitemap landroid label="Landroid"
{
Frame label="Worx Landroid Mower" {
Text label="Status" item=MyMower icon=none {
Default item=LandroidMowerCommonStatus
Default item=LandroidMowerCommonError
Default item=LandroidMowerCommonOnline
Default item=LandroidMowerCommonOnlineTimestamp
Default item=LandroidMowerConfigTimestamp
Default item=LandroidMowerConfigCommand
Default item=LandroidMowerAwsPoll
Text item=LandroidMowerAwsConnected label="AWS connected [%s]"
Default item=LandroidMowerOrientationPitch
Default item=LandroidMowerOrientationRoll
Default item=LandroidMowerOrientationYaw
Default item=LandroidMowerMetricsBladeTime
Default item=LandroidMowerMetricsBladeTimeTotal
Default item=LandroidMowerMetricsDistance
Default item=LandroidMowerMetricsTotalTime
Default item=LandroidMowerWifiRssi
Default item=LandroidMowerWifiWifiQuality
}
Text label="Control" icon=none {
Default item=LandroidMowerCommonAction
Default item=LandroidMowerCommonEnable
Default item=LandroidMowerCommonLock
Default item=LandroidMowerOneTimeEdgecut
Setpoint item=LandroidMowerOneTimeDuration minValue=30 maxValue=300 step=30
}
Text label="Multi zones" icon=none {
Default item=LandroidMowerMultiZonesEnable
Default item=LandroidMowerMultiZonesLastZone
Default item=LandroidMowerMultiZonesZone1
Default item=LandroidMowerMultiZonesZone2
Default item=LandroidMowerMultiZonesZone3
Default item=LandroidMowerMultiZonesZone4
Default item=LandroidMowerMultiZonesAllocation0
Default item=LandroidMowerMultiZonesAllocation1
Default item=LandroidMowerMultiZonesAllocation2
Default item=LandroidMowerMultiZonesAllocation3
Default item=LandroidMowerMultiZonesAllocation4
Default item=LandroidMowerMultiZonesAllocation5
Default item=LandroidMowerMultiZonesAllocation6
Default item=LandroidMowerMultiZonesAllocation7
Default item=LandroidMowerMultiZonesAllocation8
Default item=LandroidMowerMultiZonesAllocation9
}
Text label="Schedule" icon=none {
Default item=LandroidMowerScheduleMode
Setpoint item=LandroidMowerScheduleTimeExtension minValue=-100 maxValue=100 step=10
Default item=LandroidMowerScheduleNextStart
Default item=LandroidMowerScheduleNextStop
Text label="Sunday" icon=none {
Default item=LandroidMowerSundayEnable
Default item=LandroidMowerSundayTime
Default item=LandroidMowerSundayDuration
Default item=LandroidMowerSundayEdgecut
Default item=LandroidMowerSunday2Enable
Default item=LandroidMowerSunday2Time
Default item=LandroidMowerSunday2Duration
Default item=LandroidMowerSunday2Edgecut
}
Text label="Monday" icon=none {
Default item=LandroidMowerMondayEnable
Default item=LandroidMowerMondayTime
Default item=LandroidMowerMondayDuration
Default item=LandroidMowerMondayEdgecut
Default item=LandroidMowerMonday2Enable
Default item=LandroidMowerMonday2Time
Default item=LandroidMowerMonday2Duration
Default item=LandroidMowerMonday2Edgecut
}
Text label="Tuesday" icon=none {
Default item=LandroidMowerTuesdayEnable
Default item=LandroidMowerTuesdayTime
Default item=LandroidMowerTuesdayDuration
Default item=LandroidMowerTuesdayEdgecut
Default item=LandroidMowerTuesday2Enable
Default item=LandroidMowerTuesday2Time
Default item=LandroidMowerTuesday2Duration
Default item=LandroidMowerTuesday2Edgecut
}
Text label="Wednesday" icon=none {
Default item=LandroidMowerWednesdayEnable
Default item=LandroidMowerWednesdayTime
Default item=LandroidMowerWednesdayDuration
Default item=LandroidMowerWednesdayEdgecut
Default item=LandroidMowerWednesday2Enable
Default item=LandroidMowerWednesday2Time
Default item=LandroidMowerWednesday2Duration
Default item=LandroidMowerWednesday2Edgecut
}
Text label="Thursday" icon=none {
Default item=LandroidMowerThursdayEnable
Default item=LandroidMowerThursdayTime
Default item=LandroidMowerThursdayDuration
Default item=LandroidMowerThursdayEdgecut
Default item=LandroidMowerThursday2Enable
Default item=LandroidMowerThursday2Time
Default item=LandroidMowerThursday2Duration
Default item=LandroidMowerThursday2Edgecut
}
Text label="Friday" icon=none {
Default item=LandroidMowerFridayEnable
Default item=LandroidMowerFridayTime
Default item=LandroidMowerFridayDuration
Default item=LandroidMowerFridayEdgecut
Default item=LandroidMowerFriday2Enable
Default item=LandroidMowerFriday2Time
Default item=LandroidMowerFriday2Duration
Default item=LandroidMowerFriday2Edgecut
}
Text label="Saturday" icon=none {
Default item=LandroidMowerSaturdayEnable
Default item=LandroidMowerSaturdayTime
Default item=LandroidMowerSaturdayDuration
Default item=LandroidMowerSaturdayEdgecut
Default item=LandroidMowerSaturday2Enable
Default item=LandroidMowerSaturday2Time
Default item=LandroidMowerSaturday2Duration
Default item=LandroidMowerSaturday2Edgecut
}
}
Text label="Battery" icon=none {
Default item=LandroidMowerBatteryTemperature
Default item=LandroidMowerBatteryVoltage
Default item=LandroidMowerBatteryLevel
Default item=LandroidMowerBatteryChargeCycles
Default item=LandroidMowerBatteryChargeCyclesTotal
Text item=LandroidMowerBatteryCharging label="Battery charging [%s]"
}
Text label="Rainsensor" icon=none {
Text item=LandroidMowerRainState label="State [%s]"
Default item=LandroidMowerRainCounter
Setpoint item=LandroidMowerRainDelay minValue=30 maxValue=600 step=15
}
}
}
```
### $OPENHAB_CONF/rules/landroid.rules
```java
rule "Landroid mower status"
when
Item LandroidMowerCommonError changed or
Item LandroidMowerCommonStatus changed
then
if (LandroidMowerCommonError.state != "NO_ERR") {
MyMower.postUpdate(transform("MAP", "landroid_error_de.map", LandroidMowerCommonError.state.toString))
} else {
MyMower.postUpdate(transform("MAP", "landroid_status_de.map", LandroidMowerCommonStatus.state.toString))
}
end
```
### $OPENHAB_CONF/transform/landroid_error_en.map
```text
UNKNOWN=unknown
NO_ERR=no error
TRAPPED=trapped
LIFTED=lifted
WIRE_MISSING=wire missing
OUTSIDE_WIRE=outside wire
RAINING=raining
CLOSE_DOOR_TO_MOW=close door to mow
CLOSE_DOOR_TO_GO_HOME=close door to go home
BLADE_MOTOR_BLOCKED=blade motor blocked
WHEEL_MOTOR_BLOKED=wheel motor blocked
TRAPPED_TIMEOUT=trapped timeout
UPSIDE_DOWN=upside down
BATTERY_LOW=battery low
REVERSE_WIRE=reverse wire
CHARGE_ERROR=charge error
TIMEOUT_FINDING_HOME=timeout finding home
MOWER_LOCKED=mower locked
BATTERY_OVER_TEMPERATURE=battery over temperature
MOWER_OUTSIDE_WIRE=mower outside wire
```
### $OPENHAB_CONF/transform/landroid_status_en.map
```text
UNKNOWN=unknown
IDLE=idle
HOME=home
START_SEQUENCE=start sequence
LEAVING_HOME=leaving home
FOLLOW_WIRE=follow wire
SEARCHING_HOME=searching home
SEARCHING_WIRE=searching wire
MOWING=mowing
LIFTED=lifted
TRAPPED=trapped
BLADE_BLOCKED=blade blocked
DEBUG=debug
REMOTE_CONTROL=remote control
GOING_HOME=going home
ZONE_TRAINING=zone training
BORDER_CUT=border cut
SEARCHING_ZONE=searching zone
PAUSE=pause
MANUEL_STOP=manuel stop
```
@@ -0,0 +1,6 @@
<svg version="1.1" id="prefix__Layer_1" x="0" y="0" height="32" width="32" viewBox="0 0 283.46 283.46" xmlns="http://www.w3.org/2000/svg">
<style></style>
<path
d="M199.7 34.99h-28.01v-8.37a7.16 7.16 0 00-7.16-7.16h-45.59a7.16 7.16 0 00-7.16 7.16v8.37H83.77c-8.45 0-15.31 6.85-15.31 15.31v198.41c0 8.45 6.85 15.31 15.31 15.31H199.7c8.45 0 15.31-6.85 15.31-15.31V50.29c-.01-8.45-6.86-15.3-15.31-15.3z"
transform="rotate(90 141.735 141.74)" fill="#dee0e1" />
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 48 48">
<path fill="#37474F" d="M45 38H3a2 2 0 01-2-2V12a2 2 0 012-2h42a2 2 0 012 2v24a2 2 0 01-2 2z" />
<path fill="#FFF" d="M16 35H4V13h12v22zm14-22H18v22h12V13zm14 0H32v22h12V13z" />
<path fill="#CFD8DC"
d="M16 17H4v-4h12v4zm14-4H18v4h12v-4zm14 0H32v4h12v-4zM16 33H4v2h12v-2zm14 0H18v2h12v-2zm14 0H32v2h12v-2z" />
<path fill="none"
d="M37.995 17.812c.42 0 .757-.231 1.01-.694.019-.035.025-.082.043-.118h-2.093c.014.029.024.066.039.094.254.479.587.718 1.001.718z" />
<path fill="#546E7A"
d="M14 25.179c0 1.884-.345 3.325-1.035 4.324-.691.998-1.676 1.497-2.956 1.497-1.267 0-2.246-.485-2.936-1.455S6.025 27.185 6 25.376v-2.574c0-1.878.344-3.314 1.031-4.309S8.704 17 9.991 17s2.273.493 2.96 1.479c.687.986 1.037 2.393 1.05 4.221v2.479zm-2.686-2.767c0-1.077-.107-1.885-.322-2.424-.214-.539-.548-.808-1.002-.808-.851 0-1.292.987-1.324 2.959v3.411c0 1.102.109 1.923.326 2.461.217.539.556.808 1.017.808.435 0 .758-.252.969-.756.211-.504.323-1.276.336-2.316v-3.335zM28 25.179c0 1.884-.345 3.325-1.035 4.324-.691.998-1.676 1.497-2.956 1.497-1.267 0-2.246-.485-2.936-1.455s-1.048-2.36-1.073-4.169v-2.574c0-1.878.344-3.314 1.031-4.309S22.704 17 23.991 17c1.286 0 2.273.493 2.96 1.479.687.986 1.037 2.393 1.05 4.221v2.479zm-2.686-2.767c0-1.077-.107-1.885-.322-2.424-.214-.539-.548-.808-1.002-.808-.851 0-1.292.987-1.324 2.959v3.411c0 1.102.109 1.923.326 2.461.217.539.556.808 1.017.808.435 0 .758-.252.969-.756.211-.504.323-1.276.336-2.316v-3.335zM42 25.554V24h-8v2.24h5.303L36.892 33h2.609L42 25.554zm-2.994-8.436c-.253.462-.59.694-1.01.694-.414 0-.748-.239-1.001-.718-.015-.028-.025-.065-.039-.094h-2.589c.182.563.42 1.074.741 1.507.738.995 1.719 1.493 2.943 1.493.76 0 1.441-.199 2.044-.596s1.071-.956 1.404-1.676c.108-.232.192-.477.264-.728h-2.716c-.016.036-.022.083-.041.118z" />
<path fill="#455A64"
d="M36.178 35l.714-2h2.609l-.671 2h-2.652zm.436-20.078v-.963c.111-.315.28-.579.505-.793.078-.074.171-.118.262-.167H34.02c-.006.169-.02.331-.02.503v.986c0 .951.131 1.78.367 2.512h2.589c-.225-.477-.342-1.165-.342-2.078zM41.641 13h-3.144c.188.115.354.292.495.54.263.462.394 1.062.394 1.798 0 .67-.121 1.216-.338 1.662h2.716A6.032 6.032 0 0042 15.305c0-.884-.129-1.643-.359-2.305z" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path
d="M92.6 21c-32 0-64.04 24-64.04 72L92.6 221l64-128c0-48-32-72-64-72zm282.3 39c-6.9.29-13.6 1.6-19.2 2.8l3.8 17.6c5.6-1.25 11.4-2.04 16.3-2.4zM92.6 61c17.7 0 32 14.33 32 32 0 17.7-14.3 32-32 32-17.67 0-32-14.3-32-32 0-17.67 14.33-32 32-32zm302.2.2l-3 17.7c4.9 1.03 9.8 2.32 14.1 4.9l8.7-15.8c-6.1-3.25-12.9-6.17-19.8-6.8zm-57.5 6.7c-6.1 2.38-12.2 4.51-17.4 6.6L327 91c5.5-2.34 11.3-4.38 16.2-6.1zM431 81.3L417.3 93c3.6 4.12 6.4 9.2 8.6 13.3l16.1-8.1c-3.4-6.55-6.4-11.51-11-16.9zm-127.8.9c-6.1 3.11-11.1 5.88-16.5 8.6l8.8 15.8c5.2-3 10.9-5.9 15.5-8.2zm-32.3 17.9c-5.3 3.1-10.5 6.2-15.6 9.6l9.8 15c4.9-3.2 10-6.2 15-9.2zM448.2 118c-5.9 1-11.9 1.7-17.8 2.4.4 5 .1 10.4-.9 14.6l17.5 4.1c1-7.2 1.9-14.6 1.2-21.1zm-208.1 1.7c-5 3.4-9.9 6.9-14.9 10.3l10.4 14.7c4.8-3.5 9.7-6.8 14.6-10.2zm-29.6 21.1c-5 3.6-10.2 7.6-14.5 10.9l10.9 14.3c5.5-4 9.3-7 14.3-10.7zm213 8c-3 4.6-6.5 9.2-10 12.7l13.1 12.5c4.3-5.1 8.9-10.3 12.1-15.5zm-241.8 14.1c-4.9 3.8-9.8 7.7-14.1 11.3l11.4 13.9c4.7-3.9 9.5-7.9 13.9-11.1zM401.1 173c-4.6 3.7-9.4 7.3-13.8 10.3l10.3 14.8c5.3-3.6 10.5-7.5 15-11.1zm-247.4 12.9c-4.7 3.8-9.2 7.8-13.8 11.7l11.7 13.7c4.5-3.9 9-7.8 13.6-11.6zm218.9 7c-5.1 3-10.4 6.1-15.2 8.7l8.6 15.9c5.4-3.3 11.5-6.2 16-9.2zm-246.4 16.6c-4.5 4-8.9 8-13.4 12.1l12.1 13.4c4.4-4 8.9-8 13.3-12zm215.5.4c-5.3 2.6-10.6 5.3-15.9 7.9l7.7 16.2c6.2-3 10.8-5.5 16.4-8.1zm-32 15.4c-5.5 2.5-10.8 4.9-16.4 7.2l7.3 16.5c5.5-2.4 11-4.9 16.5-7.4zM99.6 234c-5.1 4.5-8.65 8-13.3 12.5l12.7 13c4.7-4.5 8.5-8.4 12.9-12.2zm177.3 5.8c-5.5 2.3-11 4.7-16.5 7l7 16.7c5.6-2.3 11.1-4.7 16.6-7.1zm-33.1 14c-5.5 2.4-11 4.8-16.6 7l7 16.7c5.5-2.3 11.1-4.7 16.6-7zm184.8 7.2c-32 0-64 24-64 72l64 128 64-128c0-48-32-72-64-72zm-218 6.8c-5.7 2.6-11.7 5-16.6 7.1l7.1 16.6c5.9-2.5 11.5-4.9 16.5-7.1zM177.4 282c-5.4 2.5-11.7 5.3-16.5 7.5l7.4 16.4c5.9-2.6 11.1-5.2 16.3-7.4zm-33 15c-5.6 2.7-11.4 5.5-16.4 8l8.1 16.1c5.4-2.8 11-5.4 15.9-7.8zm284.2 4c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zm-316.8 12.3c-5.3 2.9-10.6 5.9-16 9l9 15.6c5.1-3 10.3-5.8 15.5-8.6zM80.1 332c-5.61 3.2-11.03 7.5-15.7 10.6L75.3 357c4.97-3.6 10.32-7.3 14.6-9.9zm-29.9 22.6c-4.8 4.4-9.53 9.5-13.2 13.8l13.7 11.7c3.85-4.7 7.2-8.2 11.7-12.2zm217.8 1.3l1.6 17.9c5.2-.9 10.4-.3 15.6.5l3.1-17.7c-6.6-1-13.6-1.7-20.3-.7zm-37.2 10l6.8 16.7c5.2-2.3 10.6-4.1 16.1-5.8-1.9-5.7-3.3-11.5-4.8-17.3-6.3 1.8-12.6 4.2-18.1 6.4zm77.5-.9l-10.2 14.8c4.2 3.1 8.3 6.4 11.6 10.5l13.6-11.8c-5.1-5.2-9-10.1-15-13.5zm-94.5 9c-5.5 2.8-10.8 6-16.1 9.1l9.1 15.5c5.2-2.8 10.3-6.1 15.4-8.8zM26.01 385c-3.02 6.5-5.47 13.5-6.61 19.7l17.7 3.1c1.08-5.7 2.63-9.8 4.9-14.7-5.49-2.4-10.73-5.3-15.99-8.1zm156.09 7.8c-5.1 3.3-10.1 6.6-15.1 10l10 15c5-3.3 9.9-6.7 14.9-10zm152.7 1.2l-15.1 9.8c3.2 4.8 6.3 9.8 9.2 14.9l15.6-9c-3.5-5.6-6-10.6-9.7-15.7zm-182.7 19c-5 3.3-10 6.5-14.9 10l10 15c4.8-3.5 9.9-6.8 15-10.2zm-114.8 9.5c-5.79 1.2-11.63 2.2-17.45 3.3 1.05 7 3.86 13.8 6.4 19.2l16.25-7.8c-2.17-5-4.23-10.2-5.2-14.7zm316.1 2.8l-15.6 9c3.1 5.4 6.7 11.2 9.6 15.8l15.1-9.7c-3.4-5.3-6.3-10.3-9.1-15.1zm-231 7.5c-5 3.1-9.9 6.1-15.1 9l8.9 15.7c5.3-3.1 10.6-6.2 15.7-9.5zm-71.3 16.3l-12.3 13.2c5.56 5.3 12.42 8.8 19.9 10.4l4-17.5c-4.44-.9-8.59-3.1-11.6-6.1zm41 .3c-5.01 2.3-10.21 4.1-15.6 5.2l4.1 17.6c6.42-1.3 12.46-3.7 18.5-6.2zm280.3 4.8l-13.9 11.3c4.3 5.3 9.6 10.4 14.2 14l11.1-14.2c-4.4-3.4-8.2-7.5-11.4-11.1zm24.1 17.5l-4.5 17.5c7.9 1.6 13.8 2.1 21.2 1.3l-2.2-17.9c-4.9.8-9.7.3-14.5-.9z"
fill="#4ba82f" />
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="542" height="353">
<path d="M0 0 C178.86 0 357.72 0 542 0 C542 116.49 542 232.98 542 353 C363.14 353 184.28 353 0 353 C0 236.51 0 120.02 0 0 Z " fill="#ED7C01" transform="translate(0,0)"/>
<path d="M0 0 C0.69233356 -0.00044186 1.38466713 -0.00088371 2.0979805 -0.00133896 C47.07451039 -0.01633819 95.18842166 1.19895125 139.0625 12.25 C140.32416992 12.56360474 140.32416992 12.56360474 141.61132812 12.88354492 C145.05421529 14.80285164 145.99044108 17.50025323 147.39453125 21.0078125 C147.6760051 21.67086395 147.95747894 22.33391541 148.2474823 23.01705933 C149.13994245 25.12984181 150.00760645 27.25182056 150.875 29.375 C151.47690697 30.81187284 152.0804051 32.24808026 152.68554688 33.68359375 C154.16308401 37.19828348 155.61847147 40.72141835 157.0625 44.25 C154.06429418 47.33153506 151.0749223 50.14350012 147.62109375 52.703125 C146.79512695 53.31857178 145.96916016 53.93401855 145.11816406 54.56811523 C144.25416992 55.20563721 143.39017578 55.84315918 142.5 56.5 C141.6140918 57.1587915 140.72818359 57.81758301 139.81542969 58.49633789 C138.04914147 59.80978535 136.28174995 61.12175059 134.51318359 62.43212891 C131.27001192 64.83785278 128.04018488 67.26106356 124.8125 69.6875 C123.22916667 70.875 121.64583333 72.0625 120.0625 73.25 C117.36498072 70.59758974 114.76938664 67.97879713 112.4375 65 C104.65085888 55.86947158 94.2548759 49.37984871 84.0625 43.25 C83.27875 42.77820313 82.495 42.30640625 81.6875 41.8203125 C56.8448362 28.24557121 25.41253569 25.19715472 -1.71899414 32.7722168 C-25.50000217 39.83019358 -48.48948078 54.52005258 -62.5546875 75.11328125 C-67.89629662 79.90321599 -77.29640623 79.8427247 -84.20703125 80.87890625 C-86.22846073 81.18940273 -88.24977768 81.50063249 -90.27099609 81.8125 C-91.32631531 81.97492188 -92.38163452 82.13734375 -93.46893311 82.3046875 C-117.63771267 86.0453026 -141.56687053 90.55568857 -165.46905518 95.71868896 C-167.83754131 96.22848477 -170.20725447 96.73201281 -172.57763672 97.23291016 C-191.43441446 101.22185873 -210.13812344 105.7300046 -228.8125 110.5 C-229.81705231 110.75657379 -230.82160461 111.01314758 -231.8565979 111.27749634 C-246.00519994 114.9010163 -260.10227477 118.61818429 -274 123.125 C-279.5661772 124.91061982 -285.14050061 126.60423221 -290.77783203 128.15039062 C-304.34534578 131.87836401 -317.63837059 136.41743473 -330.95996094 140.93188477 C-331.80011246 141.21557419 -332.64026398 141.49926361 -333.50587463 141.79154968 C-335.10523532 142.3319945 -336.70369503 142.87511476 -338.3011322 143.42121887 C-343.71384006 145.25 -343.71384006 145.25 -345.9375 145.25 C-346.78815262 141.9080221 -347.55685736 138.55212181 -348.3125 135.1875 C-348.55355469 134.24455078 -348.79460937 133.30160156 -349.04296875 132.33007812 C-350.53405926 125.42777205 -350.53405926 125.42777205 -348.89355469 122.18896484 C-347.35367337 120.35992029 -345.74398305 118.8139389 -343.9375 117.25 C-343.0403125 116.35796875 -342.143125 115.4659375 -341.21875 114.546875 C-334.28622129 108.19854815 -327.11654346 102.2883689 -319.65478516 96.57324219 C-318.02248673 95.31548594 -316.40572194 94.04048786 -314.79296875 92.7578125 C-301.3044331 82.1366511 -286.86180752 72.72616353 -271.9375 64.25 C-270.60114502 63.48864746 -270.60114502 63.48864746 -269.23779297 62.71191406 C-244.23640023 48.58445711 -218.18425294 37.32069412 -190.9375 28.25 C-189.9077002 27.90630371 -188.87790039 27.56260742 -187.81689453 27.20849609 C-171.50827612 21.83283239 -155.00069884 17.67574756 -138.24414062 13.97998047 C-135.41469441 13.35534637 -132.58971602 12.71467895 -129.765625 12.06640625 C-109.37535359 7.46836649 -88.65022605 5.09872362 -67.875 3.0625 C-67.01735046 2.97782471 -66.15970093 2.89314941 -65.27606201 2.8059082 C-43.49218628 0.67484852 -21.89014188 0.00641893 0 0 Z " fill="#FEFEFD" transform="translate(366.9375,108.75)"/>
<path d="M0 0 C16.24400289 13.57933853 29.01345086 33.92712471 31.51806641 55.26904297 C33.5959331 82.48485606 28.82047364 106.9735342 11.15087891 128.30029297 C-3.65846973 144.99926176 -24.92460338 157.43141483 -47.48193359 159.26904297 C-75.72200393 160.59611957 -98.5087624 153.67674822 -120.11083984 134.99560547 C-136.92946402 119.15082964 -146.82169998 97.21576284 -147.67333984 74.15185547 C-148.25776179 46.69965004 -139.27379252 24.6864073 -120.41943359 4.76904297 C-110.03253136 -5.35997755 -98.20739159 -12.18580537 -84.48193359 -16.73095703 C-83.03753906 -17.23949219 -83.03753906 -17.23949219 -81.56396484 -17.75830078 C-53.71784374 -25.91296782 -22.16657448 -17.91916841 0 0 Z " fill="#FEFDFC" transform="translate(453.48193359375,181.73095703125)"/>
<path d="M0 0 C-0.26554687 1.09957031 -0.53109375 2.19914063 -0.8046875 3.33203125 C-1.16161327 4.82548381 -1.51837519 6.31897554 -1.875 7.8125 C-2.13603516 8.89047852 -2.13603516 8.89047852 -2.40234375 9.99023438 C-2.95712204 12.32235794 -3.48425571 14.65893687 -4 17 C-4.18747803 17.83418457 -4.37495605 18.66836914 -4.56811523 19.52783203 C-6.12528218 27.11352931 -6.34737729 34.52139432 -6.3125 42.25 C-6.30758545 43.53471191 -6.3026709 44.81942383 -6.29760742 46.14306641 C-6.13028778 59.67107385 -3.48555194 71.3549463 1.01171875 84.05078125 C2 87 2 87 2 89 C-87.1 89 -176.2 89 -268 89 C-271.375 74.375 -271.375 74.375 -271 69 C-260.8599619 64.47260864 -250.43752572 61.0709151 -239.875 57.6875 C-238.0805136 57.10937612 -236.28608052 56.53108672 -234.49169922 55.95263672 C-229.33090938 54.29195446 -224.16623111 52.64366631 -219 51 C-218.31655518 50.78220886 -217.63311035 50.56441772 -216.92895508 50.34002686 C-202.60323767 45.77837816 -188.20765617 41.6155713 -173.671875 37.77734375 C-167.94411918 36.26261423 -162.23934599 34.67400294 -156.53588867 33.07055664 C-127.78576467 25.01646464 -98.78230382 18.54478896 -69.54344177 12.55247498 C-64.31468477 11.48027189 -59.09102882 10.38781305 -53.87109375 9.2734375 C-10.27667564 0 -10.27667564 0 0 0 Z " fill="#FEFDFC" transform="translate(289,209)"/>
<path d="M0 0 C3.57520867 2.78796076 6.81579537 5.77634505 10 9 C10.63550781 9.61359375 11.27101563 10.2271875 11.92578125 10.859375 C23.02518362 22.37715839 27.5258192 38.13957295 27.27709961 53.79003906 C26.67806436 72.41648694 18.74474674 87.6650876 5.4375 100.625 C-7.23878141 111.66127544 -23.56713147 116.42122539 -40.13671875 116.28125 C-57.02925897 115.05145941 -71.60883435 107.31477368 -83 95 C-83.45761719 94.54753906 -83.91523438 94.09507812 -84.38671875 93.62890625 C-94.95860901 82.50206112 -99.60117599 66.78552036 -99.5 51.75 C-99.49427979 50.75000977 -99.48855957 49.75001953 -99.48266602 48.71972656 C-99.09487634 32.50306753 -92.32639857 19.01762626 -81.3515625 7.1953125 C-58.40741338 -14.22430844 -26.2768317 -17.35842718 0 0 Z " fill="#EC7801" transform="translate(432,199)"/>
<path d="M0 0 C5.37104009 -0.09964824 10.74152986 -0.17183292 16.11328125 -0.21972656 C17.93661892 -0.23971364 19.75989356 -0.26691286 21.58300781 -0.30175781 C42.96231957 -0.69983919 42.96231957 -0.69983919 52.4375 6.375 C55.99449598 10.01875198 57.80677326 13.22771606 58.36328125 18.33203125 C58.17615373 31.15746384 56.496419 42.58812637 47.69921875 52.625 C38.12104385 61.68181235 28.99988575 64.18084663 16.1015625 64.09765625 C15.26660248 64.0962413 14.43164246 64.09482635 13.57138062 64.09336853 C10.92254958 64.08780174 8.2738052 64.07525547 5.625 64.0625 C3.82161581 64.0574807 2.01823029 64.05291855 0.21484375 64.04882812 C-4.19013721 64.0378432 -8.59505394 64.02060163 -13 64 C-12.39813426 57.96004758 -11.52807857 52.09253591 -10.29296875 46.15234375 C-10.12630203 45.3373793 -9.95963531 44.52241486 -9.78791809 43.68275452 C-9.43602638 41.96721458 -9.08213265 40.25208418 -8.72631836 38.53735352 C-8.19410643 35.96937668 -7.66981158 33.39987844 -7.14648438 30.83007812 C-5.02307974 20.4819822 -2.65826148 10.22373964 0 0 Z " fill="#FEFDFB" transform="translate(451,22)"/>
<path d="M0 0 C5.22864743 -0.07476069 10.45697628 -0.12888763 15.68603516 -0.16479492 C17.46082996 -0.17978093 19.23558837 -0.20017682 21.01025391 -0.22631836 C45.16490533 -0.57293829 45.16490533 -0.57293829 54 7 C57.79126245 12.46776566 58.68590563 16.52980269 58.5625 23.125 C58.55081787 23.9595874 58.53913574 24.7941748 58.52709961 25.65405273 C58.12292222 37.09987178 54.36265621 45.71227871 46.32421875 53.91015625 C37.8592472 61.52151723 28.65512341 64.50385635 17.37109375 64.29296875 C16.49598648 64.28872391 15.62087921 64.28447906 14.71925354 64.28010559 C11.95833928 64.26347981 9.19818895 64.22585966 6.4375 64.1875 C4.55144293 64.17243228 2.66537427 64.15874756 0.77929688 64.14648438 C-3.81409083 64.11362937 -8.40691647 64.06196135 -13 64 C-9.01498272 42.59225275 -4.53553281 21.29654953 0 0 Z " fill="#FEFDFC" transform="translate(225,22)"/>
<path d="M0 0 C5.92350015 3.75507599 9.11980136 8.06541102 11 14.875 C12.33964614 28.61416156 9.33271665 40.79671082 1 51.875 C-6.46351775 59.94241234 -15.5487549 64.32429208 -26.48828125 65.1328125 C-35.12675512 65.393269 -43.02745683 65.14927115 -49.8359375 59.296875 C-56.29698939 52.30002857 -57.57584306 45.85956536 -57.34765625 36.50390625 C-56.2572003 25.12150654 -51.07265074 14.45389401 -42.8125 6.5625 C-31.23943747 -2.83703843 -14.1090154 -6.53470187 0 0 Z " fill="#FEFDFC" transform="translate(401,23.125)"/>
<path d="M0 0 C10.23 0 20.46 0 31 0 C32.983286 11.72725635 34.52469716 23.11742892 35 35 C35.66 35 36.32 35 37 35 C37.1134375 33.95714844 37.226875 32.91429687 37.34375 31.83984375 C38.11269588 25.69775319 39.32716719 19.63777395 40.5 13.5625 C40.72558594 12.36689453 40.95117188 11.17128906 41.18359375 9.93945312 C41.51037109 8.23885742 41.51037109 8.23885742 41.84375 6.50390625 C42.04162109 5.47080322 42.23949219 4.4377002 42.44335938 3.37329102 C43 1 43 1 44 0 C45.56172745 -0.08685603 47.12729344 -0.10702219 48.69140625 -0.09765625 C49.63564453 -0.09443359 50.57988281 -0.09121094 51.55273438 -0.08789062 C52.54595703 -0.07951172 53.53917969 -0.07113281 54.5625 -0.0625 C55.55958984 -0.05798828 56.55667969 -0.05347656 57.58398438 -0.04882812 C60.05604849 -0.03700007 62.52799518 -0.02051849 65 0 C62.21360627 15.23501746 59.10704459 30.38195799 55.75 45.5 C55.57178711 46.30642944 55.39357422 47.11285889 55.20996094 47.94372559 C54.70058244 50.2444888 54.18710719 52.54429136 53.671875 54.84375 C53.51789246 55.53845398 53.36390991 56.23315796 53.20526123 56.94891357 C52.11587444 61.76825113 52.11587444 61.76825113 51 64 C41.1 64 31.2 64 21 64 C19.74265166 58.97060663 18.66040564 54.37881241 17.9296875 49.3125 C17.77226074 48.23613281 17.61483398 47.15976563 17.45263672 46.05078125 C17.2620166 44.71402344 17.07139648 43.37726563 16.875 42 C15.946875 35.565 15.946875 35.565 15 29 C12.69 40.55 10.38 52.1 8 64 C1.07 64 -5.86 64 -13 64 C-12.44855327 56.03028178 -11.15543337 48.6228502 -9.25 40.875 C-6.77818275 30.54008496 -4.56866918 20.18057142 -2.56152344 9.74560547 C-2.27390137 8.26278076 -2.27390137 8.26278076 -1.98046875 6.75 C-1.81442139 5.87859375 -1.64837402 5.0071875 -1.47729492 4.109375 C-1 2 -1 2 0 0 Z " fill="#FEFDFB" transform="translate(156,22)"/>
<path d="M0 0 C1.2776825 -0.01441132 1.2776825 -0.01441132 2.58117676 -0.02911377 C3.51022827 -0.03336365 4.43927979 -0.03761353 5.39648438 -0.04199219 C6.82728333 -0.0505423 6.82728333 -0.0505423 8.2869873 -0.05926514 C10.31468593 -0.0687365 12.34240009 -0.07525755 14.37011719 -0.07910156 C17.44950056 -0.0887397 20.52819728 -0.11971438 23.60742188 -0.15136719 C25.57877208 -0.15790387 27.55012707 -0.16315172 29.52148438 -0.16699219 C30.43434204 -0.17933899 31.34719971 -0.19168579 32.28771973 -0.20440674 C38.88083353 -0.18184521 45.96183161 0.39430034 51.02539062 5.02050781 C53.20534749 9.85701131 52.71812886 15.4418289 51.36914062 20.48144531 C49.01141407 25.99328606 45.23408 28.51375444 39.99023438 31.16113281 C40.8075 31.84175781 41.62476563 32.52238281 42.46679688 33.22363281 C45.59374842 36.86367548 45.55532647 38.43884504 45.36523438 43.16113281 C45.32140625 44.47210938 45.27757812 45.78308594 45.23242188 47.13378906 C45.15647358 48.47651594 45.07577155 49.81898267 44.99023438 51.16113281 C44.9103125 52.43859375 44.83039063 53.71605469 44.74804688 55.03222656 C44.52789109 58.07771488 44.26359834 61.11995873 43.99023438 64.16113281 C35.74023438 64.16113281 27.49023438 64.16113281 18.99023438 64.16113281 C18.99023438 57.23113281 18.99023438 50.30113281 18.99023438 43.16113281 C16.68023437 42.83113281 14.37023437 42.50113281 11.99023438 42.16113281 C11.8690625 42.85078125 11.74789063 43.54042969 11.62304688 44.25097656 C8.67253715 60.79652727 8.67253715 60.79652727 6.99023438 64.16113281 C-0.92976563 64.16113281 -8.84976562 64.16113281 -17.00976562 64.16113281 C-15.88455676 52.90904421 -15.88455676 52.90904421 -14.90234375 48.17651367 C-14.6854187 47.11939682 -14.46849365 46.06227997 -14.24499512 44.97312927 C-14.01268188 43.86856888 -13.78036865 42.76400848 -13.54101562 41.62597656 C-13.30064575 40.46610733 -13.06027588 39.3062381 -12.81262207 38.11122131 C-12.30742083 35.67950472 -11.79969254 33.24831189 -11.28955078 30.81762695 C-10.50585919 27.08000203 -9.73187332 23.34045798 -8.95898438 19.60058594 C-8.46606575 17.23393049 -7.97259609 14.86738973 -7.47851562 12.50097656 C-7.24620239 11.37819809 -7.01388916 10.25541962 -6.77453613 9.09861755 C-6.55761108 8.06987015 -6.34068604 7.04112274 -6.1171875 5.98120117 C-5.9270105 5.07204453 -5.7368335 4.16288788 -5.54089355 3.22618103 C-4.62969922 -0.31658169 -3.40463075 0.03090147 0 0 Z " fill="#FEFDFC" transform="translate(296.009765625,21.8388671875)"/>
<path d="M0 0 C10.89 0 21.78 0 33 0 C34.57885705 3.15771409 34.56626168 6.61941122 34.90234375 10.09375 C34.98496964 10.91387573 35.06759552 11.73400146 35.15272522 12.57897949 C35.41801908 15.21896475 35.67797445 17.85944201 35.9375 20.5 C36.19342662 23.07285091 36.45049489 25.6455844 36.7081604 28.21826172 C36.8794061 29.92815338 37.0499577 31.6381147 37.21981812 33.34814453 C37.88251759 39.97371873 38.60995408 46.59088514 39.39378357 53.20324707 C39.81505107 56.8560619 40 60.30719722 40 64 C31.42 64 22.84 64 14 64 C14 60.7 14 57.4 14 54 C7.73 54 1.46 54 -5 54 C-5.66 55.98 -6.32 57.96 -7 60 C-7.66 61.32 -8.32 62.64 -9 64 C-16.92 64 -24.84 64 -33 64 C-31.56559271 59.69677813 -30.22066662 56.23130939 -28.1640625 52.28125 C-27.60001709 51.19440918 -27.03597168 50.10756836 -26.45483398 48.98779297 C-25.85098877 47.83682129 -25.24714355 46.68584961 -24.625 45.5 C-23.99735112 44.29710354 -23.36982985 43.0941405 -22.74243164 41.89111328 C-15.38034339 27.80727167 -7.71751155 13.89152078 0 0 Z " fill="#FEFDFC" transform="translate(101,22)"/>
<path d="M0 0 C1.5631897 0.00785522 1.5631897 0.00785522 3.15795898 0.01586914 C4.28266602 0.0190918 5.40737305 0.02231445 6.56616211 0.02563477 C7.75016602 0.03401367 8.93416992 0.04239258 10.15405273 0.05102539 C11.34192383 0.05553711 12.52979492 0.06004883 13.75366211 0.06469727 C16.69967499 0.07652864 19.64558954 0.09301252 22.59155273 0.11352539 C22.07629653 8.90968487 20.09884091 17.31901498 18.09155273 25.86352539 C17.60747122 27.9454144 17.12517938 30.02770011 16.64465332 32.1104126 C16.21035627 33.98950042 15.77121924 35.86746772 15.33178711 37.74536133 C14.65002471 40.84747018 14.1137073 43.98059802 13.59155273 47.11352539 C20.85155273 47.11352539 28.11155273 47.11352539 35.59155273 47.11352539 C34.59155273 56.11352539 34.59155273 56.11352539 32.59155273 64.11352539 C16.75155273 64.11352539 0.91155273 64.11352539 -15.40844727 64.11352539 C-14.75614894 57.59054215 -14.01306436 51.45795657 -12.66625977 45.09790039 C-12.49722137 44.27960754 -12.32818298 43.4613147 -12.15402222 42.6182251 C-11.61860632 40.03230578 -11.0761283 37.44792862 -10.53344727 34.86352539 C-10.17751934 33.14877689 -9.82204754 31.43393364 -9.46704102 29.71899414 C-8.61522415 25.6111578 -7.75393338 21.50538805 -6.8885498 17.40039062 C-6.42652051 15.19961341 -5.97091707 12.99759692 -5.5168457 10.79516602 C-5.25162109 9.52350586 -4.98639648 8.2518457 -4.71313477 6.94165039 C-4.48352051 5.83208984 -4.25390625 4.7225293 -4.01733398 3.5793457 C-3.17038508 0.14944035 -3.17038508 0.14944035 0 0 Z " fill="#FEFDFC" transform="translate(33.408447265625,21.886474609375)"/>
<path d="M0 0 C1.5631897 0.00785522 1.5631897 0.00785522 3.15795898 0.01586914 C4.28266602 0.0190918 5.40737305 0.02231445 6.56616211 0.02563477 C7.75016602 0.03401367 8.93416992 0.04239258 10.15405273 0.05102539 C11.34192383 0.05553711 12.52979492 0.06004883 13.75366211 0.06469727 C16.69967499 0.07652864 19.64558954 0.09301252 22.59155273 0.11352539 C21.95979699 6.58718291 20.94731306 12.86370313 19.63061523 19.23071289 C19.4393808 20.16851593 19.24814636 21.10631897 19.05111694 22.07254028 C18.64794451 24.04445059 18.24277339 26.01595321 17.83569336 27.98706055 C17.21994395 30.97208547 16.61233139 33.95868417 16.00561523 36.94555664 C15.61026941 38.87404206 15.21445139 40.80243076 14.81811523 42.73071289 C14.63987213 43.60815155 14.46162903 44.48559021 14.27798462 45.38961792 C12.95980838 51.73712964 11.30571661 57.82825784 9.59155273 64.11352539 C1.34155273 64.11352539 -6.90844727 64.11352539 -15.40844727 64.11352539 C-14.81089302 58.73553716 -14.23662825 53.75634733 -13.17407227 48.51586914 C-12.93197021 47.312771 -12.68986816 46.10967285 -12.44042969 44.87011719 C-12.18237549 43.60981689 -11.92432129 42.3495166 -11.65844727 41.05102539 C-11.39039742 39.73051493 -11.12257804 38.40995766 -10.85498047 37.08935547 C-10.17619578 33.74559944 -9.49202344 30.40296823 -8.80670166 27.06054688 C-8.17874601 23.99054148 -7.55806092 20.91906252 -6.93579102 17.84790039 C-6.48976007 15.66548936 -6.04317676 13.48319112 -5.59594727 11.30102539 C-5.389375 10.28194092 -5.18280273 9.26285645 -4.9699707 8.21289062 C-4.77822266 7.28677979 -4.58647461 6.36066895 -4.38891602 5.40649414 C-4.22286865 4.59720459 -4.05682129 3.78791504 -3.88574219 2.95410156 C-3.16778963 0.18548555 -2.92417325 0.13783483 0 0 Z " fill="#FEFDFB" transform="translate(423.408447265625,21.886474609375)"/>
<path d="M0 0 C24.09 0 48.18 0 73 0 C70.01592986 8.95221043 64.88304369 14.79512004 56.51074219 19.14477539 C46.35496875 24.08723427 36.12981464 25.8357646 24.97265625 23.31640625 C22.50496678 22.37689099 20.29050314 21.31175978 18 20 C16.81664062 19.32904297 16.81664062 19.32904297 15.609375 18.64453125 C8.82404215 14.45425119 3.66574473 9.12783697 0 2 C0 1.34 0 0.68 0 0 Z " fill="#FEFDFD" transform="translate(68,317)"/>
<path d="M0 0 C3.3125 0.4375 3.3125 0.4375 4.3125 1.4375 C5.29169828 9.68011847 4.40597342 16.5073649 1.5 24.1875 C1.23952881 24.87851807 0.97905762 25.56953613 0.71069336 26.28149414 C-0.70964442 29.78050304 -1.47251423 31.29417615 -4.6875 33.4375 C-7.25 33.75 -7.25 33.75 -9.6875 33.4375 C-12.5877122 29.0871817 -12.22835924 25.54927936 -11.6875 20.4375 C-7.52541109 0.89284538 -7.52541109 0.89284538 0 0 Z " fill="#F08608" transform="translate(381.6875,37.5625)"/>
<path d="M0 0 C3.1875 0.3125 3.1875 0.3125 5.5625 1.6875 C9.11804808 7.43107767 7.35064778 14.28531318 5.9375 20.5625 C3.17407 30.52875911 3.17407 30.52875911 -1.51953125 33.33203125 C-4.38424297 34.65281413 -6.64914742 35.3125 -9.8125 35.3125 C-9.11852092 27.86799711 -7.79774584 20.63688376 -6.3125 13.3125 C-6.08691406 12.16910156 -5.86132812 11.02570312 -5.62890625 9.84765625 C-5.41105469 8.76355469 -5.19320313 7.67945313 -4.96875 6.5625 C-4.77087891 5.57765625 -4.57300781 4.5928125 -4.36914062 3.578125 C-3.5622969 0.2941295 -3.4547066 0.38385629 0 0 Z " fill="#F08708" transform="translate(475.8125,36.6875)"/>
<path d="M0 0 C3.20449558 -0.21848833 5.42100153 -0.27649344 8.34765625 1.12109375 C11.31171954 4.49157706 10.46238432 8.29194402 10.32421875 12.5703125 C9.64136044 19.79380162 7.78344278 26.39653846 3 32 C-0.75220181 34.34512613 -2.52961855 35 -7 35 C-5.60375454 27.07181412 -4.034332 19.18950337 -2.375 11.3125 C-2.14554688 10.21744141 -1.91609375 9.12238281 -1.6796875 7.99414062 C-1.12108732 5.32915225 -0.56117263 2.66444765 0 0 Z " fill="#F08607" transform="translate(247,37)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1 7.26 1 14.52 1 22 C-2.3 22 -5.6 22 -9 22 C-6.66062389 14.33204498 -3.45231194 7.22862726 0 0 Z " fill="#F08606" transform="translate(113,38)"/>
<path d="M0 0 C6.625 -0.25 6.625 -0.25 10 2 C9.74327938 5.7652358 9.40674134 7.55839511 6.8125 10.375 C4 12 4 12 -2 12 C-1.34 8.04 -0.68 4.08 0 0 Z " fill="#F08A0F" transform="translate(313,37)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1 3.63 1 7.26 1 11 C1.66 11 2.32 11 3 11 C2.34 14.96 1.68 18.92 1 23 C0.01 22.34 -0.98 21.68 -2 21 C-1.84660156 20.34644531 -1.69320313 19.69289063 -1.53515625 19.01953125 C-0.86620406 15.24507841 -0.64592703 11.51094004 -0.4375 7.6875 C-0.39431641 6.94693359 -0.35113281 6.20636719 -0.30664062 5.44335938 C-0.20129568 3.62908536 -0.10021684 1.8145645 0 0 Z " fill="#F39E38" transform="translate(283,39)"/>
<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C1.34 2.32 0.68 3.64 0 5 C7.92 5 15.84 5 24 5 C24 5.33 24 5.66 24 6 C15.75 6 7.5 6 -1 6 C-1.33 4.68 -1.66 3.36 -2 2 C-1.34 1.34 -0.68 0.68 0 0 Z " fill="#F39F3D" transform="translate(68,81)"/>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,33 @@
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.843 511.843">
<path
d="M84.201 61.399c-1.156-4.64-5.342-8.076-10.333-8.076H10.661C4.772 53.323 0 58.095 0 63.984c0 5.889 4.772 10.661 10.661 10.661H65.62l60.771 234.624 20.642-5.342L84.201 61.399z"
fill="#424953" />
<path
d="M198.268 267.031h-96.635c-11.731 0-22.767 9.481-24.516 21.087l-12.902 85.334c-1.75 11.605 6.412 21.087 18.143 21.087h115.91c11.73 0 21.329-9.591 21.329-21.306v-84.881c0-11.714-9.599-21.321-21.329-21.321z"
fill="#fecd57" />
<path d="M490.522 330.559C294.77 223.935 104.288 223.935 104.288 223.935l49.407 190.613 336.827 1.328v-85.317z"
fill="#ec5564" />
<path
d="M170.612 447.866c-29.397 0-53.312-23.914-53.312-53.327 0-29.397 23.915-53.296 53.312-53.296 29.405 0 53.32 23.898 53.32 53.296 0 29.413-23.915 53.327-53.32 53.327z"
fill="#646c77" />
<path
d="M170.612 330.559c-35.333 0-63.98 28.647-63.98 63.98 0 35.349 28.647 63.98 63.98 63.98 35.341 0 63.98-28.632 63.98-63.98 0-35.333-28.639-63.98-63.98-63.98zm0 106.655c-23.516 0-42.651-19.15-42.651-42.675 0-23.509 19.135-42.644 42.651-42.644 23.524 0 42.659 19.135 42.659 42.644 0 23.525-19.135 42.675-42.659 42.675z"
fill="#424953" />
<path
d="M458.531 447.866c-23.523 0-42.658-19.135-42.658-42.643 0-23.524 19.135-42.675 42.658-42.675 23.509 0 42.644 19.15 42.644 42.675 0 23.509-19.135 42.643-42.644 42.643z"
fill="#646c77" />
<path
d="M458.531 351.896c-29.443 0-53.327 23.868-53.327 53.328 0 29.428 23.884 53.296 53.327 53.296 29.445 0 53.312-23.868 53.312-53.296.001-29.46-23.866-53.328-53.312-53.328zm0 85.318c-17.65 0-31.99-14.371-31.99-31.99 0-17.651 14.34-31.99 31.99-31.99 17.636 0 31.99 14.339 31.99 31.99.001 17.619-14.354 31.99-31.99 31.99z"
fill="#424953" />
<g fill="#e5e8ec">
<path
d="M469.185 405.224c0 5.873-4.765 10.652-10.653 10.652s-10.668-4.779-10.668-10.652c0-5.904 4.779-10.685 10.668-10.685s10.653 4.78 10.653 10.685zM181.28 394.539c0 5.904-4.78 10.685-10.668 10.685s-10.661-4.78-10.661-10.685c0-5.873 4.772-10.653 10.661-10.653s10.668 4.78 10.668 10.653z" />
</g>
<path
d="M203.118 233.65c20.017 44.55 64.777 75.603 116.785 75.603 27.585 0 53.14-8.748 74.039-23.603-72.399-28.389-139.481-43.721-190.824-52z"
fill="#424953" />
<g fill="#f5ba45">
<path
d="M93.846 309.691H73.86l-3.226 21.321h23.212c5.889 0 10.661-4.765 10.661-10.669 0-5.873-4.772-10.652-10.661-10.652zM65.8 363.002h23.243a10.663 10.663 0 0010.668-10.669c0-5.873-4.78-10.652-10.668-10.652H69.026L65.8 363.002z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,6 @@
<svg height="32" width="32" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path d="M42 29.59v-5.45c0-5.52-4.48-10-10-10s-10 4.48-10 10v12.73" fill="none" stroke="#b4b4b4"
stroke-miterlimit="10" stroke-width="4" />
<path d="M17.5 29.5h29v25h-29z" fill="#f8cf99" />
<path d="M46 30v24H18V30h28m1-1H17v26h30V29z" fill="#cb9d61" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="32" width="32" viewBox="0 0 55 63">
<defs>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__e">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__g">
<feMorphology radius="1.08" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__i">
<feMorphology radius="1.44" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<path
d="M22.528 23C20.887 23 11 38.241 11 44.803c0 5.174 2.987 10.572 11.528 10.572 7.82 0 11.528-5.346 11.528-10.572C34.056 38.158 24.169 23 22.528 23z"
id="prefix__a" />
<path
d="M10.125 5C9.253 5 4 13.238 4 16.785c0 2.797 1.587 5.715 6.125 5.715 4.155 0 6.125-2.89 6.125-5.715C16.25 13.193 10.997 5 10.125 5z"
id="prefix__b" />
<path
d="M35.313 0C34.129 0 27 11.122 27 15.91c0 3.776 2.154 7.715 8.313 7.715 5.64 0 8.312-3.901 8.312-7.715 0-4.849-7.129-15.91-8.313-15.91z"
id="prefix__c" />
<mask id="prefix__f" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-2" y="-2" width="27.056"
height="36.375">
<path fill="#fff" d="M9 21h27.056v36.375H9z" />
<use xlink:href="#prefix__a" />
</mask>
<mask id="prefix__h" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.08" y="-1.08"
width="14.41" height="19.66">
<path fill="#fff" d="M2.92 3.92h14.41v19.66H2.92z" />
<use xlink:href="#prefix__b" />
</mask>
<mask id="prefix__j" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.44" y="-1.44"
width="19.505" height="26.505">
<path fill="#fff" d="M25.56-1.44h19.505v26.505H25.56z" />
<use xlink:href="#prefix__c" />
</mask>
<rect id="prefix__d" x="-8.105" y="28.395" width="67" height="4" rx="2" />
</defs>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__a" fill="#000" filter="url(#prefix__e)" />
<use height="100%" width="100%" xlink:href="#prefix__a" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__a" mask="url(#prefix__f)" stroke="#fff" stroke-width="4" />
</g>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__b" fill="#000" filter="url(#prefix__g)" />
<use height="100%" width="100%" xlink:href="#prefix__b" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__b" mask="url(#prefix__h)" stroke="#fff"
stroke-width="2.16" />
</g>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__c" fill="#000" filter="url(#prefix__i)" />
<use height="100%" width="100%" xlink:href="#prefix__c" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__c" mask="url(#prefix__j)" stroke="#fff"
stroke-width="2.88" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,8 @@
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496.166 496.166">
<path
d="M.005 248.087C.005 111.063 111.073 0 248.079 0c137.014 0 248.082 111.062 248.082 248.087 0 137.002-111.068 248.079-248.082 248.079C111.073 496.166.005 385.089.005 248.087z"
fill="#32bea6" />
<path
d="M400.813 169.581c-2.502-4.865-14.695-16.012-35.262-5.891-20.564 10.122-10.625 32.351-10.625 32.351 7.666 15.722 11.98 33.371 11.98 52.046 0 65.622-53.201 118.824-118.828 118.824-65.619 0-118.82-53.202-118.82-118.824 0-61.422 46.6-111.946 106.357-118.173v30.793s-.084 1.836 1.828 2.999c1.906 1.163 3.818 0 3.818 0l98.576-58.083s2.211-1.162 2.211-3.436c0-1.873-2.211-3.205-2.211-3.205l-98.248-57.754s-2.24-1.605-4.23-.826c-1.988.773-1.744 3.481-1.744 3.481v32.993c-88.998 6.392-159.23 80.563-159.23 171.21 0 94.824 76.873 171.696 171.693 171.696 94.828 0 171.707-76.872 171.707-171.696.001-28.298-6.852-54.98-18.972-78.505z"
fill="#f7f7f7" />
</svg>

After

Width:  |  Height:  |  Size: 967 B

@@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12H2A10 10 0 0112 2z" fill="#767676" />
<path d="M12 12V2A10 10 0 112 12h10z" opacity=".5" />
<path d="M12 12V2c5.523 0 10 4.477 10 10H12zM12 12h10c0 5.523-4.477 10-10 10V12zM12 12v10C6.477 22 2 17.523 2 12h10z"
fill="#767676" /><text style="white-space:pre" x="6.187" y="10.486" fill="#fff" font-family="Arial,sans-serif"
font-size="9" font-weight="700">1</text><text style="white-space:pre" x="37.928" y="7.522" fill="#333"
font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text style="white-space:pre" x="12.861"
y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">2</text><text
style="white-space:pre" x="6.187" y="19.585" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">3</text><text style="white-space:pre" x="12.861" y="19.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.worxlandroid</artifactId>
<name>openHAB Add-ons :: Bundles :: Worx Landroid Binding</name>
<properties>
<bnd.importpackage>!org.graalvm.nativeimage.hosted</bnd.importpackage>
</properties>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk.iotdevicesdk</groupId>
<artifactId>aws-iot-device-sdk</artifactId>
<version>1.30.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>0.44.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.worxlandroid-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-worxlandroid" description="Worx Landroid Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:org.openhab.osgiify/software.amazon.awssdk.crt.aws-crt/0.44.0</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.worxlandroid/${project.version}</bundle>
</feature>
</features>
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link WorxLandroidBindingConstants} class defines datCommon constants, which are
* used across the whole binding.
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public class WorxLandroidBindingConstants {
public static final String BINDING_ID = "worxlandroid";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_MOWER = new ThingTypeUID(BINDING_ID, "mower");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_MOWER);
// Channel group ids
public static final String GROUP_COMMON = "common";
public static final String GROUP_CONFIG = "config";
public static final String GROUP_MULTI_ZONES = "multi-zones";
public static final String GROUP_SCHEDULE = "schedule";
public static final String GROUP_ONE_TIME = "one-time";
public static final String GROUP_BATTERY = "battery";
public static final String GROUP_ORIENTATION = "orientation";
public static final String GROUP_METRICS = "metrics";
public static final String GROUP_RAIN = "rain";
public static final String GROUP_WIFI = "wifi";
public static final String GROUP_AWS = "aws";
// Configuration constants
public static final String SERIAL_NUMBER = "serialNumber";
// List channel ids
// common
public static final String CHANNEL_ONLINE_TIMESTAMP = "online-timestamp";
public static final String CHANNEL_ACTION = "action";
public static final String CHANNEL_ENABLE = "enable";
public static final String CHANNEL_ONLINE = "online";
public static final String CHANNEL_LOCK = "lock";
public static final String CHANNEL_RSSI = "rssi";
// AWS
public static final String CHANNEL_POLL = "poll";
public static final String CHANNEL_CONNECTED = "connected";
// cfgCommon
public static final String CHANNEL_TIMESTAMP = "timestamp";
public static final String CHANNEL_COMMAND = "command";
public static final String CHANNEL_DELAY = "delay";
// cfgSc
public static final String CHANNEL_TIME_EXTENSION = "time-extension";
public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_START = "next-start";
public static final String CHANNEL_STOP = "next-stop";
// cfgScXXXday
public static final String CHANNEL_DURATION = "duration";
public static final String CHANNEL_EDGECUT = "edgecut";
public static final String CHANNEL_TIME = "time";
// datCommon
public static final String CHANNEL_WIFI_QUALITY = "wifi-quality";
public static final String CHANNEL_LAST_ZONE = "last-zone";
public static final String CHANNEL_STATUS_CODE = "status";
public static final String CHANNEL_ERROR_CODE = "error";
// datBattery
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_VOLTAGE = "voltage";
public static final String CHANNEL_LEVEL = "level";
public static final String CHANNEL_CHARGE_CYCLES = "charge-cycles";
public static final String CHANNEL_CHARGE_CYCLES_TOTAL = "charge-cycles-total";
public static final String CHANNEL_CHARGING = "charging";
// datDmp
public static final String CHANNEL_PITCH = "pitch";
public static final String CHANNEL_ROLL = "roll";
public static final String CHANNEL_YAW = "yaw";
// datSt
public static final String CHANNEL_BLADE_TIME = "blade-time";
public static final String CHANNEL_BLADE_TIME_TOTAL = "blade-time-total";
public static final String CHANNEL_DISTANCE = "distance";
public static final String CHANNEL_TOTAL_TIME = "total-time";
// datRain
public static final String CHANNEL_RAIN_STATE = "state";
public static final String CHANNEL_RAIN_COUNTER = "counter";
public static final String CHANNEL_PREFIX_ALLOCATION = "allocation-%d";
public static final String CHANNEL_PREFIX_ZONE = "zone-%d";
}
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal;
import static org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.api.WorxApiHandler;
import org.openhab.binding.worxlandroid.internal.discovery.MowerDiscoveryService;
import org.openhab.binding.worxlandroid.internal.handler.WorxLandroidBridgeHandler;
import org.openhab.binding.worxlandroid.internal.handler.WorxLandroidMowerHandler;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link WorxLandroidHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Nils Billing - Initial contribution
* @author Gaël L'hopital - Added oAuthFactory
*/
@NonNullByDefault
@Component(configurationPid = "binding.worxlandroid", service = ThingHandlerFactory.class)
public class WorxLandroidHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MOWER, THING_TYPE_BRIDGE);
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final OAuthFactory oAuthFactory;
private final WorxApiHandler worxApiHandler;
private final TimeZoneProvider timeZoneProvider;
@Activate
public WorxLandroidHandlerFactory(final @Reference OAuthFactory oAuthFactory,
final @Reference WorxApiHandler worxApiHandler, final @Reference TimeZoneProvider timeZoneProvider) {
this.oAuthFactory = oAuthFactory;
this.worxApiHandler = worxApiHandler;
this.timeZoneProvider = timeZoneProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
WorxLandroidBridgeHandler bridgeHandler = new WorxLandroidBridgeHandler((Bridge) thing, worxApiHandler,
oAuthFactory);
MowerDiscoveryService discoveryService = new MowerDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(thing.getUID(), bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<>()));
return bridgeHandler;
} else if (THING_TYPE_MOWER.equals(thingTypeUID)) {
return new WorxLandroidMowerHandler(thing, worxApiHandler.getDeserializer(), timeZoneProvider);
}
return null;
}
@Override
protected void removeHandler(ThingHandler handler) {
if (handler instanceof WorxLandroidBridgeHandler) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(handler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
super.removeHandler(handler);
}
}
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal;
import static org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants.BINDING_ID;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.ui.icon.IconProvider;
import org.openhab.core.ui.icon.IconSet;
import org.openhab.core.ui.icon.IconSet.Format;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WorxLandroidIconProvider} is the class providing binding related icons.
*
* @author Gaël L'hopital - Initial contribution
*/
@Component(service = { IconProvider.class, WorxLandroidIconProvider.class })
@NonNullByDefault
public class WorxLandroidIconProvider implements IconProvider {
private static final String DEFAULT_LABEL = "Worx Landroid Icons";
private static final String DEFAULT_DESCRIPTION = "Icons illustrating channels provided by Worx Landroid binding.";
private final Logger logger = LoggerFactory.getLogger(WorxLandroidIconProvider.class);
private final BundleContext context;
private final TranslationProvider i18nProvider;
@Activate
public WorxLandroidIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) {
this.context = context;
this.i18nProvider = i18nProvider;
}
@Override
public Set<IconSet> getIconSets() {
return getIconSets(null);
}
@Override
public Set<IconSet> getIconSets(@Nullable Locale locale) {
String label = getText("label", DEFAULT_LABEL, locale);
String description = getText("decription", DEFAULT_DESCRIPTION, locale);
return Set.of(new IconSet(BINDING_ID, label, description, Set.of(Format.SVG)));
}
private String getText(String entry, String defaultValue, @Nullable Locale locale) {
String text = defaultValue;
if (locale != null) {
text = i18nProvider.getText(context.getBundle(), "iconset." + entry, defaultValue, locale);
text = text == null ? defaultValue : text;
}
return text;
}
@Override
public @Nullable Integer hasIcon(String category, String iconSetId, Format format) {
return iconSetId.equals(BINDING_ID) && format == Format.SVG ? 0 : null;
}
public @Nullable InputStream getIcon(String category, String state) {
return getIcon(category, BINDING_ID, state, Format.SVG);
}
@Override
public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) {
String icon = getResource(category, true);
if (icon.isEmpty()) {
return null;
}
if (state != null) {
String withState = "%s-%s".formatted(category, state.toString().toLowerCase(Locale.ROOT));
String iconWithState = getResource(withState, false);
if (!iconWithState.isEmpty()) {
icon = iconWithState;
}
}
return new ByteArrayInputStream(icon.getBytes(StandardCharsets.UTF_8));
}
private String getResource(String iconName, boolean shouldExist) {
String result = "";
URL iconResource = context.getBundle().getEntry("icon/%s.svg".formatted(iconName));
if (iconResource != null) {
try (InputStream stream = iconResource.openStream()) {
result = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
logger.warn("Unable to load resource '{}': {}", iconResource.getPath(), e.getMessage());
}
} else if (shouldExist) {
logger.warn("Unable to find icon '{}'", iconName);
}
return result;
}
}
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WebApiException} is a class for handling the Worx Landroid API exceptions
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public class WebApiException extends Exception {
private static final long serialVersionUID = 1L;
private static final int UNKNOWN = 0;
private final int errorCode;
public WebApiException(int errorCode, String errorMsg) {
super(errorMsg);
this.errorCode = errorCode;
}
public WebApiException(String errorMsg, Throwable cause) {
super(errorMsg);
this.errorCode = UNKNOWN;
}
public WebApiException(String errorMsg) {
super(errorMsg);
this.errorCode = UNKNOWN;
}
public WebApiException(Throwable cause) {
super(cause.getMessage(), cause);
this.errorCode = UNKNOWN;
}
public int getErrorCode() {
return errorCode;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api;
import java.lang.reflect.Type;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
/**
* The {@link WorxApiDeserializer} is responsible to instantiate suitable Gson (de)serializer
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(service = WorxApiDeserializer.class)
public class WorxApiDeserializer {
private static final DateTimeFormatter WORX_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ssX");
private final Gson gson;
@Activate
public WorxApiDeserializer() {
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(ZoneId.class,
(JsonDeserializer<ZoneId>) (json, type, context) -> ZoneId
.of(json.getAsJsonPrimitive().getAsString()))
.registerTypeAdapter(Instant.class, (JsonDeserializer<Instant>) (json, type, context) -> {
String value = json.getAsString();
OffsetDateTime odt;
try {
odt = OffsetDateTime.parse(value, WORX_FORMATTER);
} catch (DateTimeParseException exc) {
odt = OffsetDateTime.parse(value + 'Z', WORX_FORMATTER);
}
return odt.toInstant();
}).registerTypeAdapter(Boolean.class, (JsonDeserializer<Boolean>) (json, type, context) -> {
String value = json.getAsJsonPrimitive().getAsString().toUpperCase(Locale.ROOT);
return "1".equals(value);
}).create();
}
public String toJson(Object object) {
return gson.toJson(object);
}
public Map<String, String> toMap(Object object) {
Map<String, String> fromObject = gson.fromJson(toJson(object), new TypeToken<HashMap<String, String>>() {
}.getType());
return fromObject != null ? Map.copyOf(fromObject) : Map.of();
}
public <T> T deserialize(Type typeToken, String json) throws WebApiException {
try {
T result = gson.fromJson(json, typeToken);
if (result == null) {
throw new WebApiException("Deserialization of '%s' resulted in null value".formatted(json));
}
return result;
} catch (JsonSyntaxException e) {
throw new WebApiException("Unexpected error deserializing '%s' : %s".formatted(json, e.getMessage()));
}
}
}
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api;
import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.worxlandroid.internal.api.dto.ProductItemStatus;
import org.openhab.binding.worxlandroid.internal.api.dto.UsersMeResponse;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.reflect.TypeToken;
/**
* {@link WorxApiHandler} is a API request
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
@Component(service = WorxApiHandler.class)
public class WorxApiHandler {
private static final String URL_BASE = "https://api.worxlandroid.com/api/v2/";
private static final String URL_PRODUCT_ITEMS = URL_BASE + "product-items";
private static final String URL_USERS_ME = URL_BASE + "users/me";
private static final int TIMEOUT_S = 15;
private static final Type PRODUCT_ITEM_STATUS_LIST = new TypeToken<List<ProductItemStatus>>() {
}.getType();
private static final Type PRODUCT_ITEM_STATUS = new TypeToken<ProductItemStatus>() {
}.getType();
private static final Type USERS_ME = new TypeToken<UsersMeResponse>() {
}.getType();
private final Logger logger = LoggerFactory.getLogger(WorxApiHandler.class);
private final HttpClient httpClient;
private final WorxApiDeserializer deserializer;
@Activate
public WorxApiHandler(final @Reference HttpClientFactory httpClientFactory,
final @Reference WorxApiDeserializer deserializer) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.deserializer = deserializer;
}
private Request buildRequest(String url, String accessToken, HttpMethod method) {
Request request = httpClient.newRequest(url).method(method);
request.header(HttpHeader.AUTHORIZATION, "Bearer %s".formatted(accessToken));
request.header(HttpHeader.CONTENT_TYPE, "application/json; utf-8");
request.timeout(TIMEOUT_S, TimeUnit.SECONDS);
return request;
}
private <T> T apiGet(String url, String accessToken, Type type) throws WebApiException {
Request request = buildRequest(url, accessToken, HttpMethod.GET);
logger.debug("URI: {}", request.getURI().toString());
try {
ContentResponse response = request.send();
if (response.getStatus() == HttpStatus.OK_200) {
String result = response.getContentAsString();
logger.trace("Worx Landroid Api Response: {}", result);
return deserializer.deserialize(type, result);
}
throw new WebApiException(
"Error calling Worx Landroid Api! HTTP Status = %d".formatted(response.getStatus()));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new WebApiException(e);
} catch (TimeoutException | ExecutionException e) {
throw new WebApiException(e);
}
}
private boolean apiPost(String url, String accessToken) {
Request request = buildRequest(url, accessToken, HttpMethod.POST);
logger.debug("URI: {}", request.getURI().toString());
try {
return request.send().getStatus() == HttpStatus.OK_200;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.error("Interrupted while posting at {}: {}", request.getURI().toString(), e.getMessage());
} catch (TimeoutException | ExecutionException e) {
logger.error("Error posting at {}: {}", request.getURI().toString(), e.getMessage());
}
return false;
}
public WorxApiDeserializer getDeserializer() {
return deserializer;
}
public List<ProductItemStatus> retrieveDeviceStatus(String token) throws WebApiException {
return apiGet("%s?status=1".formatted(URL_PRODUCT_ITEMS), token, PRODUCT_ITEM_STATUS_LIST);
}
public ProductItemStatus retrieveDeviceStatus(String token, String serialNumber) throws WebApiException {
return apiGet("%s/%s?status=1".formatted(URL_PRODUCT_ITEMS, serialNumber), token, PRODUCT_ITEM_STATUS);
}
public UsersMeResponse retrieveMe(String token) throws WebApiException {
return apiGet(URL_USERS_ME, token, USERS_ME);
}
public boolean resetBladeTime(String token, String serialNumber) {
return apiPost("%s/%s/counters/blade/reset".formatted(URL_PRODUCT_ITEMS, serialNumber), token);
}
public boolean resetBatteryCycles(String token, String serialNumber) {
return apiPost("%s/%s/counters/battery/reset".formatted(URL_PRODUCT_ITEMS, serialNumber), token);
}
}
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidActionCodes;
/**
* The {@link Commands} class hold record definition of Commands send to API
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public class Commands {
private record OTS(OTSCommand ots) {
}
private record OTSCommand( //
int bc, // bordercut
int wtm // work time minutes
) {
}
public record OneTimeCommand(OTS sc) {
public OneTimeCommand(int bc, int wtm) {
this(new OTS(new OTSCommand(bc, wtm)));
}
}
private record ScheduleDaysP(int p, Object d, @Nullable Object dd) {
}
public record ScheduleDaysCommand(ScheduleDaysP sc) {
public ScheduleDaysCommand(int p, Object[] d, Object[] dd) {
this(new ScheduleDaysP(p, d, dd));
}
public ScheduleDaysCommand(int p, Object[] d) {
this(new ScheduleDaysP(p, d, null));
}
}
private record ScheduleCommandMode(int m) {
}
public record ScheduleCommand(ScheduleCommandMode sc) {
public ScheduleCommand(int m) {
this(new ScheduleCommandMode(m));
}
}
public record MowerCommand(int cmd) {
public MowerCommand(WorxLandroidActionCodes actionCode) {
this(actionCode.code);
}
}
public record ZoneMeterCommand(int[] mz) {
}
public record ZoneMeterAlloc(int[] mzv) {
}
public record SetRainDelay(int rd) {
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api.dto;
import java.time.Instant;
/**
*
* @author Gaël L'hopital - Initial contribution
*/
public class LastStatus {
public Instant timestamp;
public Payload payload;
public LastStatus(Payload payload) {
this.payload = payload;
this.timestamp = Instant.now();
}
}
@@ -0,0 +1,183 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api.dto;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidErrorCodes;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidStatusCodes;
import com.google.gson.annotations.SerializedName;
/**
*
* @author Gaël L'hopital - Initial contribution
*/
public class Payload {
public class US {
public int enabled;
public String stat;
}
public class Ots {
@SerializedName("wtm")
public int duration = -1;
private int bc = -1;
public boolean getEdgeCut() {
return bc == 1;
}
}
public class Al {
public int lvl;
public int t;
}
public class Modules {
@SerializedName("US")
public US uS;
}
public class Rain {
@SerializedName("s")
public Boolean raining;
@SerializedName("cnt")
public int counter = -1;
}
public class Schedule {
public static enum Mode {
@SerializedName("1")
NORMAL,
@SerializedName("2")
PARTY,
UNKNOWN
}
@SerializedName("m")
public Mode scheduleMode = Mode.UNKNOWN;
@SerializedName("p")
public int timeExtension = -1;
public int distm;
public Ots ots;
public List<List<String>> d;
public List<List<String>> dd;
}
public class Battery {
@SerializedName("t")
public double temp = -1;
@SerializedName("v")
public double voltage = -1;
@SerializedName("p")
public int level = -1;
@SerializedName("nr")
public int chargeCycle = -1;
@SerializedName("c")
public Boolean charging;
public int m;
}
public class Stat {
@SerializedName("b")
public int bladeWorkTime = -1;
@SerializedName("d")
public int distanceCovered = -1;
@SerializedName("wt")
public int mowerWorkTime = -1;
@SerializedName("bl")
public int lawnPerimeter;
}
public class Cfg {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
private String dt = ""; // "dt": "13/03/2020",
private String tm = ""; // "tm": "17:09:34"
public int id = -1;
public String lg = ""; // en, fr...
public int cmd = -1;
public Schedule sc;
@SerializedName("mz")
public List<Integer> multiZones = List.of();
@SerializedName("mzv")
public List<Integer> multizoneAllocations = List.of();
@SerializedName("rd")
public int rainDelay = -1;
@SerializedName("sn")
public String serialNumber = "";
public int mzk;
public Al al;
public int tq;
public Modules modules;
public Instant getDateTime() {
if (dt.isEmpty() || tm.isEmpty()) {
return null;
}
LocalDateTime localDateTime = LocalDateTime.parse("%s %s".formatted(dt, tm), FORMATTER);
return localDateTime.atZone(ZoneOffset.UTC).toInstant();
}
}
public class Dat {
public static enum Axis {
// Don't change order - ordinal is used
PITCH,
ROLL,
YAW;
}
private int lk = -1;
@SerializedName("dmp")
private double[] dataMotionProcessor = { -1, -1, -1 }; // pitch, roll, yaw
public String mac = "";
public String fw = "";
@SerializedName("bt")
public Battery battery;
public Stat st;
@SerializedName("ls")
public WorxLandroidStatusCodes statusCode = WorxLandroidStatusCodes.UNKNOWN;
@SerializedName("le")
public WorxLandroidErrorCodes errorCode = WorxLandroidErrorCodes.UNKNOWN;
@SerializedName("lz")
public int lastZone = -1;
@SerializedName("rsi")
public int wifiQuality;
public int fwb;
public String conn;
public int act;
public int tr;
public Rain rain;
public Modules modules;
public boolean isLocked() {
return lk == 1;
}
public double getAngle(Axis axis) {
return dataMotionProcessor[axis.ordinal()];
}
}
public Cfg cfg;
public Dat dat;
}
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api.dto;
import java.time.Instant;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
/**
* The {@link ProductItemStatus} class
*
* @author Gaël L'hopital - Initial contribution
*
*/
public class ProductItemStatus {
public class Accessories {
public boolean ultrasonic;
}
public class MqttTopics {
public String commandIn;
public String commandOut;
}
public class SetupLocation {
public double latitude;
public double longitude;
}
public class AppSettings {
boolean cellularSetupCompleted;
}
public class City {
public int id;
public int countryId;
public String name;
public double latitude;
public double longitude;
public String createdAt;
public String updatedAt;
}
public class Sim {
public int id;
public String iccid;
public String simStatus;
public boolean pendingActivation;
public Instant contractStartsAt;
public Instant contractEndsAt;
public Instant createdAt;
public Instant updatedAt;
}
public class AutoSchedule {
public int boost;
public String grassType;
public boolean irrigation;
public Map<String, String> nutrition;
public String soilType;
}
public String id;
public String uuid;
public int productId;
public String userId;
public String serialNumber;
public String macAddress;
public String name;
public boolean locked;
public String firmwareVersion;
public boolean firmwareAutoUpgrade;
public boolean pushNotifications;
public Sim sim;
public String pushNotificationsLevel;
public boolean test;
public boolean iotRegistered;
public boolean mqttRegistered;
public String pinCode;
public String registeredAt;
public boolean online;
public String mqttEndpoint;
public AppSettings appSettings;
public int protocol;
public String pendingRadioLinkValidation;
public List<String> capabilities;
public List<String> capabilitiesAvailable;
public Accessories accessories;
public MqttTopics mqttTopics;
public boolean warrantyRegistered;
public String purchasedAt;
public String warrantyExpiresAt;
public SetupLocation setupLocation;
public City city;
public ZoneId timeZone;
public double lawnSize;
public double lawnPerimeter;
public AutoSchedule autoScheduleSettings;
public boolean autoSchedule;
public boolean improvement;
public boolean diagnostic;
public long distanceCovered;
public long mowerWorkTime;
public long bladeWorkTime;
public long bladeWorkTimeReset;
public Instant bladeWorkTimeResetAt;
public int batteryChargeCycles;
public int batteryChargeCyclesReset;
public Instant batteryChargeCyclesResetAt;
public Instant createdAt;
public Instant updatedAt;
public LastStatus lastStatus;
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link UsersMeResponse} class
*
* @author Nils Billing - Initial contribution
*
*/
@NonNullByDefault
public class UsersMeResponse {
public String id = "";
public String userType = "";
public boolean pushNotifications;
public String location = "";
public String actionsOnGooglePinCode = "";
public String createdAt = "";
public String updatedAt = "";
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.codes;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WorxLandroidActionCodes} hosts action codes
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public enum WorxLandroidActionCodes {
START(1, "start"),
STOP(2, "stop"),
HOME(3, "home"),
ZONETRAINING(4, "zonetraining"),
LOCK(5, "lock"),
UNLOCK(6, "unlock");
public final int code;
public final String description;
WorxLandroidActionCodes(int code, String description) {
this.code = code;
this.description = description;
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.codes;
import java.time.DayOfWeek;
import java.time.format.TextStyle;
import java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WorxLandroidDayCodes} hosts Landroid days of week
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public enum WorxLandroidDayCodes {
SUNDAY(0, DayOfWeek.SUNDAY),
MONDAY(1, DayOfWeek.MONDAY),
TUESDAY(2, DayOfWeek.TUESDAY),
WEDNESDAY(3, DayOfWeek.WEDNESDAY),
THURSDAY(4, DayOfWeek.THURSDAY),
FRIDAY(5, DayOfWeek.FRIDAY),
SATURDAY(6, DayOfWeek.SATURDAY);
public final int code;
public final DayOfWeek dayOfWeek;
WorxLandroidDayCodes(int code, DayOfWeek dayOfWeek) {
this.code = code;
this.dayOfWeek = dayOfWeek;
}
public String getDescription() {
return dayOfWeek.getDisplayName(TextStyle.FULL, Locale.US);
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.codes;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link WorxLandroidErrorCodes} hosts error codes
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public enum WorxLandroidErrorCodes {
@SerializedName("-1")
UNKNOWN,
@SerializedName("0")
NO_ERR,
@SerializedName("1")
TRAPPED,
@SerializedName("2")
LIFTED,
@SerializedName("3")
WIRE_MISSING,
@SerializedName("4")
OUTSIDE_WIRE,
@SerializedName("5")
RAINING,
@SerializedName("6")
CLOSE_DOOR_TO_MOW,
@SerializedName("7")
CLOSE_DOOR_TO_GO_HOME,
@SerializedName("8")
BLADE_MOTOR_BLOCKED,
@SerializedName("9")
WHEEL_MOTOR_BLOCKED,
@SerializedName("10")
TRAPPED_TIMEOUT,
@SerializedName("11")
UPSIDE_DOWN,
@SerializedName("12")
BATTERY_LOW,
@SerializedName("13")
REVERSE_WIRE,
@SerializedName("14")
CHARGE_ERROR,
@SerializedName("15")
TIMEOUT_FINDING_HOME,
@SerializedName("16")
MOWER_LOCKED,
@SerializedName("17")
BATTERY_OVER_TEMPERATURE,
@SerializedName("20")
MOWER_OUTSIDE_WIRE;
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.codes;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* The {@link WorxLandroidStatusCodes} hosts status codes
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public enum WorxLandroidStatusCodes {
@SerializedName("-1")
UNKNOWN,
@SerializedName("0")
IDLE,
@SerializedName("1")
HOME,
@SerializedName("2")
START_SEQUENCE,
@SerializedName("3")
LEAVING_HOME,
@SerializedName("4")
FOLLOW_WIRE,
@SerializedName("5")
SEARCHING_HOME,
@SerializedName("6")
SEARCHING_WIRE,
@SerializedName("7")
MOWING,
@SerializedName("8")
LIFTED,
@SerializedName("9")
TRAPPED,
@SerializedName("10")
BLADE_BLOCKED,
@SerializedName("11")
DEBUG,
@SerializedName("12")
REMOTE_CONTROL,
@SerializedName("13")
ESCAPE_FROM_OLM,
@SerializedName("30")
GOING_HOME,
@SerializedName("31")
ZONE_TRAINING,
@SerializedName("32")
BORDER_CUT,
@SerializedName("33")
SEARCHING_ZONE,
@SerializedName("34")
PAUSE,
@SerializedName("99")
MANUAL_STOP;
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link MowerConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Nils Billing - Initial contribution
* @author Gaël L'hopital - Added serialNumber configuration element
*/
@NonNullByDefault
public class MowerConfiguration {
public String serialNumber = "";
public int refreshStatusInterval = 600;
public int pollingInterval = 3600;
@Override
public String toString() {
return "MowerConfiguration [serialNumber='%s', pollingInterval='%d', refreshStatusInterval='%d']"
.formatted(serialNumber, pollingInterval, refreshStatusInterval);
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WebApiConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Nils Billing - Initial contribution
* @author Gaël L'hopital - Added NonNullByDefault, removed setters, removed reconnectInterval
*/
@NonNullByDefault
public class WebApiConfiguration {
public String username = "";
public String password = "";
@Override
public String toString() {
return "WebApiConfiguration [username='%s', password='*****']".formatted(username);
}
}
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.discovery;
import static org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants.*;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants;
import org.openhab.binding.worxlandroid.internal.api.WebApiException;
import org.openhab.binding.worxlandroid.internal.api.dto.ProductItemStatus;
import org.openhab.binding.worxlandroid.internal.handler.WorxLandroidBridgeHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MowerDiscoveryService} is a service for discovering your mowers through Worx Landroid API
*
* @author Nils Billing - Initial contribution
* @author Gaël L'hopital - Added representation property and serialNumber configuration element
*/
@NonNullByDefault
public class MowerDiscoveryService extends AbstractDiscoveryService {
/**
* Maximum time to search for devices in seconds.
*/
private static final int SEARCH_TIME_SEC = 20;
private final Logger logger = LoggerFactory.getLogger(MowerDiscoveryService.class);
private final WorxLandroidBridgeHandler bridgeHandler;
public MowerDiscoveryService(WorxLandroidBridgeHandler bridgeHandler) {
super(WorxLandroidBindingConstants.SUPPORTED_THING_TYPES, SEARCH_TIME_SEC);
this.bridgeHandler = bridgeHandler;
}
@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return WorxLandroidBindingConstants.SUPPORTED_THING_TYPES;
}
@Override
protected void startScan() {
try {
List<ProductItemStatus> productItemsStatusResponse = bridgeHandler.retrieveAllDevices();
productItemsStatusResponse.forEach(mower -> {
DiscoveryResult discoveryResult = DiscoveryResultBuilder
.create(new ThingUID(THING_TYPE_MOWER, bridgeHandler.getThing().getUID(), mower.id))
.withRepresentationProperty(SERIAL_NUMBER).withLabel(mower.name)
.withProperty(SERIAL_NUMBER, mower.serialNumber).withBridge(bridgeHandler.getThing().getUID())
.build();
thingDiscovered(discoveryResult);
logger.debug("Discovered a mower thing with ID '{}'", mower.serialNumber);
});
} catch (WebApiException exception) {
logger.warn("Error in WebApiException : {}", exception.getMessage());
}
}
@Override
protected void startBackgroundDiscovery() {
startScan();
}
}
@@ -0,0 +1,236 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.handler;
import static org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants.*;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.api.WebApiException;
import org.openhab.binding.worxlandroid.internal.api.WorxApiDeserializer;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload;
import org.openhab.binding.worxlandroid.internal.mqtt.AWSClient;
import org.openhab.binding.worxlandroid.internal.mqtt.AWSClientCallbackI;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.crt.mqtt.MqttMessage;
/**
* The{@link AWSClientThingHandler} is handles outside communications (AWS,API) parts
*
* @author Gaël L'hopital - Initial contribution
*
*/
@NonNullByDefault
public abstract class AWSClientThingHandler extends BaseThingHandler
implements AWSClientCallbackI, ThingHandlerHelper, AccessTokenRefreshListener {
private static final Duration MIN_PUBLISH_DELAY_S = Duration.ofSeconds(2);
private final Logger logger = LoggerFactory.getLogger(AWSClientThingHandler.class);
private final AWSClient awsClient;
protected final WorxApiDeserializer deserializer;
protected String endpoint = "";
protected String uuid = "";
protected String userId = "";
protected String topic = "";
protected String token = "";
private Instant lastPublishTS = Instant.MIN;
private int lastReqHash = 0;
public AWSClientThingHandler(Thing thing, WorxApiDeserializer deserializer) {
super(thing);
this.deserializer = deserializer;
this.awsClient = new AWSClient(this);
}
@Override
public void initialize() {
checkInitBridgeOAuth();
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
checkInitBridgeOAuth();
}
private void checkInitBridgeOAuth() {
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
setAccessToken(bridgeHandler.getAccessToken());
bridgeHandler.oAuthClientService.addAccessTokenRefreshListener(this);
}
}
@Override
public void dispose() {
if (!topic.isEmpty()) {
awsClient.unsubscribe(topic);
}
awsClient.dispose();
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
bridgeHandler.oAuthClientService.removeAccessTokenRefreshListener(this);
}
super.dispose();
}
@Override
public void onAWSConnectionSuccess() {
logger.debug("AWS connection is available");
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
updateChannelOnOff(GROUP_AWS, CHANNEL_CONNECTED, awsClient.isConnected());
if (!topic.isEmpty()) {
awsClient.subscribe(topic, this::onMqttMessage);
logger.debug("subscribed to topic: {}", topic);
} else {
logger.warn("Connected but no topic to subscribe to");
}
}
@Override
public void onAWSConnectionClosed() {
// Don't try to reconnect if the connection is closed by the thing being disable
if (thing.getStatus() == ThingStatus.ONLINE) {
updateChannelOnOff(GROUP_AWS, CHANNEL_CONNECTED, awsClient.isConnected());
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
bridgeHandler.requestTokenRefresh();
}
}
}
@Override
public void onAWSConnectionFailed(@Nullable String message) {
updateChannelOnOff(GROUP_AWS, CHANNEL_CONNECTED, awsClient.isConnected());
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "No AWS Connection");
}
@Override
public boolean isLinked(ChannelUID channelUID) {
return super.isLinked(channelUID);
}
@Override
public void updateState(ChannelUID channelUID, State state) {
super.updateState(channelUID, state);
}
public void publishMessage(String topic, String cmd) {
Instant now = Instant.now();
int requestHash = topic.hashCode() + cmd.hashCode();
if (requestHash == lastReqHash) {
if (now.isBefore(lastPublishTS.plus(MIN_PUBLISH_DELAY_S))) {
logger.debug("Won't post again too soon");
return;
}
}
lastPublishTS = now;
lastReqHash = requestHash;
logger.debug("publish on topic: '{}' - message: '{}'", topic, cmd);
awsClient.publish(topic, cmd);
}
public void onMqttMessage(MqttMessage mqttMessage) {
String messagePayload = new String(mqttMessage.getPayload(), StandardCharsets.UTF_8);
logger.debug("onMessage: {}", messagePayload);
try {
Payload payload = deserializer.deserialize(Payload.class, messagePayload);
internalHandlePayload(payload);
} catch (WebApiException e) {
logger.warn("Error processing incoming AWS message: {}", e.getMessage());
}
}
protected abstract void internalHandlePayload(Payload payload);
@Override
public void onAccessTokenResponse(AccessTokenResponse tokenResponse) {
String token = tokenResponse.getAccessToken();
if (token != null) {
setAccessToken(token);
}
}
private void setAccessToken(String token) {
this.token = token;
connectAWS();
}
public void connectAws(String mqttEndpoint, String uuid, String userId, String commandOut) {
this.endpoint = mqttEndpoint;
this.uuid = uuid;
this.userId = userId;
this.topic = commandOut;
connectAWS();
}
private void connectAWS() {
awsClient.disconnect();
if (endpoint.isEmpty() || userId.isEmpty() || uuid.isEmpty() || token.isEmpty()) {
logger.debug("Some data missing to initiate AWS connection");
return;
}
awsClient.connect(endpoint, userId, uuid, token);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
return;
}
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler == null) {
logger.error("no bridgeHandler");
return;
}
if (!isOnline()) {
logger.warn("handleCommand mower: {} is offline!", getThing().getUID());
return;
}
String groupId = channelUID.getGroupId();
String channelId = channelUID.getIdWithoutGroup();
internalHandleCommand(groupId, channelId, command);
}
@Override
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
}
protected abstract void internalHandleCommand(@Nullable String groupId, String channelId, Command command);
}
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.handler;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.Set;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* {@link ThingHandlerHelper} provides utility function for thing handlers
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public interface ThingHandlerHelper {
public boolean isLinked(ChannelUID channelUID);
public Thing getThing();
public void updateState(ChannelUID channelUID, State state);
void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description);
default @Nullable <T extends BridgeHandler> T getBridgeHandler(@Nullable Bridge bridge, Class<T> clazz) {
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
if (bridge.getHandler() instanceof BridgeHandler bridgeHandler) {
if (bridgeHandler.getClass() == clazz) {
return clazz.cast(bridgeHandler);
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/incorrect-bridge");
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "");
}
return null;
}
public default boolean isOnline() {
return getThing().getStatus() == ThingStatus.ONLINE;
}
public default ChannelGroupUID getGroupUID(String group) {
return new ChannelGroupUID(getThing().getUID(), group);
}
public default ChannelUID getChannelUID(String group, String channelId) {
return new ChannelUID(getThing().getUID(), group, channelId);
}
public default Set<ChannelUID> getChannelUIDs(String groupName, Set<String> channelIds) {
Set<ChannelUID> result = new HashSet<>();
ChannelGroupUID groupUID = getGroupUID(groupName);
channelIds.forEach(id -> result.add(new ChannelUID(groupUID, id)));
return result;
}
public default void updateIfActive(String group, String channelId, State state) {
ChannelUID id = getChannelUID(group, channelId);
if (isLinked(id)) {
updateState(id, state);
}
}
public default void updateChannelOnOff(String group, String channelId, boolean value) {
updateIfActive(group, channelId, OnOffType.from(value));
}
public default void updateChannelDateTime(String group, String channelId, Instant timestamp) {
updateIfActive(group, channelId, new DateTimeType(timestamp));
}
public default void updateChannelDateTime(String group, String channelId, ZonedDateTime timestamp) {
updateIfActive(group, channelId, new DateTimeType(timestamp));
}
public default void updateChannelString(String group, String channelId, @Nullable String value) {
updateIfActive(group, channelId, value == null || value.isEmpty() ? UnDefType.NULL : new StringType(value));
}
public default void updateChannelEnum(String group, String channelId, Enum<?> value) {
String name = value.name();
updateChannelString(group, channelId, "UNKNOWN".equals(name) ? null : name);
}
public default void updateChannelDecimal(String group, String channelId, @Nullable Number value) {
updateIfActive(group, channelId, value == null || value.equals(-1) ? UnDefType.NULL : new DecimalType(value));
}
public default void updateChannelQuantity(String group, String channelId, @Nullable QuantityType<?> quantity) {
updateIfActive(group, channelId, quantity != null ? quantity : UnDefType.NULL);
}
public default void updateChannelQuantity(String group, String channelId, @Nullable Number d, Unit<?> unit) {
if (d == null) {
updateIfActive(group, channelId, UnDefType.NULL);
} else {
updateChannelQuantity(group, channelId, new QuantityType<>(d, unit));
}
}
}
@@ -0,0 +1,222 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.handler;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.api.WebApiException;
import org.openhab.binding.worxlandroid.internal.api.WorxApiHandler;
import org.openhab.binding.worxlandroid.internal.api.dto.ProductItemStatus;
import org.openhab.binding.worxlandroid.internal.api.dto.UsersMeResponse;
import org.openhab.binding.worxlandroid.internal.config.WebApiConfiguration;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link WorxLandroidBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Nils Billing - Initial contribution
* @author Gaël L'hopital - Refactored with oAuthFactory, removed AWSClient
*/
@NonNullByDefault
public class WorxLandroidBridgeHandler extends BaseBridgeHandler
implements AccessTokenRefreshListener, ThingHandlerHelper {
private static final String URL_OAUTH_TOKEN = "https://id.worx.com/oauth/token";
private static final String CLIENT_ID = "013132A8-DB34-4101-B993-3C8348EA0EBC";
private final Logger logger = LoggerFactory.getLogger(WorxLandroidBridgeHandler.class);
private final WorxApiHandler apiHandler;
private final OAuthFactory oAuthFactory;
public final OAuthClientService oAuthClientService;
private String accessToken = "";
private int retryCount = 3;
private int retryDelayS = 1;
private @Nullable ScheduledFuture<?> tokenRefreshJob;
private @Nullable ScheduledFuture<?> connectionJob;
public WorxLandroidBridgeHandler(Bridge bridge, WorxApiHandler apiHandler, OAuthFactory oAuthFactory) {
super(bridge);
this.apiHandler = apiHandler;
this.oAuthFactory = oAuthFactory;
this.oAuthClientService = oAuthFactory.createOAuthClientService(getThing().getUID().getAsString(),
URL_OAUTH_TOKEN, null, CLIENT_ID, null, "*", true);
oAuthClientService.addAccessTokenRefreshListener(this);
}
@Override
public void initialize() {
logger.debug("Initializing Landroid API bridge handler.");
WebApiConfiguration config = getConfigAs(WebApiConfiguration.class);
if (config.username.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
return;
}
if (config.password.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-password");
return;
}
updateStatus(ThingStatus.UNKNOWN);
scheduler.execute(() -> initiateConnection(config.username, config.password));
}
private void initiateConnection(String username, String password) {
stopConnectionJob();
try {
String token = oAuthClientService.getAccessTokenByResourceOwnerPasswordCredentials(username, password, "*")
.getAccessToken();
if (token != null) {
accessToken = token;
UsersMeResponse user = apiHandler.retrieveMe(accessToken);
updateProperties(apiHandler.getDeserializer().toMap(user));
updateStatus(ThingStatus.ONLINE);
}
} catch (IOException | WebApiException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (OAuthResponseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/oauth-connection-error");
} catch (OAuthException e) {
Throwable cause = e.getCause();
if (cause != null) {
String message = cause.getMessage();
if (message != null && message.contains("http code 403")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/oauth-connection-error");
return;
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/oauth-connection-error");
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Landroid Bridge is read-only and does not handle commands");
}
@Override
public void dispose() {
stopConnectionJob();
stopTokenRefreshJob();
oAuthClientService.removeAccessTokenRefreshListener(this);
oAuthFactory.ungetOAuthService(getThing().getUID().getAsString());
super.dispose();
}
private void stopTokenRefreshJob() {
ScheduledFuture<?> job = tokenRefreshJob;
if (job != null) {
job.cancel(true);
tokenRefreshJob = null;
}
}
private void stopConnectionJob() {
ScheduledFuture<?> job = connectionJob;
if (job != null) {
job.cancel(true);
connectionJob = null;
}
}
@Override
public void onAccessTokenResponse(AccessTokenResponse tokenResponse) {
String token = tokenResponse.getAccessToken();
if (token != null) {
accessToken = token;
}
}
public void requestTokenRefresh() {
if (tokenRefreshJob != null) {
return;
}
try {
oAuthClientService.refreshToken();
retryCount = 3;
stopTokenRefreshJob();
} catch (IOException | OAuthResponseException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (OAuthException e) {
if (retryCount > 0) {
tokenRefreshJob = scheduler.schedule(() -> {
retryCount--;
requestTokenRefresh();
}, retryDelayS, TimeUnit.MINUTES);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/oauth-refresh-error");
}
}
}
public String getAccessToken() {
return accessToken;
}
public @Nullable ProductItemStatus retrieveDeviceStatus(String serialNumber) throws WebApiException {
return apiHandler.retrieveDeviceStatus(accessToken, serialNumber);
}
public List<ProductItemStatus> retrieveAllDevices() throws WebApiException {
return apiHandler.retrieveDeviceStatus(accessToken);
}
public boolean resetBladeTime(String serialNumber) {
return apiHandler.resetBladeTime(accessToken, serialNumber);
}
public boolean resetBatteryCycles(String serialNumber) {
return apiHandler.resetBatteryCycles(accessToken, serialNumber);
}
@Override
public boolean isLinked(ChannelUID channelUID) {
return super.isLinked(channelUID);
}
@Override
public void updateState(ChannelUID channelUID, State state) {
super.updateState(channelUID, state);
}
@Override
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
}
}
@@ -0,0 +1,591 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.handler;
import static org.openhab.binding.worxlandroid.internal.WorxLandroidBindingConstants.*;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.api.WebApiException;
import org.openhab.binding.worxlandroid.internal.api.WorxApiDeserializer;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.MowerCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.OneTimeCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.ScheduleCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.ScheduleDaysCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.SetRainDelay;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.ZoneMeterAlloc;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.ZoneMeterCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Dat.Axis;
import org.openhab.binding.worxlandroid.internal.api.dto.ProductItemStatus;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidActionCodes;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidDayCodes;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidStatusCodes;
import org.openhab.binding.worxlandroid.internal.config.MowerConfiguration;
import org.openhab.binding.worxlandroid.internal.vo.Mower;
import org.openhab.binding.worxlandroid.internal.vo.ScheduledDay;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The{@link WorxLandroidMowerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Nils Billing - Initial contribution
*
*/
@NonNullByDefault
public class WorxLandroidMowerHandler extends AWSClientThingHandler {
private static final String EMPTY_PAYLOAD = "{}";
private final Logger logger = LoggerFactory.getLogger(WorxLandroidMowerHandler.class);
private final TimeZoneProvider timeZoneProvider;
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable ScheduledFuture<?> pollingJob;
private @Nullable Mower mower;
public WorxLandroidMowerHandler(Thing thing, WorxApiDeserializer deserializer, TimeZoneProvider timeZoneProvider) {
super(thing, deserializer);
this.timeZoneProvider = timeZoneProvider;
}
@Override
public void initialize() {
super.initialize();
MowerConfiguration config = getConfigAs(MowerConfiguration.class);
if (config.serialNumber.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-serial");
return;
}
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
initializeData(bridgeHandler);
}
}
@Override
public void dispose() {
ScheduledFuture<?> job = refreshJob;
if (job != null) {
job.cancel(true);
refreshJob = null;
}
job = pollingJob;
if (job != null) {
job.cancel(true);
pollingJob = null;
}
super.dispose();
}
private void initializeData(WorxLandroidBridgeHandler bridgeHandler) {
MowerConfiguration config = getConfigAs(MowerConfiguration.class);
logger.debug("Initializing WorxLandroidMowerHandler for serial number '{}'", config.serialNumber);
try {
ProductItemStatus product = bridgeHandler.retrieveDeviceStatus(config.serialNumber);
if (product != null) {
connectAws(product.mqttEndpoint, product.uuid, product.userId, product.mqttTopics.commandOut);
Mower newMower = new Mower(this, product);
mower = newMower;
setChannelsAndProperties(newMower);
processStatusMessage(newMower);
updateStatus(product.online ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
startScheduledJobs(bridgeHandler, newMower, config);
}
} catch (WebApiException e) {
logger.warn("initialize mower: id {} - {}::{}", config.serialNumber, getThing().getLabel(),
getThing().getUID());
}
}
private void setChannelsAndProperties(Mower mower) {
ThingBuilder thingBuilder = editThing();
Set<ChannelUID> toRemove = new HashSet<>();
if (!mower.lockSupported()) { // lock channel only when supported
toRemove.add(getChannelUID(GROUP_COMMON, CHANNEL_LOCK));
}
if (!mower.rainDelaySupported()) { // rainDelay channel only when supported
toRemove.add(getChannelUID(GROUP_RAIN, CHANNEL_DELAY));
}
if (!mower.rainDelayStartSupported()) { // // rainDelayStart channel only when supported
toRemove.addAll(getChannelUIDs(GROUP_RAIN, Set.of(CHANNEL_RAIN_STATE, CHANNEL_RAIN_COUNTER)));
}
if (!mower.multiZoneSupported()) { // multizone channels only when supported
toRemove.add(getChannelUID(GROUP_MULTI_ZONES, CHANNEL_LAST_ZONE));
// remove zone meter channels
IntStream.range(0, mower.getMultiZoneCount())
.forEach(index -> toRemove.add(getChannelUID(GROUP_MULTI_ZONES, "zone-%d".formatted(index + 1))));
// remove allocation channels
IntStream.range(0, 10).forEach(index -> toRemove
.add(getChannelUID(GROUP_MULTI_ZONES, "%s-%d".formatted(CHANNEL_PREFIX_ALLOCATION, index))));
}
if (!mower.oneTimeSchedulerSupported()) { // oneTimeScheduler channel only when supported
toRemove.addAll(getChannelUIDs(GROUP_ONE_TIME, Set.of(CHANNEL_DURATION, CHANNEL_EDGECUT, CHANNEL_MODE)));
}
if (!mower.scheduler2Supported()) { // Scheduler 2 channels only when supported version
EnumSet.allOf(WorxLandroidDayCodes.class).stream()
.map(dayCode -> "%s2".formatted(dayCode.getDescription().toLowerCase(Locale.ROOT)))
.forEach(groupName -> toRemove.addAll(getChannelUIDs(groupName,
Set.of(CHANNEL_ENABLE, CHANNEL_DURATION, CHANNEL_EDGECUT, CHANNEL_TIME))));
}
toRemove.stream().forEach(thingBuilder::withoutChannel);
updateThing(thingBuilder.build());
updateProperties(Map.of(Thing.PROPERTY_MAC_ADDRESS, mower.getMacAddress(), Thing.PROPERTY_VENDOR, "Worx",
"productId", mower.getId(), "language", mower.getLanguage(), "mqtt_endpoint", endpoint));
}
private void processStatusMessage(Mower mower) {
updateStateCfg(mower);
updateStateDat(mower);
thing.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, mower.getFirmwareVersion());
}
/**
* Start scheduled jobs.
* Jobs are only started if interval > 0
*/
private void startScheduledJobs(WorxLandroidBridgeHandler bridgeHandler, Mower theMower,
MowerConfiguration config) {
if (config.refreshStatusInterval > 0) {
refreshJob = scheduler.scheduleWithFixedDelay(() -> {
try {
ProductItemStatus product = bridgeHandler.retrieveDeviceStatus(config.serialNumber);
updateChannelDateTime(GROUP_COMMON, CHANNEL_ONLINE_TIMESTAMP, Instant.now());
updateChannelOnOff(GROUP_COMMON, CHANNEL_ONLINE, product != null && product.online);
updateStatus(product != null ? ThingStatus.ONLINE : ThingStatus.OFFLINE);
} catch (WebApiException e) {
logger.debug("Refreshing Thing {} failed, handler might be OFFLINE", config.serialNumber);
}
}, 3, config.refreshStatusInterval, TimeUnit.SECONDS);
}
if (config.pollingInterval > 0) {
pollingJob = scheduler.scheduleWithFixedDelay(() -> sendCommand(theMower, EMPTY_PAYLOAD), 5,
config.pollingInterval, TimeUnit.SECONDS);
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
initializeData(bridgeHandler);
}
}
@Override
protected void internalHandleCommand(@Nullable String groupId, String channelId, Command command) {
Mower theMower = mower;
if (theMower == null) {
return;
}
if (GROUP_MULTI_ZONES.equals(groupId)) {
handleMultiZonesCommand(theMower, channelId, command);
} else if (GROUP_AWS.equals(groupId)) {
handleAWSCommand(theMower, channelId);
} else if (GROUP_SCHEDULE.equals(groupId)) {
handleScheduleCommand(theMower, channelId, Integer.parseInt(command.toString()));
} else if (GROUP_ONE_TIME.equals(groupId)) {
handleOneTimeSchedule(theMower, channelId, command);
} else if (GROUP_COMMON.equals(groupId)) {
handleCommonGroup(theMower, channelId, command);
} else if (groupId != null && groupId.contains("day")) {
setScheduledDays(theMower, groupId, channelId, command);
sendCommand(theMower,
theMower.scheduler2Supported()
? new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1(),
theMower.getScheduleArray2())
: new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1()));
} else if (CHANNEL_DELAY.equals(channelId)) {
int delaySec = commandToInt(command, Units.SECOND);
sendCommand(theMower, new SetRainDelay(delaySec));
} else if (CHANNEL_BLADE_TIME.equals(channelId) || CHANNEL_CHARGE_CYCLES.equals(channelId)) {
resetStat(channelId, theMower.getSerialNumber());
} else {
logger.debug("command for channel {} not supported: {}", channelId, command);
}
}
private void handleAWSCommand(Mower theMower, String channel) {
if (CHANNEL_POLL.equals(channel)) {
sendCommand(theMower, EMPTY_PAYLOAD);
updateState(CHANNEL_POLL, OnOffType.OFF);
} else {
logger.warn("No action identified on channel {}", channel);
}
}
private void handleCommonGroup(Mower theMower, String channel, Command command) {
if (CHANNEL_ACTION.equals(channel)) {
WorxLandroidActionCodes actionCode = WorxLandroidActionCodes.valueOf(command.toString());
sendCommand(theMower, new MowerCommand(actionCode));
} else if (CHANNEL_LOCK.equals(channel)) {
WorxLandroidActionCodes lockCode = OnOffType.ON.equals(command) ? WorxLandroidActionCodes.LOCK
: WorxLandroidActionCodes.UNLOCK;
sendCommand(theMower, new MowerCommand(lockCode));
} else if (CHANNEL_ENABLE.equals(channel)) {
theMower.setEnable(OnOffType.ON.equals(command));
sendCommand(theMower,
theMower.scheduler2Supported()
? new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1(),
theMower.getScheduleArray2())
: new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1()));
} else {
logger.warn("No action identified for command {} on channel {}", command, channel);
}
}
private void handleOneTimeSchedule(Mower theMower, String channel, Command command) {
if (CHANNEL_DURATION.equals(channel)) {
sendCommand(theMower, new OneTimeCommand(0, Integer.parseInt(command.toString())));
} else if (CHANNEL_EDGECUT.equals(channel)) {
sendCommand(theMower, new OneTimeCommand(OnOffType.ON.equals(command) ? 1 : 0, 0));
} else {
logger.warn("No action identified for command {} on channel {}", command, channel);
}
}
private void handleScheduleCommand(Mower theMower, String channel, int command) {
if (CHANNEL_MODE.equals(channel)) {
sendCommand(theMower, new ScheduleCommand(command));
} else if (CHANNEL_TIME_EXTENSION.equals(channel)) {
theMower.setTimeExtension(command);
sendCommand(theMower,
theMower.scheduler2Supported()
? new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1(),
theMower.getScheduleArray2())
: new ScheduleDaysCommand(theMower.getTimeExtension(), theMower.getScheduleArray1()));
} else {
logger.warn("No action identified for command {} on channel {}", command, channel);
}
}
private void handleMultiZonesCommand(Mower theMower, String channel, Command command) {
if (CHANNEL_ENABLE.equals(channel)) {
theMower.setMultiZoneEnable(OnOffType.ON.equals(command));
sendCommand(theMower, new ZoneMeterCommand(theMower.getZoneMeters()));
} else if (CHANNEL_LAST_ZONE.equals(channel)) {
if (!WorxLandroidStatusCodes.HOME.equals(theMower.getStatusCode())) {
logger.warn("Cannot start zone because mower must be at HOME!");
return;
}
theMower.setZoneTo(Integer.parseInt(command.toString()));
sendCommand(theMower, new ZoneMeterCommand(theMower.getZoneMeters()));
scheduler.schedule(() -> sendCommand(theMower, new MowerCommand(WorxLandroidActionCodes.START)), 2000,
TimeUnit.MILLISECONDS);
} else {
String[] names = channel.split("-");
int index = Integer.valueOf(names[1]);
if (CHANNEL_PREFIX_ZONE.startsWith(names[0])) {
int meterValue = commandToInt(command, SIUnits.METRE);
theMower.setZoneMeter(index - 1, meterValue);
sendCommand(theMower, new ZoneMeterCommand(theMower.getZoneMeters()));
} else if (CHANNEL_PREFIX_ALLOCATION.startsWith(names[0])) {
theMower.setAllocation(index, Integer.parseInt(command.toString()));
sendCommand(theMower, new ZoneMeterAlloc(theMower.getAllocations()));
} else {
logger.warn("No action identified for command {} on channel {}", command, channel);
}
}
}
private int commandToInt(Command command, @Nullable Unit<?> targetUnit) {
if (command instanceof QuantityType<?> qtty && targetUnit != null) {
QuantityType<?> inTarget = qtty.toUnit(targetUnit);
if (inTarget != null) {
return inTarget.intValue();
}
}
return Integer.parseInt(command.toString());
}
/**
* Set scheduled days
*
* @param theMower
*
* @param scDaysIndex 1 or 2
* @param channelUID
* @param command
*/
private void setScheduledDays(Mower theMower, String groupId, String channelId, Command command) {
int scDaysSlot = groupId.endsWith("2") ? 2 : 1;
WorxLandroidDayCodes dayCodeUpdated = WorxLandroidDayCodes
.valueOf(groupId.replace("2", "").toUpperCase(Locale.ROOT));
ScheduledDay scheduledDayUpdated = theMower.getScheduledDay(scDaysSlot, dayCodeUpdated);
if (scheduledDayUpdated == null) {
return;
}
if (CHANNEL_ENABLE.equals(channelId)) {
scheduledDayUpdated.setEnable(OnOffType.ON.equals(command));
} else if (CHANNEL_TIME.equals(channelId)) {
if (command instanceof DateTimeType dateTime) {
scheduledDayUpdated.setStartTime(dateTime.getInstant());
} else if (command instanceof StringType stringType) {
scheduledDayUpdated.setStartTime(stringType.toString());
} else {
logger.warn("Incorrect command {} on channel {}:{} ", command, groupId, channelId);
}
} else if (CHANNEL_DURATION.equals(channelId)) {
scheduledDayUpdated.setDuration(Integer.parseInt(command.toString()));
} else if (CHANNEL_EDGECUT.equals(channelId)) {
scheduledDayUpdated.setEdgecut(OnOffType.ON.equals(command));
}
}
private void sendCommand(Mower theMower, Object command) {
logger.debug("send command: {}", command);
publishMessage(theMower.getMqttCommandIn(), command);
}
/**
* Update states for data values
*
* @param theMower
*
* @param dat
*/
private void updateStateDat(Mower theMower) {
updateChannelQuantity(GROUP_ORIENTATION, CHANNEL_PITCH, theMower.getAngle(Axis.PITCH), Units.DEGREE_ANGLE);
updateChannelQuantity(GROUP_ORIENTATION, CHANNEL_ROLL, theMower.getAngle(Axis.ROLL), Units.DEGREE_ANGLE);
updateChannelQuantity(GROUP_ORIENTATION, CHANNEL_YAW, theMower.getAngle(Axis.YAW), Units.DEGREE_ANGLE);
updateChannelEnum(GROUP_COMMON, CHANNEL_STATUS_CODE, theMower.getPayloadDat().statusCode);
updateChannelEnum(GROUP_COMMON, CHANNEL_ERROR_CODE, theMower.getPayloadDat().errorCode);
updateChannelDecimal(GROUP_MULTI_ZONES, CHANNEL_LAST_ZONE, theMower.getLastZone());
updateChannelDecimal(GROUP_BATTERY, CHANNEL_CHARGE_CYCLES, theMower.getCurrentChargeCycles());
updateChannelDecimal(GROUP_BATTERY, CHANNEL_CHARGE_CYCLES_TOTAL, theMower.getTotalChargeCycles());
updateChannelQuantity(GROUP_METRICS, CHANNEL_BLADE_TIME, theMower.getCurrentBladeTime(), Units.MINUTE);
updateChannelQuantity(GROUP_METRICS, CHANNEL_BLADE_TIME_TOTAL, theMower.getTotalBladeTime(), Units.MINUTE);
theMower.getBattery().ifPresent(battery -> {
updateChannelQuantity(GROUP_BATTERY, CHANNEL_TEMPERATURE, battery.temp != -1 ? battery.temp : null,
SIUnits.CELSIUS);
updateChannelQuantity(GROUP_BATTERY, CHANNEL_VOLTAGE, battery.voltage != -1 ? battery.voltage : null,
Units.VOLT);
updateChannelDecimal(GROUP_BATTERY, CHANNEL_LEVEL, battery.level);
updateChannelOnOff(GROUP_BATTERY, CHANNEL_CHARGING, battery.charging);
});
theMower.getStats().ifPresent(stats -> {
updateChannelQuantity(GROUP_METRICS, CHANNEL_DISTANCE,
stats.distanceCovered != -1 ? stats.distanceCovered : null, SIUnits.METRE);
updateChannelQuantity(GROUP_METRICS, CHANNEL_TOTAL_TIME,
stats.mowerWorkTime != -1 ? stats.mowerWorkTime : null, Units.MINUTE);
});
int rssi = theMower.getPayloadDat().wifiQuality;
updateChannelDecimal(GROUP_WIFI, CHANNEL_WIFI_QUALITY, rssi <= 0 ? toQoS(rssi) : null);
updateChannelQuantity(GROUP_WIFI, CHANNEL_RSSI,
rssi <= 0 ? new QuantityType<>(rssi, Units.DECIBEL_MILLIWATTS) : null);
if (theMower.lockSupported()) {
updateChannelOnOff(GROUP_COMMON, CHANNEL_LOCK, theMower.getPayloadDat().isLocked());
}
theMower.getRain().ifPresent(rain -> {
if (theMower.rainDelayStartSupported()) {
updateChannelOnOff(GROUP_RAIN, CHANNEL_RAIN_STATE, rain.raining);
updateChannelQuantity(GROUP_RAIN, CHANNEL_RAIN_COUNTER, rain.counter, Units.MINUTE);
}
});
}
/**
* Update states for cfg values
*
* @param theMower
*
* @param cfg
* @param zoneId
*/
private void updateStateCfg(Mower theMower) {
updateChannelDateTime(GROUP_CONFIG, CHANNEL_TIMESTAMP, theMower.getLastUpdate());
theMower.getOneTimeSchedule().ifPresent(ots -> {
updateChannelOnOff(GROUP_ONE_TIME, CHANNEL_EDGECUT, ots.getEdgeCut());
updateChannelQuantity(GROUP_ONE_TIME, CHANNEL_DURATION, ots.duration != -1 ? ots.duration : null,
Units.MINUTE);
});
theMower.getSchedule().ifPresent(schedule -> {
if (theMower.oneTimeSchedulerSupported()) {
updateChannelEnum(GROUP_SCHEDULE, CHANNEL_MODE, schedule.scheduleMode);
}
if (schedule.timeExtension != -1) {
updateChannelQuantity(GROUP_SCHEDULE, CHANNEL_TIME_EXTENSION, schedule.timeExtension, Units.PERCENT);
updateChannelOnOff(GROUP_COMMON, CHANNEL_ENABLE, theMower.isEnable());
}
if (schedule.dd != null) {
updateStateCfgScDays(theMower, 2, schedule.dd);
}
if (schedule.d != null) {
updateStateCfgScDays(theMower, 1, schedule.d);
}
});
int command = theMower.getPayloadCfg().cmd;
updateChannelDecimal(GROUP_CONFIG, CHANNEL_COMMAND, command != -1 ? command : null);
if (theMower.multiZoneSupported()) {
for (int zoneIndex = 0; zoneIndex < theMower.getZonesSize(); zoneIndex++) {
updateChannelQuantity(GROUP_MULTI_ZONES, CHANNEL_PREFIX_ZONE.formatted(zoneIndex + 1),
theMower.getZoneMeter(zoneIndex), SIUnits.METRE);
}
for (int allocationIndex = 0; allocationIndex < theMower.getAllocationsSize(); allocationIndex++) {
updateChannelDecimal(GROUP_MULTI_ZONES, CHANNEL_PREFIX_ALLOCATION.formatted(allocationIndex),
theMower.getAllocation(allocationIndex));
}
updateChannelOnOff(GROUP_MULTI_ZONES, CHANNEL_ENABLE, theMower.isMultiZoneEnable());
}
int rainDelay = theMower.getPayloadCfg().rainDelay;
updateChannelQuantity(GROUP_RAIN, CHANNEL_DELAY,
theMower.rainDelaySupported() && rainDelay != -1 ? rainDelay : null, Units.MINUTE);
}
/**
* @param theMower
* @param scDSlot scheduled day slot
* @param d scheduled day
*/
private void updateStateCfgScDays(Mower theMower, int scDSlot, List<List<String>> d) {
List<ZonedDateTime> nextStarts = new ArrayList<>();
List<ZonedDateTime> nextEnds = new ArrayList<>();
for (WorxLandroidDayCodes dayCode : WorxLandroidDayCodes.values()) {
ScheduledDay scheduledDay = theMower.getScheduledDay(scDSlot, dayCode);
if (scheduledDay == null) {
return;
}
String groupName = "%s%s".formatted(dayCode.getDescription().toLowerCase(Locale.ROOT),
scDSlot == 1 ? "" : String.valueOf(scDSlot));
updateChannelOnOff(groupName, CHANNEL_ENABLE, scheduledDay.isEnabled());
updateChannelOnOff(groupName, CHANNEL_EDGECUT, scheduledDay.isEdgecut());
updateChannelQuantity(groupName, CHANNEL_DURATION, scheduledDay.getDuration(), Units.MINUTE);
ZonedDateTime now = ZonedDateTime.now(timeZoneProvider.getTimeZone());
if (scheduledDay.isEnabled()) {
ZonedDateTime scheduleStart = now.truncatedTo(ChronoUnit.MINUTES).with(scheduledDay.getStartTime());
scheduleStart = ZonedDateTime.from(dayCode.dayOfWeek.adjustInto(scheduleStart));
updateChannelDateTime(groupName, CHANNEL_TIME, scheduleStart);
ZonedDateTime scheduleEnd = scheduleStart.plusMinutes(scheduledDay.getDuration());
if (scheduleStart.isBefore(now)) {
scheduleStart = scheduleStart.plusDays(7);
}
if (scheduleEnd.isBefore(now)) {
scheduleEnd = scheduleEnd.plusDays(7);
}
nextStarts.add(scheduleStart);
nextEnds.add(scheduleEnd);
}
}
if (!nextStarts.isEmpty()) {
Collections.sort(nextStarts);
Collections.sort(nextEnds);
updateChannelDateTime(GROUP_SCHEDULE, CHANNEL_START, nextStarts.get(0));
updateChannelDateTime(GROUP_SCHEDULE, CHANNEL_STOP, nextEnds.get(0));
}
}
private int toQoS(int rssi) {
return rssi > -50 ? 4 : rssi > -60 ? 3 : rssi > -70 ? 2 : rssi > -85 ? 1 : 0;
}
public void publishMessage(String topic, Object command) {
publishMessage(topic, deserializer.toJson(command));
}
@Override
protected void internalHandlePayload(Payload payload) {
Mower theMower = mower;
if (theMower == null) {
return;
}
theMower.setStatus(payload);
updateStateCfg(theMower);
updateStateDat(theMower);
}
private boolean resetStat(String channelId, String serialNumber) {
WorxLandroidBridgeHandler bridgeHandler = getBridgeHandler(getBridge(), WorxLandroidBridgeHandler.class);
if (bridgeHandler != null) {
logger.debug("Resetting {}", channelId);
if (CHANNEL_BLADE_TIME.equals(channelId)) {
bridgeHandler.resetBladeTime(serialNumber);
} else {
bridgeHandler.resetBatteryCycles(serialNumber);
}
}
return true;
}
}
@@ -0,0 +1,185 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.mqtt;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.ThreadPoolManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.crt.CRT;
import software.amazon.awssdk.crt.http.HttpRequest;
import software.amazon.awssdk.crt.mqtt.MqttClientConnection;
import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents;
import software.amazon.awssdk.crt.mqtt.MqttException;
import software.amazon.awssdk.crt.mqtt.MqttMessage;
import software.amazon.awssdk.crt.mqtt.OnConnectionFailureReturn;
import software.amazon.awssdk.crt.mqtt.OnConnectionSuccessReturn;
import software.amazon.awssdk.crt.mqtt.QualityOfService;
import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder;
/**
* {@link AWSClient} AWS client
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public class AWSClient implements MqttClientConnectionEvents {
private static final QualityOfService QOS = QualityOfService.AT_MOST_ONCE;
private static final String AUTHORIZER_NAME = "com-worxlandroid-customer";
private static final String MQTT_USERNAME = "openhab";
private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("AWSClient");
private final Map<String, Consumer<MqttMessage>> subscriptions = new HashMap<>();
private final Logger logger = LoggerFactory.getLogger(AWSClient.class);
private final AWSClientCallbackI clientCallback;
private @Nullable MqttClientConnection mqttClient;
private @Nullable Instant lastResumed;
private boolean connected;
public AWSClient(AWSClientCallbackI clientCallback) {
this.clientCallback = clientCallback;
}
public void connect(String endpoint, String userId, String productUuid, String token) {
String[] tok = token.replaceAll("_", "/").replaceAll("-", "+").split("\\.");
try {
MqttClientConnection connection = AwsIotMqttConnectionBuilder.newDefaultBuilder()
.withClientId("WX/USER/%s/%s/%s".formatted(userId, MQTT_USERNAME, productUuid))
.withEndpoint(endpoint).withUsername(MQTT_USERNAME).withCleanSession(false).withKeepAliveSecs(300)
.withConnectionEventCallbacks(this).withWebsockets(true)
.withWebsocketHandshakeTransform(handshakeArgs -> {
HttpRequest httpRequest = handshakeArgs.getHttpRequest();
httpRequest.addHeader("x-amz-customauthorizer-name", AUTHORIZER_NAME);
httpRequest.addHeader("x-amz-customauthorizer-signature", tok[2]);
httpRequest.addHeader("jwt", tok[0] + "." + tok[1]);
handshakeArgs.complete(httpRequest);
}).build();
connection.connect().get();
this.mqttClient = connection;
} catch (MqttException | UnsupportedEncodingException | InterruptedException | ExecutionException e) {
clientCallback.onAWSConnectionFailed(e.getMessage());
}
}
public void dispose() {
disconnect();
subscriptions.clear();
}
@Override
public void onConnectionSuccess(@NonNullByDefault({}) OnConnectionSuccessReturn data) {
onConnectionResumed(data.getSessionPresent());
}
@Override
public void onConnectionResumed(boolean sessionPresent) {
connected = sessionPresent;
if (sessionPresent) {
lastResumed = Instant.now();
logger.debug("last connection resume {}", lastResumed);
subscriptions.forEach(this::subscribe);
clientCallback.onAWSConnectionSuccess();
} else {
clientCallback.onAWSConnectionClosed();
}
}
@Override
public void onConnectionInterrupted(int errorCode) {
Instant interrupted = Instant.now();
connected = false;
String error = CRT.awsErrorString(errorCode);
logger.debug("connection interrupted errorcode: {} : {}", errorCode, error);
scheduler.schedule(() -> {
/**
* workaround -> after 20 minutes the connection is interrupted but immediately resumed (~0,5sec).
* ConnectionBuilder with ".withKeepAliveSecs(300)" doesn't work
*/
Instant resumed = lastResumed;
boolean isBetween = resumed != null && resumed.isAfter(interrupted) && resumed.isBefore(Instant.now());
logger.debug("lastResumed: {} interrupted: {} in: {}", lastResumed, interrupted, isBetween);
if (!isBetween) {
clientCallback.onAWSConnectionClosed();
}
}, 5, TimeUnit.SECONDS);
}
@Override
public void onConnectionFailure(@NonNullByDefault({}) OnConnectionFailureReturn data) {
connected = false;
if (data.getErrorCode() == 5134) {
clientCallback.onAWSConnectionFailed("Error code 5134: banned 24h");
} else {
logger.debug("{}", data.toString());
clientCallback.onAWSConnectionClosed();
}
};
public void disconnect() {
MqttClientConnection connection = mqttClient;
if (connection != null) {
connection.disconnect();
connection.close();
mqttClient = null;
}
connected = false;
}
public void subscribe(String topic, Consumer<MqttMessage> handler) {
MqttClientConnection connection = mqttClient;
if (connection != null) {
subscriptions.put(topic, handler);
connection.subscribe(topic, QOS, handler);
} else {
logger.warn("Tried to subscribe on {} when connection is closed", topic);
}
}
public void unsubscribe(String topic) {
MqttClientConnection connection = mqttClient;
if (connection != null) {
subscriptions.remove(topic);
connection.unsubscribe(topic);
} else {
logger.warn("Tried to unsubscribe from {} when connection is closed", topic);
}
}
public void publish(String topic, String payload) {
MqttClientConnection connection = mqttClient;
if (connection != null) {
connection.publish(new MqttMessage(topic, payload.getBytes(StandardCharsets.UTF_8), QOS));
} else {
logger.warn("Tried to publish on {} when connection is closed", topic);
}
}
public boolean isConnected() {
return connected;
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.mqtt;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link AWSClientCallbackI} Callback for AWS connection events
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public interface AWSClientCallbackI {
/**
* callback method on connection success
*/
public void onAWSConnectionSuccess();
/**
* callback method on connection closed
*/
public void onAWSConnectionClosed();
/**
* callback method on connection failed
*/
public void onAWSConnectionFailed(@Nullable String message);
}
@@ -0,0 +1,413 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.vo;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.worxlandroid.internal.api.dto.Commands.ZoneMeterCommand;
import org.openhab.binding.worxlandroid.internal.api.dto.LastStatus;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Battery;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Cfg;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Dat;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Dat.Axis;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Ots;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Rain;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Schedule;
import org.openhab.binding.worxlandroid.internal.api.dto.Payload.Stat;
import org.openhab.binding.worxlandroid.internal.api.dto.ProductItemStatus;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidDayCodes;
import org.openhab.binding.worxlandroid.internal.codes.WorxLandroidStatusCodes;
import org.openhab.binding.worxlandroid.internal.handler.WorxLandroidMowerHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link Mower}
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public class Mower {
private static final int[] MULTI_ZONE_METER_DISABLE = { 0, 0, 0, 0 };
private static final int[] MULTI_ZONE_METER_ENABLE = { 1, 0, 0, 0 };
private static final int TIME_EXTENSION_DISABLE = -100;
private final Logger logger = LoggerFactory.getLogger(Mower.class);
private final WorxLandroidMowerHandler mowerHandler;
private final ProductItemStatus product;
private final int[] zoneMeter;
private final int[] zoneMeterRestore;
private final int[] allocations = new int[10];
private final List<Map<WorxLandroidDayCodes, @Nullable ScheduledDay>> schedules = new ArrayList<>();
private boolean multiZoneEnable;
private int timeExtension;
private int timeExtensionRestore = 0;
private @NonNullByDefault({}) LastStatus lastStatus;
private boolean restoreZoneMeter = false;
private int[] zoneMeterRestoreValues = {};
public Mower(WorxLandroidMowerHandler mowerHandler, ProductItemStatus product) {
this.mowerHandler = mowerHandler;
this.product = product;
this.zoneMeter = new int[getMultiZoneCount()];
this.zoneMeterRestore = new int[getMultiZoneCount()];
schedules.add(new HashMap<WorxLandroidDayCodes, @Nullable ScheduledDay>(7));
if (product.capabilities.contains("scheduler_two_slots")) {
schedules.add(new HashMap<WorxLandroidDayCodes, @Nullable ScheduledDay>(7));
}
setStatus(product.lastStatus.payload);
}
public String getSerialNumber() {
return product.serialNumber;
}
public int getTimeExtension() {
return timeExtension;
}
public String getFirmwareVersion() {
return product.firmwareVersion;
}
/**
* timeExtension = -100 disables mowing (enable=false).
* timeExtension > -100 enables mowing (enable=true).
*
* @param timeExtension
*/
public void setTimeExtension(int timeExtension) {
if (timeExtension == TIME_EXTENSION_DISABLE) {
storeTimeExtension();
}
this.timeExtension = timeExtension;
}
public boolean lockSupported() {
return product.capabilities.contains("lock");
}
public boolean rainDelaySupported() {
return product.capabilities.contains("rain_delay");
}
public boolean rainDelayStartSupported() {
return product.capabilities.contains("rain_delay_start");
}
public boolean multiZoneSupported() {
return product.capabilities.contains("multi_zone");
}
public boolean scheduler2Supported() {
return schedules.size() > 1;
}
public boolean oneTimeSchedulerSupported() {
return product.capabilities.contains("one_time_scheduler");
}
public @Nullable ScheduledDay getScheduledDay(int scDSlot, WorxLandroidDayCodes dayCode) {
return scDSlot == 1 ? schedules.get(0).get(dayCode)
: scheduler2Supported() ? schedules.get(1).get(dayCode) : null;
}
private Object[] getScheduleArray(Map<WorxLandroidDayCodes, @Nullable ScheduledDay> schedules) {
Object[] result = new Object[7];
for (WorxLandroidDayCodes dayCode : WorxLandroidDayCodes.values()) {
ScheduledDay schedule = schedules.get(dayCode);
result[dayCode.code] = schedule != null ? schedule.asArray() : ScheduledDay.BLANK.asArray();
}
return result;
}
public Object[] getScheduleArray1() {
return getScheduleArray(schedules.get(0));
}
public Object[] getScheduleArray2() {
return scheduler2Supported() ? getScheduleArray(schedules.get(1)) : new Object[] {};
}
public boolean isMultiZoneEnable() {
return multiZoneEnable;
}
public void setMultiZoneEnable(boolean multiZoneEnable) {
this.multiZoneEnable = multiZoneEnable;
if (multiZoneEnable && isZoneMeterDisabled()) {
restoreZoneMeter();
if (isZoneMeterDisabled()) {
System.arraycopy(MULTI_ZONE_METER_ENABLE, 0, zoneMeter, 0, zoneMeter.length);
}
} else {
storeZoneMeter();
System.arraycopy(MULTI_ZONE_METER_DISABLE, 0, zoneMeter, 0, zoneMeter.length);
}
}
public int getZoneMeter(int zoneIndex) {
return zoneMeter[zoneIndex];
}
public int[] getZoneMeters() {
return Arrays.copyOf(zoneMeter, zoneMeter.length);
}
public int getZonesSize() {
return getZoneMeters().length;
}
public void setZoneMeters(int[] zoneMeterInput) {
System.arraycopy(zoneMeterInput, 0, zoneMeter, 0, zoneMeter.length);
}
public void setZoneMeter(int zoneIndex, int meter) {
zoneMeter[zoneIndex] = meter;
this.multiZoneEnable = !isZoneMeterDisabled();
}
public int getAllocation(int allocationIndex) {
return allocations[allocationIndex];
}
public int[] getAllocations() {
return Arrays.copyOf(allocations, allocations.length);
}
public int getAllocationsSize() {
return getZoneMeters().length;
}
public void setAllocation(int allocationIndex, int zoneIndex) {
allocations[allocationIndex] = zoneIndex;
}
public boolean isEnable() {
return timeExtension != TIME_EXTENSION_DISABLE;
}
/**
* Enable/Disables mowing using timeExtension.
* disable: timeExtension = -100
* enable: timeExtension > -100
*
*/
public void setEnable(boolean enable) {
if (enable && timeExtension == TIME_EXTENSION_DISABLE) {
restoreTimeExtension();
} else {
storeTimeExtension();
timeExtension = TIME_EXTENSION_DISABLE;
}
}
/**
* Stores timeExtension to timeExtensionRestore for restore,
*/
private void storeTimeExtension() {
if (this.timeExtension > TIME_EXTENSION_DISABLE) {
this.timeExtensionRestore = this.timeExtension;
}
}
/**
* Restores timeExtension from timeExtensionRestore.
*/
private void restoreTimeExtension() {
this.timeExtension = this.timeExtensionRestore;
}
/**
* Stores zoneMeter to zoneMeterRestore for restore,
*/
private void storeZoneMeter() {
if (!isZoneMeterDisabled()) {
System.arraycopy(zoneMeter, 0, zoneMeterRestore, 0, zoneMeter.length);
}
}
/**
* Restores zoneMeter from zoneMeterRestore.
*/
private void restoreZoneMeter() {
System.arraycopy(zoneMeterRestore, 0, zoneMeter, 0, zoneMeter.length);
}
/**
* @return false if less than 2 meters are 0
*/
private boolean isZoneMeterDisabled() {
return Arrays.stream(zoneMeter).sum() == 0;
}
public int getMultiZoneCount() {
return multiZoneSupported() ? product.lastStatus.payload.cfg.multizoneAllocations.size() : 0;
}
public String getMqttCommandIn() {
return product.mqttTopics.commandIn;
}
public String getMqttCommandOut() {
return product.mqttTopics.commandOut;
}
public String getMacAddress() {
return product.macAddress;
}
public String getId() {
return product.id;
}
public String getLanguage() {
return getPayload().cfg.lg;
}
public Payload getPayload() {
return lastStatus.payload;
}
public Dat getPayloadDat() {
return getPayload().dat;
}
public Cfg getPayloadCfg() {
return getPayload().cfg;
}
public void setStatus(Payload payload) {
this.lastStatus = new LastStatus(payload);
if (restoreZoneMeter && getStatusCode() != WorxLandroidStatusCodes.HOME
&& getStatusCode() != WorxLandroidStatusCodes.START_SEQUENCE
&& getStatusCode() != WorxLandroidStatusCodes.LEAVING_HOME
&& getStatusCode() != WorxLandroidStatusCodes.SEARCHING_ZONE) {
restoreZoneMeter = false;
setZoneMeters(zoneMeterRestoreValues);
sendCommand(new ZoneMeterCommand(getZoneMeters()));
}
getSchedule().ifPresent(schedule -> {
setTimeExtension(schedule.timeExtension);
if (schedule.d != null) {
updateSchedules(0, schedule.d);
if (schedule.dd != null) {
updateSchedules(1, schedule.dd);
}
}
});
Cfg cfg = getPayloadCfg();
if (multiZoneSupported()) {
for (int zoneIndex = 0; zoneIndex < cfg.multiZones.size(); zoneIndex++) {
setZoneMeter(zoneIndex, cfg.multiZones.get(zoneIndex));
}
for (int allocationIndex = 0; allocationIndex < cfg.multizoneAllocations.size(); allocationIndex++) {
setAllocation(allocationIndex, cfg.multizoneAllocations.get(allocationIndex));
}
}
}
private void updateSchedules(int scDSlot, List<List<String>> d) {
Map<WorxLandroidDayCodes, @Nullable ScheduledDay> planning = schedules.get(scDSlot);
EnumSet.allOf(WorxLandroidDayCodes.class).stream().forEach(dayCode -> {
List<String> schedule = d.get(dayCode.code);
planning.put(dayCode,
new ScheduledDay(schedule.get(0), Integer.valueOf(schedule.get(1)), "1".equals(schedule.get(2))));
});
}
public Optional<Battery> getBattery() {
return Optional.ofNullable(getPayloadDat().battery);
}
public Optional<Rain> getRain() {
return Optional.ofNullable(getPayloadDat().rain);
}
public double getAngle(Axis axis) {
return getPayloadDat().getAngle(axis);
}
public Optional<Stat> getStats() {
return Optional.ofNullable(getPayloadDat().st);
}
public int getLastZone() {
return getAllocation(getPayloadDat().lastZone);
}
public long getCurrentBladeTime() {
return getTotalBladeTime() - product.bladeWorkTimeReset;
}
public long getTotalBladeTime() {
return lastStatus.payload.dat.st.bladeWorkTime;
}
public int getCurrentChargeCycles() {
return product.batteryChargeCycles - product.batteryChargeCyclesReset;
}
public int getTotalChargeCycles() {
return lastStatus.payload.dat.battery.chargeCycle;
}
public WorxLandroidStatusCodes getStatusCode() {
return getPayloadDat().statusCode;
}
public void setZoneTo(int zoneIndex) {
zoneMeterRestoreValues = getZoneMeters();
restoreZoneMeter = true;
int meter = getZoneMeter(zoneIndex);
for (int index = 0; index < 4; index++) {
setZoneMeter(index, meter);
}
}
private void sendCommand(Object command) {
logger.debug("send command: {}", command);
mowerHandler.publishMessage(getMqttCommandIn(), command);
}
public ZonedDateTime getLastUpdate() {
return getPayloadCfg().getDateTime().atZone(product.timeZone);
}
public Optional<Schedule> getSchedule() {
return Optional.ofNullable(getPayloadCfg().sc);
}
public Optional<Ots> getOneTimeSchedule() {
return getSchedule().isPresent() ? Optional.ofNullable(getSchedule().get().ots) : Optional.empty();
}
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.worxlandroid.internal.vo;
import java.time.Instant;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link ScheduledDay} holds data of the schedule details for a given day
*
* @author Nils Billing - Initial contribution
*/
@NonNullByDefault
public class ScheduledDay {
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
private static final int DEFAULT_DURATION = 15;
public static final ScheduledDay BLANK = new ScheduledDay("00:00", 0, false);
private LocalTime startTime = LocalTime.MIN;
private boolean edgecut;
private int durationRestore = DEFAULT_DURATION;
private int duration;
public ScheduledDay(String hhMm, int newDuration, boolean edgecut) {
this.startTime = LocalTime.parse(hhMm);
this.duration = newDuration;
this.edgecut = edgecut;
}
public LocalTime getStartTime() {
return startTime;
}
public void setStartTime(String hhMm) throws DateTimeParseException {
startTime = LocalTime.parse(hhMm);
}
public void setStartTime(Instant instant) {
startTime = instant.atZone(ZoneOffset.UTC).toLocalTime();
}
public int getDuration() {
return duration;
}
public void setDuration(int newDuration) {
if (newDuration == 0 && duration > 0) {
durationRestore = duration;
}
duration = newDuration;
}
public boolean isEdgecut() {
return edgecut;
}
public void setEdgecut(boolean edgecut) {
this.edgecut = edgecut;
}
public boolean isEnabled() {
return duration != 0;
}
public void setEnable(boolean newStatus) {
setDuration(newStatus && duration == 0 ? durationRestore : 0);
}
public Object[] asArray() {
return new Object[] { startTime.format(TIME_FORMAT), duration, edgecut ? 1 : 0 };
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="worxlandroid" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>WorxLandroid Binding</name>
<description>This is the binding for Worx Landroid.</description>
<connection>cloud</connection>
</addon:addon>
@@ -0,0 +1,356 @@
# add-on
addon.worxlandroid.name = WorxLandroid Binding
addon.worxlandroid.description = This is the binding for Worx Landroid.
# thing types
thing-type.worxlandroid.bridge.label = Bridge Worx Landroid API
thing-type.worxlandroid.bridge.description = Represents the API and handler for Worx Landroid.
thing-type.worxlandroid.mower.label = Landroid Mower
thing-type.worxlandroid.mower.description = Represents a Landroid Worx Mower
thing-type.worxlandroid.mower.group.friday.label = Friday Schedule
thing-type.worxlandroid.mower.group.friday2.label = Friday Slot 2 Schedule
thing-type.worxlandroid.mower.group.monday.label = Monday Schedule
thing-type.worxlandroid.mower.group.monday2.label = Monday Slot 2 Schedule
thing-type.worxlandroid.mower.group.saturday.label = Saturday Schedule
thing-type.worxlandroid.mower.group.saturday2.label = Saturday Slot 2 Schedule
thing-type.worxlandroid.mower.group.sunday.label = Sunday Schedule
thing-type.worxlandroid.mower.group.sunday2.label = Sunday Slot 2 Schedule
thing-type.worxlandroid.mower.group.thursday.label = Thursday Schedule
thing-type.worxlandroid.mower.group.thursday2.label = Thursday Slot 2 Schedule
thing-type.worxlandroid.mower.group.tuesday.label = Tuesday Schedule
thing-type.worxlandroid.mower.group.tuesday2.label = Tuesday Slot 2 Schedule
thing-type.worxlandroid.mower.group.wednesday.label = Wednesday Schedule
thing-type.worxlandroid.mower.group.wednesday2.label = Wednesday Slot 2 Schedule
# thing types config
thing-type.config.worxlandroid.bridge.password.label = Password
thing-type.config.worxlandroid.bridge.password.description = Password to access the Landroid WebAPI.
thing-type.config.worxlandroid.bridge.username.label = Username
thing-type.config.worxlandroid.bridge.username.description = Username to access the Landroid WebAPI.
thing-type.config.worxlandroid.mower.pollingInterval.label = Polling Interval
thing-type.config.worxlandroid.mower.pollingInterval.description = Interval for polling in seconds
thing-type.config.worxlandroid.mower.refreshStatusInterval.label = Refresh Status Interval
thing-type.config.worxlandroid.mower.refreshStatusInterval.description = Interval for refreshing mower status in seconds
thing-type.config.worxlandroid.mower.serialNumber.label = Serial Number
thing-type.config.worxlandroid.mower.serialNumber.description = Serial number of the mower
# channel group types
channel-group-type.worxlandroid.aws-group-type.label = Aws
channel-group-type.worxlandroid.aws-group-type.description = MQTT connection to AWS
channel-group-type.worxlandroid.aws-group-type.channel.connected.label = Connected
channel-group-type.worxlandroid.aws-group-type.channel.connected.description = Connection to AWS is alive
channel-group-type.worxlandroid.aws-group-type.channel.poll.label = Poll AWS
channel-group-type.worxlandroid.aws-group-type.channel.poll.description = Enables or disables polling Worx AWS
channel-group-type.worxlandroid.battery-group-type.label = Battery
channel-group-type.worxlandroid.battery-group-type.description = Battery channels of your mower
channel-group-type.worxlandroid.battery-group-type.channel.charge-cycles.label = Charge Cycles
channel-group-type.worxlandroid.battery-group-type.channel.charge-cycles-total.label = Total Charge Cycles
channel-group-type.worxlandroid.common-group-type.label = Common
channel-group-type.worxlandroid.common-group-type.description = Common channels of the mower
channel-group-type.worxlandroid.common-group-type.channel.enable.label = Mowing enabled
channel-group-type.worxlandroid.common-group-type.channel.online.label = Online
channel-group-type.worxlandroid.common-group-type.channel.online.description = Online status of the mower
channel-group-type.worxlandroid.common-group-type.channel.online-timestamp.label = Online Status Timestamp
channel-group-type.worxlandroid.config-group-type.label = Configuration
channel-group-type.worxlandroid.config-group-type.description = Configuration channels of your mower
channel-group-type.worxlandroid.config-group-type.channel.command.label = Command
channel-group-type.worxlandroid.day-sched-group-type.label = Daily Schedule
channel-group-type.worxlandroid.day-sched-group-type.description = Schedule For Each Day
channel-group-type.worxlandroid.day-sched-group-type.channel.edgecut.label = Edgecut
channel-group-type.worxlandroid.day-sched-group-type.channel.enable.label = Active
channel-group-type.worxlandroid.day-sched-group-type.channel.enable.description = Defines if this day schedule is active or not
channel-group-type.worxlandroid.metrics-group-type.label = Metrics
channel-group-type.worxlandroid.metrics-group-type.description = Stat channels of your mower
channel-group-type.worxlandroid.metrics-group-type.channel.blade-time.label = Current Blade Time
channel-group-type.worxlandroid.metrics-group-type.channel.blade-time-total.label = Total Blade Time
channel-group-type.worxlandroid.metrics-group-type.channel.total-time.label = Total Time
channel-group-type.worxlandroid.multi-zones-group-type.label = Multi-Zone
channel-group-type.worxlandroid.multi-zones-group-type.description = Multi-Zones configuration of your mower
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-0.label = Zone Allocation 1
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-1.label = Zone Allocation 2
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-2.label = Zone Allocation 3
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-3.label = Zone Allocation 4
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-4.label = Zone Allocation 5
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-5.label = Zone Allocation 6
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-6.label = Zone Allocation 7
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-7.label = Zone Allocation 8
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-8.label = Zone Allocation 9
channel-group-type.worxlandroid.multi-zones-group-type.channel.allocation-9.label = Zone Allocation 10
channel-group-type.worxlandroid.multi-zones-group-type.channel.enable.label = Multizone Enabled
channel-group-type.worxlandroid.multi-zones-group-type.channel.last-zone.label = Last Zone
channel-group-type.worxlandroid.multi-zones-group-type.channel.zone-1.label = Meters Zone 1
channel-group-type.worxlandroid.multi-zones-group-type.channel.zone-2.label = Meters Zone 2
channel-group-type.worxlandroid.multi-zones-group-type.channel.zone-3.label = Meters Zone 3
channel-group-type.worxlandroid.multi-zones-group-type.channel.zone-4.label = Meters Zone 4
channel-group-type.worxlandroid.orientation-group-type.label = Orientation
channel-group-type.worxlandroid.orientation-group-type.description = Orientation of your mower
channel-group-type.worxlandroid.orientation-group-type.channel.pitch.label = Pitch
channel-group-type.worxlandroid.orientation-group-type.channel.roll.label = Roll
channel-group-type.worxlandroid.orientation-group-type.channel.yaw.label = Yaw
channel-group-type.worxlandroid.ot-sched-group-type.label = One-Time Schedule
channel-group-type.worxlandroid.ot-sched-group-type.description = One time schedule configuration of your mower
channel-group-type.worxlandroid.ot-sched-group-type.channel.edgecut.label = Schedule Edgecut
channel-group-type.worxlandroid.rain-group-type.label = Rain
channel-group-type.worxlandroid.rain-group-type.description = Data rain channels of your mower
channel-group-type.worxlandroid.sched-group-type.label = Schedule
channel-group-type.worxlandroid.sched-group-type.description = Schedule channels configuration of your mower
channel-group-type.worxlandroid.sched-group-type.channel.next-start.label = Next Start
channel-group-type.worxlandroid.sched-group-type.channel.next-start.description = Next mowing start based on schedule
channel-group-type.worxlandroid.sched-group-type.channel.next-stop.label = Next Stop
channel-group-type.worxlandroid.sched-group-type.channel.next-stop.description = Next mowing stop based on schedule
channel-group-type.worxlandroid.wifi-group-type.label = Wi-Fi Information
# channel types
channel-type.worxlandroid.action-type.label = Action
channel-type.worxlandroid.action-type.description = Action channel for your mower
channel-type.worxlandroid.action-type.state.option.START = Start
channel-type.worxlandroid.action-type.state.option.STOP = Stop
channel-type.worxlandroid.action-type.state.option.HOME = Home
channel-type.worxlandroid.axis-type.label = Axis
channel-type.worxlandroid.battery-temp-type.label = Battery Temperature
channel-type.worxlandroid.battery-temp-type.description = Current temperature of the battery
channel-type.worxlandroid.charging-type.label = Battery Charging
channel-type.worxlandroid.distance-type.label = Total Distance
channel-type.worxlandroid.error-type.label = Error Code
channel-type.worxlandroid.error-type.state.option.UNKNOWN = Unknown
channel-type.worxlandroid.error-type.state.option.NO_ERR = No error
channel-type.worxlandroid.error-type.state.option.TRAPPED = Trapped
channel-type.worxlandroid.error-type.state.option.LIFTED = Lifted
channel-type.worxlandroid.error-type.state.option.WIRE_MISSING = Wire missing
channel-type.worxlandroid.error-type.state.option.OUTSIDE_WIRE = Outside wire
channel-type.worxlandroid.error-type.state.option.RAINING = Raining
channel-type.worxlandroid.error-type.state.option.CLOSE_DOOR_TO_MOW = Close door to mow
channel-type.worxlandroid.error-type.state.option.CLOSE_DOOR_TO_GO_HOME = Close door to go home
channel-type.worxlandroid.error-type.state.option.BLADE_MOTOR_BLOCKED = Blade motor blocked
channel-type.worxlandroid.error-type.state.option.WHEEL_MOTOR_BLOKED = Wheel motor blocked
channel-type.worxlandroid.error-type.state.option.TRAPPED_TIMEOUT = Trapped timeout
channel-type.worxlandroid.error-type.state.option.UPSIDE_DOWN = Upside down
channel-type.worxlandroid.error-type.state.option.BATTERY_LOW = Battery low
channel-type.worxlandroid.error-type.state.option.REVERSE_WIRE = Reverse wire
channel-type.worxlandroid.error-type.state.option.CHARGE_ERROR = Charge error
channel-type.worxlandroid.error-type.state.option.TIMEOUT_FINDING_HOME = Timeout finding home
channel-type.worxlandroid.error-type.state.option.MOWER_LOCKED = Mower locked
channel-type.worxlandroid.error-type.state.option.BATTERY_OVER_TEMPERATURE = Battery over temperature
channel-type.worxlandroid.error-type.state.option.MOWER_OUTSIDE_WIRE = Mower outside wire
channel-type.worxlandroid.lock-type.label = Lock mower
channel-type.worxlandroid.lock-type.description = Lock or unlock your mower.
channel-type.worxlandroid.metrics-duration.label = Time
channel-type.worxlandroid.number-ro.label = A Number
channel-type.worxlandroid.rain-counter.label = Rain Counter
channel-type.worxlandroid.rain-delay-type.label = Delay
channel-type.worxlandroid.rain-state-type.label = State
channel-type.worxlandroid.rssi.label = RSSI
channel-type.worxlandroid.rssi.description = Received signal strength indicator
channel-type.worxlandroid.schedule-duration-type.label = Duration
channel-type.worxlandroid.schedule-mode-type.label = Schedule Mode
channel-type.worxlandroid.schedule-mode-type.description = Sets Normal or Party mode
channel-type.worxlandroid.schedule-mode-type.state.option.NORMAL = Normal
channel-type.worxlandroid.schedule-mode-type.state.option.PARTY = Party
channel-type.worxlandroid.schedule-time.label = Start Time
channel-type.worxlandroid.schedule-time.description = Start time of the mowing on this day
channel-type.worxlandroid.schedule-time.state.pattern = %1$tH:%1$tM
channel-type.worxlandroid.status-type.label = Status Code
channel-type.worxlandroid.status-type.state.option.UNKNOWN = Unknown
channel-type.worxlandroid.status-type.state.option.IDLE = Idle
channel-type.worxlandroid.status-type.state.option.HOME = Home
channel-type.worxlandroid.status-type.state.option.START_SEQUENCE = Start sequence
channel-type.worxlandroid.status-type.state.option.LEAVING_HOME = Leaving home
channel-type.worxlandroid.status-type.state.option.FOLLOW_WIRE = Follow wire
channel-type.worxlandroid.status-type.state.option.SEARCHING_HOME = Searching home
channel-type.worxlandroid.status-type.state.option.SEARCHING_WIRE = Searching wire
channel-type.worxlandroid.status-type.state.option.MOWING = Mowing
channel-type.worxlandroid.status-type.state.option.LIFTED = Lifted
channel-type.worxlandroid.status-type.state.option.TRAPPED = Trapped
channel-type.worxlandroid.status-type.state.option.BLADE_BLOCKED = Blade blocked
channel-type.worxlandroid.status-type.state.option.DEBUG = Debug
channel-type.worxlandroid.status-type.state.option.REMOTE_CONTROL = Remote control
channel-type.worxlandroid.status-type.state.option.ESCAPE_FROM_OLM = Escape from OLM
channel-type.worxlandroid.status-type.state.option.GOING_HOME = Going home
channel-type.worxlandroid.status-type.state.option.ZONE_TRAINING = Zone training
channel-type.worxlandroid.status-type.state.option.BORDER_CUT = Border cut
channel-type.worxlandroid.status-type.state.option.SEARCHING_ZONE = Searching zone
channel-type.worxlandroid.status-type.state.option.PAUSE = Pause
channel-type.worxlandroid.status-type.state.option.MANUAL_STOP = Manual stop
channel-type.worxlandroid.switch-ro.label = Read Only Switch
channel-type.worxlandroid.switch-rw.label = Read Write Switch
channel-type.worxlandroid.time-extension-type.label = Schedule Time Extension
channel-type.worxlandroid.timestamp.label = Last Update
channel-type.worxlandroid.timestamp.description = Last device update
channel-type.worxlandroid.voltage-type.label = Battery Voltage
channel-type.worxlandroid.voltage-type.description = Battery voltage reported by the mower
channel-type.worxlandroid.zone-meter-type.label = Length of the zone
channel-type.worxlandroid.zone-type.label = Zone Number
channel-type.worxlandroid.zone-type.state.option.0 = Zone 1
channel-type.worxlandroid.zone-type.state.option.1 = Zone 2
channel-type.worxlandroid.zone-type.state.option.2 = Zone 3
channel-type.worxlandroid.zone-type.state.option.3 = Zone 4
# channel group types
channel-group-type.worxlandroid.battery-group-type.channel.charge-cycles-current.label = Current Charge Cycles
channel-group-type.worxlandroid.metrics-group-type.channel.blade-time-current.label = Current Blade Time
# thing types config
thing-type.config.worxlandroid.bridge.reconnectInterval.label = Reconnect Interval
thing-type.config.worxlandroid.bridge.reconnectInterval.description = Interval for reconnecting in seconds (after 600 seconds of inactivity, the connection is closed)
# channel group types
channel-group-type.worxlandroid.battery-group-type.channel.charge-cycle.label = Battery Charge Cycle Total
channel-group-type.worxlandroid.battery-group-type.channel.charge-cycle-current.label = Battery Charge Cycle Current
channel-group-type.worxlandroid.common-group-type.channel.poll.label = Poll AWS
channel-group-type.worxlandroid.common-group-type.channel.poll.description = Enables or disables polling Worx AWS
channel-group-type.worxlandroid.metrics-group-type.channel.current-blade-time.label = Current Blade Time
channel-group-type.worxlandroid.metrics-group-type.channel.total-blade-time.label = Total Blade Time
channel-group-type.worxlandroid.rain-group-type.channel.counter.label = Counter
# channel types
channel-type.worxlandroid.schedule-mode-type.state.option.1 = Normal
channel-type.worxlandroid.schedule-mode-type.state.option.2 = Party
# thing types
thing-type.worxlandroid.mower.group.cfgScFriday.label = Schedule For Friday
thing-type.worxlandroid.mower.group.cfgScFriday2.label = Schedule For Friday Slot 2
thing-type.worxlandroid.mower.group.cfgScMonday.label = Schedule For Monday
thing-type.worxlandroid.mower.group.cfgScMonday2.label = Schedule For Monday Slot 2
thing-type.worxlandroid.mower.group.cfgScSaturday.label = Schedule For Saturday
thing-type.worxlandroid.mower.group.cfgScSaturday2.label = Schedule For Saturday Slot 2
thing-type.worxlandroid.mower.group.cfgScSunday.label = Schedule For Sunday
thing-type.worxlandroid.mower.group.cfgScSunday2.label = Schedule For Sunday Slot 2
thing-type.worxlandroid.mower.group.cfgScThursday.label = Schedule For Thursday
thing-type.worxlandroid.mower.group.cfgScThursday2.label = Schedule For Thursday Slot 2
thing-type.worxlandroid.mower.group.cfgScTuesday.label = Schedule For Tuesday
thing-type.worxlandroid.mower.group.cfgScTuesday2.label = Schedule For Tuesday Slot 2
thing-type.worxlandroid.mower.group.cfgScWednesday.label = Schedule For Wednesday
thing-type.worxlandroid.mower.group.cfgScWednesday2.label = Schedule For Wednesday Slot 2
# thing types config
thing-type.config.worxlandroid.mower.reconnectInterval.label = Reconnect Interval
thing-type.config.worxlandroid.mower.reconnectInterval.description = Interval for reconnecting in seconds (after 10 minutes / 600 seconds of inactivity, the connection is closed)
thing-type.config.worxlandroid.bridge.webapiPassword.label = Password
thing-type.config.worxlandroid.bridge.webapiPassword.description = Password to access the Landroid WebAPI.
thing-type.config.worxlandroid.bridge.webapiUsername.label = Username
thing-type.config.worxlandroid.bridge.webapiUsername.description = Username to access the Landroid WebAPI.
# channel group types
channel-group-type.worxlandroid.cfgCommon-type.label = Configuration common channels
channel-group-type.worxlandroid.cfgCommon-type.description = Configuration common channels of your mower
channel-group-type.worxlandroid.cfgMultiZones-type.label = Configuration multi zone channels
channel-group-type.worxlandroid.cfgMultiZones-type.description = Configuration multi zones of your mower
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation0.label = Zone Allocation 1
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation1.label = Zone Allocation 2
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation2.label = Zone Allocation 3
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation3.label = Zone Allocation 4
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation4.label = Zone Allocation 5
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation5.label = Zone Allocation 6
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation6.label = Zone Allocation 7
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation7.label = Zone Allocation 8
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation8.label = Zone Allocation 9
channel-group-type.worxlandroid.cfgMultiZones-type.channel.allocation9.label = Zone Allocation 10
channel-group-type.worxlandroid.cfgMultiZones-type.channel.enable.label = Multizone enabled
channel-group-type.worxlandroid.cfgMultiZones-type.channel.zone1Meter.label = Meters Zone 1
channel-group-type.worxlandroid.cfgMultiZones-type.channel.zone2Meter.label = Meters Zone 2
channel-group-type.worxlandroid.cfgMultiZones-type.channel.zone3Meter.label = Meters Zone 3
channel-group-type.worxlandroid.cfgMultiZones-type.channel.zone4Meter.label = Meters Zone 4
channel-group-type.worxlandroid.cfgOneTimeSc-type.label = Configuration one time schedule channels
channel-group-type.worxlandroid.cfgOneTimeSc-type.description = Configuration one time schedule channels of your mower
channel-group-type.worxlandroid.cfgSc-type.label = Schedule For
channel-group-type.worxlandroid.cfgSc-type.description = Schedule For of your mower
channel-group-type.worxlandroid.cfgScDay-type.label = Schedule For for each day
channel-group-type.worxlandroid.cfgScDay-type.description = Schedule For for each day
channel-group-type.worxlandroid.common-type.label = Common channels
channel-group-type.worxlandroid.common-type.description = Common channels of your mower
channel-group-type.worxlandroid.common-type.channel.enable.label = Mowing enabled
channel-group-type.worxlandroid.datBattery-type.label = Data battery channels
channel-group-type.worxlandroid.datBattery-type.description = Data battery channels of your mower
channel-group-type.worxlandroid.datBattery-type.channel.batteryChargeCycle.label = Battery Charge Cycle Total
channel-group-type.worxlandroid.datBattery-type.channel.batteryChargeCycleCurrent.label = Battery Charge Cycle Current
channel-group-type.worxlandroid.datCommon-type.label = Data common channels
channel-group-type.worxlandroid.datCommon-type.description = Data common channels of your mower
channel-group-type.worxlandroid.datDmp-type.label = Data dmp channels
channel-group-type.worxlandroid.datDmp-type.description = Data dmp channels of your mower
channel-group-type.worxlandroid.datRain-type.label = Data rain channels
channel-group-type.worxlandroid.datRain-type.description = Data rain channels of your mower
channel-group-type.worxlandroid.datSt-type.label = Data st channels
channel-group-type.worxlandroid.datSt-type.description = Data st channels of your mower
channel-group-type.worxlandroid.datSt-type.channel.currentBladeTime.label = Current Blade Time
channel-group-type.worxlandroid.datSt-type.channel.totalBladeTime.label = Total Blade Time
channel-group-type.worxlandroid.datSt-type.channel.totalTime.label = Total Time
# channel types
channel-type.worxlandroid.chAction.label = Action
channel-type.worxlandroid.chAction.description = Action channel for your mower
channel-type.worxlandroid.chAction.state.option.START = Start
channel-type.worxlandroid.chAction.state.option.STOP = Stop
channel-type.worxlandroid.chAction.state.option.HOME = Home
channel-type.worxlandroid.chAllocation.label = Zone Allocation
channel-type.worxlandroid.chAllocation.state.option.0 = Zone 1
channel-type.worxlandroid.chAllocation.state.option.1 = Zone 2
channel-type.worxlandroid.chAllocation.state.option.2 = Zone 3
channel-type.worxlandroid.chAllocation.state.option.3 = Zone 4
channel-type.worxlandroid.chBatteryChargeCycle.label = Battery Charge Cycle
channel-type.worxlandroid.chBatteryCharging.label = Battery Charging
channel-type.worxlandroid.chBatteryLevel.label = Battery Level
channel-type.worxlandroid.chBatteryTemperature.label = Battery Temperature
channel-type.worxlandroid.chBatteryVoltage.label = Battery Voltage
channel-type.worxlandroid.chCommand.label = Command
channel-type.worxlandroid.chEnable.label = Activation / Deactivation
channel-type.worxlandroid.chErrorCode.label = Error Code
channel-type.worxlandroid.chErrorDescription.label = Error Description
channel-type.worxlandroid.chFirmware.label = Firmware
channel-type.worxlandroid.chId.label = Id
channel-type.worxlandroid.chLanguage.label = Language
channel-type.worxlandroid.chLastUpdate.label = Last Update
channel-type.worxlandroid.chLastUpdateOnlineStatus.label = Last Update Online Status
channel-type.worxlandroid.chLastZone.label = Last Zone
channel-type.worxlandroid.chLastZone.state.option.0 = Zone 1
channel-type.worxlandroid.chLastZone.state.option.1 = Zone 2
channel-type.worxlandroid.chLastZone.state.option.2 = Zone 3
channel-type.worxlandroid.chLastZone.state.option.3 = Zone 4
channel-type.worxlandroid.chLock.label = Lock mower
channel-type.worxlandroid.chLock.description = Lock or unlock your mower.
channel-type.worxlandroid.chMacAdress.label = MacAdress
channel-type.worxlandroid.chOnline.label = Online
channel-type.worxlandroid.chPitch.label = Pitch
channel-type.worxlandroid.chPoll.label = Poll Worx AWS
channel-type.worxlandroid.chPoll.description = Poll Worx AWS
channel-type.worxlandroid.chRainCounter.label = Rain counter
channel-type.worxlandroid.chRainDelay.label = Rain Delay
channel-type.worxlandroid.chRainState.label = Rain state
channel-type.worxlandroid.chRoll.label = Roll
channel-type.worxlandroid.chScheduleDuration.label = Schedule Duration
channel-type.worxlandroid.chScheduleEdgecut.label = Schedule Edgecut
channel-type.worxlandroid.chScheduleMode.label = Schedule Mode (Party)
channel-type.worxlandroid.chScheduleMode.state.option.1 = Normal
channel-type.worxlandroid.chScheduleMode.state.option.2 = Party
channel-type.worxlandroid.chScheduleStartHour.label = Schedule Start Hour
channel-type.worxlandroid.chScheduleStartMinutes.label = Schedule Start Minutes
channel-type.worxlandroid.chScheduleTimeExtension.label = Schedule Time Extension
channel-type.worxlandroid.chStatusCode.label = Status Code
channel-type.worxlandroid.chStatusDescription.label = Status Description
channel-type.worxlandroid.chTimeMinutes.label = Time
channel-type.worxlandroid.chTotalDistance.label = Total Distance
channel-type.worxlandroid.chWifiQuality.label = Wifi Quality
channel-type.worxlandroid.chYaw.label = Yaw
channel-type.worxlandroid.chZoneMeter.label = Meters of zone
# error messages
conf-error-no-username = Cannot connect to Landroid bridge as no username is configured
conf-error-no-password = Cannot connect to Landroid bridge as no password is configured
conf-error-no-serial = No serial number configured for this mower
oauth-connection-error = Error getting access token
oauth-refresh-error = To many token refresh failed
oauth-connection-error = Unable to connect to Worx Api (403) - check your credentials
incorrect-bridge = Wrong bridge type
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bindingId="worxlandroid"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="bridge">
<label>Bridge Worx Landroid API</label>
<description>Represents the API and handler for Worx Landroid.</description>
<config-description>
<parameter name="username" type="text">
<label>Username</label>
<description>Username to access the Landroid WebAPI.</description>
<required>true</required>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password</label>
<description>Password to access the Landroid WebAPI.</description>
<required>true</required>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>
@@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="worxlandroid"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="timestamp">
<item-type>DateTime</item-type>
<label>Last Update</label>
<description>Last device update</description>
<category>Time</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="battery-temp-type" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Battery Temperature</label>
<description>Current temperature of the battery</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="voltage-type">
<item-type>Number:ElectricPotential</item-type>
<label>Battery Voltage</label>
<description>Battery voltage reported by the mower</description>
<category>Energy</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="axis-type">
<item-type>Number:Angle</item-type>
<label>Axis</label>
<category>Incline</category>
<state pattern="%.2f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="distance-type">
<item-type>Number:Length</item-type>
<label>Total Distance</label>
<category>oh:worxlandroid:distance</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="metrics-duration">
<item-type>Number:Time</item-type>
<label>Time</label>
<category>time</category>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="status-type">
<item-type>String</item-type>
<label>Status Code</label>
<state readOnly="true" pattern="%s">
<options>
<option value="UNKNOWN">Unknown</option>
<option value="IDLE">Idle</option>
<option value="HOME">Home</option>
<option value="START_SEQUENCE">Start sequence</option>
<option value="LEAVING_HOME">Leaving home</option>
<option value="FOLLOW_WIRE">Follow wire</option>
<option value="SEARCHING_HOME">Searching home</option>
<option value="SEARCHING_WIRE">Searching wire</option>
<option value="MOWING">Mowing</option>
<option value="LIFTED">Lifted</option>
<option value="TRAPPED">Trapped</option>
<option value="BLADE_BLOCKED">Blade blocked</option>
<option value="DEBUG">Debug</option>
<option value="REMOTE_CONTROL">Remote control</option>
<option value="ESCAPE_FROM_OLM">Escape from OLM</option>
<option value="GOING_HOME">Going home</option>
<option value="ZONE_TRAINING">Zone training</option>
<option value="BORDER_CUT">Border cut</option>
<option value="SEARCHING_ZONE">Searching zone</option>
<option value="PAUSE">Pause</option>
<option value="MANUAL_STOP">Manual stop</option>
</options>
</state>
</channel-type>
<channel-type id="error-type">
<item-type>String</item-type>
<label>Error Code</label>
<category>Error</category>
<state readOnly="true" pattern="%s">
<options>
<option value="UNKNOWN">Unknown</option>
<option value="NO_ERR">No error</option>
<option value="TRAPPED">Trapped</option>
<option value="LIFTED">Lifted</option>
<option value="WIRE_MISSING">Wire missing</option>
<option value="OUTSIDE_WIRE">Outside wire</option>
<option value="RAINING">Raining</option>
<option value="CLOSE_DOOR_TO_MOW">Close door to mow</option>
<option value="CLOSE_DOOR_TO_GO_HOME">Close door to go home</option>
<option value="BLADE_MOTOR_BLOCKED">Blade motor blocked</option>
<option value="WHEEL_MOTOR_BLOKED">Wheel motor blocked</option>
<option value="TRAPPED_TIMEOUT">Trapped timeout</option>
<option value="UPSIDE_DOWN">Upside down</option>
<option value="BATTERY_LOW">Battery low</option>
<option value="REVERSE_WIRE">Reverse wire</option>
<option value="CHARGE_ERROR">Charge error</option>
<option value="TIMEOUT_FINDING_HOME">Timeout finding home</option>
<option value="MOWER_LOCKED">Mower locked</option>
<option value="BATTERY_OVER_TEMPERATURE">Battery over temperature</option>
<option value="MOWER_OUTSIDE_WIRE">Mower outside wire</option>
</options>
</state>
</channel-type>
<channel-type id="time-extension-type">
<item-type>Number:Dimensionless</item-type>
<label>Schedule Time Extension</label>
<state readOnly="false" min="-100" max="100" step="10" pattern="%d %%"/>
</channel-type>
<channel-type id="schedule-mode-type">
<item-type>String</item-type>
<label>Schedule Mode</label>
<description>Sets Normal or Party mode</description>
<state readOnly="false">
<options>
<option value="NORMAL">Normal</option>
<option value="PARTY">Party</option>
</options>
</state>
</channel-type>
<channel-type id="schedule-duration-type">
<item-type>Number:Time</item-type>
<label>Duration</label>
<state readOnly="false" min="0" max="1425" step="15" pattern="%d %unit%"/>
</channel-type>
<channel-type id="zone-meter-type">
<item-type>Number:Length</item-type>
<label>Length of the zone</label>
<category>oh:worxlandroid:distance</category>
<state readOnly="false" min="0" step="1" pattern="%d %unit%"/>
</channel-type>
<channel-type id="zone-type">
<item-type>Number</item-type>
<label>Zone Number</label>
<category>oh:worxlandroid:zones</category>
<state readOnly="false">
<options>
<option value="0">Zone 1</option>
<option value="1">Zone 2</option>
<option value="2">Zone 3</option>
<option value="3">Zone 4</option>
</options>
</state>
</channel-type>
<channel-type id="action-type">
<item-type>String</item-type>
<label>Action</label>
<description>Action channel for your mower</description>
<state>
<options>
<option value="START">Start</option>
<option value="STOP">Stop</option>
<option value="HOME">Home</option>
</options>
</state>
</channel-type>
<channel-type id="switch-rw">
<item-type>Switch</item-type>
<label>Read Write Switch</label>
</channel-type>
<channel-type id="switch-ro">
<item-type>Switch</item-type>
<label>Read Only Switch</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="charging-type">
<item-type>Switch</item-type>
<label>Battery Charging</label>
<category>oh:worxlandroid:charging</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="lock-type">
<item-type>Switch</item-type>
<label>Lock mower</label>
<description>Lock or unlock your mower.</description>
<category>oh:worxlandroid:lock</category>
</channel-type>
<channel-type id="number-ro">
<item-type>Number</item-type>
<label>A Number</label>
<category>oh:worxlandroid:counter</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="schedule-time">
<item-type>DateTime</item-type>
<label>Start Time</label>
<description>Start time of the mowing on this day</description>
<category>Time</category>
<state readOnly="false" pattern="%1$tH:%1$tM"/>
</channel-type>
<channel-type id="rain-delay-type">
<item-type>Number:Time</item-type>
<label>Delay</label>
<category>Time</category>
<state min="0" max="750" step="30" pattern="%d %unit%"/>
</channel-type>
<channel-type id="rain-counter">
<item-type>Number:Time</item-type>
<label>Rain Counter</label>
<category>Time</category>
<state min="0" pattern="%d %unit%"/>
</channel-type>
<channel-type id="rain-state-type">
<item-type>Switch</item-type>
<label>State</label>
<category>oh:worxlandroid:rain</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="rssi" advanced="true">
<item-type unitHint="dBm">Number:Power</item-type>
<label>RSSI</label>
<description>Received signal strength indicator</description>
<category>QualityOfService</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
</thing:thing-descriptions>
@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="worxlandroid"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="common-group-type">
<label>Common</label>
<description>Common channels of the mower</description>
<channels>
<channel id="status" typeId="status-type"/>
<channel id="error" typeId="error-type"/>
<channel id="online" typeId="switch-ro">
<label>Online</label>
<description>Online status of the mower</description>
</channel>
<channel id="online-timestamp" typeId="timestamp">
<label>Online Status Timestamp</label>
</channel>
<channel id="action" typeId="action-type"/>
<channel id="enable" typeId="switch-rw">
<label>Mowing enabled</label>
</channel>
<channel id="lock" typeId="lock-type"/>
</channels>
</channel-group-type>
<channel-group-type id="aws-group-type">
<label>Aws</label>
<description>MQTT connection to AWS</description>
<channels>
<channel id="poll" typeId="switch-rw">
<label>Poll AWS</label>
<description>Enables or disables polling Worx AWS</description>
</channel>
<channel id="connected" typeId="switch-ro">
<label>Connected</label>
<description>Connection to AWS is alive</description>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="config-group-type">
<label>Configuration</label>
<description>Configuration channels of your mower</description>
<channels>
<channel id="timestamp" typeId="timestamp"/>
<channel id="command" typeId="number-ro">
<label>Command</label>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="multi-zones-group-type">
<label>Multi-Zone</label>
<description>Multi-Zones configuration of your mower</description>
<channels>
<channel id="enable" typeId="switch-rw">
<label>Multizone Enabled</label>
</channel>
<channel id="last-zone" typeId="zone-type">
<label>Last Zone</label>
</channel>
<channel id="zone-1" typeId="zone-meter-type">
<label>Meters Zone 1</label>
</channel>
<channel id="zone-2" typeId="zone-meter-type">
<label>Meters Zone 2</label>
</channel>
<channel id="zone-3" typeId="zone-meter-type">
<label>Meters Zone 3</label>
</channel>
<channel id="zone-4" typeId="zone-meter-type">
<label>Meters Zone 4</label>
</channel>
<channel id="allocation-0" typeId="zone-type">
<label>Zone Allocation 1</label>
</channel>
<channel id="allocation-1" typeId="zone-type">
<label>Zone Allocation 2</label>
</channel>
<channel id="allocation-2" typeId="zone-type">
<label>Zone Allocation 3</label>
</channel>
<channel id="allocation-3" typeId="zone-type">
<label>Zone Allocation 4</label>
</channel>
<channel id="allocation-4" typeId="zone-type">
<label>Zone Allocation 5</label>
</channel>
<channel id="allocation-5" typeId="zone-type">
<label>Zone Allocation 6</label>
</channel>
<channel id="allocation-6" typeId="zone-type">
<label>Zone Allocation 7</label>
</channel>
<channel id="allocation-7" typeId="zone-type">
<label>Zone Allocation 8</label>
</channel>
<channel id="allocation-8" typeId="zone-type">
<label>Zone Allocation 9</label>
</channel>
<channel id="allocation-9" typeId="zone-type">
<label>Zone Allocation 10</label>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="battery-group-type">
<label>Battery</label>
<description>Battery channels of your mower</description>
<channels>
<channel id="temperature" typeId="battery-temp-type"/>
<channel id="voltage" typeId="voltage-type"/>
<channel id="level" typeId="system.battery-level"/>
<channel id="charge-cycles" typeId="number-ro">
<label>Charge Cycles</label>
</channel>
<channel id="charge-cycles-total" typeId="number-ro">
<label>Total Charge Cycles</label>
</channel>
<channel id="charging" typeId="charging-type"/>
</channels>
</channel-group-type>
<channel-group-type id="orientation-group-type">
<label>Orientation</label>
<description>Orientation of your mower</description>
<channels>
<channel id="pitch" typeId="axis-type">
<label>Pitch</label>
</channel>
<channel id="roll" typeId="axis-type">
<label>Roll</label>
</channel>
<channel id="yaw" typeId="axis-type">
<label>Yaw</label>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="metrics-group-type">
<label>Metrics</label>
<description>Stat channels of your mower</description>
<channels>
<channel id="blade-time" typeId="metrics-duration">
<label>Current Blade Time</label>
</channel>
<channel id="blade-time-total" typeId="metrics-duration">
<label>Total Blade Time</label>
</channel>
<channel id="distance" typeId="distance-type"/>
<channel id="total-time" typeId="metrics-duration">
<label>Total Time</label>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="sched-group-type">
<label>Schedule</label>
<description>Schedule channels configuration of your mower</description>
<channels>
<channel id="mode" typeId="schedule-mode-type"/>
<channel id="time-extension" typeId="time-extension-type"/>
<channel id="next-start" typeId="timestamp">
<label>Next Start</label>
<description>Next mowing start based on schedule</description>
</channel>
<channel id="next-stop" typeId="timestamp">
<label>Next Stop</label>
<description>Next mowing stop based on schedule</description>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="ot-sched-group-type">
<label>One-Time Schedule</label>
<description>One time schedule configuration of your mower</description>
<channels>
<channel id="edgecut" typeId="switch-rw">
<label>Schedule Edgecut</label>
</channel>
<channel id="duration" typeId="schedule-duration-type"/>
</channels>
</channel-group-type>
<channel-group-type id="day-sched-group-type">
<label>Daily Schedule</label>
<description>Schedule For Each Day</description>
<channels>
<channel id="enable" typeId="switch-rw">
<label>Active</label>
<description>Defines if this day schedule is active or not</description>
</channel>
<channel id="time" typeId="schedule-time"/>
<channel id="duration" typeId="schedule-duration-type"/>
<channel id="edgecut" typeId="switch-rw">
<label>Edgecut</label>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="rain-group-type">
<label>Rain</label>
<description>Data rain channels of your mower</description>
<channels>
<channel id="state" typeId="rain-state-type"/>
<channel id="counter" typeId="rain-counter"/>
<channel id="delay" typeId="rain-delay-type"/>
</channels>
</channel-group-type>
<channel-group-type id="wifi-group-type">
<label>Wi-Fi Information</label>
<channels>
<channel id="rssi" typeId="rssi"/>
<channel id="wifi-quality" typeId="system.signal-strength"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="worxlandroid"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="mower">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Landroid Mower</label>
<description>Represents a Landroid Worx Mower</description>
<channel-groups>
<channel-group id="common" typeId="common-group-type"/>
<channel-group id="config" typeId="config-group-type"/>
<channel-group id="multi-zones" typeId="multi-zones-group-type"/>
<channel-group id="schedule" typeId="sched-group-type"/>
<channel-group id="aws" typeId="aws-group-type"/>
<channel-group id="sunday" typeId="day-sched-group-type">
<label>Sunday Schedule</label>
</channel-group>
<channel-group id="sunday2" typeId="day-sched-group-type">
<label>Sunday Slot 2 Schedule</label>
</channel-group>
<channel-group id="monday" typeId="day-sched-group-type">
<label>Monday Schedule</label>
</channel-group>
<channel-group id="monday2" typeId="day-sched-group-type">
<label>Monday Slot 2 Schedule</label>
</channel-group>
<channel-group id="tuesday" typeId="day-sched-group-type">
<label>Tuesday Schedule</label>
</channel-group>
<channel-group id="tuesday2" typeId="day-sched-group-type">
<label>Tuesday Slot 2 Schedule</label>
</channel-group>
<channel-group id="wednesday" typeId="day-sched-group-type">
<label>Wednesday Schedule</label>
</channel-group>
<channel-group id="wednesday2" typeId="day-sched-group-type">
<label>Wednesday Slot 2 Schedule</label>
</channel-group>
<channel-group id="thursday" typeId="day-sched-group-type">
<label>Thursday Schedule</label>
</channel-group>
<channel-group id="thursday2" typeId="day-sched-group-type">
<label>Thursday Slot 2 Schedule</label>
</channel-group>
<channel-group id="friday" typeId="day-sched-group-type">
<label>Friday Schedule</label>
</channel-group>
<channel-group id="friday2" typeId="day-sched-group-type">
<label>Friday Slot 2 Schedule</label>
</channel-group>
<channel-group id="saturday" typeId="day-sched-group-type">
<label>Saturday Schedule</label>
</channel-group>
<channel-group id="saturday2" typeId="day-sched-group-type">
<label>Saturday Slot 2 Schedule</label>
</channel-group>
<channel-group id="one-time" typeId="ot-sched-group-type"/>
<channel-group id="battery" typeId="battery-group-type"/>
<channel-group id="orientation" typeId="orientation-group-type"/>
<channel-group id="metrics" typeId="metrics-group-type"/>
<channel-group id="rain" typeId="rain-group-type"/>
<channel-group id="wifi" typeId="wifi-group-type"/>
</channel-groups>
<representation-property>serialNumber</representation-property>
<config-description>
<parameter name="serialNumber" type="text">
<label>Serial Number</label>
<description>Serial number of the mower</description>
<required>true</required>
</parameter>
<parameter name="refreshStatusInterval" unit="s" type="integer" min="10">
<label>Refresh Status Interval</label>
<description>Interval for refreshing mower status in seconds</description>
<default>3600</default>
<required>true</required>
<advanced>true</advanced>
</parameter>
<parameter name="pollingInterval" unit="s" type="integer" min="10" max="7200">
<label>Polling Interval</label>
<description>Interval for polling in seconds</description>
<default>1200</default>
<required>true</required>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>
@@ -0,0 +1,6 @@
<svg version="1.1" id="prefix__Layer_1" x="0" y="0" viewBox="0 0 283.46 283.46" height="32" width="32" xmlns="http://www.w3.org/2000/svg">
<style></style>
<path
d="M199.7 34.99h-28.01v-8.37a7.16 7.16 0 00-7.16-7.16h-45.59a7.16 7.16 0 00-7.16 7.16v8.37H83.77c-8.45 0-15.31 6.85-15.31 15.31v198.41c0 8.45 6.85 15.31 15.31 15.31H199.7c8.45 0 15.31-6.85 15.31-15.31V50.29c-.01-8.45-6.86-15.3-15.31-15.3z"
transform="rotate(90 141.735 141.74)" fill="#8bc53f" />
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,9 @@
<svg version="1.1" id="prefix__Layer_1" x="0" y="0" height="32" width="32" viewBox="0 0 283.46 283.46" xmlns="http://www.w3.org/2000/svg">
<style></style>
<path
d="M199.7 34.99h-28.01v-8.37a7.16 7.16 0 00-7.16-7.16h-45.59a7.16 7.16 0 00-7.16 7.16v8.37H83.77c-8.45 0-15.31 6.85-15.31 15.31v198.41c0 8.45 6.85 15.31 15.31 15.31H199.7c8.45 0 15.31-6.85 15.31-15.31V50.29c-.01-8.45-6.86-15.3-15.31-15.3z"
transform="rotate(90 141.735 141.74)" fill="#dee0e1" />
<path d="M7.964 80.61v106.97c0 8.45 6.85 15.31 15.31 15.31h115.93c8.45 0 15.31-6.85 15.31-15.31V80.61H7.964z"
transform="rotate(90 81.239 141.75)" fill="#8bc53f" />
<path fill="#febe10" d="M154.69 70.732l-46.35 76.549 32.06.074-13.36 66.308 50.09-78.596-34.83.127z" />
</svg>

After

Width:  |  Height:  |  Size: 742 B

@@ -0,0 +1,6 @@
<svg version="1.1" id="prefix__Layer_1" x="0" y="0" height="32" width="32" viewBox="0 0 283.46 283.46" xmlns="http://www.w3.org/2000/svg">
<style></style>
<path
d="M199.7 34.99h-28.01v-8.37a7.16 7.16 0 00-7.16-7.16h-45.59a7.16 7.16 0 00-7.16 7.16v8.37H83.77c-8.45 0-15.31 6.85-15.31 15.31v198.41c0 8.45 6.85 15.31 15.31 15.31H199.7c8.45 0 15.31-6.85 15.31-15.31V50.29c-.01-8.45-6.86-15.3-15.31-15.3z"
transform="rotate(90 141.735 141.74)" fill="#dee0e1" />
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 48 48">
<path fill="#37474F" d="M45 38H3a2 2 0 01-2-2V12a2 2 0 012-2h42a2 2 0 012 2v24a2 2 0 01-2 2z" />
<path fill="#FFF" d="M16 35H4V13h12v22zm14-22H18v22h12V13zm14 0H32v22h12V13z" />
<path fill="#CFD8DC"
d="M16 17H4v-4h12v4zm14-4H18v4h12v-4zm14 0H32v4h12v-4zM16 33H4v2h12v-2zm14 0H18v2h12v-2zm14 0H32v2h12v-2z" />
<path fill="none"
d="M37.995 17.812c.42 0 .757-.231 1.01-.694.019-.035.025-.082.043-.118h-2.093c.014.029.024.066.039.094.254.479.587.718 1.001.718z" />
<path fill="#546E7A"
d="M14 25.179c0 1.884-.345 3.325-1.035 4.324-.691.998-1.676 1.497-2.956 1.497-1.267 0-2.246-.485-2.936-1.455S6.025 27.185 6 25.376v-2.574c0-1.878.344-3.314 1.031-4.309S8.704 17 9.991 17s2.273.493 2.96 1.479c.687.986 1.037 2.393 1.05 4.221v2.479zm-2.686-2.767c0-1.077-.107-1.885-.322-2.424-.214-.539-.548-.808-1.002-.808-.851 0-1.292.987-1.324 2.959v3.411c0 1.102.109 1.923.326 2.461.217.539.556.808 1.017.808.435 0 .758-.252.969-.756.211-.504.323-1.276.336-2.316v-3.335zM28 25.179c0 1.884-.345 3.325-1.035 4.324-.691.998-1.676 1.497-2.956 1.497-1.267 0-2.246-.485-2.936-1.455s-1.048-2.36-1.073-4.169v-2.574c0-1.878.344-3.314 1.031-4.309S22.704 17 23.991 17c1.286 0 2.273.493 2.96 1.479.687.986 1.037 2.393 1.05 4.221v2.479zm-2.686-2.767c0-1.077-.107-1.885-.322-2.424-.214-.539-.548-.808-1.002-.808-.851 0-1.292.987-1.324 2.959v3.411c0 1.102.109 1.923.326 2.461.217.539.556.808 1.017.808.435 0 .758-.252.969-.756.211-.504.323-1.276.336-2.316v-3.335zM42 25.554V24h-8v2.24h5.303L36.892 33h2.609L42 25.554zm-2.994-8.436c-.253.462-.59.694-1.01.694-.414 0-.748-.239-1.001-.718-.015-.028-.025-.065-.039-.094h-2.589c.182.563.42 1.074.741 1.507.738.995 1.719 1.493 2.943 1.493.76 0 1.441-.199 2.044-.596s1.071-.956 1.404-1.676c.108-.232.192-.477.264-.728h-2.716c-.016.036-.022.083-.041.118z" />
<path fill="#455A64"
d="M36.178 35l.714-2h2.609l-.671 2h-2.652zm.436-20.078v-.963c.111-.315.28-.579.505-.793.078-.074.171-.118.262-.167H34.02c-.006.169-.02.331-.02.503v.986c0 .951.131 1.78.367 2.512h2.589c-.225-.477-.342-1.165-.342-2.078zM41.641 13h-3.144c.188.115.354.292.495.54.263.462.394 1.062.394 1.798 0 .67-.121 1.216-.338 1.662h2.716A6.032 6.032 0 0042 15.305c0-.884-.129-1.643-.359-2.305z" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path
d="M92.6 21c-32 0-64.04 24-64.04 72L92.6 221l64-128c0-48-32-72-64-72zm282.3 39c-6.9.29-13.6 1.6-19.2 2.8l3.8 17.6c5.6-1.25 11.4-2.04 16.3-2.4zM92.6 61c17.7 0 32 14.33 32 32 0 17.7-14.3 32-32 32-17.67 0-32-14.3-32-32 0-17.67 14.33-32 32-32zm302.2.2l-3 17.7c4.9 1.03 9.8 2.32 14.1 4.9l8.7-15.8c-6.1-3.25-12.9-6.17-19.8-6.8zm-57.5 6.7c-6.1 2.38-12.2 4.51-17.4 6.6L327 91c5.5-2.34 11.3-4.38 16.2-6.1zM431 81.3L417.3 93c3.6 4.12 6.4 9.2 8.6 13.3l16.1-8.1c-3.4-6.55-6.4-11.51-11-16.9zm-127.8.9c-6.1 3.11-11.1 5.88-16.5 8.6l8.8 15.8c5.2-3 10.9-5.9 15.5-8.2zm-32.3 17.9c-5.3 3.1-10.5 6.2-15.6 9.6l9.8 15c4.9-3.2 10-6.2 15-9.2zM448.2 118c-5.9 1-11.9 1.7-17.8 2.4.4 5 .1 10.4-.9 14.6l17.5 4.1c1-7.2 1.9-14.6 1.2-21.1zm-208.1 1.7c-5 3.4-9.9 6.9-14.9 10.3l10.4 14.7c4.8-3.5 9.7-6.8 14.6-10.2zm-29.6 21.1c-5 3.6-10.2 7.6-14.5 10.9l10.9 14.3c5.5-4 9.3-7 14.3-10.7zm213 8c-3 4.6-6.5 9.2-10 12.7l13.1 12.5c4.3-5.1 8.9-10.3 12.1-15.5zm-241.8 14.1c-4.9 3.8-9.8 7.7-14.1 11.3l11.4 13.9c4.7-3.9 9.5-7.9 13.9-11.1zM401.1 173c-4.6 3.7-9.4 7.3-13.8 10.3l10.3 14.8c5.3-3.6 10.5-7.5 15-11.1zm-247.4 12.9c-4.7 3.8-9.2 7.8-13.8 11.7l11.7 13.7c4.5-3.9 9-7.8 13.6-11.6zm218.9 7c-5.1 3-10.4 6.1-15.2 8.7l8.6 15.9c5.4-3.3 11.5-6.2 16-9.2zm-246.4 16.6c-4.5 4-8.9 8-13.4 12.1l12.1 13.4c4.4-4 8.9-8 13.3-12zm215.5.4c-5.3 2.6-10.6 5.3-15.9 7.9l7.7 16.2c6.2-3 10.8-5.5 16.4-8.1zm-32 15.4c-5.5 2.5-10.8 4.9-16.4 7.2l7.3 16.5c5.5-2.4 11-4.9 16.5-7.4zM99.6 234c-5.1 4.5-8.65 8-13.3 12.5l12.7 13c4.7-4.5 8.5-8.4 12.9-12.2zm177.3 5.8c-5.5 2.3-11 4.7-16.5 7l7 16.7c5.6-2.3 11.1-4.7 16.6-7.1zm-33.1 14c-5.5 2.4-11 4.8-16.6 7l7 16.7c5.5-2.3 11.1-4.7 16.6-7zm184.8 7.2c-32 0-64 24-64 72l64 128 64-128c0-48-32-72-64-72zm-218 6.8c-5.7 2.6-11.7 5-16.6 7.1l7.1 16.6c5.9-2.5 11.5-4.9 16.5-7.1zM177.4 282c-5.4 2.5-11.7 5.3-16.5 7.5l7.4 16.4c5.9-2.6 11.1-5.2 16.3-7.4zm-33 15c-5.6 2.7-11.4 5.5-16.4 8l8.1 16.1c5.4-2.8 11-5.4 15.9-7.8zm284.2 4c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zm-316.8 12.3c-5.3 2.9-10.6 5.9-16 9l9 15.6c5.1-3 10.3-5.8 15.5-8.6zM80.1 332c-5.61 3.2-11.03 7.5-15.7 10.6L75.3 357c4.97-3.6 10.32-7.3 14.6-9.9zm-29.9 22.6c-4.8 4.4-9.53 9.5-13.2 13.8l13.7 11.7c3.85-4.7 7.2-8.2 11.7-12.2zm217.8 1.3l1.6 17.9c5.2-.9 10.4-.3 15.6.5l3.1-17.7c-6.6-1-13.6-1.7-20.3-.7zm-37.2 10l6.8 16.7c5.2-2.3 10.6-4.1 16.1-5.8-1.9-5.7-3.3-11.5-4.8-17.3-6.3 1.8-12.6 4.2-18.1 6.4zm77.5-.9l-10.2 14.8c4.2 3.1 8.3 6.4 11.6 10.5l13.6-11.8c-5.1-5.2-9-10.1-15-13.5zm-94.5 9c-5.5 2.8-10.8 6-16.1 9.1l9.1 15.5c5.2-2.8 10.3-6.1 15.4-8.8zM26.01 385c-3.02 6.5-5.47 13.5-6.61 19.7l17.7 3.1c1.08-5.7 2.63-9.8 4.9-14.7-5.49-2.4-10.73-5.3-15.99-8.1zm156.09 7.8c-5.1 3.3-10.1 6.6-15.1 10l10 15c5-3.3 9.9-6.7 14.9-10zm152.7 1.2l-15.1 9.8c3.2 4.8 6.3 9.8 9.2 14.9l15.6-9c-3.5-5.6-6-10.6-9.7-15.7zm-182.7 19c-5 3.3-10 6.5-14.9 10l10 15c4.8-3.5 9.9-6.8 15-10.2zm-114.8 9.5c-5.79 1.2-11.63 2.2-17.45 3.3 1.05 7 3.86 13.8 6.4 19.2l16.25-7.8c-2.17-5-4.23-10.2-5.2-14.7zm316.1 2.8l-15.6 9c3.1 5.4 6.7 11.2 9.6 15.8l15.1-9.7c-3.4-5.3-6.3-10.3-9.1-15.1zm-231 7.5c-5 3.1-9.9 6.1-15.1 9l8.9 15.7c5.3-3.1 10.6-6.2 15.7-9.5zm-71.3 16.3l-12.3 13.2c5.56 5.3 12.42 8.8 19.9 10.4l4-17.5c-4.44-.9-8.59-3.1-11.6-6.1zm41 .3c-5.01 2.3-10.21 4.1-15.6 5.2l4.1 17.6c6.42-1.3 12.46-3.7 18.5-6.2zm280.3 4.8l-13.9 11.3c4.3 5.3 9.6 10.4 14.2 14l11.1-14.2c-4.4-3.4-8.2-7.5-11.4-11.1zm24.1 17.5l-4.5 17.5c7.9 1.6 13.8 2.1 21.2 1.3l-2.2-17.9c-4.9.8-9.7.3-14.5-.9z"
fill="#a8a8a8" />
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<path
d="M92.6 21c-32 0-64.04 24-64.04 72L92.6 221l64-128c0-48-32-72-64-72zm282.3 39c-6.9.29-13.6 1.6-19.2 2.8l3.8 17.6c5.6-1.25 11.4-2.04 16.3-2.4zM92.6 61c17.7 0 32 14.33 32 32 0 17.7-14.3 32-32 32-17.67 0-32-14.3-32-32 0-17.67 14.33-32 32-32zm302.2.2l-3 17.7c4.9 1.03 9.8 2.32 14.1 4.9l8.7-15.8c-6.1-3.25-12.9-6.17-19.8-6.8zm-57.5 6.7c-6.1 2.38-12.2 4.51-17.4 6.6L327 91c5.5-2.34 11.3-4.38 16.2-6.1zM431 81.3L417.3 93c3.6 4.12 6.4 9.2 8.6 13.3l16.1-8.1c-3.4-6.55-6.4-11.51-11-16.9zm-127.8.9c-6.1 3.11-11.1 5.88-16.5 8.6l8.8 15.8c5.2-3 10.9-5.9 15.5-8.2zm-32.3 17.9c-5.3 3.1-10.5 6.2-15.6 9.6l9.8 15c4.9-3.2 10-6.2 15-9.2zM448.2 118c-5.9 1-11.9 1.7-17.8 2.4.4 5 .1 10.4-.9 14.6l17.5 4.1c1-7.2 1.9-14.6 1.2-21.1zm-208.1 1.7c-5 3.4-9.9 6.9-14.9 10.3l10.4 14.7c4.8-3.5 9.7-6.8 14.6-10.2zm-29.6 21.1c-5 3.6-10.2 7.6-14.5 10.9l10.9 14.3c5.5-4 9.3-7 14.3-10.7zm213 8c-3 4.6-6.5 9.2-10 12.7l13.1 12.5c4.3-5.1 8.9-10.3 12.1-15.5zm-241.8 14.1c-4.9 3.8-9.8 7.7-14.1 11.3l11.4 13.9c4.7-3.9 9.5-7.9 13.9-11.1zM401.1 173c-4.6 3.7-9.4 7.3-13.8 10.3l10.3 14.8c5.3-3.6 10.5-7.5 15-11.1zm-247.4 12.9c-4.7 3.8-9.2 7.8-13.8 11.7l11.7 13.7c4.5-3.9 9-7.8 13.6-11.6zm218.9 7c-5.1 3-10.4 6.1-15.2 8.7l8.6 15.9c5.4-3.3 11.5-6.2 16-9.2zm-246.4 16.6c-4.5 4-8.9 8-13.4 12.1l12.1 13.4c4.4-4 8.9-8 13.3-12zm215.5.4c-5.3 2.6-10.6 5.3-15.9 7.9l7.7 16.2c6.2-3 10.8-5.5 16.4-8.1zm-32 15.4c-5.5 2.5-10.8 4.9-16.4 7.2l7.3 16.5c5.5-2.4 11-4.9 16.5-7.4zM99.6 234c-5.1 4.5-8.65 8-13.3 12.5l12.7 13c4.7-4.5 8.5-8.4 12.9-12.2zm177.3 5.8c-5.5 2.3-11 4.7-16.5 7l7 16.7c5.6-2.3 11.1-4.7 16.6-7.1zm-33.1 14c-5.5 2.4-11 4.8-16.6 7l7 16.7c5.5-2.3 11.1-4.7 16.6-7zm184.8 7.2c-32 0-64 24-64 72l64 128 64-128c0-48-32-72-64-72zm-218 6.8c-5.7 2.6-11.7 5-16.6 7.1l7.1 16.6c5.9-2.5 11.5-4.9 16.5-7.1zM177.4 282c-5.4 2.5-11.7 5.3-16.5 7.5l7.4 16.4c5.9-2.6 11.1-5.2 16.3-7.4zm-33 15c-5.6 2.7-11.4 5.5-16.4 8l8.1 16.1c5.4-2.8 11-5.4 15.9-7.8zm284.2 4c17.7 0 32 14.3 32 32s-14.3 32-32 32-32-14.3-32-32 14.3-32 32-32zm-316.8 12.3c-5.3 2.9-10.6 5.9-16 9l9 15.6c5.1-3 10.3-5.8 15.5-8.6zM80.1 332c-5.61 3.2-11.03 7.5-15.7 10.6L75.3 357c4.97-3.6 10.32-7.3 14.6-9.9zm-29.9 22.6c-4.8 4.4-9.53 9.5-13.2 13.8l13.7 11.7c3.85-4.7 7.2-8.2 11.7-12.2zm217.8 1.3l1.6 17.9c5.2-.9 10.4-.3 15.6.5l3.1-17.7c-6.6-1-13.6-1.7-20.3-.7zm-37.2 10l6.8 16.7c5.2-2.3 10.6-4.1 16.1-5.8-1.9-5.7-3.3-11.5-4.8-17.3-6.3 1.8-12.6 4.2-18.1 6.4zm77.5-.9l-10.2 14.8c4.2 3.1 8.3 6.4 11.6 10.5l13.6-11.8c-5.1-5.2-9-10.1-15-13.5zm-94.5 9c-5.5 2.8-10.8 6-16.1 9.1l9.1 15.5c5.2-2.8 10.3-6.1 15.4-8.8zM26.01 385c-3.02 6.5-5.47 13.5-6.61 19.7l17.7 3.1c1.08-5.7 2.63-9.8 4.9-14.7-5.49-2.4-10.73-5.3-15.99-8.1zm156.09 7.8c-5.1 3.3-10.1 6.6-15.1 10l10 15c5-3.3 9.9-6.7 14.9-10zm152.7 1.2l-15.1 9.8c3.2 4.8 6.3 9.8 9.2 14.9l15.6-9c-3.5-5.6-6-10.6-9.7-15.7zm-182.7 19c-5 3.3-10 6.5-14.9 10l10 15c4.8-3.5 9.9-6.8 15-10.2zm-114.8 9.5c-5.79 1.2-11.63 2.2-17.45 3.3 1.05 7 3.86 13.8 6.4 19.2l16.25-7.8c-2.17-5-4.23-10.2-5.2-14.7zm316.1 2.8l-15.6 9c3.1 5.4 6.7 11.2 9.6 15.8l15.1-9.7c-3.4-5.3-6.3-10.3-9.1-15.1zm-231 7.5c-5 3.1-9.9 6.1-15.1 9l8.9 15.7c5.3-3.1 10.6-6.2 15.7-9.5zm-71.3 16.3l-12.3 13.2c5.56 5.3 12.42 8.8 19.9 10.4l4-17.5c-4.44-.9-8.59-3.1-11.6-6.1zm41 .3c-5.01 2.3-10.21 4.1-15.6 5.2l4.1 17.6c6.42-1.3 12.46-3.7 18.5-6.2zm280.3 4.8l-13.9 11.3c4.3 5.3 9.6 10.4 14.2 14l11.1-14.2c-4.4-3.4-8.2-7.5-11.4-11.1zm24.1 17.5l-4.5 17.5c7.9 1.6 13.8 2.1 21.2 1.3l-2.2-17.9c-4.9.8-9.7.3-14.5-.9z"
fill="#4ba82f" />
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,33 @@
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.843 511.843">
<path
d="M84.201 61.399c-1.156-4.64-5.342-8.076-10.333-8.076H10.661C4.772 53.323 0 58.095 0 63.984c0 5.889 4.772 10.661 10.661 10.661H65.62l60.771 234.624 20.642-5.342L84.201 61.399z"
fill="#424953" />
<path
d="M198.268 267.031h-96.635c-11.731 0-22.767 9.481-24.516 21.087l-12.902 85.334c-1.75 11.605 6.412 21.087 18.143 21.087h115.91c11.73 0 21.329-9.591 21.329-21.306v-84.881c0-11.714-9.599-21.321-21.329-21.321z"
fill="#fecd57" />
<path d="M490.522 330.559C294.77 223.935 104.288 223.935 104.288 223.935l49.407 190.613 336.827 1.328v-85.317z"
fill="#ec5564" />
<path
d="M170.612 447.866c-29.397 0-53.312-23.914-53.312-53.327 0-29.397 23.915-53.296 53.312-53.296 29.405 0 53.32 23.898 53.32 53.296 0 29.413-23.915 53.327-53.32 53.327z"
fill="#646c77" />
<path
d="M170.612 330.559c-35.333 0-63.98 28.647-63.98 63.98 0 35.349 28.647 63.98 63.98 63.98 35.341 0 63.98-28.632 63.98-63.98 0-35.333-28.639-63.98-63.98-63.98zm0 106.655c-23.516 0-42.651-19.15-42.651-42.675 0-23.509 19.135-42.644 42.651-42.644 23.524 0 42.659 19.135 42.659 42.644 0 23.525-19.135 42.675-42.659 42.675z"
fill="#424953" />
<path
d="M458.531 447.866c-23.523 0-42.658-19.135-42.658-42.643 0-23.524 19.135-42.675 42.658-42.675 23.509 0 42.644 19.15 42.644 42.675 0 23.509-19.135 42.643-42.644 42.643z"
fill="#646c77" />
<path
d="M458.531 351.896c-29.443 0-53.327 23.868-53.327 53.328 0 29.428 23.884 53.296 53.327 53.296 29.445 0 53.312-23.868 53.312-53.296.001-29.46-23.866-53.328-53.312-53.328zm0 85.318c-17.65 0-31.99-14.371-31.99-31.99 0-17.651 14.34-31.99 31.99-31.99 17.636 0 31.99 14.339 31.99 31.99.001 17.619-14.354 31.99-31.99 31.99z"
fill="#424953" />
<g fill="#e5e8ec">
<path
d="M469.185 405.224c0 5.873-4.765 10.652-10.653 10.652s-10.668-4.779-10.668-10.652c0-5.904 4.779-10.685 10.668-10.685s10.653 4.78 10.653 10.685zM181.28 394.539c0 5.904-4.78 10.685-10.668 10.685s-10.661-4.78-10.661-10.685c0-5.873 4.772-10.653 10.661-10.653s10.668 4.78 10.668 10.653z" />
</g>
<path
d="M203.118 233.65c20.017 44.55 64.777 75.603 116.785 75.603 27.585 0 53.14-8.748 74.039-23.603-72.399-28.389-139.481-43.721-190.824-52z"
fill="#424953" />
<g fill="#f5ba45">
<path
d="M93.846 309.691H73.86l-3.226 21.321h23.212c5.889 0 10.661-4.765 10.661-10.669 0-5.873-4.772-10.652-10.661-10.652zM65.8 363.002h23.243a10.663 10.663 0 0010.668-10.669c0-5.873-4.78-10.652-10.668-10.652H69.026L65.8 363.002z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,6 @@
<svg height="32" width="32" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path d="M9 25v-5c0-5.52 4.48-10 10-10s10 4.48 10 10v12.73" fill="none" stroke="#b4b4b4" stroke-miterlimit="10"
stroke-width="4" />
<path d="M23.5 29.5h29v25h-29z" fill="#f8cf99" />
<path d="M52 30v24H24V30h28m1-1H23v26h30V29z" fill="#cb9d61" />
</svg>

After

Width:  |  Height:  |  Size: 351 B

@@ -0,0 +1,6 @@
<svg height="32" width="32" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path d="M42 29.59v-5.45c0-5.52-4.48-10-10-10s-10 4.48-10 10v12.73" fill="none" stroke="#b4b4b4"
stroke-miterlimit="10" stroke-width="4" />
<path d="M17.5 29.5h29v25h-29z" fill="#f8cf99" />
<path d="M46 30v24H18V30h28m1-1H17v26h30V29z" fill="#cb9d61" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

@@ -0,0 +1,6 @@
<svg height="32" width="32" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path d="M42 29.59v-5.45c0-5.52-4.48-10-10-10s-10 4.48-10 10v12.73" fill="none" stroke="#b4b4b4"
stroke-miterlimit="10" stroke-width="4" />
<path d="M17.5 29.5h29v25h-29z" fill="#f8cf99" />
<path d="M46 30v24H18V30h28m1-1H17v26h30V29z" fill="#cb9d61" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

@@ -0,0 +1,81 @@
<svg height="32" width="32" viewBox="0 0 55 63" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__e">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__g">
<feMorphology radius="1.08" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__i">
<feMorphology radius="1.44" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__k">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<mask id="prefix__f" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-2" y="-2"
width="27.056" height="36.375">
<path fill="#fff" d="M9 21h27.056v36.375H9z" />
<use xlink:href="#prefix__a" />
</mask>
<mask id="prefix__h" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.08" y="-1.08"
width="14.41" height="19.66">
<path fill="#fff" d="M2.92 3.92h14.41v19.66H2.92z" />
<use xlink:href="#prefix__b" />
</mask>
<mask id="prefix__j" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.44" y="-1.44"
width="19.505" height="26.505">
<path fill="#fff" d="M25.56-1.44h19.505v26.505H25.56z" />
<use xlink:href="#prefix__c" />
</mask>
<mask id="prefix__l" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="67"
height="4" fill="#fff">
<use xlink:href="#prefix__d" />
</mask>
<path
d="M22.528 23C20.887 23 11 38.241 11 44.803c0 5.174 2.987 10.572 11.528 10.572 7.82 0 11.528-5.346 11.528-10.572C34.056 38.158 24.169 23 22.528 23z"
id="prefix__a" />
<path
d="M10.125 5C9.253 5 4 13.238 4 16.785c0 2.797 1.587 5.715 6.125 5.715 4.155 0 6.125-2.89 6.125-5.715C16.25 13.193 10.997 5 10.125 5z"
id="prefix__b" />
<path
d="M35.313 0C34.129 0 27 11.122 27 15.91c0 3.776 2.154 7.715 8.313 7.715 5.64 0 8.312-3.901 8.312-7.715 0-4.849-7.129-15.91-8.313-15.91z"
id="prefix__c" />
<rect id="prefix__d" x="-8.105" y="28.395" width="67" height="4" rx="2" />
</defs>
<g fill="none" fill-rule="evenodd">
<g transform="translate(2 2)">
<use fill="#000" filter="url(#prefix__e)" xlink:href="#prefix__a" />
<use fill-opacity=".3" fill="#55A4FF" xlink:href="#prefix__a" />
<use stroke="#FFF" mask="url(#prefix__f)" stroke-width="4" xlink:href="#prefix__a" />
</g>
<g transform="translate(2 2)">
<use fill="#000" filter="url(#prefix__g)" xlink:href="#prefix__b" />
<use fill-opacity=".3" fill="#55A4FF" xlink:href="#prefix__b" />
<use stroke="#FFF" mask="url(#prefix__h)" stroke-width="2.16" xlink:href="#prefix__b" />
</g>
<g transform="translate(2 2)">
<use fill="#000" filter="url(#prefix__i)" xlink:href="#prefix__c" />
<use fill-opacity=".3" fill="#55A4FF" xlink:href="#prefix__c" />
<use stroke="#FFF" mask="url(#prefix__j)" stroke-width="2.88" xlink:href="#prefix__c" />
</g>
<g transform="rotate(-45 28.81 28.981)">
<use fill="#000" filter="url(#prefix__k)" xlink:href="#prefix__d" />
<use stroke="#FFF" mask="url(#prefix__l)" stroke-width="2" fill="#464646" xlink:href="#prefix__d" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,66 @@
<svg height="32" width="32" viewBox="0 0 46 63" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__d">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__f">
<feMorphology radius="1.08" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__h">
<feMorphology radius="1.44" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<path
d="M18.528 23C16.887 23 7 38.241 7 44.803c0 5.174 2.987 10.572 11.528 10.572 7.82 0 11.528-5.346 11.528-10.572C30.056 38.158 20.169 23 18.528 23z"
id="prefix__a" />
<path
d="M6.125 5C5.253 5 0 13.238 0 16.785 0 19.582 1.587 22.5 6.125 22.5c4.155 0 6.125-2.89 6.125-5.715C12.25 13.193 6.997 5 6.125 5z"
id="prefix__b" />
<path
d="M31.313 0C30.128 0 23 11.122 23 15.91c0 3.776 2.154 7.715 8.313 7.715 5.64 0 8.312-3.901 8.312-7.715 0-4.849-7.129-15.91-8.313-15.91z"
id="prefix__c" />
<mask id="prefix__e" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-2" y="-2"
width="27.056" height="36.375">
<path fill="#fff" d="M5 21h27.056v36.375H5z" />
<use xlink:href="#prefix__a" />
</mask>
<mask id="prefix__g" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.08" y="-1.08"
width="14.41" height="19.66">
<path fill="#fff" d="M-1.08 3.92h14.41v19.66H-1.08z" />
<use xlink:href="#prefix__b" />
</mask>
<mask id="prefix__i" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.44" y="-1.44"
width="19.505" height="26.505">
<path fill="#fff" d="M21.56-1.44h19.505v26.505H21.56z" />
<use xlink:href="#prefix__c" />
</mask>
</defs>
<g fill="none" fill-rule="evenodd">
<g transform="translate(3 2)">
<use fill="#000" filter="url(#prefix__d)" xlink:href="#prefix__a" />
<use fill="#55A4FF" xlink:href="#prefix__a" />
<use stroke="#FFF" mask="url(#prefix__e)" stroke-width="4" xlink:href="#prefix__a" />
</g>
<g transform="translate(3 2)">
<use fill="#000" filter="url(#prefix__f)" xlink:href="#prefix__b" />
<use fill="#55A4FF" xlink:href="#prefix__b" />
<use stroke="#FFF" mask="url(#prefix__g)" stroke-width="2.16" xlink:href="#prefix__b" />
</g>
<g transform="translate(3 2)">
<use fill="#000" filter="url(#prefix__h)" xlink:href="#prefix__c" />
<use fill="#55A4FF" xlink:href="#prefix__c" />
<use stroke="#FFF" mask="url(#prefix__i)" stroke-width="2.88" xlink:href="#prefix__c" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="32" width="32" viewBox="0 0 55 63">
<defs>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__e">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__g">
<feMorphology radius="1.08" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="prefix__i">
<feMorphology radius="1.44" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1" />
<feOffset dy="1" in="shadowSpreadOuter1" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feComposite in="shadowBlurOuter1" in2="SourceAlpha" operator="out" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1" />
</filter>
<path
d="M22.528 23C20.887 23 11 38.241 11 44.803c0 5.174 2.987 10.572 11.528 10.572 7.82 0 11.528-5.346 11.528-10.572C34.056 38.158 24.169 23 22.528 23z"
id="prefix__a" />
<path
d="M10.125 5C9.253 5 4 13.238 4 16.785c0 2.797 1.587 5.715 6.125 5.715 4.155 0 6.125-2.89 6.125-5.715C16.25 13.193 10.997 5 10.125 5z"
id="prefix__b" />
<path
d="M35.313 0C34.129 0 27 11.122 27 15.91c0 3.776 2.154 7.715 8.313 7.715 5.64 0 8.312-3.901 8.312-7.715 0-4.849-7.129-15.91-8.313-15.91z"
id="prefix__c" />
<mask id="prefix__f" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-2" y="-2" width="27.056"
height="36.375">
<path fill="#fff" d="M9 21h27.056v36.375H9z" />
<use xlink:href="#prefix__a" />
</mask>
<mask id="prefix__h" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.08" y="-1.08"
width="14.41" height="19.66">
<path fill="#fff" d="M2.92 3.92h14.41v19.66H2.92z" />
<use xlink:href="#prefix__b" />
</mask>
<mask id="prefix__j" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="-1.44" y="-1.44"
width="19.505" height="26.505">
<path fill="#fff" d="M25.56-1.44h19.505v26.505H25.56z" />
<use xlink:href="#prefix__c" />
</mask>
<rect id="prefix__d" x="-8.105" y="28.395" width="67" height="4" rx="2" />
</defs>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__a" fill="#000" filter="url(#prefix__e)" />
<use height="100%" width="100%" xlink:href="#prefix__a" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__a" mask="url(#prefix__f)" stroke="#fff" stroke-width="4" />
</g>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__b" fill="#000" filter="url(#prefix__g)" />
<use height="100%" width="100%" xlink:href="#prefix__b" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__b" mask="url(#prefix__h)" stroke="#fff"
stroke-width="2.16" />
</g>
<g transform="translate(2 2)" fill="none" fill-rule="evenodd">
<use height="100%" width="100%" xlink:href="#prefix__c" fill="#000" filter="url(#prefix__i)" />
<use height="100%" width="100%" xlink:href="#prefix__c" fill="#55a4ff" fill-opacity=".3" />
<use height="100%" width="100%" xlink:href="#prefix__c" mask="url(#prefix__j)" stroke="#fff"
stroke-width="2.88" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

@@ -0,0 +1,8 @@
<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496.166 496.166">
<path
d="M.005 248.087C.005 111.063 111.073 0 248.079 0c137.014 0 248.082 111.062 248.082 248.087 0 137.002-111.068 248.079-248.082 248.079C111.073 496.166.005 385.089.005 248.087z"
fill="#32bea6" />
<path
d="M400.813 169.581c-2.502-4.865-14.695-16.012-35.262-5.891-20.564 10.122-10.625 32.351-10.625 32.351 7.666 15.722 11.98 33.371 11.98 52.046 0 65.622-53.201 118.824-118.828 118.824-65.619 0-118.82-53.202-118.82-118.824 0-61.422 46.6-111.946 106.357-118.173v30.793s-.084 1.836 1.828 2.999c1.906 1.163 3.818 0 3.818 0l98.576-58.083s2.211-1.162 2.211-3.436c0-1.873-2.211-3.205-2.211-3.205l-98.248-57.754s-2.24-1.605-4.23-.826c-1.988.773-1.744 3.481-1.744 3.481v32.993c-88.998 6.392-159.23 80.563-159.23 171.21 0 94.824 76.873 171.696 171.693 171.696 94.828 0 171.707-76.872 171.707-171.696.001-28.298-6.852-54.98-18.972-78.505z"
fill="#f7f7f7" />
</svg>

After

Width:  |  Height:  |  Size: 967 B

@@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M11 11H1C1 5.477 5.477 1 11 1v10z" fill="#39762b" />
<path d="M12 12V2A10 10 0 112 12h10z" opacity=".5" />
<path d="M12 12V2c5.523 0 10 4.477 10 10H12zM12 12h10c0 5.523-4.477 10-10 10V12zM12 12v10C6.477 22 2 17.523 2 12h10z"
fill="#767676" /><text style="white-space:pre" x="5.187" y="9.486" fill="#fff" font-family="Arial,sans-serif"
font-size="9" font-weight="700">1</text><text style="white-space:pre" x="37.928" y="7.522" fill="#333"
font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text style="white-space:pre" x="12.861"
y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">2</text><text
style="white-space:pre" x="6.187" y="19.585" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">3</text><text style="white-space:pre" x="12.861" y="19.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12H2A10 10 0 0112 2z" fill="#767676" />
<path d="M13 11V1c5.523 0 10 4.477 10 10H13z" fill="#2e7614" />
<path d="M12 12h10c0 5.523-4.477 10-10 10V12zm0 0v10C6.477 22 2 17.523 2 12h10z" fill="#767676" /><text
style="white-space:pre" x="6.187" y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">1</text><text style="white-space:pre" x="37.928" y="7.522" fill="#333"
font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text style="white-space:pre" x="13.861"
y="9.486" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">2</text><text
style="white-space:pre" x="6.187" y="19.585" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">3</text><text style="white-space:pre" x="12.861" y="19.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,12 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12H2A10 10 0 0112 2zM12 12V2c5.523 0 10 4.477 10 10H12zM12 12h10c0 5.523-4.477 10-10 10V12z"
fill="#767676" />
<path d="M11 13v10C5.477 23 1 18.523 1 13h10z" fill="#37761e" /><text style="white-space:pre" x="6.187" y="10.486"
fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">1</text><text style="white-space:pre"
x="37.928" y="7.522" fill="#333" font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text
style="white-space:pre" x="12.861" y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">2</text><text style="white-space:pre" x="5.187" y="20.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">3</text><text style="white-space:pre" x="12.861"
y="19.585" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,12 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12H2A10 10 0 0112 2zM12 12V2c5.523 0 10 4.477 10 10H12z" fill="#767676" />
<path d="M13 13h10c0 5.523-4.477 10-10 10V13z" fill="#2a7617" />
<path d="M12 12v10C6.477 22 2 17.523 2 12h10z" fill="#767676" /><text style="white-space:pre" x="6.187" y="10.486"
fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">1</text><text style="white-space:pre"
x="37.928" y="7.522" fill="#333" font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text
style="white-space:pre" x="12.861" y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">2</text><text style="white-space:pre" x="6.187" y="19.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">3</text><text style="white-space:pre" x="13.861"
y="20.585" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,13 @@
<svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12H2A10 10 0 0112 2z" fill="#767676" />
<path d="M12 12V2A10 10 0 112 12h10z" opacity=".5" />
<path d="M12 12V2c5.523 0 10 4.477 10 10H12zM12 12h10c0 5.523-4.477 10-10 10V12zM12 12v10C6.477 22 2 17.523 2 12h10z"
fill="#767676" /><text style="white-space:pre" x="6.187" y="10.486" fill="#fff" font-family="Arial,sans-serif"
font-size="9" font-weight="700">1</text><text style="white-space:pre" x="37.928" y="7.522" fill="#333"
font-family="Arial,sans-serif" font-size=".9">Enter your text here</text><text style="white-space:pre" x="12.861"
y="10.486" fill="#fff" font-family="Arial,sans-serif" font-size="9" font-weight="700">2</text><text
style="white-space:pre" x="6.187" y="19.585" fill="#fff" font-family="Arial,sans-serif" font-size="9"
font-weight="700">3</text><text style="white-space:pre" x="12.861" y="19.585" fill="#fff"
font-family="Arial,sans-serif" font-size="9" font-weight="700">4</text>
<path fill="none" d="M0 0h24v24H0z" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,20 @@
UNKNOWN=unknown
NO_ERR=no error
TRAPPED=trapped
LIFTED=lifted
WIRE_MISSING=wire missing
OUTSIDE_WIRE=outside wire
RAINING=raining
CLOSE_DOOR_TO_MOW=close door to mow
CLOSE_DOOR_TO_GO_HOME=close door to go home
BLADE_MOTOR_BLOCKED=blade motor blocked
WHEEL_MOTOR_BLOCKED=wheel motor blocked
TRAPPED_TIMEOUT=trapped timeout
UPSIDE_DOWN=upside down
BATTERY_LOW=battery low
REVERSE_WIRE=reverse wire
CHARGE_ERROR=charge errir
TIMEOUT_FINDING_HOME=timeout finding home
MOWER_LOCKED=mower locked
BATTERY_OVER_TEMPERATURE=batter over temperature
MOWER_OUTSIDE_WIRE=mower outside wire
@@ -0,0 +1,20 @@
UNKNOWN=unknown
IDLE=idle
HOME=home
START_SEQUENCE=start sequence
LEAVING_HOME=leaving home
FOLLOW_WIRE=follow wire
SEARCHING_HOME=searching home
SEARCHING_WIRE=searching wire
MOWING=mowing
LIFTED=lifted
TRAPPED=trapped
BLADE_BLOCKED=blade blocked
DEBUG=debug
REMOTE_CONTROL=remote control
GOING_HOME=going home
ZONE_TRAINING=zone training
BORDER_CUT=border cut
SEARCHING_ZONE=searching zone
PAUSE=pause
MANUAL_STOP=manual stop
+1
View File
@@ -489,6 +489,7 @@
<module>org.openhab.binding.wlanthermo</module>
<module>org.openhab.binding.wled</module>
<module>org.openhab.binding.wolfsmartset</module>
<module>org.openhab.binding.worxlandroid</module>
<module>org.openhab.binding.wundergroundupdatereceiver</module>
<module>org.openhab.binding.x</module>
<module>org.openhab.binding.xmltv</module>