[VolvoOnCall] OH3 update (#8595)
* [VolvoOnCall] OH3 update Solving issue #8554 and issue #8518 Some code corrections and enhancements. Introduced new trigger channels. * Doing spotless apply * Some other code improvements * Moving back to Jetty HttpClient Introduced Expiring cache for request to avoid flooding voc servers Reduced the number of requests emitted Changed user agent identification * Correcting spotless * Pleasing Travis * Code review corrections * Adressing cpmeister code review Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
parent
d42efe0b22
commit
5648f5e51d
|
@ -21,7 +21,7 @@ The 'VolvoOnCall API' bridge uses the owner's email address and password in orde
|
|||
This is the same email address and password as used in the VolvoOnCall smartphone app, that allows to remotely control your car(s).
|
||||
|
||||
| Parameter | Description | Required |
|
||||
|-----------|------------------------------------------------------------------------- |--------- |
|
||||
|-----------------|------------------------------------------------------|--------- |
|
||||
| username | Username from the VolvoOnCall app (email address) | yes |
|
||||
| password | Password from the VolvoOnCall app | yes |
|
||||
|
||||
|
@ -35,7 +35,8 @@ The 'VolvoOnCall API' bridge uses the owner's email address and password in orde
|
|||
| Parameter | Name | Description | Required |
|
||||
|-----------------|------------------|---------------------------------------------------------|----------|
|
||||
| vin | Vin | Vehicle Identification Number of the car | yes |
|
||||
| refreshinterval | Refresh interval | Interval in minutes to refresh the data (default=10) | no |
|
||||
| refreshinterval | Refresj Interval | Interval in minutes to refresh the data (default=10) | yes |
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -100,6 +101,15 @@ Following channels are currently available:
|
|||
| lasttrip#endPosition | Location | Last trip end location | |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
| Channel Type ID | Options | Description |
|
||||
|--------------------|-------------|----------------------------------------------------------------|
|
||||
| other#carEvent | | |
|
||||
| | CAR_STOPPED | Triggered when the car has finished a trip |
|
||||
| | CAR_MOVED | Triggered if the car mileage has changed between two polls |
|
||||
| | CAR_STARTED | Triggered when the engine of the car went on between two polls |
|
||||
|
||||
## Full Example
|
||||
|
||||
demo.things:
|
||||
|
|
|
@ -30,16 +30,6 @@ public class VolvoOnCallBindingConstants {
|
|||
|
||||
public static final String BINDING_ID = "volvooncall";
|
||||
|
||||
// Vehicle properties
|
||||
public static final String VIN = "vin";
|
||||
|
||||
// The URL to use to connect to VocAPI with.
|
||||
// TODO : for North America and China syntax changes to vocapi-cn.xxx
|
||||
public static final String SERVICE_URL = "https://vocapi.wirelesscar.net/customerapi/rest/v3.0/";
|
||||
|
||||
// The JSON content type used when talking to VocAPI.
|
||||
public static final String JSON_CONTENT_TYPE = "application/json";
|
||||
|
||||
// List of Thing Type UIDs
|
||||
public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "vocapi");
|
||||
public static final ThingTypeUID VEHICLE_THING_TYPE = new ThingTypeUID(BINDING_ID, "vehicle");
|
||||
|
@ -49,6 +39,10 @@ public class VolvoOnCallBindingConstants {
|
|||
public static final String GROUP_WINDOWS = "windows";
|
||||
public static final String GROUP_TYRES = "tyrePressure";
|
||||
public static final String GROUP_BATTERY = "battery";
|
||||
public static final String GROUP_OTHER = "other";
|
||||
public static final String GROUP_POSITION = "position";
|
||||
public static final String GROUP_ODOMETER = "odometer";
|
||||
public static final String GROUP_TANK = "tank";
|
||||
|
||||
// List of Channel id's
|
||||
public static final String TAILGATE = "tailgate";
|
||||
|
@ -90,6 +84,11 @@ public class VolvoOnCallBindingConstants {
|
|||
public static final String CHARGING_END = "chargingEnd";
|
||||
public static final String BULB_FAILURE = "bulbFailure";
|
||||
|
||||
// Car Events
|
||||
public static final String CAR_EVENT = "carEvent";
|
||||
public static final String EVENT_CAR_STOPPED = "CAR_STOPPED";
|
||||
public static final String EVENT_CAR_MOVED = "CAR_MOVED";
|
||||
public static final String EVENT_CAR_STARTED = "CAR_STARTED";
|
||||
// Last Trip Channel Id's
|
||||
public static final String LAST_TRIP_GROUP = "lasttrip";
|
||||
public static final String TRIP_CONSUMPTION = "tripConsumption";
|
||||
|
|
|
@ -34,7 +34,9 @@ public class VolvoOnCallException extends Exception {
|
|||
public static enum ErrorType {
|
||||
UNKNOWN,
|
||||
SERVICE_UNAVAILABLE,
|
||||
SERVICE_UNABLE_TO_START,
|
||||
IOEXCEPTION,
|
||||
INTERRUPTED,
|
||||
JSON_SYNTAX;
|
||||
}
|
||||
|
||||
|
@ -44,6 +46,8 @@ public class VolvoOnCallException extends Exception {
|
|||
super(label);
|
||||
if ("FoundationServicesUnavailable".equalsIgnoreCase(label)) {
|
||||
cause = ErrorType.SERVICE_UNAVAILABLE;
|
||||
} else if ("ServiceUnableToStart".equalsIgnoreCase(label)) {
|
||||
cause = ErrorType.SERVICE_UNABLE_TO_START;
|
||||
} else {
|
||||
cause = ErrorType.UNKNOWN;
|
||||
logger.warn("Unhandled VoC error : {} : {}", label, description);
|
||||
|
@ -56,6 +60,8 @@ public class VolvoOnCallException extends Exception {
|
|||
cause = ErrorType.IOEXCEPTION;
|
||||
} else if (e instanceof JsonSyntaxException) {
|
||||
cause = ErrorType.JSON_SYNTAX;
|
||||
} else if (e instanceof InterruptedException) {
|
||||
cause = ErrorType.INTERRUPTED;
|
||||
} else {
|
||||
cause = ErrorType.UNKNOWN;
|
||||
logger.warn("Unhandled VoC error : {}", e.getMessage());
|
||||
|
|
|
@ -14,31 +14,34 @@ package org.openhab.binding.volvooncall.internal;
|
|||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.discovery.VolvoOnCallDiscoveryService;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VehicleHandler;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VehicleStateDescriptionProvider;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VolvoOnCallBridgeHandler;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
|
@ -48,13 +51,31 @@ import org.slf4j.LoggerFactory;
|
|||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.volvooncall", service = ThingHandlerFactory.class)
|
||||
public class VolvoOnCallHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallHandlerFactory.class);
|
||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
private final VehicleStateDescriptionProvider stateDescriptionProvider;
|
||||
private final Gson gson;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public VolvoOnCallHandlerFactory(@Reference VehicleStateDescriptionProvider provider) {
|
||||
public VolvoOnCallHandlerFactory(@Reference VehicleStateDescriptionProvider provider,
|
||||
@Reference HttpClientFactory httpClientFactory) {
|
||||
this.stateDescriptionProvider = provider;
|
||||
this.httpClient = httpClientFactory.createHttpClient(BINDING_ID);
|
||||
this.gson = new GsonBuilder()
|
||||
.registerTypeAdapter(ZonedDateTime.class,
|
||||
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
|
||||
.parse(json.getAsJsonPrimitive().getAsString().replaceAll("\\+0000", "Z")))
|
||||
.registerTypeAdapter(OpenClosedType.class,
|
||||
(JsonDeserializer<OpenClosedType>) (json, type,
|
||||
jsonDeserializationContext) -> json.getAsBoolean() ? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED)
|
||||
.registerTypeAdapter(OnOffType.class,
|
||||
(JsonDeserializer<OnOffType>) (json, type,
|
||||
jsonDeserializationContext) -> json.getAsBoolean() ? OnOffType.ON : OnOffType.OFF)
|
||||
.registerTypeAdapter(StringType.class, (JsonDeserializer<StringType>) (json, type,
|
||||
jsonDeserializationContext) -> StringType.valueOf(json.getAsString()))
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -66,36 +87,11 @@ public class VolvoOnCallHandlerFactory extends BaseThingHandlerFactory {
|
|||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = new VolvoOnCallBridgeHandler((Bridge) thing);
|
||||
registerDeviceDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
return new VolvoOnCallBridgeHandler((Bridge) thing, gson, httpClient);
|
||||
} else if (VEHICLE_THING_TYPE.equals(thingTypeUID)) {
|
||||
return new VehicleHandler(thing, stateDescriptionProvider);
|
||||
}
|
||||
logger.warn("ThingHandler not found for {}", thing.getThingTypeUID());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof VolvoOnCallBridgeHandler) {
|
||||
ThingUID thingUID = thingHandler.getThing().getUID();
|
||||
unregisterDeviceDiscoveryService(thingUID);
|
||||
}
|
||||
super.removeHandler(thingHandler);
|
||||
}
|
||||
|
||||
private void registerDeviceDiscoveryService(VolvoOnCallBridgeHandler bridgeHandler) {
|
||||
VolvoOnCallDiscoveryService discoveryService = new VolvoOnCallDiscoveryService(bridgeHandler);
|
||||
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
|
||||
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
private void unregisterDeviceDiscoveryService(ThingUID thingUID) {
|
||||
if (discoveryServiceRegs.containsKey(thingUID)) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegs.get(thingUID);
|
||||
serviceReg.unregister();
|
||||
discoveryServiceRegs.remove(thingUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2020 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.volvooncall.internal.action;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link IVolvoOnCallActions} defines the interface for all thing actions supported by the binding.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface IVolvoOnCallActions {
|
||||
public void honkBlinkCommand(Boolean honk, Boolean blink);
|
||||
|
||||
public void preclimatizationStopCommand();
|
||||
|
||||
public void heaterStopCommand();
|
||||
|
||||
public void heaterStartCommand();
|
||||
|
||||
public void preclimatizationStartCommand();
|
||||
|
||||
public void engineStartCommand(@Nullable Integer runtime);
|
||||
|
||||
public void openCarCommand();
|
||||
|
||||
public void closeCarCommand();
|
||||
}
|
|
@ -12,14 +12,14 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.action;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VehicleHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
|
@ -34,7 +34,7 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
@ThingActionsScope(name = "volvooncall")
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallActions implements ThingActions, IVolvoOnCallActions {
|
||||
public class VolvoOnCallActions implements ThingActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallActions.class);
|
||||
|
||||
|
@ -56,40 +56,29 @@ public class VolvoOnCallActions implements ThingActions, IVolvoOnCallActions {
|
|||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Close", description = "Closes the car")
|
||||
@RuleAction(label = "close the car", description = "Closes the car")
|
||||
public void closeCarCommand() {
|
||||
logger.debug("closeCarCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionClose();
|
||||
handler.actionOpenClose(LOCK, OnOffType.ON);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeCarCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).closeCarCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Open", description = "Opens the car")
|
||||
@RuleAction(label = "open the car", description = "Opens the car")
|
||||
public void openCarCommand() {
|
||||
logger.debug("openCarCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionOpen();
|
||||
handler.actionOpenClose(UNLOCK, OnOffType.OFF);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void openCarCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).openCarCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Start Engine", description = "Starts the engine")
|
||||
@RuleAction(label = "start the engine", description = "Starts the engine")
|
||||
public void engineStartCommand(@ActionInput(name = "runtime", label = "Runtime") @Nullable Integer runtime) {
|
||||
logger.debug("engineStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
|
@ -100,76 +89,51 @@ public class VolvoOnCallActions implements ThingActions, IVolvoOnCallActions {
|
|||
}
|
||||
}
|
||||
|
||||
public static void engineStartCommand(@Nullable ThingActions actions, @Nullable Integer runtime) {
|
||||
invokeMethodOf(actions).engineStartCommand(runtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Heater Start", description = "Starts car heater")
|
||||
@RuleAction(label = "start the heater", description = "Starts car heater")
|
||||
public void heaterStartCommand() {
|
||||
logger.debug("heaterStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionHeater(true);
|
||||
handler.actionHeater(REMOTE_HEATER, true);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void heaterStartCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).heaterStartCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Preclimatization Start", description = "Starts car heater")
|
||||
@RuleAction(label = "start preclimatization", description = "Starts the car heater")
|
||||
public void preclimatizationStartCommand() {
|
||||
logger.debug("preclimatizationStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionPreclimatization(true);
|
||||
handler.actionHeater(PRECLIMATIZATION, true);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void preclimatizationStartCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).preclimatizationStartCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Heater Stop", description = "Stops car heater")
|
||||
@RuleAction(label = "stop the heater", description = "Stops car heater")
|
||||
public void heaterStopCommand() {
|
||||
logger.debug("heaterStopCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionHeater(false);
|
||||
handler.actionHeater(REMOTE_HEATER, false);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void heaterStopCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).heaterStopCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Preclimatization Stop", description = "Stops car heater")
|
||||
@RuleAction(label = "stop preclimatization", description = "Stops the car heater")
|
||||
public void preclimatizationStopCommand() {
|
||||
logger.debug("preclimatizationStopCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionPreclimatization(false);
|
||||
handler.actionHeater(PRECLIMATIZATION, false);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void preclimatizationStopCommand(@Nullable ThingActions actions) {
|
||||
invokeMethodOf(actions).preclimatizationStopCommand();
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Honk-blink", description = "Activates the horn and or lights of the car")
|
||||
@RuleAction(label = "honk-blink", description = "Activates the horn and or lights of the car")
|
||||
public void honkBlinkCommand(@ActionInput(name = "honk", label = "Honk") Boolean honk,
|
||||
@ActionInput(name = "blink", label = "Blink") Boolean blink) {
|
||||
logger.debug("honkBlinkCommand called");
|
||||
|
@ -180,27 +144,4 @@ public class VolvoOnCallActions implements ThingActions, IVolvoOnCallActions {
|
|||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void honkBlinkCommand(@Nullable ThingActions actions, Boolean honk, Boolean blink) {
|
||||
invokeMethodOf(actions).honkBlinkCommand(honk, blink);
|
||||
}
|
||||
|
||||
private static IVolvoOnCallActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(VolvoOnCallActions.class.getName())) {
|
||||
if (actions instanceof IVolvoOnCallActions) {
|
||||
return (IVolvoOnCallActions) actions;
|
||||
} else {
|
||||
return (IVolvoOnCallActions) Proxy.newProxyInstance(IVolvoOnCallActions.class.getClassLoader(),
|
||||
new Class[] { IVolvoOnCallActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of VolvoOnCallActions");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2020 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.volvooncall.internal.api;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
|
||||
import org.openhab.binding.volvooncall.internal.dto.PostResponse;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ActionResultController} is responsible for triggering information
|
||||
* update after a post has been submitted to the webservice.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ActionResultController implements Runnable {
|
||||
private final Logger logger = LoggerFactory.getLogger(ActionResultController.class);
|
||||
|
||||
private final VocHttpApi service;
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final PostResponse postResponse;
|
||||
private final ThingHandler vehicle;
|
||||
|
||||
public ActionResultController(VocHttpApi service, PostResponse postResponse, ScheduledExecutorService scheduler,
|
||||
ThingHandler vehicle) {
|
||||
this.postResponse = postResponse;
|
||||
this.service = service;
|
||||
this.scheduler = scheduler;
|
||||
this.vehicle = vehicle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
switch (postResponse.status) {
|
||||
case SUCCESSFULL:
|
||||
case FAILED:
|
||||
logger.debug("Action {} for vehicle {} resulted : {}.", postResponse.serviceType,
|
||||
postResponse.vehicleId, postResponse.status);
|
||||
vehicle.handleCommand(vehicle.getThing().getChannels().get(0).getUID(), RefreshType.REFRESH);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
scheduler.schedule(
|
||||
new ActionResultController(service,
|
||||
service.getURL(postResponse.serviceURL, PostResponse.class), scheduler, vehicle),
|
||||
10000, TimeUnit.MILLISECONDS);
|
||||
} catch (VolvoOnCallException e) {
|
||||
if (e.getType() == ErrorType.SERVICE_UNAVAILABLE) {
|
||||
scheduler.schedule(this, 10000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2020 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.volvooncall.internal.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentProvider;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
|
||||
import org.openhab.binding.volvooncall.internal.config.ApiBridgeConfiguration;
|
||||
import org.openhab.binding.volvooncall.internal.dto.PostResponse;
|
||||
import org.openhab.binding.volvooncall.internal.dto.VocAnswer;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
import org.openhab.core.id.InstanceUUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* {@link VocHttpApi} wraps the VolvoOnCall REST API.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VocHttpApi {
|
||||
// The URL to use to connect to VocAPI.
|
||||
// For North America and China syntax changes to vocapi-cn.xxx
|
||||
private static final String SERVICE_URL = "https://vocapi.wirelesscar.net/customerapi/rest/v3.0/";
|
||||
private static final int TIMEOUT_MS = 10000;
|
||||
private static final String JSON_CONTENT_TYPE = "application/json";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VocHttpApi.class);
|
||||
private final Gson gson;
|
||||
private final ExpiringCacheMap<String, @Nullable String> cache;
|
||||
private final HttpClient httpClient;
|
||||
private final ApiBridgeConfiguration configuration;
|
||||
|
||||
public VocHttpApi(ApiBridgeConfiguration configuration, Gson gson, HttpClient httpClient)
|
||||
throws VolvoOnCallException {
|
||||
this.gson = gson;
|
||||
this.cache = new ExpiringCacheMap<>(120 * 1000);
|
||||
this.configuration = configuration;
|
||||
this.httpClient = httpClient;
|
||||
|
||||
httpClient.setUserAgentField(new HttpField(HttpHeader.USER_AGENT, "openhab/voc_binding/" + InstanceUUID.get()));
|
||||
try {
|
||||
httpClient.start();
|
||||
} catch (Exception e) {
|
||||
throw new VolvoOnCallException(new IOException("Unable to start Jetty HttpClient", e));
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() throws Exception {
|
||||
httpClient.stop();
|
||||
}
|
||||
|
||||
private @Nullable String getResponse(HttpMethod method, String url, @Nullable String body) {
|
||||
try {
|
||||
Request request = httpClient.newRequest(url).header(HttpHeader.CACHE_CONTROL, "no-cache")
|
||||
.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE).header(HttpHeader.ACCEPT, "*/*")
|
||||
.header(HttpHeader.AUTHORIZATION, configuration.getAuthorization()).header("x-device-id", "Device")
|
||||
.header("x-originator-type", "App").header("x-os-type", "Android").header("x-os-version", "22")
|
||||
.timeout(TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
if (body != null) {
|
||||
ContentProvider content = new StringContentProvider(JSON_CONTENT_TYPE, body, StandardCharsets.UTF_8);
|
||||
request = request.content(content);
|
||||
}
|
||||
ContentResponse contentResponse = request.method(method).send();
|
||||
return contentResponse.getContentAsString();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends VocAnswer> T callUrl(HttpMethod method, String endpoint, Class<T> objectClass,
|
||||
@Nullable String body) throws VolvoOnCallException {
|
||||
try {
|
||||
String url = endpoint.startsWith("http") ? endpoint : SERVICE_URL + endpoint;
|
||||
String jsonResponse = method == HttpMethod.GET
|
||||
? cache.putIfAbsentAndGet(endpoint, () -> getResponse(method, url, body))
|
||||
: getResponse(method, url, body);
|
||||
if (jsonResponse == null) {
|
||||
throw new IOException();
|
||||
} else {
|
||||
logger.debug("Request to `{}` answered : {}", url, jsonResponse);
|
||||
T responseDTO = gson.fromJson(jsonResponse, objectClass);
|
||||
String error = responseDTO.getErrorLabel();
|
||||
if (error != null) {
|
||||
throw new VolvoOnCallException(error, responseDTO.getErrorDescription());
|
||||
}
|
||||
return responseDTO;
|
||||
}
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
throw new VolvoOnCallException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends VocAnswer> T getURL(String endpoint, Class<T> objectClass) throws VolvoOnCallException {
|
||||
return callUrl(HttpMethod.GET, endpoint, objectClass, null);
|
||||
}
|
||||
|
||||
public @Nullable PostResponse postURL(String endpoint, @Nullable String body) throws VolvoOnCallException {
|
||||
try {
|
||||
return callUrl(HttpMethod.POST, endpoint, PostResponse.class, body);
|
||||
} catch (VolvoOnCallException e) {
|
||||
if (e.getType() == ErrorType.SERVICE_UNABLE_TO_START) {
|
||||
logger.info("Unable to start service request sent to VoC");
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends VocAnswer> T getURL(Class<T> objectClass, String vin) throws VolvoOnCallException {
|
||||
String url = String.format("vehicles/%s/%s", vin, objectClass.getSimpleName().toLowerCase());
|
||||
return getURL(url, objectClass);
|
||||
}
|
||||
}
|
|
@ -18,13 +18,13 @@ import java.util.Base64;
|
|||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallBridgeConfiguration} is responsible for holding
|
||||
* The {@link ApiBridgeConfiguration} is responsible for holding
|
||||
* configuration informations needed to access VOC API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallBridgeConfiguration {
|
||||
public class ApiBridgeConfiguration {
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
|
|
@ -22,6 +22,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleConfiguration {
|
||||
public static String VIN = "vin";
|
||||
|
||||
public String vin = "";
|
||||
public Integer refresh = 5;
|
||||
public int refresh = 10;
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2020 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.volvooncall.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.dto.AccountVehicleRelation;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Attributes;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Vehicles;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VolvoOnCallBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallDiscoveryService} searches for available
|
||||
* cars discoverable through VocAPI
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallDiscoveryService extends AbstractDiscoveryService {
|
||||
private static final int SEARCH_TIME = 2;
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallDiscoveryService.class);
|
||||
private final VolvoOnCallBridgeHandler bridgeHandler;
|
||||
|
||||
public VolvoOnCallDiscoveryService(VolvoOnCallBridgeHandler bridgeHandler) {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startScan() {
|
||||
String[] relations = bridgeHandler.getVehiclesRelationsURL();
|
||||
Arrays.stream(relations).forEach(relationURL -> {
|
||||
try {
|
||||
AccountVehicleRelation accountVehicle = bridgeHandler.getURL(relationURL, AccountVehicleRelation.class);
|
||||
logger.debug("Found vehicle : {}", accountVehicle.vehicleId);
|
||||
|
||||
Vehicles vehicle = bridgeHandler.getURL(accountVehicle.vehicleURL, Vehicles.class);
|
||||
Attributes attributes = bridgeHandler.getURL(Attributes.class, vehicle.vehicleId);
|
||||
|
||||
thingDiscovered(DiscoveryResultBuilder
|
||||
.create(new ThingUID(VEHICLE_THING_TYPE, bridgeHandler.getThing().getUID(),
|
||||
accountVehicle.vehicleId))
|
||||
.withLabel(attributes.vehicleType + " " + attributes.registrationNumber)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(VIN, attributes.vin)
|
||||
.withRepresentationProperty(accountVehicle.vehicleId).build());
|
||||
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Error while discovering vehicle: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
stopScan();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2020 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.volvooncall.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.api.VocHttpApi;
|
||||
import org.openhab.binding.volvooncall.internal.config.VehicleConfiguration;
|
||||
import org.openhab.binding.volvooncall.internal.dto.AccountVehicleRelation;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Attributes;
|
||||
import org.openhab.binding.volvooncall.internal.dto.CustomerAccounts;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Vehicles;
|
||||
import org.openhab.binding.volvooncall.internal.handler.VolvoOnCallBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VolvoVehicleDiscoveryService} searches for available
|
||||
* cars discoverable through VocAPI
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoVehicleDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
private static final int SEARCH_TIME = 2;
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoVehicleDiscoveryService.class);
|
||||
private @Nullable VolvoOnCallBridgeHandler handler;
|
||||
|
||||
public VolvoVehicleDiscoveryService() {
|
||||
super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof VolvoOnCallBridgeHandler) {
|
||||
this.handler = (VolvoOnCallBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(@Nullable Map<String, @Nullable Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = this.handler;
|
||||
if (bridgeHandler != null) {
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
VocHttpApi api = bridgeHandler.getApi();
|
||||
if (api != null) {
|
||||
try {
|
||||
CustomerAccounts account = api.getURL("customeraccounts/", CustomerAccounts.class);
|
||||
account.accountVehicleRelationsURL.forEach(relationURL -> {
|
||||
try {
|
||||
AccountVehicleRelation accountVehicle = api.getURL(relationURL,
|
||||
AccountVehicleRelation.class);
|
||||
logger.debug("Found vehicle : {}", accountVehicle.vehicleId);
|
||||
|
||||
Vehicles vehicle = api.getURL(accountVehicle.vehicleURL, Vehicles.class);
|
||||
Attributes attributes = api.getURL(Attributes.class, vehicle.vehicleId);
|
||||
|
||||
thingDiscovered(DiscoveryResultBuilder
|
||||
.create(new ThingUID(VEHICLE_THING_TYPE, bridgeUID, accountVehicle.vehicleId))
|
||||
.withLabel(attributes.vehicleType + " " + attributes.registrationNumber)
|
||||
.withBridge(bridgeUID).withProperty(VehicleConfiguration.VIN, attributes.vin)
|
||||
.withRepresentationProperty(VehicleConfiguration.VIN).build());
|
||||
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Error while getting vehicle informations : {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Error while discovering vehicle: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
stopScan();
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
|
@ -26,7 +29,7 @@ import com.google.gson.annotations.SerializedName;
|
|||
@NonNullByDefault
|
||||
public class CustomerAccounts extends VocAnswer {
|
||||
@SerializedName("accountVehicleRelations")
|
||||
public @NonNullByDefault({}) String[] accountVehicleRelationsURL;
|
||||
public List<String> accountVehicleRelationsURL = new ArrayList<>();
|
||||
public @Nullable String username;
|
||||
|
||||
/*
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.openhab.binding.volvooncall.internal.dto;
|
|||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.UNDEFINED;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
|
||||
/**
|
||||
* The {@link HvBattery} is responsible for storing
|
||||
|
@ -26,7 +27,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
public class HvBattery {
|
||||
public int hvBatteryLevel = UNDEFINED;
|
||||
public int distanceToHVBatteryEmpty = UNDEFINED;
|
||||
public @NonNullByDefault({}) String hvBatteryChargeStatusDerived;
|
||||
public @NonNullByDefault({}) StringType hvBatteryChargeStatusDerived;
|
||||
public int timeToHVBatteryFullyCharged = UNDEFINED;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
@ -48,7 +50,7 @@ public class PostResponse extends VocAnswer {
|
|||
@SerializedName("service")
|
||||
public @NonNullByDefault({}) String serviceURL;
|
||||
public @NonNullByDefault({}) ServiceType serviceType;
|
||||
|
||||
public @NonNullByDefault({}) ZonedDateTime startTime;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
*
|
||||
|
@ -59,7 +61,7 @@ public class PostResponse extends VocAnswer {
|
|||
* }
|
||||
*
|
||||
* private ZonedDateTime statusTimestamp;
|
||||
* private ZonedDateTime startTime;
|
||||
*
|
||||
* private FailureReason failureReason;
|
||||
*
|
||||
* private Integer customerServiceId;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
@ -27,8 +28,8 @@ import com.google.gson.annotations.SerializedName;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class Trip {
|
||||
public int id;
|
||||
public @NonNullByDefault({}) List<TripDetail> tripDetails;
|
||||
public long id;
|
||||
public List<TripDetail> tripDetails = new ArrayList<>();
|
||||
@SerializedName("trip")
|
||||
public @Nullable String tripURL;
|
||||
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Trips} is responsible for storing
|
||||
|
@ -25,5 +25,5 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class Trips extends VocAnswer {
|
||||
public @Nullable List<Trip> trips;
|
||||
public List<Trip> trips = new ArrayList<>();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
package org.openhab.binding.volvooncall.internal.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
|
||||
/**
|
||||
* The {@link TyrePressure} is responsible for storing
|
||||
|
@ -22,10 +23,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class TyrePressure {
|
||||
public @NonNullByDefault({}) String frontLeftTyrePressure;
|
||||
public @NonNullByDefault({}) String frontRightTyrePressure;
|
||||
public @NonNullByDefault({}) String rearLeftTyrePressure;
|
||||
public @NonNullByDefault({}) String rearRightTyrePressure;
|
||||
public @NonNullByDefault({}) StringType frontLeftTyrePressure;
|
||||
public @NonNullByDefault({}) StringType frontRightTyrePressure;
|
||||
public @NonNullByDefault({}) StringType rearLeftTyrePressure;
|
||||
public @NonNullByDefault({}) StringType rearRightTyrePressure;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
|
|
|
@ -17,13 +17,13 @@ import static org.openhab.core.library.unit.MetricPrefix.KILO;
|
|||
import static org.openhab.core.library.unit.SIUnits.*;
|
||||
import static org.openhab.core.library.unit.SmartHomeUnits.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -32,12 +32,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.action.VolvoOnCallActions;
|
||||
import org.openhab.binding.volvooncall.internal.api.ActionResultController;
|
||||
import org.openhab.binding.volvooncall.internal.api.VocHttpApi;
|
||||
import org.openhab.binding.volvooncall.internal.config.VehicleConfiguration;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Attributes;
|
||||
import org.openhab.binding.volvooncall.internal.dto.DoorsStatus;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Heater;
|
||||
import org.openhab.binding.volvooncall.internal.dto.HvBattery;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Position;
|
||||
import org.openhab.binding.volvooncall.internal.dto.PostResponse;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Status;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Trip;
|
||||
import org.openhab.binding.volvooncall.internal.dto.TripDetail;
|
||||
|
@ -57,8 +60,9 @@ 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.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
@ -67,8 +71,6 @@ import org.openhab.core.types.UnDefType;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link VehicleHandler} is responsible for handling commands, which are sent
|
||||
* to one of the channels.
|
||||
|
@ -80,20 +82,76 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
|
||||
private final Map<String, String> activeOptions = new HashMap<>();
|
||||
private @Nullable ScheduledFuture<?> refreshJob;
|
||||
private final List<ScheduledFuture<?>> pendingActions = new Stack<>();
|
||||
|
||||
private Vehicles vehicle = new Vehicles();
|
||||
private VehiclePositionWrapper vehiclePosition = new VehiclePositionWrapper(new Position());
|
||||
private Status vehicleStatus = new Status();
|
||||
private @NonNullByDefault({}) VehicleConfiguration configuration;
|
||||
private Integer lastTripId = 0;
|
||||
private @NonNullByDefault({}) VolvoOnCallBridgeHandler bridgeHandler;
|
||||
private long lastTripId;
|
||||
|
||||
public VehicleHandler(Thing thing, VehicleStateDescriptionProvider stateDescriptionProvider) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
private Map<String, String> discoverAttributes(VolvoOnCallBridgeHandler bridgeHandler)
|
||||
throws JsonSyntaxException, IOException, VolvoOnCallException {
|
||||
Attributes attributes = bridgeHandler.getURL(vehicle.attributesURL, Attributes.class);
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.trace("Initializing the Volvo On Call handler for {}", getThing().getUID());
|
||||
|
||||
Bridge bridge = getBridge();
|
||||
initializeBridge(bridge == null ? null : bridge.getHandler(), bridge == null ? null : bridge.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("bridgeStatusChanged {} for thing {}", bridgeStatusInfo, getThing().getUID());
|
||||
|
||||
Bridge bridge = getBridge();
|
||||
initializeBridge(bridge == null ? null : bridge.getHandler(), bridgeStatusInfo.getStatus());
|
||||
}
|
||||
|
||||
private void initializeBridge(@Nullable ThingHandler thingHandler, @Nullable ThingStatus bridgeStatus) {
|
||||
logger.debug("initializeBridge {} for thing {}", bridgeStatus, getThing().getUID());
|
||||
|
||||
if (thingHandler != null && bridgeStatus != null) {
|
||||
bridgeHandler = (VolvoOnCallBridgeHandler) thingHandler;
|
||||
if (bridgeStatus == ThingStatus.ONLINE) {
|
||||
configuration = getConfigAs(VehicleConfiguration.class);
|
||||
VocHttpApi api = bridgeHandler.getApi();
|
||||
if (api != null) {
|
||||
try {
|
||||
vehicle = api.getURL("vehicles/" + configuration.vin, Vehicles.class);
|
||||
if (thing.getProperties().isEmpty()) {
|
||||
Map<String, String> properties = discoverAttributes(api);
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
activeOptions.putAll(
|
||||
thing.getProperties().entrySet().stream().filter(p -> "true".equals(p.getValue()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
|
||||
if (thing.getProperties().containsKey(LAST_TRIP_ID)) {
|
||||
lastTripId = Long.parseLong(thing.getProperties().get(LAST_TRIP_ID));
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startAutomaticRefresh(configuration.refresh, api);
|
||||
} catch (VolvoOnCallException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> discoverAttributes(VocHttpApi service) throws VolvoOnCallException {
|
||||
Attributes attributes = service.getURL(vehicle.attributesURL, Attributes.class);
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(CAR_LOCATOR, attributes.carLocatorSupported.toString());
|
||||
|
@ -112,71 +170,50 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.trace("Initializing the Volvo On Call handler for {}", getThing().getUID());
|
||||
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
configuration = getConfigAs(VehicleConfiguration.class);
|
||||
try {
|
||||
vehicle = bridgeHandler.getURL(SERVICE_URL + "vehicles/" + configuration.vin, Vehicles.class);
|
||||
|
||||
if (thing.getProperties().isEmpty()) {
|
||||
Map<String, String> properties = discoverAttributes(bridgeHandler);
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
activeOptions.putAll(thing.getProperties().entrySet().stream().filter(p -> "true".equals(p.getValue()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
|
||||
if (thing.getProperties().containsKey(LAST_TRIP_ID)) {
|
||||
lastTripId = Integer.parseInt(thing.getProperties().get(LAST_TRIP_ID));
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
startAutomaticRefresh(configuration.refresh);
|
||||
} catch (JsonSyntaxException | IOException | VolvoOnCallException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the job refreshing the vehicle data
|
||||
*
|
||||
* @param refresh : refresh frequency in minutes
|
||||
* @param service
|
||||
*/
|
||||
private void startAutomaticRefresh(int refresh) {
|
||||
private void startAutomaticRefresh(int refresh, VocHttpApi service) {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(this::queryApiAndUpdateChannels, 10, refresh,
|
||||
this.refreshJob = scheduler.scheduleWithFixedDelay(() -> queryApiAndUpdateChannels(service), 1, refresh,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryApiAndUpdateChannels() {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
private void queryApiAndUpdateChannels(VocHttpApi service) {
|
||||
try {
|
||||
vehicleStatus = bridgeHandler.getURL(vehicle.statusURL, Status.class);
|
||||
vehiclePosition = new VehiclePositionWrapper(bridgeHandler.getURL(Position.class, configuration.vin));
|
||||
Status newVehicleStatus = service.getURL(vehicle.statusURL, Status.class);
|
||||
vehiclePosition = new VehiclePositionWrapper(service.getURL(Position.class, configuration.vin));
|
||||
// Update all channels from the updated data
|
||||
getThing().getChannels().stream().map(Channel::getUID)
|
||||
.filter(channelUID -> isLinked(channelUID) && !LAST_TRIP_GROUP.equals(channelUID.getGroupId()))
|
||||
.forEach(channelUID -> {
|
||||
State state = getValue(channelUID.getGroupId(), channelUID.getIdWithoutGroup(),
|
||||
vehicleStatus, vehiclePosition);
|
||||
|
||||
String groupID = channelUID.getGroupId();
|
||||
if (groupID != null) {
|
||||
State state = getValue(groupID, channelUID.getIdWithoutGroup(), newVehicleStatus,
|
||||
vehiclePosition);
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
});
|
||||
updateTrips(bridgeHandler);
|
||||
if (newVehicleStatus.odometer != vehicleStatus.odometer) {
|
||||
triggerChannel(GROUP_OTHER + "#" + CAR_EVENT, EVENT_CAR_MOVED);
|
||||
// We will update trips only if car position has changed to save server queries
|
||||
updateTrips(service);
|
||||
}
|
||||
if (!vehicleStatus.getEngineRunning().equals(newVehicleStatus.getEngineRunning())
|
||||
&& newVehicleStatus.getEngineRunning().get() == OnOffType.ON) {
|
||||
triggerChannel(GROUP_OTHER + "#" + CAR_EVENT, EVENT_CAR_STARTED);
|
||||
}
|
||||
vehicleStatus = newVehicleStatus;
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
freeRefreshJob();
|
||||
startAutomaticRefresh(configuration.refresh);
|
||||
}
|
||||
startAutomaticRefresh(configuration.refresh, service);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,6 +223,7 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
refreshJob.cancel(true);
|
||||
this.refreshJob = null;
|
||||
}
|
||||
pendingActions.stream().filter(f -> !f.isCancelled()).forEach(f -> f.cancel(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -194,28 +232,27 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
private void updateTrips(VolvoOnCallBridgeHandler bridgeHandler) throws VolvoOnCallException {
|
||||
private void updateTrips(VocHttpApi service) throws VolvoOnCallException {
|
||||
// This seems to rewind 100 days by default, did not find any way to filter it
|
||||
Trips carTrips = bridgeHandler.getURL(Trips.class, configuration.vin);
|
||||
List<Trip> tripList = carTrips.trips;
|
||||
|
||||
if (tripList != null) {
|
||||
List<Trip> newTrips = tripList.stream().filter(trip -> trip.id >= lastTripId).collect(Collectors.toList());
|
||||
Trips carTrips = service.getURL(Trips.class, configuration.vin);
|
||||
List<Trip> newTrips = carTrips.trips.stream().filter(trip -> trip.id >= lastTripId)
|
||||
.collect(Collectors.toList());
|
||||
Collections.reverse(newTrips);
|
||||
|
||||
logger.debug("Trips discovered : {}", newTrips.size());
|
||||
|
||||
if (!newTrips.isEmpty()) {
|
||||
Integer newTripId = newTrips.get(newTrips.size() - 1).id;
|
||||
Long newTripId = newTrips.get(newTrips.size() - 1).id;
|
||||
if (newTripId > lastTripId) {
|
||||
updateProperty(LAST_TRIP_ID, newTripId.toString());
|
||||
triggerChannel(GROUP_OTHER + "#" + CAR_EVENT, EVENT_CAR_STOPPED);
|
||||
lastTripId = newTripId;
|
||||
}
|
||||
|
||||
newTrips.stream().map(t -> t.tripDetails.get(0)).forEach(catchUpTrip -> {
|
||||
logger.debug("Trip found {}", catchUpTrip.getStartTime());
|
||||
getThing().getChannels().stream().map(Channel::getUID).filter(
|
||||
channelUID -> isLinked(channelUID) && LAST_TRIP_GROUP.equals(channelUID.getGroupId()))
|
||||
getThing().getChannels().stream().map(Channel::getUID)
|
||||
.filter(channelUID -> isLinked(channelUID) && LAST_TRIP_GROUP.equals(channelUID.getGroupId()))
|
||||
.forEach(channelUID -> {
|
||||
State state = getTripValue(channelUID.getIdWithoutGroup(), catchUpTrip);
|
||||
updateState(channelUID, state);
|
||||
|
@ -223,27 +260,23 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelID = channelUID.getIdWithoutGroup();
|
||||
if (command instanceof RefreshType) {
|
||||
queryApiAndUpdateChannels();
|
||||
VocHttpApi api = bridgeHandler.getApi();
|
||||
if (api != null) {
|
||||
queryApiAndUpdateChannels(api);
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
OnOffType onOffCommand = (OnOffType) command;
|
||||
if (ENGINE_START.equals(channelID) && onOffCommand == OnOffType.ON) {
|
||||
actionStart(5);
|
||||
} else if (REMOTE_HEATER.equals(channelID)) {
|
||||
actionHeater(onOffCommand == OnOffType.ON);
|
||||
} else if (PRECLIMATIZATION.equals(channelID)) {
|
||||
actionPreclimatization(onOffCommand == OnOffType.ON);
|
||||
} else if (REMOTE_HEATER.equals(channelID) || PRECLIMATIZATION.equals(channelID)) {
|
||||
actionHeater(channelID, onOffCommand == OnOffType.ON);
|
||||
} else if (CAR_LOCKED.equals(channelID)) {
|
||||
if (onOffCommand == OnOffType.ON) {
|
||||
actionClose();
|
||||
} else {
|
||||
actionOpen();
|
||||
}
|
||||
actionOpenClose((onOffCommand == OnOffType.ON) ? LOCK : UNLOCK, onOffCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +304,6 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
case TRIP_END_POSITION:
|
||||
return tripDetails.getEndPosition();
|
||||
}
|
||||
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
|
@ -310,13 +342,13 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
private State getTyresValue(String channelId, TyrePressure tyrePressure) {
|
||||
switch (channelId) {
|
||||
case REAR_RIGHT_TYRE:
|
||||
return new StringType(tyrePressure.rearRightTyrePressure);
|
||||
return tyrePressure.rearRightTyrePressure;
|
||||
case REAR_LEFT_TYRE:
|
||||
return new StringType(tyrePressure.rearLeftTyrePressure);
|
||||
return tyrePressure.rearLeftTyrePressure;
|
||||
case FRONT_RIGHT_TYRE:
|
||||
return new StringType(tyrePressure.frontRightTyrePressure);
|
||||
return tyrePressure.frontRightTyrePressure;
|
||||
case FRONT_LEFT_TYRE:
|
||||
return new StringType(tyrePressure.frontLeftTyrePressure);
|
||||
return tyrePressure.frontLeftTyrePressure;
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
@ -340,8 +372,7 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
? new QuantityType<>(hvBattery.distanceToHVBatteryEmpty, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case CHARGE_STATUS:
|
||||
return hvBattery.hvBatteryChargeStatusDerived != null
|
||||
? new StringType(hvBattery.hvBatteryChargeStatusDerived)
|
||||
return hvBattery.hvBatteryChargeStatusDerived != null ? hvBattery.hvBatteryChargeStatusDerived
|
||||
: UnDefType.UNDEF;
|
||||
case TIME_TO_BATTERY_FULLY_CHARGED:
|
||||
return hvBattery.timeToHVBatteryFullyCharged != UNDEFINED
|
||||
|
@ -351,43 +382,12 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
return hvBattery.timeToHVBatteryFullyCharged != UNDEFINED && hvBattery.timeToHVBatteryFullyCharged > 0
|
||||
? new DateTimeType(ZonedDateTime.now().plusMinutes(hvBattery.timeToHVBatteryFullyCharged))
|
||||
: UnDefType.UNDEF;
|
||||
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getValue(@Nullable String groupId, String channelId, Status status, VehiclePositionWrapper position) {
|
||||
private State getValue(String groupId, String channelId, Status status, VehiclePositionWrapper position) {
|
||||
switch (channelId) {
|
||||
case ODOMETER:
|
||||
return status.odometer != UNDEFINED ? new QuantityType<>((double) status.odometer / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case TRIPMETER1:
|
||||
return status.tripMeter1 != UNDEFINED
|
||||
? new QuantityType<>((double) status.tripMeter1 / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case TRIPMETER2:
|
||||
return status.tripMeter2 != UNDEFINED
|
||||
? new QuantityType<>((double) status.tripMeter2 / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case DISTANCE_TO_EMPTY:
|
||||
return status.distanceToEmpty != UNDEFINED ? new QuantityType<>(status.distanceToEmpty, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case FUEL_AMOUNT:
|
||||
return status.fuelAmount != UNDEFINED ? new QuantityType<>(status.fuelAmount, LITRE) : UnDefType.UNDEF;
|
||||
case FUEL_LEVEL:
|
||||
return status.fuelAmountLevel != UNDEFINED ? new QuantityType<>(status.fuelAmountLevel, PERCENT)
|
||||
: UnDefType.UNDEF;
|
||||
case FUEL_CONSUMPTION:
|
||||
return status.averageFuelConsumption != UNDEFINED ? new DecimalType(status.averageFuelConsumption / 10)
|
||||
: UnDefType.UNDEF;
|
||||
case ACTUAL_LOCATION:
|
||||
return position.getPosition();
|
||||
case CALCULATED_LOCATION:
|
||||
return position.isCalculated();
|
||||
case HEADING:
|
||||
return position.isHeading();
|
||||
case LOCATION_TIMESTAMP:
|
||||
return position.getTimestamp();
|
||||
case CAR_LOCKED:
|
||||
// Warning : carLocked is in the Doors group but is part of general status informations.
|
||||
// Did not change it to avoid breaking change for users
|
||||
|
@ -403,36 +403,84 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
: UnDefType.UNDEF;
|
||||
case SERVICE_WARNING:
|
||||
return new StringType(status.serviceWarningStatus);
|
||||
case FUEL_ALERT:
|
||||
return status.distanceToEmpty < 100 ? OnOffType.ON : OnOffType.OFF;
|
||||
case BULB_FAILURE:
|
||||
return status.aFailedBulb() ? OnOffType.ON : OnOffType.OFF;
|
||||
case REMOTE_HEATER:
|
||||
case PRECLIMATIZATION:
|
||||
return status.getHeater().map(heater -> getHeaterValue(channelId, heater)).orElse(UnDefType.NULL);
|
||||
}
|
||||
if (groupId != null) {
|
||||
switch (groupId) {
|
||||
case GROUP_TANK:
|
||||
return getTankValue(channelId, status);
|
||||
case GROUP_ODOMETER:
|
||||
return getOdometerValue(channelId, status);
|
||||
case GROUP_POSITION:
|
||||
return getPositionValue(channelId, position);
|
||||
case GROUP_DOORS:
|
||||
return status.getDoors().map(doors -> getDoorsValue(channelId, doors)).orElse(UnDefType.NULL);
|
||||
case GROUP_WINDOWS:
|
||||
return status.getWindows().map(windows -> getWindowsValue(channelId, windows))
|
||||
.orElse(UnDefType.NULL);
|
||||
return status.getWindows().map(windows -> getWindowsValue(channelId, windows)).orElse(UnDefType.NULL);
|
||||
case GROUP_TYRES:
|
||||
return status.getTyrePressure().map(tyres -> getTyresValue(channelId, tyres))
|
||||
.orElse(UnDefType.NULL);
|
||||
return status.getTyrePressure().map(tyres -> getTyresValue(channelId, tyres)).orElse(UnDefType.NULL);
|
||||
case GROUP_BATTERY:
|
||||
return status.getHvBattery().map(batteries -> getBatteryValue(channelId, batteries))
|
||||
.orElse(UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getTankValue(String channelId, Status status) {
|
||||
switch (channelId) {
|
||||
case DISTANCE_TO_EMPTY:
|
||||
return status.distanceToEmpty != UNDEFINED ? new QuantityType<>(status.distanceToEmpty, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case FUEL_AMOUNT:
|
||||
return status.fuelAmount != UNDEFINED ? new QuantityType<>(status.fuelAmount, LITRE) : UnDefType.UNDEF;
|
||||
case FUEL_LEVEL:
|
||||
return status.fuelAmountLevel != UNDEFINED ? new QuantityType<>(status.fuelAmountLevel, PERCENT)
|
||||
: UnDefType.UNDEF;
|
||||
case FUEL_CONSUMPTION:
|
||||
return status.averageFuelConsumption != UNDEFINED ? new DecimalType(status.averageFuelConsumption / 10)
|
||||
: UnDefType.UNDEF;
|
||||
case FUEL_ALERT:
|
||||
return status.distanceToEmpty < 100 ? OnOffType.ON : OnOffType.OFF;
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
private State getOdometerValue(String channelId, Status status) {
|
||||
switch (channelId) {
|
||||
case ODOMETER:
|
||||
return status.odometer != UNDEFINED ? new QuantityType<>((double) status.odometer / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case TRIPMETER1:
|
||||
return status.tripMeter1 != UNDEFINED
|
||||
? new QuantityType<>((double) status.tripMeter1 / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case TRIPMETER2:
|
||||
return status.tripMeter2 != UNDEFINED
|
||||
? new QuantityType<>((double) status.tripMeter2 / 1000, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
private State getPositionValue(String channelId, VehiclePositionWrapper position) {
|
||||
switch (channelId) {
|
||||
case ACTUAL_LOCATION:
|
||||
return position.getPosition();
|
||||
case CALCULATED_LOCATION:
|
||||
return position.isCalculated();
|
||||
case HEADING:
|
||||
return position.isHeading();
|
||||
case LOCATION_TIMESTAMP:
|
||||
return position.getTimestamp();
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
|
||||
public void actionHonkBlink(Boolean honk, Boolean blink) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
StringBuilder url = new StringBuilder(SERVICE_URL + "vehicles/" + vehicle.vehicleId + "/honk_blink/");
|
||||
StringBuilder url = new StringBuilder("vehicles/" + vehicle.vehicleId + "/honk_blink/");
|
||||
|
||||
if (honk && blink && activeOptions.containsKey(HONK_BLINK)) {
|
||||
url.append("both");
|
||||
|
@ -445,31 +493,32 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
bridgeHandler.postURL(url.toString(), vehiclePosition.getPositionAsJSon());
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
post(url.toString(), vehiclePosition.getPositionAsJSon());
|
||||
}
|
||||
|
||||
private void actionOpenClose(String action, OnOffType controlState) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
if (activeOptions.containsKey(action)) {
|
||||
if (!vehicleStatus.getCarLocked().isPresent() || vehicleStatus.getCarLocked().get() != controlState) {
|
||||
private void post(String url, @Nullable String param) {
|
||||
VocHttpApi api = bridgeHandler.getApi();
|
||||
if (api != null) {
|
||||
try {
|
||||
StringBuilder address = new StringBuilder(SERVICE_URL);
|
||||
address.append("vehicles/");
|
||||
address.append(configuration.vin);
|
||||
address.append("/");
|
||||
address.append(action);
|
||||
bridgeHandler.postURL(address.toString(), "{}");
|
||||
PostResponse postResponse = api.postURL(url.toString(), param);
|
||||
if (postResponse != null) {
|
||||
pendingActions
|
||||
.add(scheduler.schedule(new ActionResultController(api, postResponse, scheduler, this),
|
||||
1000, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
;
|
||||
pendingActions.removeIf(ScheduledFuture::isDone);
|
||||
}
|
||||
|
||||
public void actionOpenClose(String action, OnOffType controlState) {
|
||||
if (activeOptions.containsKey(action)) {
|
||||
if (!vehicleStatus.getCarLocked().isPresent() || vehicleStatus.getCarLocked().get() != controlState) {
|
||||
post(String.format("vehicles/%s/%s", configuration.vin, action), "{}");
|
||||
} else {
|
||||
logger.info("The car {} is already {}ed", configuration.vin, action);
|
||||
}
|
||||
|
@ -477,89 +526,27 @@ public class VehicleHandler extends BaseThingHandler {
|
|||
logger.warn("The car {} does not support remote {}ing", configuration.vin, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionHeater(String action, Boolean start) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
public void actionHeater(String action, Boolean start) {
|
||||
if (activeOptions.containsKey(action)) {
|
||||
try {
|
||||
if (action.contains(REMOTE_HEATER)) {
|
||||
String command = start ? "start" : "stop";
|
||||
String address = SERVICE_URL + "vehicles/" + configuration.vin + "/heater/" + command;
|
||||
bridgeHandler.postURL(address, start ? "{}" : null);
|
||||
} else if (action.contains(PRECLIMATIZATION)) {
|
||||
String command = start ? "start" : "stop";
|
||||
String address = SERVICE_URL + "vehicles/" + configuration.vin + "/preclimatization/" + command;
|
||||
bridgeHandler.postURL(address, start ? "{}" : null);
|
||||
}
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
String address = String.format("vehicles/%s/%s/%s", configuration.vin,
|
||||
action.contains(REMOTE_HEATER) ? "heater" : "preclimatization", start ? "start" : "stop");
|
||||
post(address, start ? "{}" : null);
|
||||
} else {
|
||||
logger.warn("The car {} does not support {}", configuration.vin, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void actionHeater(Boolean start) {
|
||||
actionHeater(REMOTE_HEATER, start);
|
||||
}
|
||||
|
||||
public void actionPreclimatization(Boolean start) {
|
||||
actionHeater(PRECLIMATIZATION, start);
|
||||
}
|
||||
|
||||
public void actionOpen() {
|
||||
actionOpenClose(UNLOCK, OnOffType.OFF);
|
||||
}
|
||||
|
||||
public void actionClose() {
|
||||
actionOpenClose(LOCK, OnOffType.ON);
|
||||
}
|
||||
|
||||
public void actionStart(Integer runtime) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
if (activeOptions.containsKey(ENGINE_START)) {
|
||||
String url = SERVICE_URL + "vehicles/" + vehicle.vehicleId + "/engine/start";
|
||||
String address = String.format("vehicles/%s/engine/start", vehicle.vehicleId);
|
||||
String json = "{\"runtime\":" + runtime.toString() + "}";
|
||||
|
||||
try {
|
||||
bridgeHandler.postURL(url, json);
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
post(address, json);
|
||||
} else {
|
||||
logger.warn("The car {} does not support remote engine starting", vehicle.vehicleId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called by Bridge when it has to notify this of a potential state
|
||||
* update
|
||||
*
|
||||
*/
|
||||
void updateIfMatches(String vin) {
|
||||
if (vin.equalsIgnoreCase(configuration.vin)) {
|
||||
queryApiAndUpdateChannels();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable VolvoOnCallBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if (handler != null) {
|
||||
return (VolvoOnCallBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
|
|
|
@ -12,43 +12,28 @@
|
|||
*/
|
||||
package org.openhab.binding.volvooncall.internal.handler;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
|
||||
import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
|
||||
import org.openhab.binding.volvooncall.internal.config.VolvoOnCallBridgeConfiguration;
|
||||
import org.openhab.binding.volvooncall.internal.api.VocHttpApi;
|
||||
import org.openhab.binding.volvooncall.internal.config.ApiBridgeConfiguration;
|
||||
import org.openhab.binding.volvooncall.internal.discovery.VolvoVehicleDiscoveryService;
|
||||
import org.openhab.binding.volvooncall.internal.dto.CustomerAccounts;
|
||||
import org.openhab.binding.volvooncall.internal.dto.PostResponse;
|
||||
import org.openhab.binding.volvooncall.internal.dto.VocAnswer;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
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.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallBridgeHandler} is responsible for handling commands, which are
|
||||
|
@ -58,144 +43,60 @@ import com.google.gson.JsonSyntaxException;
|
|||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
|
||||
private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallBridgeHandler.class);
|
||||
private final Properties httpHeader = new Properties();
|
||||
private final List<ScheduledFuture<?>> pendingActions = new Stack<>();
|
||||
private final Gson gson;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private @NonNullByDefault({}) CustomerAccounts customerAccount;
|
||||
private @Nullable VocHttpApi api;
|
||||
|
||||
public VolvoOnCallBridgeHandler(Bridge bridge) {
|
||||
public VolvoOnCallBridgeHandler(Bridge bridge, Gson gson, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
|
||||
httpHeader.put("cache-control", "no-cache");
|
||||
httpHeader.put("content-type", JSON_CONTENT_TYPE);
|
||||
httpHeader.put("x-device-id", "Device");
|
||||
httpHeader.put("x-originator-type", "App");
|
||||
httpHeader.put("x-os-type", "Android");
|
||||
httpHeader.put("x-os-version", "22");
|
||||
httpHeader.put("Accept", "*/*");
|
||||
|
||||
gson = new GsonBuilder()
|
||||
.registerTypeAdapter(ZonedDateTime.class,
|
||||
(JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
|
||||
.parse(json.getAsJsonPrimitive().getAsString().replaceAll("\\+0000", "Z")))
|
||||
.registerTypeAdapter(OpenClosedType.class,
|
||||
(JsonDeserializer<OpenClosedType>) (json, type,
|
||||
jsonDeserializationContext) -> json.getAsBoolean() ? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED)
|
||||
.registerTypeAdapter(OnOffType.class,
|
||||
(JsonDeserializer<OnOffType>) (json, type,
|
||||
jsonDeserializationContext) -> json.getAsBoolean() ? OnOffType.ON : OnOffType.OFF)
|
||||
.create();
|
||||
this.gson = gson;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing VolvoOnCall API bridge handler.");
|
||||
VolvoOnCallBridgeConfiguration configuration = getConfigAs(VolvoOnCallBridgeConfiguration.class);
|
||||
ApiBridgeConfiguration configuration = getConfigAs(ApiBridgeConfiguration.class);
|
||||
|
||||
httpHeader.setProperty("Authorization", configuration.getAuthorization());
|
||||
try {
|
||||
customerAccount = getURL(SERVICE_URL + "customeraccounts/", CustomerAccounts.class);
|
||||
if (customerAccount.username != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
api = new VocHttpApi(configuration, gson, httpClient);
|
||||
CustomerAccounts account = api.getURL("customeraccounts/", CustomerAccounts.class);
|
||||
if (account.username != null) {
|
||||
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, account.username);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Incorrect username or password");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Incorrect login credentials");
|
||||
}
|
||||
} catch (JsonSyntaxException | VolvoOnCallException e) {
|
||||
} catch (VolvoOnCallException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("VolvoOnCall Bridge is read-only and does not handle commands");
|
||||
}
|
||||
|
||||
public String[] getVehiclesRelationsURL() {
|
||||
if (customerAccount != null) {
|
||||
return customerAccount.accountVehicleRelationsURL;
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
public <T extends VocAnswer> T getURL(Class<T> objectClass, String vin) throws VolvoOnCallException {
|
||||
String url = SERVICE_URL + "vehicles/" + vin + "/" + objectClass.getSimpleName().toLowerCase();
|
||||
return getURL(url, objectClass);
|
||||
}
|
||||
|
||||
public <T extends VocAnswer> T getURL(String url, Class<T> objectClass) throws VolvoOnCallException {
|
||||
try {
|
||||
String jsonResponse = HttpUtil.executeUrl("GET", url, httpHeader, null, JSON_CONTENT_TYPE, REQUEST_TIMEOUT);
|
||||
logger.debug("Request for : {}", url);
|
||||
logger.debug("Received : {}", jsonResponse);
|
||||
T response = gson.fromJson(jsonResponse, objectClass);
|
||||
String error = response.getErrorLabel();
|
||||
if (error != null) {
|
||||
throw new VolvoOnCallException(error, response.getErrorDescription());
|
||||
}
|
||||
return response;
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
throw new VolvoOnCallException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class ActionResultControler implements Runnable {
|
||||
PostResponse postResponse;
|
||||
|
||||
ActionResultControler(PostResponse postResponse) {
|
||||
this.postResponse = postResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
switch (postResponse.status) {
|
||||
case SUCCESSFULL:
|
||||
case FAILED:
|
||||
logger.info("Action status : {} for vehicle : {}.", postResponse.status.toString(),
|
||||
postResponse.vehicleId);
|
||||
getThing().getThings().stream().filter(VehicleHandler.class::isInstance)
|
||||
.map(VehicleHandler.class::cast)
|
||||
.forEach(handler -> handler.updateIfMatches(postResponse.vehicleId));
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
postResponse = getURL(postResponse.serviceURL, PostResponse.class);
|
||||
scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS);
|
||||
} catch (VolvoOnCallException e) {
|
||||
if (e.getType() == ErrorType.SERVICE_UNAVAILABLE) {
|
||||
scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void postURL(String URL, @Nullable String body) throws VolvoOnCallException {
|
||||
InputStream inputStream = body != null ? new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)) : null;
|
||||
try {
|
||||
String jsonString = HttpUtil.executeUrl("POST", URL, httpHeader, inputStream, null, REQUEST_TIMEOUT);
|
||||
logger.debug("Post URL: {} Attributes {}", URL, httpHeader);
|
||||
PostResponse postResponse = gson.fromJson(jsonString, PostResponse.class);
|
||||
String error = postResponse.getErrorLabel();
|
||||
if (error == null) {
|
||||
pendingActions
|
||||
.add(scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS));
|
||||
} else {
|
||||
throw new VolvoOnCallException(error, postResponse.getErrorDescription());
|
||||
}
|
||||
pendingActions.removeIf(ScheduledFuture::isDone);
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
throw new VolvoOnCallException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
pendingActions.stream().filter(f -> !f.isCancelled()).forEach(f -> f.cancel(true));
|
||||
if (api != null) {
|
||||
try {
|
||||
api.dispose();
|
||||
api = null;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Unable to stop VocHttpApi : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable VocHttpApi getApi() {
|
||||
return api;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(VolvoVehicleDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>VolvoOnCall Binding</name>
|
||||
<description>This binding enables the access to VolvoOnCall features.</description>
|
||||
<description>This binding enables the access to VolvoOnCall services.</description>
|
||||
<author>Gaël L'hopital</author>
|
||||
|
||||
</binding:binding>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# binding
|
||||
binding.volvooncall.name = Extension VolvoOnCall
|
||||
binding.volvooncall.description = Cette extension fournit l'accès aux services de Volvo On Call.
|
||||
|
||||
# thing types
|
||||
thing-type.volvooncall.vocapi.label = API Volvo On Call
|
||||
thing-type.volvooncall.vocapi.description = Fournit l'interface avec le service en ligne Volvo On Call. Pour recevoir les données, vous devez vous munir de vos informations de connection (nom d'utilisateur, mot de passe).
|
||||
|
||||
thing-type.volvooncall.vehicle.label = Véhicule
|
||||
thing-type.volvooncall.vehicle.description = Toutes les informations disponibles sur le véhicule Volvo.
|
|
@ -32,10 +32,11 @@
|
|||
<description>VIN of the vehicle associated with this Thing</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refresh" type="integer" min="5" required="false">
|
||||
<parameter name="refresh" type="integer" min="5" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in minutes.</description>
|
||||
<default>5</default>
|
||||
<default>10</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
@ -51,6 +52,7 @@
|
|||
<channel id="washerFluidLevel" typeId="washerFluidLevel"/>
|
||||
<channel id="serviceWarningStatus" typeId="serviceWarningStatus"/>
|
||||
<channel id="bulbFailure" typeId="bulbFailure"/>
|
||||
<channel id="carEvent" typeId="carEvent"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
@ -58,34 +60,34 @@
|
|||
<label>Last Trip</label>
|
||||
<channels>
|
||||
<channel id="tripConsumption" typeId="fuelQuantity">
|
||||
<label>Consumption</label>
|
||||
<label>Trip Consumption</label>
|
||||
<description>Indicates the quantity of fuel consumed by the trip</description>
|
||||
</channel>
|
||||
<channel id="tripDistance" typeId="odometer">
|
||||
<label>Distance</label>
|
||||
<label>Trip Distance</label>
|
||||
<description>Distance traveled</description>
|
||||
</channel>
|
||||
<channel id="tripStartTime" typeId="timestamp">
|
||||
<label>Start Time</label>
|
||||
<label>Trip Start Time</label>
|
||||
<description>Trip start time</description>
|
||||
</channel>
|
||||
<channel id="tripEndTime" typeId="timestamp">
|
||||
<label>End Time</label>
|
||||
<label>Trip End Time</label>
|
||||
<description>Trip end time</description>
|
||||
</channel>
|
||||
<channel id="tripDuration" typeId="tripDuration"/>
|
||||
<channel id="tripStartOdometer" typeId="odometer">
|
||||
<label>Start Odometer</label>
|
||||
<label>Trip Start Odometer</label>
|
||||
</channel>
|
||||
<channel id="tripStopOdometer" typeId="odometer">
|
||||
<label>Stop Odometer</label>
|
||||
<label>Trip Stop Odometer</label>
|
||||
</channel>
|
||||
<channel id="startPosition" typeId="location">
|
||||
<label>From</label>
|
||||
<label>Trip From</label>
|
||||
<description>Starting location of the car</description>
|
||||
</channel>
|
||||
<channel id="endPosition" typeId="location">
|
||||
<label>To</label>
|
||||
<label>Trip To</label>
|
||||
<description>Stopping location of the car</description>
|
||||
</channel>
|
||||
</channels>
|
||||
|
@ -95,16 +97,16 @@
|
|||
<label>Doors Opening Status</label>
|
||||
<channels>
|
||||
<channel id="frontLeft" typeId="door">
|
||||
<label>Front Left</label>
|
||||
<label>Front Left Door</label>
|
||||
</channel>
|
||||
<channel id="frontRight" typeId="door">
|
||||
<label>Front Right</label>
|
||||
<label>Front Right Door</label>
|
||||
</channel>
|
||||
<channel id="rearLeft" typeId="door">
|
||||
<label>Rear Left</label>
|
||||
<label>Rear Left Door</label>
|
||||
</channel>
|
||||
<channel id="rearRight" typeId="door">
|
||||
<label>Rear Right</label>
|
||||
<label>Rear Right Door</label>
|
||||
</channel>
|
||||
<channel id="hood" typeId="door">
|
||||
<label>Hood</label>
|
||||
|
@ -120,16 +122,16 @@
|
|||
<label>Windows Opening Status</label>
|
||||
<channels>
|
||||
<channel id="frontLeftWnd" typeId="window">
|
||||
<label>Front Left</label>
|
||||
<label>Front Left Window</label>
|
||||
</channel>
|
||||
<channel id="frontRightWnd" typeId="window">
|
||||
<label>Front Right</label>
|
||||
<label>Front Right Window</label>
|
||||
</channel>
|
||||
<channel id="rearLeftWnd" typeId="window">
|
||||
<label>Rear Left</label>
|
||||
<label>Rear Left Window</label>
|
||||
</channel>
|
||||
<channel id="rearRightWnd" typeId="window">
|
||||
<label>Rear Right</label>
|
||||
<label>Rear Right Window</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
@ -138,16 +140,16 @@
|
|||
<label>Tyre pressure status</label>
|
||||
<channels>
|
||||
<channel id="frontLeftTyre" typeId="tyrePressure">
|
||||
<label>Front Left</label>
|
||||
<label>Front Left Tyre</label>
|
||||
</channel>
|
||||
<channel id="frontRightTyre" typeId="tyrePressure">
|
||||
<label>Front Right</label>
|
||||
<label>Front Right Tyre</label>
|
||||
</channel>
|
||||
<channel id="rearLeftTyre" typeId="tyrePressure">
|
||||
<label>Rear Left</label>
|
||||
<label>Rear Left Tyre</label>
|
||||
</channel>
|
||||
<channel id="rearRightTyre" typeId="tyrePressure">
|
||||
<label>Rear Right</label>
|
||||
<label>Rear Right Tyre</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
@ -186,7 +188,7 @@
|
|||
<label>Location Info</label>
|
||||
<channels>
|
||||
<channel id="location" typeId="location">
|
||||
<label>Location</label>
|
||||
<label>Current Location</label>
|
||||
<description>The position of the vehicle</description>
|
||||
</channel>
|
||||
<channel id="calculatedLocation" typeId="calculatedLocation"/>
|
||||
|
@ -245,7 +247,7 @@
|
|||
<item-type>Number:Speed</item-type>
|
||||
<label>Average speed</label>
|
||||
<description>Average speed of the vehicle</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
<state pattern="%.2f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fuelQuantity">
|
||||
|
@ -264,8 +266,8 @@
|
|||
<channel-type id="fuelConsumption" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Average Consumption</label>
|
||||
<description>Indicates the average fuel consumption in L/100km</description>
|
||||
<state pattern="%.1f L/100km" readOnly="true"></state>
|
||||
<description>Indicates the average fuel consumption in l/100km</description>
|
||||
<state pattern="%.1f l/100km" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="location">
|
||||
|
@ -388,4 +390,16 @@
|
|||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carEvent">
|
||||
<kind>trigger</kind>
|
||||
<label>Car Event</label>
|
||||
<event>
|
||||
<options>
|
||||
<option value="CAR_STOPPED">Car stopped</option>
|
||||
<option value="CAR_STARTED">Car started</option>
|
||||
<option value="CAR_MOVED">Car has moved</option>
|
||||
</options>
|
||||
</event>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
|
Loading…
Reference in New Issue