mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[boschindego] Add device properties (#14829)
* Add device properties * Add vendor and model properties * Use model as label in discovery Resolves #14828 --------- Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
b21913f5be
commit
275329d485
@ -32,6 +32,8 @@ public class BoschIndegoBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_INDEGO = new ThingTypeUID(BINDING_ID, "indego");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_INDEGO);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String STATE = "state";
|
||||
public static final String TEXTUAL_STATE = "textualstate";
|
||||
@ -48,7 +50,11 @@ public class BoschIndegoBindingConstants {
|
||||
public static final String GARDEN_SIZE = "gardenSize";
|
||||
public static final String GARDEN_MAP = "gardenMap";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_INDEGO);
|
||||
// Device properties
|
||||
public static final String PROPERTY_BARE_TOOL_NUMBER = "bareToolNumber";
|
||||
public static final String PROPERTY_SERVICE_COUNTER = "serviceCounter";
|
||||
public static final String PROPERTY_NEEDS_SERVICE = "needsService";
|
||||
public static final String PROPERTY_RENEW_DATE = "renewDate";
|
||||
|
||||
// Bosch SingleKey ID OAuth2
|
||||
private static final String BSK_BASE_URI = "https://prodindego.b2clogin.com/prodindego.onmicrosoft.com/b2c_1a_signup_signin/oauth2/v2.0/";
|
||||
|
@ -15,6 +15,7 @@ package org.openhab.binding.boschindego.internal;
|
||||
import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -31,8 +32,10 @@ import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.ErrorResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.Mower;
|
||||
import org.openhab.binding.boschindego.internal.dto.serialization.InstantDeserializer;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
|
||||
@ -48,6 +51,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
@ -62,11 +66,10 @@ public class IndegoController {
|
||||
|
||||
private static final String BASE_URL = "https://api.indego-cloud.iot.bosch-si.com/api/v1/";
|
||||
private static final String CONTENT_TYPE_HEADER = "application/json";
|
||||
|
||||
private static final String BEARER = "Bearer ";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IndegoController.class);
|
||||
private final Gson gson = new Gson();
|
||||
private final Gson gson = new GsonBuilder().registerTypeAdapter(Instant.class, new InstantDeserializer()).create();
|
||||
private final HttpClient httpClient;
|
||||
private final OAuthClientService oAuthClientService;
|
||||
private final String userAgent;
|
||||
@ -96,6 +99,19 @@ public class IndegoController {
|
||||
return Arrays.stream(mowers).map(m -> m.serialNumber).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the serial number and device service properties from the server.
|
||||
*
|
||||
* @param serialNumber the serial number of the device
|
||||
* @return the device serial number and properties
|
||||
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||
* @throws IndegoException if any communication or parsing error occurred
|
||||
*/
|
||||
public DevicePropertiesResponse getDeviceProperties(String serialNumber)
|
||||
throws IndegoAuthenticationException, IndegoException {
|
||||
return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/", DevicePropertiesResponse.class);
|
||||
}
|
||||
|
||||
private String getAuthorizationUrl() {
|
||||
try {
|
||||
return oAuthClientService.getAuthorizationUrl(BSK_REDIRECT_URI, BSK_SCOPE, null);
|
||||
|
@ -24,6 +24,7 @@ import org.openhab.binding.boschindego.internal.dto.PredictiveAdjustment;
|
||||
import org.openhab.binding.boschindego.internal.dto.PredictiveStatus;
|
||||
import org.openhab.binding.boschindego.internal.dto.request.SetStateRequest;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceCalendarResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.LocationWeatherResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.OperatingDataResponse;
|
||||
@ -71,6 +72,17 @@ public class IndegoDeviceController extends IndegoController {
|
||||
this.serialNumber = serialNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the serial number and device service properties from the server.
|
||||
*
|
||||
* @return the device serial number and properties
|
||||
* @throws IndegoAuthenticationException if request was rejected as unauthorized
|
||||
* @throws IndegoException if any communication or parsing error occurred
|
||||
*/
|
||||
public DevicePropertiesResponse getDeviceProperties() throws IndegoAuthenticationException, IndegoException {
|
||||
return super.getDeviceProperties(serialNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the device state from the server.
|
||||
*
|
||||
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Translates from tool number to model names.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class IndegoTypeDatabase {
|
||||
|
||||
/**
|
||||
* Return tool name from tool type number.
|
||||
*
|
||||
* @see https://www.boschtoolservice.com/gb/en/boschdiy/spareparts/search-results?q=Indego
|
||||
*
|
||||
* @param toolTypeNumber condensed tool type number, e.g. "3600HA2200" rather than "3 600 HA2 200".
|
||||
* @return tool type name
|
||||
*/
|
||||
public static String nameFromTypeNumber(String toolTypeNumber) {
|
||||
String name = switch (toolTypeNumber) {
|
||||
case "3600HA2103" -> "800";
|
||||
case "3600HA2104" -> "850";
|
||||
case "3600HA2200", "3600HA2201" -> "1300";
|
||||
case "3600HA2300" -> "1000 Connect";
|
||||
case "3600HA2301" -> "1200 Connect";
|
||||
case "3600HA2302" -> "1100 Connect";
|
||||
case "3600HA2303" -> "13C";
|
||||
case "3600HA2304" -> "10C";
|
||||
case "3600HB0000" -> "350";
|
||||
case "3600HB0001" -> "400";
|
||||
case "3600HB0004" -> "XS 300";
|
||||
case "3600HB0006" -> "350";
|
||||
case "3600HB0007" -> "400";
|
||||
case "3600HB0100" -> "350 Connect";
|
||||
case "3600HB0101" -> "400 Connect";
|
||||
case "3600HB0102" -> "S+ 350";
|
||||
case "3600HB0103" -> "S+ 400";
|
||||
case "3600HB0105" -> "S+ 350";
|
||||
case "3600HB0106" -> "S+ 400";
|
||||
case "3600HB0201" -> "M";
|
||||
case "3600HB0202" -> "S 500";
|
||||
case "3600HB0203" -> "M 700";
|
||||
case "3600HB0301" -> "M+";
|
||||
case "3600HB0302" -> "S+ 500";
|
||||
case "3600HB0303" -> "M+ 700";
|
||||
default -> "";
|
||||
};
|
||||
|
||||
return (name.isEmpty() ? "Indego" : "Indego " + name);
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschindego.internal.IndegoTypeDatabase;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
||||
import org.openhab.binding.boschindego.internal.handler.BoschAccountHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
@ -71,15 +73,15 @@ public class IndegoDiscoveryService extends AbstractDiscoveryService implements
|
||||
@Override
|
||||
public void startScan() {
|
||||
try {
|
||||
Collection<String> serialNumbers = accountHandler.getSerialNumbers();
|
||||
Collection<DevicePropertiesResponse> devices = accountHandler.getDevices();
|
||||
|
||||
ThingUID bridgeUID = accountHandler.getThing().getUID();
|
||||
for (String serialNumber : serialNumbers) {
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_INDEGO, bridgeUID, serialNumber);
|
||||
for (DevicePropertiesResponse device : devices) {
|
||||
ThingUID thingUID = new ThingUID(THING_TYPE_INDEGO, bridgeUID, device.serialNumber);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber).withBridge(bridgeUID)
|
||||
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, device.serialNumber).withBridge(bridgeUID)
|
||||
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER)
|
||||
.withLabel("Indego (" + serialNumber + ")").build();
|
||||
.withLabel(IndegoTypeDatabase.nameFromTypeNumber(device.bareToolNumber)).build();
|
||||
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.dto.response;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Response for serial number and other device service properties.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
public class DevicePropertiesResponse {
|
||||
|
||||
@SerializedName("alm_sn")
|
||||
public String serialNumber = "";
|
||||
|
||||
@SerializedName("service_counter")
|
||||
public int serviceCounter;
|
||||
|
||||
@SerializedName("needs_service")
|
||||
public boolean needsService;
|
||||
|
||||
/**
|
||||
* Mode: manual, smart
|
||||
*/
|
||||
@SerializedName("alm_mode")
|
||||
public String mode;
|
||||
|
||||
@SerializedName("bareToolnumber")
|
||||
public String bareToolNumber;
|
||||
|
||||
@SerializedName("alm_firmware_version")
|
||||
public String firmwareVersion;
|
||||
|
||||
@SerializedName("renew_date")
|
||||
public Instant renewDate;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.dto.serialization;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The {@link InstantDeserializer} converts a formatted UTC string to {@link Instant}.
|
||||
*
|
||||
* @author Jacob Laursen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class InstantDeserializer implements JsonDeserializer<Instant> {
|
||||
|
||||
@Override
|
||||
public @Nullable Instant deserialize(JsonElement element, Type arg1, JsonDeserializationContext arg2)
|
||||
throws JsonParseException {
|
||||
try {
|
||||
return Instant.parse(element.getAsString());
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new JsonParseException("Could not parse as Instant: " + element.getAsString(), e);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ package org.openhab.binding.boschindego.internal.handler;
|
||||
import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@ -22,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.boschindego.internal.IndegoController;
|
||||
import org.openhab.binding.boschindego.internal.discovery.IndegoDiscoveryService;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
@ -119,7 +121,18 @@ public class BoschAccountHandler extends BaseBridgeHandler {
|
||||
return oAuthClientService;
|
||||
}
|
||||
|
||||
public Collection<String> getSerialNumbers() throws IndegoException {
|
||||
return controller.getSerialNumbers();
|
||||
public Collection<DevicePropertiesResponse> getDevices() throws IndegoException {
|
||||
Collection<String> serialNumbers = controller.getSerialNumbers();
|
||||
List<DevicePropertiesResponse> devices = new ArrayList<DevicePropertiesResponse>(serialNumbers.size());
|
||||
|
||||
for (String serialNumber : serialNumbers) {
|
||||
DevicePropertiesResponse properties = controller.getDeviceProperties(serialNumber);
|
||||
if (properties.serialNumber == null) {
|
||||
properties.serialNumber = serialNumber;
|
||||
}
|
||||
devices.add(properties);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,10 @@ import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstan
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -29,8 +31,10 @@ import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.boschindego.internal.BoschIndegoTranslationProvider;
|
||||
import org.openhab.binding.boschindego.internal.DeviceStatus;
|
||||
import org.openhab.binding.boschindego.internal.IndegoDeviceController;
|
||||
import org.openhab.binding.boschindego.internal.IndegoTypeDatabase;
|
||||
import org.openhab.binding.boschindego.internal.config.BoschIndegoConfiguration;
|
||||
import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
|
||||
import org.openhab.binding.boschindego.internal.dto.response.OperatingDataResponse;
|
||||
import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
|
||||
@ -75,6 +79,7 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||
private static final String MAP_POSITION_STROKE_COLOR = "#8c8b6d";
|
||||
private static final String MAP_POSITION_FILL_COLOR = "#fff701";
|
||||
private static final int MAP_POSITION_RADIUS = 10;
|
||||
private static final Duration DEVICE_PROPERTIES_VALIDITY_PERIOD = Duration.ofDays(1);
|
||||
|
||||
private static final Duration MAP_REFRESH_INTERVAL = Duration.ofDays(1);
|
||||
private static final Duration OPERATING_DATA_INACTIVE_REFRESH_INTERVAL = Duration.ofHours(6);
|
||||
@ -87,6 +92,7 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||
private final HttpClient httpClient;
|
||||
private final BoschIndegoTranslationProvider translationProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private Instant devicePropertiesUpdated = Instant.MIN;
|
||||
|
||||
private @NonNullByDefault({}) OAuthClientService oAuthClientService;
|
||||
private @NonNullByDefault({}) IndegoDeviceController controller;
|
||||
@ -133,7 +139,8 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateProperty(Thing.PROPERTY_SERIAL_NUMBER, config.serialNumber);
|
||||
devicePropertiesUpdated = Instant.MIN;
|
||||
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, config.serialNumber);
|
||||
|
||||
controller = new IndegoDeviceController(httpClient, oAuthClientService, config.serialNumber);
|
||||
|
||||
@ -306,6 +313,10 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||
DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
|
||||
updateState(state);
|
||||
|
||||
if (devicePropertiesUpdated.isBefore(Instant.now().minus(DEVICE_PROPERTIES_VALIDITY_PERIOD))) {
|
||||
refreshDeviceProperties();
|
||||
}
|
||||
|
||||
// Update map and start tracking positions if mower is active.
|
||||
if (state.mapUpdateAvailable) {
|
||||
cachedMapTimestamp = Instant.MIN;
|
||||
@ -348,6 +359,26 @@ public class BoschIndegoHandler extends BaseThingHandler {
|
||||
rescheduleStatePollAccordingToState(deviceStatus);
|
||||
}
|
||||
|
||||
private void refreshDeviceProperties() throws IndegoAuthenticationException, IndegoException {
|
||||
DevicePropertiesResponse deviceProperties = controller.getDeviceProperties();
|
||||
Map<String, String> properties = editProperties();
|
||||
if (deviceProperties.firmwareVersion != null) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceProperties.firmwareVersion);
|
||||
}
|
||||
if (deviceProperties.bareToolNumber != null) {
|
||||
properties.put(Thing.PROPERTY_MODEL_ID,
|
||||
IndegoTypeDatabase.nameFromTypeNumber(deviceProperties.bareToolNumber));
|
||||
properties.put(PROPERTY_BARE_TOOL_NUMBER, deviceProperties.bareToolNumber);
|
||||
}
|
||||
properties.put(PROPERTY_SERVICE_COUNTER, String.valueOf(deviceProperties.serviceCounter));
|
||||
properties.put(PROPERTY_NEEDS_SERVICE, String.valueOf(deviceProperties.needsService));
|
||||
properties.put(PROPERTY_RENEW_DATE,
|
||||
LocalDateTime.ofInstant(deviceProperties.renewDate, timeZoneProvider.getTimeZone()).toString());
|
||||
|
||||
updateProperties(properties);
|
||||
devicePropertiesUpdated = Instant.now();
|
||||
}
|
||||
|
||||
private void rescheduleStatePollAccordingToState(DeviceStatus deviceStatus) {
|
||||
int refreshIntervalSeconds;
|
||||
if (deviceStatus.isActive()) {
|
||||
|
@ -34,6 +34,10 @@
|
||||
<channel id="gardenMap" typeId="gardenMap"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Bosch</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
|
Loading…
Reference in New Issue
Block a user