added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.astro-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-volvooncall" description="Volvo On Call Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.volvooncall/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
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");
|
||||
|
||||
// List of Channel groups
|
||||
public static final String GROUP_DOORS = "doors";
|
||||
public static final String GROUP_WINDOWS = "windows";
|
||||
public static final String GROUP_TYRES = "tyrePressure";
|
||||
public static final String GROUP_BATTERY = "battery";
|
||||
|
||||
// List of Channel id's
|
||||
public static final String TAILGATE = "tailgate";
|
||||
public static final String REAR_RIGHT = "rearRight";
|
||||
public static final String REAR_LEFT = "rearLeft";
|
||||
public static final String FRONT_RIGHT = "frontRight";
|
||||
public static final String FRONT_LEFT = "frontLeft";
|
||||
public static final String HOOD = "hood";
|
||||
public static final String REAR_RIGHT_WND = "rearRightWnd";
|
||||
public static final String REAR_LEFT_WND = "rearLeftWnd";
|
||||
public static final String FRONT_RIGHT_WND = "frontRightWnd";
|
||||
public static final String FRONT_LEFT_WND = "frontLeftWnd";
|
||||
public static final String REAR_RIGHT_TYRE = "rearRightTyre";
|
||||
public static final String REAR_LEFT_TYRE = "rearLeftTyre";
|
||||
public static final String FRONT_RIGHT_TYRE = "frontRightTyre";
|
||||
public static final String FRONT_LEFT_TYRE = "frontLeftTyre";
|
||||
public static final String ODOMETER = "odometer";
|
||||
public static final String TRIPMETER1 = "tripmeter1";
|
||||
public static final String TRIPMETER2 = "tripmeter2";
|
||||
public static final String DISTANCE_TO_EMPTY = "distanceToEmpty";
|
||||
public static final String FUEL_AMOUNT = "fuelAmount";
|
||||
public static final String FUEL_LEVEL = "fuelLevel";
|
||||
public static final String FUEL_CONSUMPTION = "fuelConsumption";
|
||||
public static final String FUEL_ALERT = "fuelAlert";
|
||||
public static final String CALCULATED_LOCATION = "calculatedLocation";
|
||||
public static final String ACTUAL_LOCATION = "location";
|
||||
public static final String LOCATION_TIMESTAMP = "locationTimestamp";
|
||||
public static final String HEADING = "heading";
|
||||
public static final String CAR_LOCKED = "carLocked";
|
||||
public static final String ENGINE_RUNNING = "engineRunning";
|
||||
public static final String BRAKE_FLUID_LEVEL = "brakeFluidLevel";
|
||||
public static final String WASHER_FLUID_LEVEL = "washerFluidLevel";
|
||||
public static final String AVERAGE_SPEED = "averageSpeed";
|
||||
public static final String SERVICE_WARNING = "serviceWarningStatus";
|
||||
public static final String BATTERY_LEVEL = "batteryLevel";
|
||||
public static final String BATTERY_DISTANCE_TO_EMPTY = "batteryDistanceToEmpty";
|
||||
public static final String CHARGE_STATUS = "chargeStatus";
|
||||
public static final String TIME_TO_BATTERY_FULLY_CHARGED = "timeToHVBatteryFullyCharged";
|
||||
public static final String CHARGING_END = "chargingEnd";
|
||||
public static final String BULB_FAILURE = "bulbFailure";
|
||||
|
||||
// Last Trip Channel Id's
|
||||
public static final String LAST_TRIP_GROUP = "lasttrip";
|
||||
public static final String TRIP_CONSUMPTION = "tripConsumption";
|
||||
public static final String TRIP_DISTANCE = "tripDistance";
|
||||
public static final String TRIP_DURATION = "tripDuration";
|
||||
public static final String TRIP_START_TIME = "tripStartTime";
|
||||
public static final String TRIP_END_TIME = "tripEndTime";
|
||||
public static final String TRIP_START_ODOMETER = "tripStartOdometer";
|
||||
public static final String TRIP_STOP_ODOMETER = "tripStopOdometer";
|
||||
public static final String TRIP_START_POSITION = "startPosition";
|
||||
public static final String TRIP_END_POSITION = "endPosition";
|
||||
|
||||
// Optional Channels depends upon car version
|
||||
public static final String CAR_LOCATOR = "carLocator";
|
||||
public static final String JOURNAL_LOG = "journalLog";
|
||||
|
||||
// Car properties
|
||||
public static final String ENGINE_START = "engineStart";
|
||||
public static final String UNLOCK = "unlock";
|
||||
public static final String UNLOCK_TIME = "unlockTimeFrame";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String HONK = "honk";
|
||||
public static final String BLINK = "blink";
|
||||
public static final String HONK_BLINK = "honkAndBlink";
|
||||
public static final String HONK_AND_OR_BLINK = "honkAndOrBlink";
|
||||
public static final String REMOTE_HEATER = "remoteHeater";
|
||||
public static final String PRECLIMATIZATION = "preclimatization";
|
||||
public static final String LAST_TRIP_ID = "lastTripId";
|
||||
|
||||
// List of all adressable things in OH = SUPPORTED_DEVICE_THING_TYPES_UIDS + the virtual bridge
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(APIBRIDGE_THING_TYPE, VEHICLE_THING_TYPE).collect(Collectors.toSet());
|
||||
|
||||
// Default value for undefined integers
|
||||
public static final int UNDEFINED = -1;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Exception for errors when using the VolvoOnCall API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallException extends Exception {
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallException.class);
|
||||
private static final long serialVersionUID = -6215621577081394328L;
|
||||
|
||||
public static enum ErrorType {
|
||||
UNKNOWN,
|
||||
SERVICE_UNAVAILABLE,
|
||||
IOEXCEPTION,
|
||||
JSON_SYNTAX;
|
||||
}
|
||||
|
||||
private final ErrorType cause;
|
||||
|
||||
public VolvoOnCallException(String label, @Nullable String description) {
|
||||
super(label);
|
||||
if ("FoundationServicesUnavailable".equalsIgnoreCase(label)) {
|
||||
cause = ErrorType.SERVICE_UNAVAILABLE;
|
||||
} else {
|
||||
cause = ErrorType.UNKNOWN;
|
||||
logger.warn("Unhandled VoC error : {} : {}", label, description);
|
||||
}
|
||||
}
|
||||
|
||||
public VolvoOnCallException(Exception e) {
|
||||
super(e);
|
||||
if (e instanceof IOException) {
|
||||
cause = ErrorType.IOEXCEPTION;
|
||||
} else if (e instanceof JsonSyntaxException) {
|
||||
cause = ErrorType.JSON_SYNTAX;
|
||||
} else {
|
||||
cause = ErrorType.UNKNOWN;
|
||||
logger.warn("Unhandled VoC error : {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public ErrorType getType() {
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.discovery.VolvoOnCallDiscoveryService;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@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;
|
||||
|
||||
@Activate
|
||||
public VolvoOnCallHandlerFactory(@Reference VehicleStateDescriptionProvider provider) {
|
||||
this.stateDescriptionProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = new VolvoOnCallBridgeHandler((Bridge) thing);
|
||||
registerDeviceDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
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.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@VehicleAction } class is responsible to call corresponding
|
||||
* action on Vehicle Handler
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@ThingActionsScope(name = "volvooncall")
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallActions implements ThingActions, IVolvoOnCallActions {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VolvoOnCallActions.class);
|
||||
|
||||
private @Nullable VehicleHandler handler;
|
||||
|
||||
public VolvoOnCallActions() {
|
||||
logger.info("Volvo On Call actions service instanciated");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
if (handler instanceof VehicleHandler) {
|
||||
this.handler = (VehicleHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Volvo On Call : Close", description = "Closes the car")
|
||||
public void closeCarCommand() {
|
||||
logger.debug("closeCarCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionClose();
|
||||
} 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")
|
||||
public void openCarCommand() {
|
||||
logger.debug("openCarCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionOpen();
|
||||
} 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")
|
||||
public void engineStartCommand(@ActionInput(name = "runtime", label = "Runtime") @Nullable Integer runtime) {
|
||||
logger.debug("engineStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionStart(runtime != null ? runtime : 5);
|
||||
} else {
|
||||
logger.warn("VolvoOnCall Action service ThingHandler is null!");
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
public void heaterStartCommand() {
|
||||
logger.debug("heaterStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionHeater(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")
|
||||
public void preclimatizationStartCommand() {
|
||||
logger.debug("preclimatizationStartCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionPreclimatization(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")
|
||||
public void heaterStopCommand() {
|
||||
logger.debug("heaterStopCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionHeater(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")
|
||||
public void preclimatizationStopCommand() {
|
||||
logger.debug("preclimatizationStopCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionPreclimatization(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")
|
||||
public void honkBlinkCommand(@ActionInput(name = "honk", label = "Honk") Boolean honk,
|
||||
@ActionInput(name = "blink", label = "Blink") Boolean blink) {
|
||||
logger.debug("honkBlinkCommand called");
|
||||
VehicleHandler handler = this.handler;
|
||||
if (handler != null) {
|
||||
handler.actionHonkBlink(honk, blink);
|
||||
} else {
|
||||
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,27 @@
|
||||
/**
|
||||
* 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.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VehicleConfiguration} is the class used to match the
|
||||
* Vehicle thing configuration.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleConfiguration {
|
||||
public String vin = "";
|
||||
public Integer refresh = 5;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.config;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link VolvoOnCallBridgeConfiguration} is responsible for holding
|
||||
* configuration informations needed to access VOC API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VolvoOnCallBridgeConfiguration {
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
|
||||
public String getAuthorization() {
|
||||
byte[] authorization = Base64.getEncoder().encode((String.format("%s:%s", username, password)).getBytes());
|
||||
return "Basic " + new String(authorization, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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,41 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link AccountVehicleRelation} is responsible for storing
|
||||
* informations returned by vehicle position rest
|
||||
* answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AccountVehicleRelation extends VocAnswer {
|
||||
@SerializedName("vehicle")
|
||||
public @NonNullByDefault({}) String vehicleURL;
|
||||
public @NonNullByDefault({}) String vehicleId;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private String account;
|
||||
* private String username;
|
||||
* private String status;
|
||||
* private Integer customerVehicleRelationId;
|
||||
* private String accountId;
|
||||
* private String accountVehicleRelation;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link Attributes} is responsible for storing
|
||||
* informations returned by vehicule attributes rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
public class Attributes extends VocAnswer {
|
||||
public String vehicleType;
|
||||
public String registrationNumber;
|
||||
public Boolean carLocatorSupported;
|
||||
public Boolean honkAndBlinkSupported;
|
||||
public List<String> honkAndBlinkVersionsSupported;
|
||||
public Boolean remoteHeaterSupported;
|
||||
public Boolean unlockSupported;
|
||||
public Boolean lockSupported;
|
||||
public Boolean journalLogSupported;
|
||||
public Integer unlockTimeFrame;
|
||||
public Boolean journalLogEnabled;
|
||||
public Boolean preclimatizationSupported;
|
||||
public String vin;
|
||||
public Boolean engineStartSupported;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* public class Country {
|
||||
* public @NonNullByDefault({}) String iso2;
|
||||
* }
|
||||
* private String engineCode;
|
||||
* private String exteriorCode;
|
||||
* private String interiorCode;
|
||||
* private String tyreDimensionCode;
|
||||
* private Object tyreInflationPressureLightCode;
|
||||
* private Object tyreInflationPressureHeavyCode;
|
||||
* private String gearboxCode;
|
||||
* private String fuelType;
|
||||
* private Integer fuelTankVolume;
|
||||
* private Integer grossWeight;
|
||||
* private Integer modelYear;
|
||||
* private String vehicleTypeCode;
|
||||
* private Integer numberOfDoors;
|
||||
* private Country country;
|
||||
* private Integer carLocatorDistance;
|
||||
* private Integer honkAndBlinkDistance;
|
||||
* private String bCallAssistanceNumber;
|
||||
* private Boolean assistanceCallSupported;
|
||||
* private Integer verificationTimeFrame;
|
||||
* private Integer timeFullyAccessible;
|
||||
* private Integer timePartiallyAccessible;
|
||||
* private String subscriptionType;
|
||||
* private String subscriptionStartDate;
|
||||
* private String subscriptionEndDate;
|
||||
* private String serverVersion;
|
||||
* private Boolean highVoltageBatterySupported;
|
||||
* private Object maxActiveDelayChargingLocations;
|
||||
* private Integer climatizationCalendarMaxTimers;
|
||||
* private String vehiclePlatform;
|
||||
* private Boolean statusParkedIndoorSupported;
|
||||
* private Boolean overrideDelayChargingSupported;
|
||||
* private @Nullable List<String> sendPOIToVehicleVersionsSupported = null;
|
||||
* private @Nullable List<String> climatizationCalendarVersionsSupported = null;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link CustomerAccounts} is responsible for storing
|
||||
* informations returned by customerAccount rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CustomerAccounts extends VocAnswer {
|
||||
@SerializedName("accountVehicleRelations")
|
||||
public @NonNullByDefault({}) String[] accountVehicleRelationsURL;
|
||||
public @Nullable String username;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private String firstName;
|
||||
* private String lastName;
|
||||
* private String accountId;
|
||||
* private String account;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
|
||||
/**
|
||||
* The {@link DoorsStatus} is responsible for storing
|
||||
* informations returned by vehicle status rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DoorsStatus {
|
||||
public @NonNullByDefault({}) OpenClosedType tailgateOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType rearRightDoorOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType rearLeftDoorOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType frontRightDoorOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType frontLeftDoorOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType hoodOpen;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ERSStatus} is responsible for storing
|
||||
* ERS Status informations returned by vehicule status rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ERSStatus {
|
||||
public @NonNullByDefault({}) String status;
|
||||
public @NonNullByDefault({}) ZonedDateTime timestamp;
|
||||
public @NonNullByDefault({}) String engineStartWarning;
|
||||
public @NonNullByDefault({}) ZonedDateTime engineStartWarningTimestamp;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link Heater} is responsible for storing
|
||||
* heater information returned by vehicle status rest answer
|
||||
*
|
||||
* @author Arie van der Lee - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Heater {
|
||||
private String status = "";
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
*/
|
||||
public State getStatus() {
|
||||
if ("off".equalsIgnoreCase(status)) {
|
||||
return OnOffType.OFF;
|
||||
} else if ("on".equalsIgnoreCase(status) || "onOther".equalsIgnoreCase(status)) {
|
||||
return OnOffType.ON;
|
||||
}
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.UNDEFINED;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HvBattery} is responsible for storing
|
||||
* PHEV Battery information returned by vehicle status rest answer
|
||||
*
|
||||
* @author Arie van der Lee - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HvBattery {
|
||||
public int hvBatteryLevel = UNDEFINED;
|
||||
public int distanceToHVBatteryEmpty = UNDEFINED;
|
||||
public @NonNullByDefault({}) String hvBatteryChargeStatusDerived;
|
||||
public int timeToHVBatteryFullyCharged = UNDEFINED;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Position} is responsible for storing
|
||||
* informations returned by vehicle position rest
|
||||
* answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Position extends VocAnswer {
|
||||
public @NonNullByDefault({}) PositionData position;
|
||||
public @NonNullByDefault({}) PositionData calculatedPosition;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link PositionData} is responsible for storing
|
||||
* informations returned by vehicle position rest
|
||||
* answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PositionData {
|
||||
public @Nullable Double longitude;
|
||||
public @Nullable Double latitude;
|
||||
private @Nullable ZonedDateTime timestamp;
|
||||
public @Nullable String speed;
|
||||
private @Nullable String heading;
|
||||
|
||||
public Boolean isHeading() {
|
||||
return "true".equalsIgnoreCase(heading);
|
||||
}
|
||||
|
||||
public Optional<ZonedDateTime> getTimestamp() {
|
||||
ZonedDateTime timestamp = this.timestamp;
|
||||
if (timestamp != null) {
|
||||
return Optional.of(timestamp);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private String streetAddress;
|
||||
* private String postalCode;
|
||||
* private String city;
|
||||
* private String iSO2CountryCode;
|
||||
* private String region;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link PostResponse} is responsible for storing
|
||||
* elements given back after a post to VOC api
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PostResponse extends VocAnswer {
|
||||
|
||||
public static enum Status {
|
||||
@SerializedName("Started")
|
||||
STARTED,
|
||||
@SerializedName("MessageDelivered")
|
||||
DELIVERED,
|
||||
@SerializedName("Failed")
|
||||
FAILED,
|
||||
@SerializedName("Successful")
|
||||
SUCCESSFULL
|
||||
}
|
||||
|
||||
public static enum ServiceType {
|
||||
RHBLF, // Remote Honk and Blink Lights ?
|
||||
RDU, // Remote door unlock
|
||||
ERS, // Remote engine start
|
||||
TN // Theft notification
|
||||
}
|
||||
|
||||
public @NonNullByDefault({}) Status status;
|
||||
public @NonNullByDefault({}) String vehicleId;
|
||||
@SerializedName("service")
|
||||
public @NonNullByDefault({}) String serviceURL;
|
||||
public @NonNullByDefault({}) ServiceType serviceType;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
*
|
||||
* public static enum FailureReason {
|
||||
*
|
||||
* @SerializedName("TimeframePassed")
|
||||
* TIME_FRAME_PASSED
|
||||
* }
|
||||
*
|
||||
* private ZonedDateTime statusTimestamp;
|
||||
* private ZonedDateTime startTime;
|
||||
* private FailureReason failureReason;
|
||||
*
|
||||
* private Integer customerServiceId;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.UNDEFINED;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* The {@link Status} is responsible for storing
|
||||
* Door Status informations returned by vehicule status rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Status extends VocAnswer {
|
||||
public double averageFuelConsumption = UNDEFINED;
|
||||
public int averageSpeed = UNDEFINED;
|
||||
public int fuelAmount = UNDEFINED;
|
||||
public int fuelAmountLevel = UNDEFINED;
|
||||
public int distanceToEmpty = UNDEFINED;
|
||||
public int odometer = UNDEFINED;
|
||||
public int tripMeter1 = UNDEFINED;
|
||||
public int tripMeter2 = UNDEFINED;
|
||||
|
||||
private @Nullable OnOffType carLocked;
|
||||
private @Nullable OnOffType engineRunning;
|
||||
public String brakeFluid = "";
|
||||
public String washerFluidLevel = "";
|
||||
private @Nullable WindowsStatus windows;
|
||||
private @Nullable DoorsStatus doors;
|
||||
private @Nullable TyrePressure tyrePressure;
|
||||
private @Nullable HvBattery hvBattery;
|
||||
private @Nullable Heater heater;
|
||||
public String serviceWarningStatus = "";
|
||||
private @NonNullByDefault({}) List<Object> bulbFailures;
|
||||
|
||||
public Optional<WindowsStatus> getWindows() {
|
||||
WindowsStatus windows = this.windows;
|
||||
if (windows != null) {
|
||||
return Optional.of(windows);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<DoorsStatus> getDoors() {
|
||||
DoorsStatus doors = this.doors;
|
||||
if (doors != null) {
|
||||
return Optional.of(doors);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<TyrePressure> getTyrePressure() {
|
||||
TyrePressure tyrePressure = this.tyrePressure;
|
||||
if (tyrePressure != null) {
|
||||
return Optional.of(tyrePressure);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<HvBattery> getHvBattery() {
|
||||
HvBattery hvBattery = this.hvBattery;
|
||||
if (hvBattery != null) {
|
||||
return Optional.of(hvBattery);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Heater> getHeater() {
|
||||
Heater heater = this.heater;
|
||||
if (heater != null) {
|
||||
return Optional.of(heater);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<OnOffType> getCarLocked() {
|
||||
OnOffType carLocked = this.carLocked;
|
||||
if (carLocked != null) {
|
||||
return Optional.of(carLocked);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<OnOffType> getEngineRunning() {
|
||||
OnOffType engineRunning = this.engineRunning;
|
||||
if (engineRunning != null) {
|
||||
return Optional.of(engineRunning);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public boolean aFailedBulb() {
|
||||
return bulbFailures.size() > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Currently not used in the binding, maybe interesting for the future
|
||||
*
|
||||
* @SerializedName("ERS")
|
||||
* private ERSStatus ers;
|
||||
* private ZonedDateTime averageFuelConsumptionTimestamp;
|
||||
* private ZonedDateTime averageSpeedTimestamp;
|
||||
* private ZonedDateTime brakeFluidTimestamp;
|
||||
* private ZonedDateTime bulbFailuresTimestamp;
|
||||
* private ZonedDateTime carLockedTimestamp;
|
||||
* private ZonedDateTime distanceToEmptyTimestamp;
|
||||
* private ZonedDateTime engineRunningTimestamp;
|
||||
* private ZonedDateTime fuelAmountLevelTimestamp;
|
||||
* private ZonedDateTime fuelAmountTimestamp;
|
||||
* private ZonedDateTime odometerTimestamp;
|
||||
* private Boolean privacyPolicyEnabled;
|
||||
* private ZonedDateTime privacyPolicyEnabledTimestamp;
|
||||
* private String remoteClimatizationStatus;
|
||||
* private ZonedDateTime remoteClimatizationStatusTimestamp;
|
||||
* private ZonedDateTime serviceWarningStatusTimestamp;
|
||||
* private Object theftAlarm;
|
||||
* private String timeFullyAccessibleUntil;
|
||||
* private String timePartiallyAccessibleUntil;
|
||||
* private ZonedDateTime tripMeter1Timestamp;
|
||||
* private ZonedDateTime tripMeter2Timestamp;
|
||||
* private ZonedDateTime washerFluidLevelTimestamp;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link Trip} is responsible for storing
|
||||
* trip informations returned by trip rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Trip {
|
||||
public int id;
|
||||
public @NonNullByDefault({}) List<TripDetail> tripDetails;
|
||||
@SerializedName("trip")
|
||||
public @Nullable String tripURL;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private String name;
|
||||
* private String category;
|
||||
* private String userNotes;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.UNDEFINED;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link TripDetail} is responsible for storing
|
||||
* trip details returned by trip rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TripDetail {
|
||||
private @Nullable Integer fuelConsumption;
|
||||
private @Nullable Integer electricalConsumption;
|
||||
private @Nullable Integer electricalRegeneration;
|
||||
public int distance = UNDEFINED;
|
||||
public int startOdometer = UNDEFINED;
|
||||
public int endOdometer = UNDEFINED;
|
||||
private @Nullable ZonedDateTime endTime;
|
||||
private @Nullable ZonedDateTime startTime;
|
||||
private @NonNullByDefault({}) PositionData startPosition;
|
||||
private @NonNullByDefault({}) PositionData endPosition;
|
||||
|
||||
private State ZonedDateTimeToState(@Nullable ZonedDateTime datetime) {
|
||||
return datetime != null ? new DateTimeType(datetime.withZoneSameInstant(ZoneId.systemDefault()))
|
||||
: UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getPositionAsState(PositionData details) {
|
||||
if (details.latitude != null && details.longitude != null) {
|
||||
return new PointType(details.latitude + "," + details.longitude);
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
public State getStartTime() {
|
||||
return ZonedDateTimeToState(startTime);
|
||||
}
|
||||
|
||||
public State getStartPosition() {
|
||||
return getPositionAsState(startPosition);
|
||||
}
|
||||
|
||||
public State getEndTime() {
|
||||
return ZonedDateTimeToState(endTime);
|
||||
}
|
||||
|
||||
public State getEndPosition() {
|
||||
return getPositionAsState(endPosition);
|
||||
}
|
||||
|
||||
public long getDurationInMinutes() {
|
||||
return Duration.between(startTime, endTime).toMinutes();
|
||||
}
|
||||
|
||||
public Optional<Integer> getFuelConsumption() {
|
||||
Integer fuelConsumption = this.fuelConsumption;
|
||||
if (fuelConsumption != null) {
|
||||
return Optional.of(fuelConsumption);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Integer> getElectricalConsumption() {
|
||||
Integer electricalConsumption = this.electricalConsumption;
|
||||
if (electricalConsumption != null) {
|
||||
return Optional.of(electricalConsumption);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Integer> getElectricalRegeneration() {
|
||||
Integer electricalRegeneration = this.electricalRegeneration;
|
||||
if (electricalRegeneration != null) {
|
||||
return Optional.of(electricalRegeneration);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link Trips} is responsible for storing
|
||||
* trip informations returned by trip rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Trips extends VocAnswer {
|
||||
public @Nullable List<Trip> trips;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link TyrePressure} is responsible for storing
|
||||
* Tyre Pressure informations returned by vehicule status rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TyrePressure {
|
||||
public @NonNullByDefault({}) String frontLeftTyrePressure;
|
||||
public @NonNullByDefault({}) String frontRightTyrePressure;
|
||||
public @NonNullByDefault({}) String rearLeftTyrePressure;
|
||||
public @NonNullByDefault({}) String rearRightTyrePressure;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link Vehicles} is responsible for storing
|
||||
* informations returned by vehicule rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Vehicles extends VocAnswer {
|
||||
public @NonNullByDefault({}) String vehicleId;
|
||||
@SerializedName("attributes")
|
||||
public @NonNullByDefault({}) String attributesURL;
|
||||
@SerializedName("status")
|
||||
public @NonNullByDefault({}) String statusURL;
|
||||
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
*
|
||||
*
|
||||
* private String[] vehicleAccountRelations;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link VocAnswer} is the base class for all Voc API requests
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class VocAnswer {
|
||||
|
||||
private @Nullable String errorLabel;
|
||||
private @Nullable String errorDescription;
|
||||
|
||||
public @Nullable String getErrorLabel() {
|
||||
return errorLabel;
|
||||
}
|
||||
|
||||
public @Nullable String getErrorDescription() {
|
||||
return errorDescription;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 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.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
|
||||
/**
|
||||
* The {@link WindowsStatus} is responsible for storing
|
||||
* Windows Status informations returned by vehicule status rest answer
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WindowsStatus {
|
||||
public @NonNullByDefault({}) OpenClosedType frontLeftWindowOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType frontRightWindowOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType rearLeftWindowOpen;
|
||||
public @NonNullByDefault({}) OpenClosedType rearRightWindowOpen;
|
||||
/*
|
||||
* Currently unused in the binding, maybe interesting in the future
|
||||
* private ZonedDateTime timestamp;
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* 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.handler;
|
||||
|
||||
import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
|
||||
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.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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.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.Status;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Trip;
|
||||
import org.openhab.binding.volvooncall.internal.dto.TripDetail;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Trips;
|
||||
import org.openhab.binding.volvooncall.internal.dto.TyrePressure;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Vehicles;
|
||||
import org.openhab.binding.volvooncall.internal.dto.WindowsStatus;
|
||||
import org.openhab.binding.volvooncall.internal.wrapper.VehiclePositionWrapper;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
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.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
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 Vehicles vehicle = new Vehicles();
|
||||
private VehiclePositionWrapper vehiclePosition = new VehiclePositionWrapper(new Position());
|
||||
private Status vehicleStatus = new Status();
|
||||
private @NonNullByDefault({}) VehicleConfiguration configuration;
|
||||
private Integer lastTripId = 0;
|
||||
|
||||
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);
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(CAR_LOCATOR, attributes.carLocatorSupported.toString());
|
||||
properties.put(HONK_AND_OR_BLINK, Boolean.toString(attributes.honkAndBlinkSupported
|
||||
&& attributes.honkAndBlinkVersionsSupported.contains(HONK_AND_OR_BLINK)));
|
||||
properties.put(HONK_BLINK, Boolean.toString(
|
||||
attributes.honkAndBlinkSupported && attributes.honkAndBlinkVersionsSupported.contains(HONK_BLINK)));
|
||||
properties.put(REMOTE_HEATER, attributes.remoteHeaterSupported.toString());
|
||||
properties.put(UNLOCK, attributes.unlockSupported.toString());
|
||||
properties.put(LOCK, attributes.lockSupported.toString());
|
||||
properties.put(JOURNAL_LOG, Boolean.toString(attributes.journalLogSupported && attributes.journalLogEnabled));
|
||||
properties.put(PRECLIMATIZATION, attributes.preclimatizationSupported.toString());
|
||||
properties.put(ENGINE_START, attributes.engineStartSupported.toString());
|
||||
properties.put(UNLOCK_TIME, attributes.unlockTimeFrame.toString());
|
||||
|
||||
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
|
||||
*/
|
||||
private void startAutomaticRefresh(int refresh) {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||
refreshJob = scheduler.scheduleWithFixedDelay(this::queryApiAndUpdateChannels, 10, refresh,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private void queryApiAndUpdateChannels() {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
try {
|
||||
vehicleStatus = bridgeHandler.getURL(vehicle.statusURL, Status.class);
|
||||
vehiclePosition = new VehiclePositionWrapper(bridgeHandler.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);
|
||||
|
||||
updateState(channelUID, state);
|
||||
});
|
||||
updateTrips(bridgeHandler);
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
freeRefreshJob();
|
||||
startAutomaticRefresh(configuration.refresh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void freeRefreshJob() {
|
||||
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||
if (refreshJob != null) {
|
||||
refreshJob.cancel(true);
|
||||
this.refreshJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
freeRefreshJob();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void updateTrips(VolvoOnCallBridgeHandler bridgeHandler) 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());
|
||||
Collections.reverse(newTrips);
|
||||
|
||||
logger.debug("Trips discovered : {}", newTrips.size());
|
||||
|
||||
if (!newTrips.isEmpty()) {
|
||||
Integer newTripId = newTrips.get(newTrips.size() - 1).id;
|
||||
if (newTripId > lastTripId) {
|
||||
updateProperty(LAST_TRIP_ID, newTripId.toString());
|
||||
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()))
|
||||
.forEach(channelUID -> {
|
||||
State state = getTripValue(channelUID.getIdWithoutGroup(), catchUpTrip);
|
||||
updateState(channelUID, state);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
String channelID = channelUID.getIdWithoutGroup();
|
||||
if (command instanceof RefreshType) {
|
||||
queryApiAndUpdateChannels();
|
||||
} 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 (CAR_LOCKED.equals(channelID)) {
|
||||
if (onOffCommand == OnOffType.ON) {
|
||||
actionClose();
|
||||
} else {
|
||||
actionOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private State getTripValue(String channelId, TripDetail tripDetails) {
|
||||
switch (channelId) {
|
||||
case TRIP_CONSUMPTION:
|
||||
return tripDetails.getFuelConsumption()
|
||||
.map(value -> (State) new QuantityType<>(value.floatValue() / 100, LITRE))
|
||||
.orElse(UnDefType.UNDEF);
|
||||
case TRIP_DISTANCE:
|
||||
return new QuantityType<>((double) tripDetails.distance / 1000, KILO(METRE));
|
||||
case TRIP_START_TIME:
|
||||
return tripDetails.getStartTime();
|
||||
case TRIP_END_TIME:
|
||||
return tripDetails.getEndTime();
|
||||
case TRIP_DURATION:
|
||||
return new QuantityType<>(tripDetails.getDurationInMinutes(), MINUTE);
|
||||
case TRIP_START_ODOMETER:
|
||||
return new QuantityType<>((double) tripDetails.startOdometer / 1000, KILO(METRE));
|
||||
case TRIP_STOP_ODOMETER:
|
||||
return new QuantityType<>((double) tripDetails.endOdometer / 1000, KILO(METRE));
|
||||
case TRIP_START_POSITION:
|
||||
return tripDetails.getStartPosition();
|
||||
case TRIP_END_POSITION:
|
||||
return tripDetails.getEndPosition();
|
||||
}
|
||||
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getDoorsValue(String channelId, DoorsStatus doors) {
|
||||
switch (channelId) {
|
||||
case TAILGATE:
|
||||
return doors.tailgateOpen;
|
||||
case REAR_RIGHT:
|
||||
return doors.rearRightDoorOpen;
|
||||
case REAR_LEFT:
|
||||
return doors.rearLeftDoorOpen;
|
||||
case FRONT_RIGHT:
|
||||
return doors.frontRightDoorOpen;
|
||||
case FRONT_LEFT:
|
||||
return doors.frontLeftDoorOpen;
|
||||
case HOOD:
|
||||
return doors.hoodOpen;
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getWindowsValue(String channelId, WindowsStatus windows) {
|
||||
switch (channelId) {
|
||||
case REAR_RIGHT_WND:
|
||||
return windows.rearRightWindowOpen;
|
||||
case REAR_LEFT_WND:
|
||||
return windows.rearLeftWindowOpen;
|
||||
case FRONT_RIGHT_WND:
|
||||
return windows.frontRightWindowOpen;
|
||||
case FRONT_LEFT_WND:
|
||||
return windows.frontLeftWindowOpen;
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getTyresValue(String channelId, TyrePressure tyrePressure) {
|
||||
switch (channelId) {
|
||||
case REAR_RIGHT_TYRE:
|
||||
return new StringType(tyrePressure.rearRightTyrePressure);
|
||||
case REAR_LEFT_TYRE:
|
||||
return new StringType(tyrePressure.rearLeftTyrePressure);
|
||||
case FRONT_RIGHT_TYRE:
|
||||
return new StringType(tyrePressure.frontRightTyrePressure);
|
||||
case FRONT_LEFT_TYRE:
|
||||
return new StringType(tyrePressure.frontLeftTyrePressure);
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getHeaterValue(String channelId, Heater heater) {
|
||||
switch (channelId) {
|
||||
case REMOTE_HEATER:
|
||||
case PRECLIMATIZATION:
|
||||
return heater.getStatus();
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
private State getBatteryValue(String channelId, HvBattery hvBattery) {
|
||||
switch (channelId) {
|
||||
case BATTERY_LEVEL:
|
||||
return hvBattery.hvBatteryLevel != UNDEFINED ? new QuantityType<>(hvBattery.hvBatteryLevel, PERCENT)
|
||||
: UnDefType.UNDEF;
|
||||
case BATTERY_DISTANCE_TO_EMPTY:
|
||||
return hvBattery.distanceToHVBatteryEmpty != UNDEFINED
|
||||
? new QuantityType<>(hvBattery.distanceToHVBatteryEmpty, KILO(METRE))
|
||||
: UnDefType.UNDEF;
|
||||
case CHARGE_STATUS:
|
||||
return hvBattery.hvBatteryChargeStatusDerived != null
|
||||
? new StringType(hvBattery.hvBatteryChargeStatusDerived)
|
||||
: UnDefType.UNDEF;
|
||||
case TIME_TO_BATTERY_FULLY_CHARGED:
|
||||
return hvBattery.timeToHVBatteryFullyCharged != UNDEFINED
|
||||
? new QuantityType<>(hvBattery.timeToHVBatteryFullyCharged, MINUTE)
|
||||
: UnDefType.UNDEF;
|
||||
case CHARGING_END:
|
||||
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) {
|
||||
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
|
||||
return status.getCarLocked().map(State.class::cast).orElse(UnDefType.UNDEF);
|
||||
case ENGINE_RUNNING:
|
||||
return status.getEngineRunning().map(State.class::cast).orElse(UnDefType.UNDEF);
|
||||
case BRAKE_FLUID_LEVEL:
|
||||
return new StringType(status.brakeFluid);
|
||||
case WASHER_FLUID_LEVEL:
|
||||
return new StringType(status.washerFluidLevel);
|
||||
case AVERAGE_SPEED:
|
||||
return status.averageSpeed != UNDEFINED ? new QuantityType<>(status.averageSpeed, KILOMETRE_PER_HOUR)
|
||||
: 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_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);
|
||||
case GROUP_TYRES:
|
||||
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;
|
||||
}
|
||||
|
||||
public void actionHonkBlink(Boolean honk, Boolean blink) {
|
||||
VolvoOnCallBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
StringBuilder url = new StringBuilder(SERVICE_URL + "vehicles/" + vehicle.vehicleId + "/honk_blink/");
|
||||
|
||||
if (honk && blink && activeOptions.containsKey(HONK_BLINK)) {
|
||||
url.append("both");
|
||||
} else if (honk && activeOptions.containsKey(HONK_AND_OR_BLINK)) {
|
||||
url.append("horn");
|
||||
} else if (blink && activeOptions.containsKey(HONK_AND_OR_BLINK)) {
|
||||
url.append("lights");
|
||||
} else {
|
||||
logger.warn("The vehicle is not capable of this action");
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
StringBuilder address = new StringBuilder(SERVICE_URL);
|
||||
address.append("vehicles/");
|
||||
address.append(configuration.vin);
|
||||
address.append("/");
|
||||
address.append(action);
|
||||
bridgeHandler.postURL(address.toString(), "{}");
|
||||
} catch (VolvoOnCallException e) {
|
||||
logger.warn("Exception occurred during execution: {}", e.getMessage(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
} else {
|
||||
logger.info("The car {} is already {}ed", configuration.vin, action);
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
} 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 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());
|
||||
}
|
||||
} 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() {
|
||||
return Collections.singletonList(VolvoOnCallActions.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of state options for VehicleHandler.
|
||||
*
|
||||
* @author Gregory Moyer - Initial contribution
|
||||
* @author Gaël L'hopital - Copied as-is in VolvoOnCall binding
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, VehicleStateDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class VehicleStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Reference
|
||||
protected void setChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
|
||||
protected void unsetChannelTypeI18nLocalizationService(
|
||||
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.channelTypeI18nLocalizationService = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 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.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 org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.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.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
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@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 @NonNullByDefault({}) CustomerAccounts customerAccount;
|
||||
|
||||
public VolvoOnCallBridgeHandler(Bridge bridge) {
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing VolvoOnCall API bridge handler.");
|
||||
VolvoOnCallBridgeConfiguration configuration = getConfigAs(VolvoOnCallBridgeConfiguration.class);
|
||||
|
||||
httpHeader.setProperty("Authorization", configuration.getAuthorization());
|
||||
try {
|
||||
customerAccount = getURL(SERVICE_URL + "customeraccounts/", CustomerAccounts.class);
|
||||
if (customerAccount.username != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Incorrect username or password");
|
||||
}
|
||||
} catch (JsonSyntaxException | 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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.wrapper;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.volvooncall.internal.dto.Position;
|
||||
import org.openhab.binding.volvooncall.internal.dto.PositionData;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link VehiclePositionWrapper} stores provides utility functions
|
||||
* over a {@link Position} provided by the rest API
|
||||
*
|
||||
* @author Gaël L'hopital - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehiclePositionWrapper {
|
||||
private final Optional<PositionData> position;
|
||||
private boolean isCalculated;
|
||||
|
||||
public VehiclePositionWrapper(Position vehicle) {
|
||||
if (vehicle.calculatedPosition != null && vehicle.position.latitude != null) {
|
||||
position = Optional.of(vehicle.position);
|
||||
isCalculated = false;
|
||||
} else if (vehicle.calculatedPosition != null && vehicle.calculatedPosition.latitude != null) {
|
||||
position = Optional.of(vehicle.calculatedPosition);
|
||||
isCalculated = true;
|
||||
} else {
|
||||
position = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private State getPositionAsState(PositionData details) {
|
||||
if (details.latitude != null && details.longitude != null) {
|
||||
return new PointType(details.latitude + "," + details.longitude);
|
||||
}
|
||||
return UnDefType.NULL;
|
||||
}
|
||||
|
||||
public State getPosition() {
|
||||
return position.map(pos -> getPositionAsState(pos)).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
public @Nullable String getPositionAsJSon() {
|
||||
if (getPosition() != UnDefType.UNDEF) {
|
||||
StringBuilder json = new StringBuilder("{\"clientLatitude\":");
|
||||
json.append(position.get().latitude);
|
||||
json.append(",\"clientLongitude\":");
|
||||
json.append(position.get().longitude);
|
||||
json.append(",\"clientAccuracy\":0}");
|
||||
|
||||
return json.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public State isCalculated() {
|
||||
return position.map(pos -> isCalculated ? (State) OnOffType.ON : OnOffType.OFF).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
public State isHeading() {
|
||||
return position.map(pos -> pos.isHeading() ? (State) OnOffType.ON : OnOffType.OFF).orElse(UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
public State getTimestamp() {
|
||||
return position.flatMap(pos -> pos.getTimestamp())
|
||||
.map(dt -> (State) new DateTimeType(dt.withZoneSameInstant(ZoneId.systemDefault())))
|
||||
.orElse(UnDefType.NULL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="volvooncall" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
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>
|
||||
<author>Gaël L'hopital</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
|
||||
https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:volvooncall:vocapi">
|
||||
<parameter name="username" type="text">
|
||||
<label>Username</label>
|
||||
<description>Your VOC username (email)</description>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
|
||||
<parameter name="password" type="text">
|
||||
<label>Password</label>
|
||||
<description>Your VOC password</description>
|
||||
<required>true</required>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="volvooncall"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- VOC API Bridge -->
|
||||
<bridge-type id="vocapi">
|
||||
<label>VolvoOnCall API</label>
|
||||
<description>This bridge represents the gateway to Volvo On Call API.</description>
|
||||
<config-description-ref uri="thing-type:volvooncall:vocapi"/>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,391 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="volvooncall"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- Volvo Vehicle -->
|
||||
<thing-type id="vehicle">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="vocapi"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Volvo Vehicle</label>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="doors" typeId="doors"/>
|
||||
<channel-group id="windows" typeId="windows"/>
|
||||
<channel-group id="odometer" typeId="odometer"/>
|
||||
<channel-group id="tank" typeId="tank"/>
|
||||
<channel-group id="position" typeId="position"/>
|
||||
<channel-group id="tyrePressure" typeId="tyrePressure"/>
|
||||
<channel-group id="battery" typeId="battery"/>
|
||||
<channel-group id="other" typeId="other"/>
|
||||
<channel-group id="lasttrip" typeId="lasttrip"/>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>vin</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>Vehicle Identification Number</label>
|
||||
<description>VIN of the vehicle associated with this Thing</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="refresh" type="integer" min="5" required="false">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in minutes.</description>
|
||||
<default>5</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="other">
|
||||
<label>Other</label>
|
||||
<channels>
|
||||
<channel id="averageSpeed" typeId="averageSpeed"/>
|
||||
<channel id="engineRunning" typeId="engineRunning"/>
|
||||
<channel id="remoteHeater" typeId="remoteHeater"/>
|
||||
<channel id="preclimatization" typeId="preclimatization"/>
|
||||
<channel id="brakeFluidLevel" typeId="brakeFluidLevel"/>
|
||||
<channel id="washerFluidLevel" typeId="washerFluidLevel"/>
|
||||
<channel id="serviceWarningStatus" typeId="serviceWarningStatus"/>
|
||||
<channel id="bulbFailure" typeId="bulbFailure"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="lasttrip">
|
||||
<label>Last Trip</label>
|
||||
<channels>
|
||||
<channel id="tripConsumption" typeId="fuelQuantity">
|
||||
<label>Consumption</label>
|
||||
<description>Indicates the quantity of fuel consumed by the trip</description>
|
||||
</channel>
|
||||
<channel id="tripDistance" typeId="odometer">
|
||||
<label>Distance</label>
|
||||
<description>Distance traveled</description>
|
||||
</channel>
|
||||
<channel id="tripStartTime" typeId="timestamp">
|
||||
<label>Start Time</label>
|
||||
<description>Trip start time</description>
|
||||
</channel>
|
||||
<channel id="tripEndTime" typeId="timestamp">
|
||||
<label>End Time</label>
|
||||
<description>Trip end time</description>
|
||||
</channel>
|
||||
<channel id="tripDuration" typeId="tripDuration"/>
|
||||
<channel id="tripStartOdometer" typeId="odometer">
|
||||
<label>Start Odometer</label>
|
||||
</channel>
|
||||
<channel id="tripStopOdometer" typeId="odometer">
|
||||
<label>Stop Odometer</label>
|
||||
</channel>
|
||||
<channel id="startPosition" typeId="location">
|
||||
<label>From</label>
|
||||
<description>Starting location of the car</description>
|
||||
</channel>
|
||||
<channel id="endPosition" typeId="location">
|
||||
<label>To</label>
|
||||
<description>Stopping location of the car</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="doors">
|
||||
<label>Doors Opening Status</label>
|
||||
<channels>
|
||||
<channel id="frontLeft" typeId="door">
|
||||
<label>Front Left</label>
|
||||
</channel>
|
||||
<channel id="frontRight" typeId="door">
|
||||
<label>Front Right</label>
|
||||
</channel>
|
||||
<channel id="rearLeft" typeId="door">
|
||||
<label>Rear Left</label>
|
||||
</channel>
|
||||
<channel id="rearRight" typeId="door">
|
||||
<label>Rear Right</label>
|
||||
</channel>
|
||||
<channel id="hood" typeId="door">
|
||||
<label>Hood</label>
|
||||
</channel>
|
||||
<channel id="tailgate" typeId="door">
|
||||
<label>Tailgate</label>
|
||||
</channel>
|
||||
<channel id="carLocked" typeId="carLocked"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="windows">
|
||||
<label>Windows Opening Status</label>
|
||||
<channels>
|
||||
<channel id="frontLeftWnd" typeId="window">
|
||||
<label>Front Left</label>
|
||||
</channel>
|
||||
<channel id="frontRightWnd" typeId="window">
|
||||
<label>Front Right</label>
|
||||
</channel>
|
||||
<channel id="rearLeftWnd" typeId="window">
|
||||
<label>Rear Left</label>
|
||||
</channel>
|
||||
<channel id="rearRightWnd" typeId="window">
|
||||
<label>Rear Right</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="tyrePressure">
|
||||
<label>Tyre pressure status</label>
|
||||
<channels>
|
||||
<channel id="frontLeftTyre" typeId="tyrePressure">
|
||||
<label>Front Left</label>
|
||||
</channel>
|
||||
<channel id="frontRightTyre" typeId="tyrePressure">
|
||||
<label>Front Right</label>
|
||||
</channel>
|
||||
<channel id="rearLeftTyre" typeId="tyrePressure">
|
||||
<label>Rear Left</label>
|
||||
</channel>
|
||||
<channel id="rearRightTyre" typeId="tyrePressure">
|
||||
<label>Rear Right</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="odometer">
|
||||
<label>Trip Meters</label>
|
||||
<channels>
|
||||
<channel id="odometer" typeId="odometer"/>
|
||||
<channel id="tripmeter1" typeId="odometer">
|
||||
<label>Tripmeter 1</label>
|
||||
</channel>
|
||||
<channel id="tripmeter2" typeId="odometer">
|
||||
<label>Tripmeter 2</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="tank">
|
||||
<label>Tank Info</label>
|
||||
<channels>
|
||||
<channel id="fuelAmount" typeId="fuelQuantity">
|
||||
<label>Fuel Amount</label>
|
||||
<description>Indicates the quantity of fuel available in the tank</description>
|
||||
</channel>
|
||||
<channel id="fuelLevel" typeId="fuelLevel"/>
|
||||
<channel id="fuelConsumption" typeId="fuelConsumption"/>
|
||||
<channel id="fuelAlert" typeId="fuelAlert"/>
|
||||
<channel id="distanceToEmpty" typeId="odometer">
|
||||
<label>Distance Left</label>
|
||||
<description>Distance left with given quantity of fuel</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="position">
|
||||
<label>Location Info</label>
|
||||
<channels>
|
||||
<channel id="location" typeId="location">
|
||||
<label>Location</label>
|
||||
<description>The position of the vehicle</description>
|
||||
</channel>
|
||||
<channel id="calculatedLocation" typeId="calculatedLocation"/>
|
||||
<channel id="heading" typeId="heading"/>
|
||||
<channel id="locationTimestamp" typeId="timestamp">
|
||||
<label>Location Timestamp</label>
|
||||
<description>Timestamp of location value update</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="battery">
|
||||
<label>Plugin Hybrid / Twin Engine info</label>
|
||||
<channels>
|
||||
<channel id="batteryLevel" typeId="batteryLevel"/>
|
||||
<channel id="batteryDistanceToEmpty" typeId="odometer">
|
||||
<label>Distance to battery empty</label>
|
||||
</channel>
|
||||
<channel id="chargeStatus" typeId="chargeStatus"/>
|
||||
<channel id="timeToHVBatteryFullyCharged" typeId="timeToHVBatteryFullyCharged"/>
|
||||
<channel id="chargingEnd" typeId="timestamp">
|
||||
<label>Charging end</label>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="door">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Door</label>
|
||||
<description>Indicates if the door is opened</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tripDuration">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Duration</label>
|
||||
<description>Trip Duration</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="window">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Window Status</label>
|
||||
<description>Indicates if the window is opened</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="odometer">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Odometer</label>
|
||||
<description>Odometer of the vehicle</description>
|
||||
<state pattern="%.2f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="averageSpeed">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Average speed</label>
|
||||
<description>Average speed of the vehicle</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fuelQuantity">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Fuel Quantity</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fuelLevel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fuel Level</label>
|
||||
<description>Indicates the level of fuel in the tank</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<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>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="location">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location</label>
|
||||
<category>Location</category>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="calculatedLocation" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Calculated Location</label>
|
||||
<description>Indicates if the location is actual or calculated</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="heading" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Heading</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timestamp">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Timestamp</label>
|
||||
<description>Data timestamp</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="engineStart">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Start Engine</label>
|
||||
<description>Starts the vehicle engine</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="carLocked">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Locked</label>
|
||||
<description>Car locking status</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="engineRunning">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Engine Started</label>
|
||||
<description>Engine status (running or not)</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="preclimatization">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Preclimatization</label>
|
||||
<description>Starts pre-climatization</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="remoteHeater">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Remote Heater</label>
|
||||
<description>(De)Activates remote heater</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="bulbFailure">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Bulb Failure</label>
|
||||
<description>At least on bulb is reported as dead</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="brakeFluidLevel">
|
||||
<item-type>String</item-type>
|
||||
<label>Brake Fluid</label>
|
||||
<description>“VeryLow”,"Low", "Normal"</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="washerFluidLevel">
|
||||
<item-type>String</item-type>
|
||||
<label>Washer Fluid</label>
|
||||
<description>“VeryLow”,"Low", "Normal"</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tyrePressure">
|
||||
<item-type>String</item-type>
|
||||
<label>Tyre pressure</label>
|
||||
<description>“LowSoft”, "Normal"</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="serviceWarningStatus">
|
||||
<item-type>String</item-type>
|
||||
<label>Service Warning</label>
|
||||
<description>Is car service needed ?</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="batteryLevel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Battery Level</label>
|
||||
<description>Indicates the level of power in the battery (in case of PHEV / Twin Engine)</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chargeStatus">
|
||||
<item-type>String</item-type>
|
||||
<label>Charging status</label>
|
||||
<description>Status of charging (in case of PHEV / Twin Engine)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="timeToHVBatteryFullyCharged">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Time to battery fully charged</label>
|
||||
<description>Time in seconds until the battery is fully charged (in case of PHEV / Twin Engine)</description>
|
||||
<state pattern="%d %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fuelAlert">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Fuel Alarm</label>
|
||||
<description>set to 'ON' when the tank level is low</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user