Skip to content

Commit

Permalink
[boschindego] Rewrite to avoid external dependencies (#12905)
Browse files Browse the repository at this point in the history
* Rewrite to avoid external dependencies

Fixes #12720

* Improve session handling
* Avoid reauthorization for each command/poll
* Further improve session handling
* Refactor SSO cookie handling
* Optimize getting DeviceStatus for unknown status code

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
  • Loading branch information
jlaur authored Jun 14, 2022
1 parent fd9fa72 commit 960be6b
Show file tree
Hide file tree
Showing 35 changed files with 1,719 additions and 237 deletions.
110 changes: 62 additions & 48 deletions bundles/org.openhab.binding.boschindego/README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,83 @@
# Bosch Indego Binding

This is the Binding for Bosch Indego Connect lawn mowers.
Thank´s to zazaz-de who found out how the API works. His [Java Library](https://github.com/zazaz-de/iot-device-bosch-indego-controller) made this Binding possible.
Thank´s to zazaz-de who found out how the API works.
His [Java Library](https://github.com/zazaz-de/iot-device-bosch-indego-controller) made this Binding possible.

## Configuration of the Thing
## Thing Configuration

Currently the binding supports ***indego*** mowers as a thing type with this parameters:
Currently the binding supports ***indego*** mowers as a thing type with these configuration parameters:

| parameter | datatype | required |
|-----------|----------|--------------------------------|
| username | String | yes |
| password | String | yes |
| refresh | integer | no (default: 180, minimum: 60) |

The refresh interval is specified in seconds.

A possible entry in your thing file could be:

```java
boschindego:indego:lawnmower [username="mail@example.com", password="idontneedtocutthelawnagain", refresh=120]
```
| Parameter | Description |
|-----------|----------------------------------------------------------------------|
| username | Username for the Bosch Indego account |
| password | Password for the Bosch Indego account |
| refresh | Specifies the refresh interval in seconds (default 180, minimum: 60) |

## Channels

| item-type | description | |
| Channel | Item Type | Description |
|--------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------|
| state | Number | You can send commands to this channel to control the mower and read the simplified state from it (1=mow, 2=return to dock, 3=pause) |
| errorcode | Number | Errorcode of the mower (0=no error, readonly) |
| statecode | Number | Detailed state of the mower. I included English and German map-files to read the state easier (readonly) |
| errorcode | Number | Error code of the mower (0=no error, readonly) |
| statecode | Number | Detailed state of the mower (readonly) |
| textualstate | String | State as a text. (readonly) |
| ready | Number | Shows if the mower is ready to mow (1=ready, 0=not ready, readonly) |
| mowed | Dimmer | Cut grass in percent (readonly) |

For example you can use this sitemap entry to control the mower manually:
### State Codes

| Code | Description |
|-------|---------------------------------------------|
| 0 | Reading status |
| 257 | Charging |
| 258 | Docked |
| 259 | Docked - Software update |
| 260 | Docked |
| 261 | Docked |
| 262 | Docked - Loading map |
| 263 | Docked - Saving map |
| 513 | Mowing |
| 514 | Relocalising |
| 515 | Loading map |
| 516 | Learning lawn |
| 517 | Paused |
| 518 | Border cut |
| 519 | Idle in lawn |
| 769 | Returning to dock |
| 770 | Returning to dock |
| 771 | Returning to dock - Battery low |
| 772 | Returning to dock - Calendar timeslot ended |
| 773 | Returning to dock - Battery temp range |
| 774 | Returning to dock |
| 775 | Returning to dock - Lawn complete |
| 776 | Returning to dock - Relocalising |
| 1025 | Diagnostic mode |
| 1026 | End of life |
| 1281 | Software update |
| 64513 | Docked |

## Full Example

```perl
Switch item=indegostate mappings=[ 1="Mow", 2="Return",3="Pause" ]
### `indego.things` File

```
boschindego:indego:lawnmower [username="mail@example.com", password="idontneedtocutthelawnagain", refresh=120]
```

## Meaning of the numeric statecodes
### `indego.items` File

You can use this as .map file
```
Number Indego_State { channel="boschindego:indego:lawnmower:state" }
Number Indego_ErrorCode { channel="boschindego:indego:lawnmower:errorcode" }
Number Indego_StateCode { channel="boschindego:indego:lawnmower:statecode" }
String Indego_TextualState { channel="boschindego:indego:lawnmower:textualstate" }
Number Indego_Ready { channel="boschindego:indego:lawnmower:ready" }
Dimmer Indego_Mowed { channel="boschindego:indego:lawnmower:mowed" }
```

### `indego.sitemap` File

```text
0=Reading status
257=Charging
258=Docked
259=Docked - Software update
260=Docked
261=Docked
262=Docked - Loading map
263=Docked - Saving map
513=Mowing
514=Relocalising
515=Loading map
516=Learning lawn
517=Paused
518=Border cut
519=Idle in lawn
769=Returning to Dock
770=Returning to Dock
771=Returning to Dock - Battery low
772=Returning to dock - Calendar timeslot ended
773=Returning to dock - Battery temp range
774=Returning to dock
775=Returning to dock - Lawn complete
776=Returning to dock - Relocalising
```
Switch item=Indego_State mappings=[1="Mow", 2="Return",3="Pause"]
```
31 changes: 0 additions & 31 deletions bundles/org.openhab.binding.boschindego/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,4 @@

<name>openHAB Add-ons :: Bundles :: Bosch Indego Binding</name>

<properties>
<dep.noembedding>httpclient-osgi,httpcore-osgi,commons-codec</dep.noembedding>
</properties>

<dependencies>
<dependency>
<groupId>de.zazaz.iot.bosch.indego</groupId>
<artifactId>bosch-indego-controller-lib</artifactId>
<version>0.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-osgi</artifactId>
<version>4.5.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-osgi</artifactId>
<version>4.4.9</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@

<feature name="openhab-binding-boschindego" description="Bosch Indego Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature dependency="true">openhab.tp-jackson</feature>
<bundle dependency="true">mvn:org.apache.httpcomponents/httpcore-osgi/4.4.9</bundle>
<bundle dependency="true">mvn:org.apache.httpcomponents/httpclient-osgi/4.5.5</bundle>
<bundle dependency="true">mvn:commons-codec/commons-codec/1.10</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.boschindego/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.boschindego.internal;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;

Expand All @@ -36,4 +38,6 @@ public class BoschIndegoBindingConstants {
public static final String ERRORCODE = "errorcode";
public static final String STATECODE = "statecode";
public static final String READY = "ready";

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_INDEGO);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,51 @@

import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.THING_TYPE_INDEGO;

import java.util.Collections;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.boschindego.internal.handler.BoschIndegoHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* The {@link BoschIndegoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Jonas Fleck - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.boschindego")
public class BoschIndegoHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_INDEGO);
private final HttpClient httpClient;

@Activate
public BoschIndegoHandlerFactory(@Reference HttpClientFactory httpClientFactory,
ComponentContext componentContext) {
super.activate(componentContext);
this.httpClient = httpClientFactory.getCommonHttpClient();
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
return BoschIndegoBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}

@Override
protected ThingHandler createHandler(Thing thing) {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (thingTypeUID.equals(THING_TYPE_INDEGO)) {
return new BoschIndegoHandler(thing);
if (THING_TYPE_INDEGO.equals(thingTypeUID)) {
return new BoschIndegoHandler(thing, httpClient);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2010-2022 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.boschindego.internal;

import static java.util.Map.entry;

import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.boschindego.internal.dto.DeviceCommand;

/**
* {@link DeviceStatus} describes status codes from the device with corresponding
* ready state and associated command.
*
* @author Jacob Laursen - Initial contribution
*/
@NonNullByDefault
public class DeviceStatus {

private static final Map<Integer, DeviceStatus> STATUS_MAP = Map.ofEntries(
entry(0, new DeviceStatus("Reading status", false, DeviceCommand.RETURN)),
entry(257, new DeviceStatus("Charging", false, DeviceCommand.RETURN)),
entry(258, new DeviceStatus("Docked", true, DeviceCommand.RETURN)),
entry(259, new DeviceStatus("Docked - Software update", false, DeviceCommand.RETURN)),
entry(260, new DeviceStatus("Docked", true, DeviceCommand.RETURN)),
entry(261, new DeviceStatus("Docked", true, DeviceCommand.RETURN)),
entry(262, new DeviceStatus("Docked - Loading map", false, DeviceCommand.MOW)),
entry(263, new DeviceStatus("Docked - Saving map", false, DeviceCommand.RETURN)),
entry(513, new DeviceStatus("Mowing", false, DeviceCommand.MOW)),
entry(514, new DeviceStatus("Relocalising", false, DeviceCommand.MOW)),
entry(515, new DeviceStatus("Loading map", false, DeviceCommand.MOW)),
entry(516, new DeviceStatus("Learning lawn", false, DeviceCommand.MOW)),
entry(517, new DeviceStatus("Paused", true, DeviceCommand.PAUSE)),
entry(518, new DeviceStatus("Border cut", false, DeviceCommand.MOW)),
entry(519, new DeviceStatus("Idle in lawn", true, DeviceCommand.MOW)),
entry(769, new DeviceStatus("Returning to dock", false, DeviceCommand.RETURN)),
entry(770, new DeviceStatus("Returning to dock", false, DeviceCommand.RETURN)),
entry(771, new DeviceStatus("Returning to dock - Battery low", false, DeviceCommand.RETURN)),
entry(772, new DeviceStatus("Returning to dock - Calendar timeslot ended", false, DeviceCommand.RETURN)),
entry(773, new DeviceStatus("Returning to dock - Battery temp range", false, DeviceCommand.RETURN)),
entry(774, new DeviceStatus("Returning to dock", false, DeviceCommand.RETURN)),
entry(775, new DeviceStatus("Returning to dock - Lawn complete", false, DeviceCommand.RETURN)),
entry(776, new DeviceStatus("Returning to dock - Relocalising", false, DeviceCommand.RETURN)),
entry(1025, new DeviceStatus("Diagnostic mode", false, null)),
entry(1026, new DeviceStatus("End of life", false, null)),
entry(1281, new DeviceStatus("Software update", false, null)),
entry(64513, new DeviceStatus("Docked", true, DeviceCommand.RETURN)));

private String message;

private boolean isReadyToMow;

private @Nullable DeviceCommand associatedCommand;

private DeviceStatus(String message, boolean isReadyToMow, @Nullable DeviceCommand associatedCommand) {
this.message = message;
this.isReadyToMow = isReadyToMow;
this.associatedCommand = associatedCommand;
}

/**
* Returns a {@link DeviceStatus} instance describing the status code.
*
* @param code the status code
* @return the {@link DeviceStatus} providing additional context for the code
*/
public static DeviceStatus fromCode(int code) {
DeviceStatus status = STATUS_MAP.get(code);
if (status != null) {
return status;
}

DeviceCommand command = null;
switch (code & 0xff00) {
case 0x100:
command = DeviceCommand.RETURN;
break;
case 0x200:
command = DeviceCommand.MOW;
break;
case 0x300:
command = DeviceCommand.RETURN;
break;
}

return new DeviceStatus(String.format("Unknown status code %d", code), false, command);
}

public String getMessage() {
return message;
}

public boolean isReadyToMow() {
return isReadyToMow;
}

public @Nullable DeviceCommand getAssociatedCommand() {
return associatedCommand;
}
}
Loading

0 comments on commit 960be6b

Please sign in to comment.