[groupepsa] Initial Contribution (#10332)

* Initial commit: template only

Signed-off-by: Arjan Mels <github@mels.email>

* Initial version

Signed-off-by: Arjan Mels <github@mels.email>

* Minor cleanup after rebase

Signed-off-by: Arjan Mels <github@mels.email>

* Corrected type "peugot" => "peugeot"

Signed-off-by: Arjan Mels <github@mels.email>

* Improved Exception messages
Corrected URL

Signed-off-by: Arjan Mels <github@mels.email>

* Switched from vin to id
Changed nextDelayedTime from ZonedDateTime to Duration
Added vehcile etails and additional info retrieval

Signed-off-by: Arjan Mels <github@mels.email>

* Before rebase

Signed-off-by: Arjan Mels <github@mels.email>

* Various fixes

Signed-off-by: Arjan Mels <github@mels.email>

* Updated tests

Signed-off-by: Arjan Mels <github@mels.email>

* Changed double to BigDecimal to keep accuracy
Added lastupdated channel
Small corrections

Signed-off-by: Arjan Mels <github@mels.email>

* Updated to 3.1.0-SNAPSHOT

Signed-off-by: Arjan Mels <github@mels.email>

* Added README.md

Signed-off-by: Arjan Mels <github@mels.email>

* Changed channels to lowerCamelCase

Signed-off-by: Arjan Mels <github@mels.email>

* Minor corrections

Signed-off-by: Arjan Mels <github@mels.email>

* Corrected lastUpdated in thing-types.xml

Signed-off-by: Arjan Mels <github@mels.email>

* Fixed ToStringbuilder

Signed-off-by: Arjan Mels <github@mels.email>

* corrected capitalization in thing-types.xml and Constants

Signed-off-by: Arjan Mels <github@mels.email>

* Corrected URL for Citroen

Signed-off-by: Arjan Mels <github@mels.email>

* Added groupepsa binding to bom pom.xml

Signed-off-by: Arjan Mels <github@mels.email>

* Updated copyrights

Signed-off-by: Arjan Mels <github@mels.email>

* Fixed CheckStyle warnings

Signed-off-by: Arjan Mels <github@mels.email>

* Fixed check style notifications

Signed-off-by: Arjan Mels <github@mels.email>

* Updated readme

Signed-off-by: Arjan Mels <github@mels.email>

* Corrected capitalization of thing-types.xml

Signed-off-by: Arjan Mels <github@mels.email>

* Updated pom. xml to 3.3.0-SNAPSHOT

Signed-off-by: Arjan Mels <github@mels.email>

* Updated copyright messages

Signed-off-by: Arjan Mels <github@mels.email>

* Various minor cleanups
Cleanup of the README.md
Removed unnecessary license header form the feature.xml file
Various null annotation corrections
Further cleanup of thing-types.xml
Cleanup of groupepsa.properties

Signed-off-by: Arjan Mels <github@mels.email>

* Logging updates

Signed-off-by: Arjan Mels <github@mels.email>

* Updated dependencies
Added dependencies to NOTICE

Signed-off-by: Arjan Mels <github@mels.email>

* Ignore lastposition for isConnected check

Signed-off-by: Arjan Mels <github@mels.email>

* Updated to use ThingHandlerService

Signed-off-by: Arjan Mels <github@mels.email>

* Fixed various review findings

Signed-off-by: Arjan Mels <github@mels.email>

Co-authored-by: Arjan Mels <github@mels.email>
This commit is contained in:
Arjan Mels 2022-04-30 19:44:53 +02:00 committed by GitHub
parent 7adfc482da
commit 8e7c78cef5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 4369 additions and 0 deletions

View File

@ -546,6 +546,11 @@
<artifactId>org.openhab.binding.groheondus</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.groupepsa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.guntamatic</artifactId>

View File

@ -0,0 +1,20 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
== Third-party Content
GeoJSON support for Gson
* License: Apache License 2.0
* Project: https://github.com/filosganga/geogson
* Source: https://github.com/filosganga/geogson

View File

@ -0,0 +1,151 @@
# Groupe PSA Binding
Binding to retrieve information via the Groupe PSA Web API for cars from Opel, Peugeot, Citroen, DS and Vauxhall.
## Supported Things
bridge - Groupe PSA Web Api Bridge: The Thing to auto discover your cars
vehicle - Groupe PSA Car: The actual car thing.
## Discovery
Use the "Groupe PSA Web Api bridge" to auto discover your cars. You need to select the brand for the bridge binding and only cars for the brand will be auto discovered. If you need to add for multiple brands or multiple different users, add multiple bridges.
## Bridge Configuration
You need to select a brand and enter the User Name and Password.
The Polling interval (in minutes) determines how often the API will polled for new cars.
The Client ID and Client Secret should not need to be updated. (However you can register your own app via https://developer.groupe-psa.com/inc/ and use this client information if you wish.)
### parameters
| Property | Default | Required | Description |
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| vendor | None | Yes | The brand of the car (PEUGEOT, CITROEN, DS, OPEL or VAUXHALL) |
| userName | None | Yes | The user name for the mypeugot/mycitroen/myds/myopel/myvauxhall website or app |
| password | None | Yes | The password for the given user |
| pollingInterval | 60 | No | The Polling interval (in minutes) determines how often the available vehicles are queried |
| clientId | | Yes | The client ID for API access: can normally left at the default value. (see: https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article) |
| clientSecret | | Yes | The client secret for API access: can normally left at the default value. (see: https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article) |
## Vehicle Configuration
Normally the vehicles will be autodiscovered. The Polling Interval and Online Timeout can be adjusted.
### parameters
| Property | Default | Required | Description |
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| id | None | Yes | Vehicle API ID | The ID is the vehicle API ID (not equal to the VIN), which is autodiscoverd by the bridge. |
| pollingInterval | 5 | No | The Polling interval (in minutes) determines how often the car is polled for updated information |
| onlineInterval | 15 | No | The Online Timeout (in minutes) determines when the car is deemed to be offline. |
## Channels
| Channel Type ID | Item Type | Description |
| ----------------------- | ------------------------- | ------------------------------------------------ |
| current | Number:ElectricCurrent | Electrical current |
| voltage | Number:ElectricPotential | Voltage |
| temperature | Number:Temperature | Temperature |
| daytime | Contact | Enabled if it is daytime |
| doorLock | String | Door lock state |
| doorOpen | Contact | Door is open |
| ignition | String | Ignition state |
| moving | Contact | Vehicle is moving |
| acceleration | Number:Acceleration | Current acceleration |
| speed | Number:Speed | Current speed |
| mileage | Number:Length | Total travelled distance |
| position | Location | Last known position |
| heading | Number:Angle | Direction of travel |
| type | String | Position acquisition type |
| signal | Number:Dimensionless | Strength of the position localization signal |
| lastUpdated | DateTime | Last time the results were updated on the server |
| privacy | String | Privacy status |
| belt | String | Seat belt status |
| emergency | String | Emergency call status |
| service | String | Service Type |
| preconditioning | String | Air conditioning status |
| preconditioningFailure | String | Airr conditioning failure cause |
| level | Number:Dimensionless | Fuel level |
| autonomy | Number:Length | Remaining distance |
| consumption | Number:VolumetricFlowRate | Fuel consumption |
| residual | Number:Energy | Remaining battery charge |
| capacity | Number:Energy | Battery capacity |
| healthCapacity | Number:Dimensionless | Health of the battery capacity |
| healthResistance | Number:Dimensionless | Health of the battery resistance |
| chargingStatus | String | Battery charging status |
| chargingMode | String | Battery charging mode |
| chargingPlugged | Contact | Vehicle plugged in to charger |
| chargingRate | Number:Speed | Battery Charging Rate |
| chargingRemainingTime | Number:Time | Time remaining till charged |
| chargingNextDelayedTime | Number:Time | Time till the next charging starts |
Further documentation can be found at: https://developer.groupe-psa.io/webapi/b2c/api-reference/specification/#article
## Full Example
### Things file
```
Bridge groupepsa:bridge:opel "Auto Interface" [
pollingInterval=60,
userName="anonymous@anonymous.email",
password="password",
vendor="OPEL"
] {
Things:
vehicle zafira "Auto" @ "Outdoors"
[
id="<web api id here>",
pollingInterval=5,
onlineInterval=1440
]
}
```
### Items file
```
Group Auto
Number:ElectricCurrent Auto_Aux_Current "Auxillliary Battery Current [%.1f %unit%]" (Auto) ["Measurement","Current"] {channel="groupepsa:vehicle:opel:zafira:battery#current"}
Number Auto_Aux_Level "Auxillliary Battery Level [%.1f %unit%]" (Auto) ["Measurement","Level"] {channel="groupepsa:vehicle:opel:zafira:battery#voltage"}
Number:Temperature Auto_Outside_Temperature "Outside Temperature [%.1f %unit%]" (Auto) ["Measurement","Temperature"] {channel="groupepsa:vehicle:opel:zafira:environment#temperature"}
Number:Length Auto_Mileage "Mileage [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:motion#mileage"}
Number:Speed Auto_Speed "Speed [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:motion#speed"}
Number:Acceleration Auto_Acceleration "Acceleration [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:motion#acceleration"}
Location Auto_Location "Locatie" (Auto) ["Point"] {channel="groupepsa:vehicle:opel:zafira:position#position"}
Number:Angle Auto_Heading "Richting" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:position#heading"}
String Auto_Location_Type "Locatie Type" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:position#type"}
Number Auto_Signal_Strength "Signaal Sterkte [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:position#signal"}
DateTime Auto_Last_Update "Laatst bijgewerkt [%1$tA, %1$te %1$tb %1$tY %1$tR]" (Auto) ["Measurement", "Timestamp"] {channel="groupepsa:vehicle:opel:zafira:various#lastUpdated"}
String Auto_Privacy_Status "Privacy Status" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#privacyStatus"}
String Auto_Belt_Status "Belt Status" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#beltStatus"}
String Auto_Emergency_Call "Emergency Call" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#emergencyCall"}
String Auto_Service "Service" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#service"}
String Auto_Air_Conditioning "Air Conditioning" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#preconditioning"}
String Auto_Air_Conditioning_Failure "Air Conditioning Failure" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:various#preconditioningFailure"}
Number:Length Auto_Autonomy "Autonomy [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:electric#autonomy"}
Number Auto_Level "Battery Level [%.1f %unit%]" (Auto) ["Measurement", "Level"] {channel="groupepsa:vehicle:opel:zafira:electric#level"}
Number:Energy Auto_Residual "Battery Residual [%.1f %unit%]" (Auto) ["Measurement", "Level"] {channel="groupepsa:vehicle:opel:zafira:electric#residual"}
Number:Energy Auto_Capacity "Battery Capacity [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:electric#capacity"}
Number Auto_Health_Capacity "Battery Health Capacity [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:electric#batteryHealthCapacity"}
Number Auto_Health_Resistance "Battery Health Resistance [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:electric#batteryHealthResistance"}
String Auto_Charging_Status "Charging Status" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingStatus"}
String Auto_Charging_Mode "Charging Mode" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingMode"}
Contact Auto_Plugged_In "Plugged In" (Auto) ["Status"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingPlugged"}
Number:Speed Auto_Charging_Rate "Charging Rate [%.1f %unit%]" (Auto) ["Measurement"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingRate"}
Number:Time Auto_Charging_Time_Remaining "Charging Time Remaining [%.1f %unit%]" (Auto) ["Measurement", "Duration"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingRemainingTime"}
Number:Time Auto_Charging_Time_Till_Next "Charging Time Till Next Charging [%.1f %unit%]" (Auto) ["Measurement", "Duration"] {channel="groupepsa:vehicle:opel:zafira:electric#chargingNextDelayedTime"}
```

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.groupepsa</artifactId>
<name>openHAB Add-ons :: Bundles :: Groupe PSA Binding</name>
<dependencies>
<dependency>
<groupId>com.github.filosganga</groupId>
<artifactId>geogson-core</artifactId>
<version>1.4.31</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.groupepsa-${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-groupepsa" description="Groupe PSA Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.groupepsa/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,109 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link GroupePSABindingConstants} class defines common constants, which
* are used across the whole binding.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSABindingConstants {
public static final String BINDING_ID = "groupepsa";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_VEHICLE = new ThingTypeUID(BINDING_ID, "vehicle");
// Vehicle properties
public static final String VEHICLE_ID = "id";
public static final String VEHICLE_VIN = "vin";
public static final String VEHICLE_VENDOR = "vendor";
public static final String VEHICLE_MODEL = "model";
// List of all Channel ids
public static final String CHANNEL_BATTERY_CURRENT = "battery#current";
public static final String CHANNEL_BATTERY_VOLTAGE = "battery#voltage";
public static final String CHANNEL_TYPE_DOORLOCK = "doorLock";
public static final String CHANNEL_TYPE_DOOROPEN = "doorOpen";
public static final String CHANNEL_GROUP_DOORS = "doors";
public static final String CHANNEL_DOORS_LOCK = "doors#locked";
public static final String CHANNEL_ENVIRONMENT_TEMPERATURE = "environment#temperature";
public static final String CHANNEL_ENVIRONMENT_DAYTIME = "environment#daytime";
public static final String CHANNEL_MOTION_IGNITION = "motion#ignition";
public static final String CHANNEL_MOTION_ACCELERATION = "motion#acceleration";
public static final String CHANNEL_MOTION_MOVING = "motion#moving";
public static final String CHANNEL_MOTION_SPEED = "motion#speed";
public static final String CHANNEL_MOTION_MILEAGE = "motion#mileage";
public static final String CHANNEL_POSITION_POSITION = "position#position";
public static final String CHANNEL_POSITION_HEADING = "position#heading";
public static final String CHANNEL_POSITION_TYPE = "position#type";
public static final String CHANNEL_POSITION_SIGNALSTRENGTH = "position#signal";
public static final String CHANNEL_VARIOUS_LAST_UPDATED = "various#lastUpdated";
public static final String CHANNEL_VARIOUS_PRIVACY = "various#privacy";
public static final String CHANNEL_VARIOUS_BELT = "various#belt";
public static final String CHANNEL_VARIOUS_EMERGENCY = "various#emergency";
public static final String CHANNEL_VARIOUS_SERVICE = "various#service";
public static final String CHANNEL_VARIOUS_PRECONDITINING = "various#preconditioning";
public static final String CHANNEL_VARIOUS_PRECONDITINING_FAILURE = "various#preconditioningFailure";
public static final String CHANNEL_FUEL_AUTONOMY = "fuel#autonomy";
public static final String CHANNEL_FUEL_CONSUMPTION = "fuel#consumption";
public static final String CHANNEL_FUEL_LEVEL = "fuel#level";
public static final String CHANNEL_ELECTRIC_AUTONOMY = "electric#autonomy";
public static final String CHANNEL_ELECTRIC_LEVEL = "electric#level";
public static final String CHANNEL_ELECTRIC_RESIDUAL = "electric#residual";
public static final String CHANNEL_ELECTRIC_BATTERY_CAPACITY = "electric#batteryCapacity";
public static final String CHANNEL_ELECTRIC_BATTERY_HEALTH_CAPACITY = "electric#batteryHealthCapacity";
public static final String CHANNEL_ELECTRIC_BATTERY_HEALTH_RESISTANCE = "electric#batteryHealthResistance";
public static final String CHANNEL_ELECTRIC_CHARGING_STATUS = "electric#chargingStatus";
public static final String CHANNEL_ELECTRIC_CHARGING_MODE = "electric#chargingMode";
public static final String CHANNEL_ELECTRIC_CHARGING_PLUGGED = "electric#chargingPlugged";
public static final String CHANNEL_ELECTRIC_CHARGING_RATE = "electric#chargingRate";
public static final String CHANNEL_ELECTRIC_CHARGING_REMAININGTIME = "electric#chargingRemainingTime";
public static final String CHANNEL_ELECTRIC_CHARGING_NEXTDELAYEDTIME = "electric#chargingNextDelayedTime";
public enum VendorConstants {
PEUGEOT("https://idpcvs.peugeot.com/am/oauth2/access_token", "clientsB2CPeugeot"),
CITROEN("https://idpcvs.citroen.com/am/oauth2/access_token", "clientsB2CCitroen"),
DS("https://idpcvs.driveds.com/am/oauth2/access_token", "clientsB2CDS"),
OPEL("https://idpcvs.opel.com/am/oauth2/access_token", "clientsB2COpel"),
VAUXHALL("https://idpcvs.vauxhall.co.uk/am/oauth2/access_token", "clientsB2CVauxhall");
public final String url;
public final String realm;
public final String scope;
VendorConstants(String url, String realm) {
this.url = url;
this.realm = realm;
this.scope = "profile openid";
}
}
public static final String API_URL = "https://api.groupe-psa.com/connectedcar/v4";
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
import org.openhab.binding.groupepsa.internal.things.GroupePSAHandler;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link GroupePSAHandlerFactory} is responsible for creating things and
* thing handlers.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.groupepsa", service = ThingHandlerFactory.class)
public class GroupePSAHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_VEHICLE);
private final OAuthFactory oAuthFactory;
protected final HttpClient httpClient;
@Activate
public GroupePSAHandlerFactory(@Reference OAuthFactory oAuthFactory,
@Reference HttpClientFactory httpClientFactory) {
this.oAuthFactory = oAuthFactory;
this.httpClient = httpClientFactory.getCommonHttpClient();
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_VEHICLE.equals(thingTypeUID)) {
return new GroupePSAHandler(thing);
} else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) {
return new GroupePSABridgeHandler((Bridge) thing, oAuthFactory, httpClient);
}
return null;
}
}

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.bridge;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link GroupePSABridgeConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public final class GroupePSABridgeConfiguration {
private String vendor = "";
private String userName = "";
private String password = "";
private String clientId = "";
private String clientSecret = "";
private Integer pollingInterval = 5;
/**
* @return The polling interval for the groupepsa state in s
*/
public Integer getPollingInterval() {
return pollingInterval;
}
public void setPollingInterval(Integer pollingInterval) {
this.pollingInterval = pollingInterval;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
}

View File

@ -0,0 +1,225 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.bridge;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.THING_TYPE_BRIDGE;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.VendorConstants;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.groupepsa.internal.discovery.GroupePSADiscoveryService;
import org.openhab.binding.groupepsa.internal.rest.api.GroupePSAConnectApi;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Vehicle;
import org.openhab.binding.groupepsa.internal.rest.api.dto.VehicleStatus;
import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
/**
* The {@link GroupePSABridgeHandler} is responsible for handling commands,
* which are sent to one of the channels.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSABridgeHandler extends BaseBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
private static final long DEFAULT_POLLING_INTERVAL_M = TimeUnit.HOURS.toMinutes(1);
private final OAuthFactory oAuthFactory;
private @Nullable OAuthClientService oAuthService;
private @Nullable ScheduledFuture<?> groupepsaBridgePollingJob;
private final HttpClient httpClient;
private String vendor = "";
private @Nullable VendorConstants vendorConstants;
private String userName = "";
private String password = "";
private String clientId = "";
private String clientSecret = "";
private @Nullable GroupePSAConnectApi groupePSAApi;
public GroupePSABridgeHandler(Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient) {
super(bridge);
this.oAuthFactory = oAuthFactory;
this.httpClient = httpClient;
}
private void pollGroupePSAs() {
try {
List<Vehicle> vehicles = getVehicles();
if (vehicles != null) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/comm-error-query-vehicles-failed");
}
} catch (GroupePSACommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void dispose() {
stopGroupePSABridgePolling();
oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
}
@Override
public void initialize() {
GroupePSABridgeConfiguration bridgeConfiguration = getConfigAs(GroupePSABridgeConfiguration.class);
vendor = bridgeConfiguration.getVendor();
userName = bridgeConfiguration.getUserName();
password = bridgeConfiguration.getPassword();
clientId = bridgeConfiguration.getClientId();
clientSecret = bridgeConfiguration.getClientSecret();
final Integer pollingIntervalM = bridgeConfiguration.getPollingInterval();
if (vendor.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-vendor");
} else if (userName.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
} else if (password.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-password");
} else if (clientId.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-clientid");
} else if (clientSecret.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/conf-error-no-clientsecret");
} else if (pollingIntervalM < 1) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/conf-error-invalid-polling-interval");
} else {
VendorConstants localVendorConstants = VendorConstants.valueOf(vendor);
vendorConstants = localVendorConstants;
oAuthService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), localVendorConstants.url,
null, clientId, clientSecret, localVendorConstants.scope, true);
groupePSAApi = new GroupePSAConnectApi(httpClient, this, clientId, localVendorConstants.realm);
startGroupePSABridgePolling(pollingIntervalM);
updateStatus(ThingStatus.UNKNOWN);
}
}
private void startGroupePSABridgePolling(@Nullable Integer pollingIntervalM) {
if (groupepsaBridgePollingJob == null) {
final long pollingIntervalToUse = pollingIntervalM == null ? DEFAULT_POLLING_INTERVAL_M : pollingIntervalM;
groupepsaBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollGroupePSAs(), 1,
TimeUnit.MINUTES.toSeconds(pollingIntervalToUse), TimeUnit.SECONDS);
}
}
private void stopGroupePSABridgePolling() {
final ScheduledFuture<?> job = groupepsaBridgePollingJob;
if (job != null) {
job.cancel(true);
groupepsaBridgePollingJob = null;
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
static Throwable getRootCause(Throwable e) {
Throwable nextE;
do {
nextE = e.getCause();
if (nextE != null) {
e = nextE;
}
} while (nextE != null);
return e;
}
public String authenticate() throws GroupePSACommunicationException {
OAuthClientService localOAuthService = oAuthService;
VendorConstants localVendorConstants = vendorConstants;
if (localOAuthService == null) {
throw new GroupePSACommunicationException("OAuth service is unexpectedly null");
}
if (localVendorConstants == null) {
throw new GroupePSACommunicationException("Vendor constants are unexpectedly null");
}
try {
AccessTokenResponse result = localOAuthService.getAccessTokenResponse();
if (result == null) {
result = localOAuthService.getAccessTokenByResourceOwnerPasswordCredentials(userName, password,
localVendorConstants.scope);
}
return result.getAccessToken();
} catch (OAuthException | IOException | OAuthResponseException e) {
throw new GroupePSACommunicationException("Unable to authenticate: " + getRootCause(e).getMessage(), e);
}
}
public GroupePSAConnectApi getAPI() throws GroupePSACommunicationException {
GroupePSAConnectApi localGroupePSAApi = groupePSAApi;
if (localGroupePSAApi == null) {
throw new GroupePSACommunicationException("groupePSAApi is unexpectedly null");
} else {
return localGroupePSAApi;
}
}
/**
* @return A list of vehicles
* @throws GroupePSACommunicationException In case the query cannot be executed
* successfully
*/
public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
return getAPI().getVehicles();
}
/**
* @param id The id of the mower to query
* @return A detailed status of the mower with the specified id
* @throws GroupePSACommunicationException In case the query cannot be executed
* successfully
*/
public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
return getAPI().getVehicleStatus(vin);
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(GroupePSADiscoveryService.class);
}
}

View File

@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.discovery;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.THING_TYPE_VEHICLE;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.groupepsa.internal.GroupePSABindingConstants;
import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Vehicle;
import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GroupePSADiscoveryService} is responsible for discovering new
* vehicles available for the configured app key.
*
* @author Arjan Mels - Initial contribution
*/
@Component(service = ThingHandlerService.class)
@NonNullByDefault
public class GroupePSADiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(GroupePSADiscoveryService.class);
private @Nullable GroupePSABridgeHandler bridgeHandler;
public GroupePSADiscoveryService() {
super(Collections.singleton(THING_TYPE_VEHICLE), 10, false);
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof GroupePSABridgeHandler) {
bridgeHandler = (GroupePSABridgeHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void startScan() {
try {
GroupePSABridgeHandler localBridgeHandler = bridgeHandler;
if (localBridgeHandler == null) {
return;
}
List<Vehicle> vehicles = localBridgeHandler.getVehicles();
if (vehicles == null || vehicles.isEmpty()) {
logger.warn("No vehicles found");
return;
}
for (Vehicle vehicle : vehicles) {
ThingUID bridgeUID = localBridgeHandler.getThing().getUID();
ThingTypeUID thingTypeUID = THING_TYPE_VEHICLE;
String id = vehicle.getId();
if (id != null) {
ThingUID vehicleThingUid = new ThingUID(THING_TYPE_VEHICLE, bridgeUID, id);
Map<String, Object> properties = new HashMap<>();
putProperty(properties, GroupePSABindingConstants.VEHICLE_ID, id);
putProperty(properties, GroupePSABindingConstants.VEHICLE_VIN, vehicle.getVin());
putProperty(properties, GroupePSABindingConstants.VEHICLE_VENDOR, vehicle.getBrand());
putProperty(properties, GroupePSABindingConstants.VEHICLE_MODEL, vehicle.getLabel());
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(vehicleThingUid)
.withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID)
.withRepresentationProperty(GroupePSABindingConstants.VEHICLE_VIN)
.withLabel(vehicle.getBrand() + " (" + vehicle.getVin() + ")").build();
thingDiscovered(discoveryResult);
}
}
} catch (GroupePSACommunicationException e) {
logger.warn("No vehicles found", e);
return;
}
}
private void putProperty(Map<String, Object> properties, String key, @Nullable String value) {
if (value == null) {
value = "Unknown";
}
properties.put(key, value);
}
}

View File

@ -0,0 +1,200 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.API_URL;
import java.lang.reflect.Type;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
import org.openhab.binding.groupepsa.internal.rest.api.dto.ErrorObject;
import org.openhab.binding.groupepsa.internal.rest.api.dto.User;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Vehicle;
import org.openhab.binding.groupepsa.internal.rest.api.dto.VehicleStatus;
import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
import org.openhab.binding.groupepsa.internal.rest.exceptions.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.filosganga.geogson.gson.GeometryAdapterFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
/**
* Allows access to the GroupePSAConnectApi
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSAConnectApi {
private final Logger logger = LoggerFactory.getLogger(GroupePSAConnectApi.class);
private final HttpClient httpClient;
private final GroupePSABridgeHandler bridge;
private final String clientId;
private final String realm;
protected final Gson gson;
public GroupePSAConnectApi(HttpClient httpClient, GroupePSABridgeHandler bridge, String clientId, String realm) {
this.httpClient = httpClient;
this.bridge = bridge;
this.clientId = clientId;
this.realm = realm;
gson = new GsonBuilder().registerTypeAdapterFactory(new GeometryAdapterFactory())
.registerTypeAdapter(ZonedDateTime.class, new JsonDeserializer<ZonedDateTime>() {
@Override
public @Nullable ZonedDateTime deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString());
}
}).registerTypeAdapter(Duration.class, new JsonDeserializer<Duration>() {
@Override
public @Nullable Duration deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
return Duration.parse(json.getAsJsonPrimitive().getAsString());
}
}).create();
}
protected HttpClient getHttpClient() {
return httpClient;
}
protected GroupePSABridgeHandler getBridge() {
return bridge;
}
public String getBaseUrl() {
return API_URL;
}
private ContentResponse executeRequest(final String uri) throws GroupePSACommunicationException {
return executeRequest(uri, "application/hal+json");
}
static Throwable getRootCause(Throwable e) {
Throwable nextE;
do {
nextE = e.getCause();
if (nextE != null) {
e = nextE;
}
} while (nextE != null);
return e;
}
public ContentResponse executeRequest(final String uri, final String accept)
throws GroupePSACommunicationException {
Request request = getHttpClient().newRequest(uri);
String token = getBridge().authenticate();
request.timeout(10, TimeUnit.SECONDS);
request.param("client_id", this.clientId);
request.header("Authorization", "Bearer " + token);
request.header("Accept", accept);
request.header("x-introspect-realm", this.realm);
request.method(HttpMethod.GET);
logger.trace("HttpRequest {}", request.getURI());
logger.trace("HttpRequest Headers:\n{}", request.getHeaders());
try {
ContentResponse response = request.send();
logger.trace("HttpResponse {}", response);
logger.trace("HttpResponse Headers:\n{}", response.getHeaders());
logger.trace("HttpResponse Content: {}", response.getContentAsString());
return response;
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new GroupePSACommunicationException("Unable to perform Http Request: " + getRootCause(e).getMessage(),
e);
}
}
private void checkForError(ContentResponse response, int statusCode) throws GroupePSACommunicationException {
if (statusCode >= 200 && statusCode < 300) {
return;
}
switch (statusCode) {
case HttpStatus.NOT_FOUND_404:
ErrorObject error = gson.fromJson(response.getContentAsString(), ErrorObject.class);
String message = (error != null) ? error.getMessage() : null;
if (message == null) {
message = "Unknown";
}
throw new GroupePSACommunicationException(statusCode, message);
case HttpStatus.FORBIDDEN_403:
case HttpStatus.UNAUTHORIZED_401:
throw new UnauthorizedException(statusCode, response.getContentAsString());
default:
throw new GroupePSACommunicationException(statusCode, response.getContentAsString());
}
}
private <T> @Nullable T parseResponse(ContentResponse response, Class<T> type)
throws GroupePSACommunicationException {
int statusCode = response.getStatus();
checkForError(response, statusCode);
try {
return gson.fromJson(response.getContentAsString(), type);
} catch (JsonSyntaxException e) {
throw new GroupePSACommunicationException("Error in received JSON: " + getRootCause(e).getMessage(), e);
}
}
public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
ContentResponse response = executeRequest(getBaseUrl() + "/user");
User user = parseResponse(response, User.class);
if (user != null) {
return user.getVehicles();
} else {
return null;
}
}
public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
ContentResponse responseOdometer = executeRequest(getBaseUrl() + "/user/vehicles/" + vin + "/status");
VehicleStatus status = parseResponse(responseOdometer, VehicleStatus.class);
return status;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Air {
private @Nullable BigDecimal temp;
public @Nullable BigDecimal getTemp() {
return temp;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("temp", temp).toString();
}
}

View File

@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class AirConditioning {
private @Nullable String failureCause;
private @Nullable List<Program> programs = null;
private @Nullable String status;
private @Nullable ZonedDateTime updatedAt;
public @Nullable String getFailureCause() {
return failureCause;
}
public @Nullable List<Program> getPrograms() {
return programs;
}
public @Nullable String getStatus() {
return status;
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("failureCause", failureCause).append("programs", programs)
.append("status", status).append("updatedAt", updatedAt).toString();
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Battery {
private @Nullable ZonedDateTime createdAt;
private @Nullable BigDecimal current;
private @Nullable BigDecimal voltage;
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable BigDecimal getCurrent() {
return current;
}
public @Nullable BigDecimal getVoltage() {
return voltage;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("current", current)
.append("voltage", voltage).toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class BatteryStatus {
private @Nullable BigDecimal capacity;
private @Nullable Health health;
public @Nullable BigDecimal getCapacity() {
return capacity;
}
public @Nullable Health getHealth() {
return health;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("capacity", capacity).append("health", health).toString();
}
}

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import java.time.Duration;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Charging {
private @Nullable String chargingMode;
private @Nullable BigDecimal chargingRate;
private @Nullable Duration nextDelayedTime;
private @Nullable Boolean plugged;
private @Nullable Duration remainingTime;
private @Nullable String status;
public @Nullable String getChargingMode() {
return chargingMode;
}
public @Nullable BigDecimal getChargingRate() {
return chargingRate;
}
public void setChargingRate(BigDecimal chargingRate) {
this.chargingRate = chargingRate;
}
public @Nullable Duration getNextDelayedTime() {
return nextDelayedTime;
}
public @Nullable Boolean isPlugged() {
return plugged;
}
public @Nullable Duration getRemainingTime() {
return remainingTime;
}
public @Nullable String getStatus() {
return status;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("chargingMode", chargingMode).append("chargingRate", chargingRate)
.append("nextDelayedTime", nextDelayedTime).append("plugged", plugged)
.append("remainingTime", remainingTime).append("status", status).toString();
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class DoorsState {
private @Nullable List<String> lockedState = null;
private @Nullable List<Opening> opening = null;
private @Nullable ZonedDateTime updatedAt;
public @Nullable List<String> getLockedState() {
return lockedState;
}
public @Nullable List<Opening> getOpening() {
return opening;
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("lockedState", lockedState).append("opening", opening)
.append("updatedAt", updatedAt).toString();
}
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Energy {
private @Nullable ZonedDateTime updatedAt;
private @Nullable ZonedDateTime createdAt;
private @Nullable BigDecimal autonomy;
private @Nullable BatteryStatus battery;
private @Nullable Charging charging;
private @Nullable BigDecimal consumption;
private @Nullable BigDecimal level;
private @Nullable BigDecimal residual;
private @Nullable String type;
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable BigDecimal getAutonomy() {
return autonomy;
}
public @Nullable BatteryStatus getBattery() {
return battery;
}
public @Nullable Charging getCharging() {
return charging;
}
public @Nullable BigDecimal getConsumption() {
return consumption;
}
public @Nullable BigDecimal getLevel() {
return level;
}
public @Nullable BigDecimal getResidual() {
return residual;
}
public @Nullable String getType() {
return type;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("updatedAt", updatedAt).append("createdAt", createdAt)
.append("autonomy", autonomy).append("battery", battery).append("charging", charging)
.append("consumption", consumption).append("level", level).append("residual", residual)
.append("type", type).toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Engine {
@SerializedName("class")
private @Nullable String type;
private @Nullable String energy;
public @Nullable String getType() {
return type;
}
public @Nullable String getEnergy() {
return energy;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("type", type).append("energy", energy).toString();
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Environment {
private @Nullable ZonedDateTime updatedAt;
private @Nullable ZonedDateTime createdAt;
private @Nullable Air air;
private @Nullable Luminosity luminosity;
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable Air getAir() {
return air;
}
public @Nullable Luminosity getLuminosity() {
return luminosity;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("updatedAt", updatedAt).append("createdAt", createdAt)
.append("air", air).append("luminosity", luminosity).toString();
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class ErrorObject {
private @Nullable String uuid;
private @Nullable String code;
private @Nullable String message;
public @Nullable String getUuid() {
return uuid;
}
public @Nullable String getMessage() {
return message;
}
public @Nullable String getCode() {
return code;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("uuid", uuid).append("code", code).append("message", message)
.toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Health {
private @Nullable BigDecimal capacity;
private @Nullable BigDecimal resistance;
public @Nullable BigDecimal getCapacity() {
return capacity;
}
public @Nullable BigDecimal getResistance() {
return resistance;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("capacity", capacity).append("resistance", resistance).toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Ignition {
private @Nullable ZonedDateTime updatedAt;
private @Nullable String type;
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
public @Nullable String getType() {
return type;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("updatedAt", updatedAt).append("type", type).toString();
}
}

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Kinetic {
private @Nullable ZonedDateTime createdAt;
private @Nullable BigDecimal acceleration;
private @Nullable Boolean moving;
private @Nullable BigDecimal pace;
private @Nullable BigDecimal speed;
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable BigDecimal getAcceleration() {
return acceleration;
}
public @Nullable Boolean isMoving() {
return moving;
}
public @Nullable BigDecimal getPace() {
return pace;
}
public @Nullable BigDecimal getSpeed() {
return speed;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("acceleration", acceleration)
.append("moving", moving).append("pace", pace).append("speed", speed).toString();
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Luminosity {
private @Nullable Boolean day;
public @Nullable Boolean isDay() {
return day;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("day", day).toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Occurence {
private @Nullable List<String> day = null;
private @Nullable List<String> week = null;
public @Nullable List<String> getDay() {
return day;
}
public @Nullable List<String> getWeek() {
return week;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("day", day).append("week", week).toString();
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Odometer {
private @Nullable ZonedDateTime createdAt;
private @Nullable BigDecimal mileage;
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable BigDecimal getMileage() {
return mileage;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("mileage", mileage).toString();
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Opening {
private @Nullable String identifier;
private @Nullable String state;
public @Nullable String getIdentifier() {
return identifier;
}
public @Nullable String getState() {
return state;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("identifier", identifier).append("state", state).toString();
}
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.github.filosganga.geogson.model.Geometry;
import com.github.filosganga.geogson.model.positions.SinglePosition;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Position {
private @Nullable ZonedDateTime updatedAt;
private @Nullable ZonedDateTime createdAt;
private @Nullable Geometry<SinglePosition> geometry;
private @Nullable Properties properties;
private @Nullable String type;
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable Geometry<SinglePosition> getGeometry() {
return geometry;
}
public @Nullable Properties getProperties() {
return properties;
}
public @Nullable String getType() {
return type;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("updatedAt", updatedAt).append("createdAt", createdAt)
.append("geometry", geometry).append("properties", properties).append("type", type).toString();
}
}

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Preconditionning {
private @Nullable AirConditioning airConditioning;
public @Nullable AirConditioning getAirConditioning() {
return airConditioning;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("airConditioning", airConditioning).toString();
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Privacy {
private @Nullable ZonedDateTime createdAt;
private @Nullable String state;
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable String getState() {
return state;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("state", state).toString();
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Program {
private @Nullable Occurence occurence;
private @Nullable String recurrence;
private @Nullable String start;
private @Nullable Boolean enabled;
private @Nullable BigDecimal slot;
public @Nullable Occurence getOccurence() {
return occurence;
}
public @Nullable String getRecurrence() {
return recurrence;
}
public @Nullable String getStart() {
return start;
}
public @Nullable Boolean isEnabled() {
return enabled;
}
public @Nullable BigDecimal getSlot() {
return slot;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("occurence", occurence).append("recurrence", recurrence)
.append("start", start).append("enabled", enabled).append("slot", slot).toString();
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Properties {
private @Nullable BigDecimal heading;
private @Nullable BigDecimal signalQuality;
private @Nullable String type;
public @Nullable BigDecimal getHeading() {
return heading;
}
public @Nullable BigDecimal getSignalQuality() {
return signalQuality;
}
public @Nullable String getType() {
return type;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("heading", heading).append("signalQuality", signalQuality)
.append("type", type).toString();
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Safety {
private @Nullable ZonedDateTime createdAt;
private @Nullable String beltWarning;
private @Nullable String eCallTriggeringRequest;
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable String getBeltWarning() {
return beltWarning;
}
public @Nullable String getECallTriggeringRequest() {
return eCallTriggeringRequest;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("beltWarning", beltWarning)
.append("eCallTriggeringRequest", eCallTriggeringRequest).toString();
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Service {
private @Nullable String type;
private @Nullable ZonedDateTime updatedAt;
public @Nullable String getType() {
return type;
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("type", type).append("updatedAt", updatedAt).toString();
}
}

View File

@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
class ToStringBuilder implements Appendable, CharSequence {
protected StringBuilder stringBuilder = new StringBuilder();
ToStringBuilder(Object obj) {
}
@Override
public int length() {
return stringBuilder.length();
}
@Override
public char charAt(int index) {
return stringBuilder.charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return stringBuilder.subSequence(start, end);
}
@Override
public ToStringBuilder append(@Nullable CharSequence csq) throws IOException {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(csq);
return this;
}
@Override
public ToStringBuilder append(@Nullable CharSequence csq, int start, int end) throws IOException {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(csq, start, end);
return this;
}
@Override
public ToStringBuilder append(char c) throws IOException {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(c);
return this;
}
public ToStringBuilder append(Object key, @Nullable Object value) {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(key.toString() + ": " + ((value == null) ? "(null)" : value.toString()));
return this;
}
@Override
public String toString() {
return "{ " + stringBuilder.toString() + " }";
}
}

View File

@ -0,0 +1,80 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class User {
private @Nullable String email;
private @Nullable String firstName;
private @Nullable String lastName;
@SerializedName("_embedded")
private @Nullable Embedded embedded;
private @Nullable ZonedDateTime createdAt;
private @Nullable ZonedDateTime updatedAt;
private static class Embedded {
private @Nullable List<Vehicle> vehicles;
@Override
public String toString() {
return new ToStringBuilder(this).append("vehicles", vehicles).toString();
}
}
public @Nullable String getEmail() {
return email;
}
public @Nullable String getLastName() {
return lastName;
}
public @Nullable String getFirstName() {
return firstName;
}
public @Nullable List<Vehicle> getVehicles() {
final Embedded resEmbedded = embedded;
if (resEmbedded != null) {
return resEmbedded.vehicles;
} else {
return null;
}
}
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("updatedAt", createdAt)
.append("email", email).append("firstName", firstName).append("lastName", lastName)
.append("vehicles", embedded != null ? embedded : null).toString();
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class Vehicle {
private @Nullable String id;
private @Nullable String vin;
private @Nullable String brand;
private @Nullable String label;
@SerializedName("engine")
private @Nullable List<Engine> engines;
private @Nullable ZonedDateTime createdAt;
private @Nullable ZonedDateTime updatedAt;
public @Nullable String getId() {
return id;
}
public @Nullable String getVin() {
return vin;
}
public @Nullable String getBrand() {
return brand;
}
public @Nullable String getLabel() {
return label;
}
public @Nullable List<Engine> getEngines() {
return engines;
}
public @Nullable ZonedDateTime getCreatedAt() {
return createdAt;
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("createdAt", createdAt).append("updatedAt", createdAt).append("id", id)
.append("vin", vin).append("brand", brand).append("label", label).append("engines", engines).toString();
}
}

View File

@ -0,0 +1,148 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.api.dto;
import java.time.ZonedDateTime;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class VehicleStatus {
private @Nullable ZonedDateTime updatedAt;
@SerializedName("_embedded")
private @Nullable Embedded embedded;
private @Nullable Battery battery;
private @Nullable DoorsState doorsState;
private @Nullable List<Energy> energy = null;
private @Nullable Environment environment;
private @Nullable Ignition ignition;
private @Nullable Kinetic kinetic;
@SerializedName("timed.odometer")
private @Nullable Odometer odometer;
private @Nullable Position lastPosition;
private @Nullable Preconditionning preconditionning;
private @Nullable Privacy privacy;
private @Nullable Safety safety;
private @Nullable Service service;
private static class Embedded {
private @Nullable Extension extension;
@Override
public String toString() {
return new ToStringBuilder(this).append("extension", extension).toString();
}
}
private static class Extension {
private @Nullable Kinetic kinetic;
private @Nullable Odometer odometer;
@Override
public String toString() {
return new ToStringBuilder(this).append("kinetic", kinetic).append("odometer", odometer).toString();
}
}
public @Nullable Kinetic getKinetic() {
if (kinetic != null) {
return kinetic;
} else {
final Embedded finalEmbedded = embedded;
if (finalEmbedded != null) {
final Extension finalExtension = finalEmbedded.extension;
if (finalExtension != null) {
return finalExtension.kinetic;
}
}
return null;
}
}
public @Nullable Odometer getOdometer() {
if (odometer != null) {
return odometer;
} else {
Embedded finalEmbedded = embedded;
if (finalEmbedded != null) {
final Extension finalExtension = finalEmbedded.extension;
if (finalExtension != null) {
return finalExtension.odometer;
}
}
return null;
}
}
public @Nullable ZonedDateTime getUpdatedAt() {
return updatedAt;
}
public @Nullable Battery getBattery() {
return battery;
}
public @Nullable DoorsState getDoorsState() {
return doorsState;
}
public @Nullable List<Energy> getEnergy() {
return energy;
}
public @Nullable Environment getEnvironment() {
return environment;
}
public @Nullable Ignition getIgnition() {
return ignition;
}
public @Nullable Position getLastPosition() {
return lastPosition;
}
public @Nullable Preconditionning getPreconditionning() {
return preconditionning;
}
public @Nullable Privacy getPrivacy() {
return privacy;
}
public @Nullable Safety getSafety() {
return safety;
}
public @Nullable Service getService() {
return service;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("updatedAt", updatedAt).append("_embedded", embedded)
.append("battery", battery).append("doorsState", doorsState).append("energy", energy)
.append("environment", environment).append("ignition", ignition).append("kinetic", kinetic)
.append("odometer", odometer).append("lastPosition", lastPosition)
.append("preconditionning", preconditionning).append("privacy", privacy).append("safety", safety)
.append("service", service).toString();
}
}

View File

@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.exceptions;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* An exception that occurred while communicating with an groupepsa or an groupepsa bridge
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSACommunicationException extends IOException {
private static final long serialVersionUID = 1L;
private int statusCode = -1;
public GroupePSACommunicationException(Exception e) {
super(e);
}
public GroupePSACommunicationException(int statusCode, Exception e) {
super(e);
this.statusCode = statusCode;
}
public GroupePSACommunicationException(int statusCode) {
this.statusCode = statusCode;
}
public GroupePSACommunicationException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
public GroupePSACommunicationException(String message, Exception e) {
super(message, e);
}
public GroupePSACommunicationException(String message) {
super(message);
}
public int getStatusCode() {
return statusCode;
}
@Override
public @Nullable String getMessage() {
String message = super.getMessage();
return message == null ? null : "Rest call failed: statusCode=" + statusCode + ", message=" + message;
}
@Override
public String toString() {
return getClass().getSimpleName() + ": statusCode=" + statusCode + ", message=" + super.getMessage()
+ ", cause: " + getCause();
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class UnauthorizedException extends GroupePSACommunicationException {
private static final long serialVersionUID = 1L;
public UnauthorizedException(int statusCode, Exception e) {
super(statusCode, e);
}
public UnauthorizedException(int statusCode) {
super(statusCode);
}
public UnauthorizedException(int statusCode, String message) {
super(statusCode, message);
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.rest.exceptions;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class UnavailableException extends GroupePSACommunicationException {
private static final long serialVersionUID = 1L;
public UnavailableException(int statusCode, Exception e) {
super(statusCode, e);
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.things;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link GroupePSAConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSAConfiguration {
private @Nullable String id;
private @Nullable Integer pollingInterval;
private @Nullable Integer onlineInterval;
@Nullable
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public @Nullable Integer getPollingInterval() {
return pollingInterval;
}
public void setPollingInterval(Integer pollingInterval) {
this.pollingInterval = pollingInterval;
}
public @Nullable Integer getOnlineInterval() {
return onlineInterval;
}
public void setOnlineInterval(Integer onlineInterval) {
this.onlineInterval = onlineInterval;
}
}

View File

@ -0,0 +1,479 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.things;
import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.*;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.measure.Quantity;
import javax.measure.Unit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Air;
import org.openhab.binding.groupepsa.internal.rest.api.dto.AirConditioning;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Battery;
import org.openhab.binding.groupepsa.internal.rest.api.dto.BatteryStatus;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Charging;
import org.openhab.binding.groupepsa.internal.rest.api.dto.DoorsState;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Energy;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Environment;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Health;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Ignition;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Kinetic;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Luminosity;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Odometer;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Opening;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Position;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Preconditionning;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Privacy;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Properties;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Safety;
import org.openhab.binding.groupepsa.internal.rest.api.dto.Service;
import org.openhab.binding.groupepsa.internal.rest.api.dto.VehicleStatus;
import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OpenClosedType;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
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.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.filosganga.geogson.model.Geometry;
import com.github.filosganga.geogson.model.positions.SinglePosition;
/**
* The {@link GroupePSAHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSAHandler extends BaseThingHandler {
private static final long DEFAULT_POLLING_INTERVAL_M = TimeUnit.MINUTES.toMinutes(1);
private static final long DEFAULT_ONLINE_INTERVAL_M = TimeUnit.MINUTES.toMinutes(60);
private final Logger logger = LoggerFactory.getLogger(GroupePSAHandler.class);
private @Nullable String id = null;
private long lastQueryTimeNs = 0L;
private @Nullable ScheduledFuture<?> groupepsaPollingJob;
private long maxQueryFrequencyNanos = TimeUnit.MINUTES.toNanos(1);
private long onlineIntervalM;
public GroupePSAHandler(Thing thing) {
super(thing);
}
@Override
protected @Nullable Bridge getBridge() {
return super.getBridge();
}
private void pollStatus() {
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
updateGroupePSAState();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
};
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refreshChannels(channelUID);
}
}
private void refreshChannels(ChannelUID channelUID) {
updateGroupePSAState();
}
@Override
public void initialize() {
if (getBridgeHandler() != null) {
GroupePSAConfiguration currentConfig = getConfigAs(GroupePSAConfiguration.class);
final String id = currentConfig.getId();
final Integer pollingIntervalM = currentConfig.getPollingInterval();
final Integer onlineIntervalM = currentConfig.getOnlineInterval();
if (id == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/conf-error-no-vehicle-id");
} else if (pollingIntervalM != null && pollingIntervalM < 1) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/conf-error-invalid-polling-interval");
} else if (onlineIntervalM != null && onlineIntervalM < 1) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/conf-error-invalid-online-interval");
} else {
this.id = id;
this.onlineIntervalM = onlineIntervalM != null ? onlineIntervalM : DEFAULT_ONLINE_INTERVAL_M;
startGroupePSAPolling(pollingIntervalM);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
}
}
@Nullable
public GroupePSABridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
ThingHandler handler = bridge.getHandler();
if (handler instanceof GroupePSABridgeHandler) {
return (GroupePSABridgeHandler) handler;
}
}
return null;
}
@Override
public void dispose() {
stopGroupePSAPolling();
id = null;
}
private void startGroupePSAPolling(@Nullable Integer pollingIntervalM) {
if (groupepsaPollingJob == null) {
final long pollingIntervalToUse = pollingIntervalM == null ? DEFAULT_POLLING_INTERVAL_M : pollingIntervalM;
groupepsaPollingJob = scheduler.scheduleWithFixedDelay(() -> pollStatus(), 1, pollingIntervalToUse * 60,
TimeUnit.SECONDS);
}
}
private void stopGroupePSAPolling() {
ScheduledFuture<?> job = groupepsaPollingJob;
if (job != null) {
job.cancel(true);
groupepsaPollingJob = null;
}
}
private boolean isValidResult(VehicleStatus vehicle) {
return vehicle.getUpdatedAt() != null;
}
private boolean isConnected(VehicleStatus vehicle) {
ZonedDateTime updatedAt = vehicle.getUpdatedAt();
if (updatedAt == null) {
return false;
}
return updatedAt.isAfter(ZonedDateTime.now().minusMinutes(onlineIntervalM));
}
private synchronized void updateGroupePSAState() {
if (System.nanoTime() - lastQueryTimeNs <= maxQueryFrequencyNanos) {
return;
}
lastQueryTimeNs = System.nanoTime();
String id = this.id;
if (id == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/conf-error-no-vehicle-id");
return;
}
GroupePSABridgeHandler groupepsaBridge = getBridgeHandler();
if (groupepsaBridge == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/conf-error-no-bridge");
return;
}
try {
VehicleStatus vehicle = groupepsaBridge.getVehicleStatus(id);
if (vehicle != null && isValidResult(vehicle)) {
logger.trace("Vehicle: {}", vehicle.toString());
logger.debug("Update vehicle state now: {}, lastupdate: {}", ZonedDateTime.now(),
vehicle.getUpdatedAt());
updateChannelState(vehicle);
if (isConnected(vehicle)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"@text/comm-error-vehicle-not-connected-to-cloud");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/comm-error-query-vehicle-failed");
}
} catch (GroupePSACommunicationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
MessageFormat.format("@text/comm-error-query-vehicle-failed", e.getMessage()));
}
}
private void updateChannelState(VehicleStatus vehicle) {
final DoorsState doorsState = vehicle.getDoorsState();
if (doorsState != null) {
buildDoorChannels(doorsState);
List<Opening> openings = doorsState.getOpening();
if (openings != null) {
for (Opening opening : openings) {
String id = opening.getIdentifier();
if (id != null) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), CHANNEL_GROUP_DOORS,
id.toLowerCase());
updateState(channelUID, "open".equalsIgnoreCase(opening.getState()) ? OpenClosedType.OPEN
: OpenClosedType.CLOSED);
}
}
}
List<String> lockedState = doorsState.getLockedState();
updateState(CHANNEL_DOORS_LOCK, lockedState, x -> x.get(0));
} else {
updateState(CHANNEL_DOORS_LOCK, UnDefType.UNDEF);
}
updateState(CHANNEL_BATTERY_CURRENT, vehicle.getBattery(), Battery::getCurrent, Units.AMPERE);
updateState(CHANNEL_BATTERY_VOLTAGE, vehicle.getBattery(), Battery::getVoltage, Units.VOLT);
updateState(CHANNEL_ENVIRONMENT_TEMPERATURE, vehicle.getEnvironment(), Environment::getAir, Air::getTemp,
SIUnits.CELSIUS);
updateStateBoolean(CHANNEL_ENVIRONMENT_DAYTIME, vehicle.getEnvironment(), Environment::getLuminosity,
Luminosity::isDay);
updateState(CHANNEL_MOTION_IGNITION, vehicle.getIgnition(), Ignition::getType);
updateStateBoolean(CHANNEL_MOTION_MOVING, vehicle.getKinetic(), Kinetic::isMoving);
updateState(CHANNEL_MOTION_ACCELERATION, vehicle.getKinetic(), Kinetic::getAcceleration,
Units.METRE_PER_SQUARE_SECOND);
updateState(CHANNEL_MOTION_SPEED, vehicle.getKinetic(), Kinetic::getSpeed, SIUnits.KILOMETRE_PER_HOUR);
updateState(CHANNEL_MOTION_MILEAGE, vehicle.getOdometer(), Odometer::getMileage,
MetricPrefix.KILO(SIUnits.METRE));
Position lastPosition = vehicle.getLastPosition();
if (lastPosition != null) {
Geometry<SinglePosition> geometry = lastPosition.getGeometry();
if (geometry != null) {
SinglePosition position = (SinglePosition) geometry.positions();
if (Double.isFinite(position.alt())) {
updateState(CHANNEL_POSITION_POSITION, new PointType(new DecimalType(position.lat()),
new DecimalType(position.lon()), new DecimalType(position.alt())));
} else {
updateState(CHANNEL_POSITION_POSITION,
new PointType(new DecimalType(position.lat()), new DecimalType(position.lon())));
}
} else {
updateState(CHANNEL_POSITION_POSITION, UnDefType.UNDEF);
}
updateState(CHANNEL_POSITION_HEADING, lastPosition.getProperties(), Properties::getHeading,
Units.DEGREE_ANGLE);
updateState(CHANNEL_POSITION_TYPE, lastPosition.getProperties(), Properties::getType);
updateState(CHANNEL_POSITION_SIGNALSTRENGTH, lastPosition.getProperties(), Properties::getSignalQuality,
Units.PERCENT);
}
updateState(CHANNEL_VARIOUS_LAST_UPDATED, vehicle.getUpdatedAt());
updateState(CHANNEL_VARIOUS_PRIVACY, vehicle.getPrivacy(), Privacy::getState);
updateState(CHANNEL_VARIOUS_BELT, vehicle.getSafety(), Safety::getBeltWarning);
updateState(CHANNEL_VARIOUS_EMERGENCY, vehicle.getSafety(), Safety::getECallTriggeringRequest);
updateState(CHANNEL_VARIOUS_SERVICE, vehicle.getService(), Service::getType);
updateState(CHANNEL_VARIOUS_PRECONDITINING, vehicle.getPreconditionning(), Preconditionning::getAirConditioning,
AirConditioning::getStatus);
updateState(CHANNEL_VARIOUS_PRECONDITINING_FAILURE, vehicle.getPreconditionning(),
Preconditionning::getAirConditioning, AirConditioning::getFailureCause);
List<Energy> energies = vehicle.getEnergy();
if (energies != null) {
for (Energy energy : energies) {
if ("Fuel".equalsIgnoreCase(energy.getType())) {
updateState(CHANNEL_FUEL_AUTONOMY, energy, Energy::getAutonomy, MetricPrefix.KILO(SIUnits.METRE));
updateState(CHANNEL_FUEL_CONSUMPTION, energy, Energy::getConsumption,
Units.LITRE.divide(MetricPrefix.KILO(SIUnits.METRE)));
updateState(CHANNEL_FUEL_LEVEL, energy, Energy::getLevel, Units.PERCENT);
} else if ("Electric".equalsIgnoreCase(energy.getType())) {
updateState(CHANNEL_ELECTRIC_AUTONOMY, energy, Energy::getAutonomy,
MetricPrefix.KILO(SIUnits.METRE));
updateState(CHANNEL_ELECTRIC_RESIDUAL, energy, Energy::getResidual, Units.KILOWATT_HOUR);
updateState(CHANNEL_ELECTRIC_LEVEL, energy, Energy::getLevel, Units.PERCENT);
updateState(CHANNEL_ELECTRIC_BATTERY_CAPACITY, energy, Energy::getBattery,
BatteryStatus::getCapacity, Units.KILOWATT_HOUR);
updateState(CHANNEL_ELECTRIC_BATTERY_HEALTH_CAPACITY, energy, Energy::getBattery,
BatteryStatus::getHealth, Health::getCapacity, Units.PERCENT);
updateState(CHANNEL_ELECTRIC_BATTERY_HEALTH_RESISTANCE, energy, Energy::getBattery,
BatteryStatus::getHealth, Health::getResistance, Units.PERCENT);
updateState(CHANNEL_ELECTRIC_CHARGING_STATUS, energy, Energy::getCharging, Charging::getStatus);
updateState(CHANNEL_ELECTRIC_CHARGING_MODE, energy, Energy::getCharging, Charging::getChargingMode);
updateStateBoolean(CHANNEL_ELECTRIC_CHARGING_PLUGGED, energy, Energy::getCharging,
Charging::isPlugged);
updateState(CHANNEL_ELECTRIC_CHARGING_RATE, energy, Energy::getCharging, Charging::getChargingRate,
SIUnits.KILOMETRE_PER_HOUR);
updateState(CHANNEL_ELECTRIC_CHARGING_REMAININGTIME, energy, Energy::getCharging,
Charging::getRemainingTime, x -> new BigDecimal(x.getSeconds()), Units.SECOND);
updateState(CHANNEL_ELECTRIC_CHARGING_NEXTDELAYEDTIME, energy, Energy::getCharging,
Charging::getNextDelayedTime, x -> new BigDecimal(x.getSeconds()), Units.SECOND);
}
}
}
}
void buildDoorChannels(final DoorsState doorsState) {
ThingHandlerCallback callback = getCallback();
if (callback == null) {
return;
}
ThingBuilder thingBuilder = editThing();
List<Channel> channels = getThing().getChannelsOfGroup(CHANNEL_GROUP_DOORS);
thingBuilder.withoutChannels(channels);
ChannelUID channelUID = new ChannelUID(getThing().getUID(), CHANNEL_DOORS_LOCK);
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_DOORLOCK);
thingBuilder.withChannel(callback.createChannelBuilder(channelUID, channelTypeUID).build());
List<Opening> openings = doorsState.getOpening();
if (openings != null) {
for (Opening opening : openings) {
String id = opening.getIdentifier();
if (id != null) {
channelUID = new ChannelUID(getThing().getUID(), CHANNEL_GROUP_DOORS, id.toLowerCase());
channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_DOOROPEN);
thingBuilder.withChannel(callback.createChannelBuilder(channelUID, channelTypeUID).build());
}
}
}
updateThing(thingBuilder.build());
}
// Various update helper functions
protected <T extends Quantity<T>> void updateState(String channelID, @Nullable BigDecimal number, Unit<T> unit) {
if (number != null) {
updateState(channelID, new QuantityType<T>(number, unit));
} else {
updateState(channelID, UnDefType.UNDEF);
}
}
protected <T1, T2 extends Quantity<T2>> void updateState(String channelID, final @Nullable T1 object,
Function<? super T1, @Nullable BigDecimal> mapper, Unit<T2> unit) {
updateState(channelID, object != null ? mapper.apply(object) : null, unit);
}
protected <T1, T2, T3 extends Quantity<T3>> void updateState(String channelID, final @Nullable T1 object1,
Function<? super T1, @Nullable T2> mapper1, Function<? super T2, @Nullable BigDecimal> mapper2,
Unit<T3> unit) {
final @Nullable T2 object2 = object1 != null ? mapper1.apply(object1) : null;
updateState(channelID, object2 != null ? mapper2.apply(object2) : null, unit);
}
protected <T1, T2, T3, T4 extends Quantity<T4>> void updateState(String channelID, final @Nullable T1 object1,
Function<? super T1, @Nullable T2> mapper1, Function<? super T2, @Nullable T3> mapper2,
Function<? super T3, @Nullable BigDecimal> mapper3, Unit<T4> unit) {
final @Nullable T2 object2 = object1 != null ? mapper1.apply(object1) : null;
final @Nullable T3 object3 = object2 != null ? mapper2.apply(object2) : null;
updateState(channelID, object3 != null ? mapper3.apply(object3) : null, unit);
}
protected void updateState(String channelID, @Nullable ZonedDateTime date) {
if (date != null) {
updateState(channelID, new DateTimeType(date));
} else {
updateState(channelID, UnDefType.UNDEF);
}
}
protected <T1> void updateStateDate(String channelID, @Nullable T1 object,
Function<? super T1, @Nullable ZonedDateTime> mapper) {
updateState(channelID, object != null ? mapper.apply(object) : null);
}
protected <T1, T2> void updateStateDate(String channelID, @Nullable T1 object1,
Function<? super T1, @Nullable T2> mapper1, Function<? super T2, @Nullable ZonedDateTime> mapper2) {
final @Nullable T2 object2 = object1 != null ? mapper1.apply(object1) : null;
updateState(channelID, object2 != null ? mapper2.apply(object2) : null);
}
protected void updateState(String channelID, @Nullable String text) {
if (text != null) {
updateState(channelID, new StringType(text));
} else {
updateState(channelID, UnDefType.UNDEF);
}
}
protected <T1> void updateState(String channelID, @Nullable T1 object,
Function<? super T1, @Nullable String> mapper) {
updateState(channelID, object != null ? mapper.apply(object) : null);
}
protected <T1, T2> void updateState(String channelID, @Nullable T1 object1,
Function<? super T1, @Nullable T2> mapper1, Function<? super T2, @Nullable String> mapper2) {
final @Nullable T2 object2 = object1 != null ? mapper1.apply(object1) : null;
updateState(channelID, object2 != null ? mapper2.apply(object2) : null);
}
protected void updateState(String channelID, @Nullable Boolean value) {
if (value != null) {
updateState(channelID, value ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
} else {
updateState(channelID, UnDefType.UNDEF);
}
}
protected <T1> void updateStateBoolean(String channelID, @Nullable T1 object,
Function<? super T1, @Nullable Boolean> mapper) {
updateState(channelID, object != null ? mapper.apply(object) : null);
}
protected <T1, T2> void updateStateBoolean(String channelID, final @Nullable T1 object1,
Function<? super T1, @Nullable T2> mapper1, Function<? super T2, @Nullable Boolean> mapper2) {
final @Nullable T2 object2 = object1 != null ? mapper1.apply(object1) : null;
updateState(channelID, object2 != null ? mapper2.apply(object2) : null);
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="groupepsa" 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>Groupe PSA Binding</name>
<description>Provides information and (limited) control of cars from the Groupe PSA (Peugeot, Citroen, Opel, etc.).</description>
</binding:binding>

View File

@ -0,0 +1,21 @@
# binding
binding.groupepsa.name = Groupe PSA Binding
binding.groupepsa.description = Provides information and (limited) control of cars from the Groupe PSA (Peugeot, Citroen, Opel, etc.).
#configuration errors
conf-error-no-vendor = Cannot connect to Groupe PSA bridge as no vendor is available in the configuration
conf-error-unknown-vendor = Cannot connect to Groupe PSA bridge as the vendor is not valid
conf-error-no-username = Cannot connect to Groupe PSA bridge as no username is available in the configuration
conf-error-no-password = Cannot connect to Groupe PSA bridge as no password is available in the configuration
conf-error-no-clientid = Cannot connect to Groupe PSA bridge as no client ID is available in the configuration
conf-error-no-clientsecret = Cannot connect to Groupe PSA bridge as no client secret is available in the configuration
conf-error-invalid-polling-interval = Invalid polling interval specified. The interval has to be >= 1
conf-error-invalid-online-interval = Invalid online check interval specified. The interval has to be >= 1
conf-error-no-vehicle-id = No VIN specified. Unable to communicate with the vehicle without an ID
conf-error-no-bridge = No valid bridge for the vehicle is available
#communication errors
comm-error-vehicle-not-connected-to-cloud = Vehicle not connected to the cloud
comm-error-query-vehicle-failed = Unable to query the vehicle status ({0})
comm-error-query-vehicles-failed = Unable to get a list of vehicles ({0})

View File

@ -0,0 +1,535 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="groupepsa"
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">
<bridge-type id="bridge">
<label>Groupe PSA Bridge</label>
<description>The bridge to communicate with the PSA (Peugeot, Citroen, Vauxhall, Opel, DS) Web API for End-Users</description>
<category>WebService</category>
<representation-property>vendor</representation-property>
<config-description>
<parameter name="vendor" type="text" required="true">
<label>Brand</label>
<description>The brand of the car</description>
<options>
<option value="PEUGEOT">Peugeot</option>
<option value="CITROEN">Citroen</option>
<option value="DS">DS</option>
<option value="OPEL">Opel</option>
<option value="VAUXHALL">Vauxhall</option>
</options>
</parameter>
<parameter name="userName" type="text" required="true">
<label>User Name</label>
<description>The user name for the mypeugot/mycitroen/myds/myopel/myvauxhall website or app</description>
</parameter>
<parameter name="password" type="text" required="true">
<context>password</context>
<description>The password for the given user</description>
</parameter>
<parameter name="pollingInterval" type="integer" required="false" unit="min">
<label>Polling Interval</label>
<default>60</default>
<description>How often the available vehicles should be queried in minutes</description>
</parameter>
<parameter name="clientId" type="text" required="true">
<advanced>true</advanced>
<default>07364655-93cb-4194-8158-6b035ac2c24c</default>
<label>Client ID</label>
<description>The client ID for API access: can normally left at the default value. (see:
https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article)</description>
</parameter>
<parameter name="clientSecret" type="text" required="true">
<advanced>true</advanced>
<default>F2kK7lC5kF5qN7tM0wT8kE3cW1dP0wC5pI6vC0sQ5iP5cN8cJ8</default>
<label>Client Secret</label>
<description>The client secret for API access: can normally left at the default value. (see:
https://developer.groupe-psa.io/webapi/b2c/quickstart/connect/#article)</description>
</parameter>
</config-description>
</bridge-type>
<thing-type id="vehicle">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Groupe PSA Car</label>
<description>Car connected via Groupe PSA (Peugeot, Citroen, Vauxhall, Opel, DS) Bridge</description>
<channel-groups>
<channel-group id="battery" typeId="battery"/>
<channel-group id="doors" typeId="doors"/>
<channel-group id="environment" typeId="environment"/>
<channel-group id="motion" typeId="motion"/>
<channel-group id="position" typeId="position"/>
<channel-group id="various" typeId="various"/>
<channel-group id="fuel" typeId="fuel"/>
<channel-group id="electric" typeId="electric"/>
</channel-groups>
<properties>
<property name="vendor">N/A</property>
<property name="model">N/A</property>
<property name="vin">N/A</property>
</properties>
<representation-property>id</representation-property>
<config-description>
<parameter name="id" type="text" required="true">
<label>ID</label>
<description>Vehicle API ID (obtained automatically during discovery, not equal to the VIN)</description>
</parameter>
<parameter name="pollingInterval" type="integer" required="false" unit="min">
<label>Polling Interval</label>
<default>5</default>
<description>How often this vehicle should be polled for updated information</description>
</parameter>
<parameter name="onlineInterval" type="integer" required="false" unit="min">
<label>Online Timeout</label>
<default>15</default>
<description>Timeout when the vehicle is considered offline</description>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="battery">
<label>Battery</label>
<description>Main battery</description>
<category>Energy</category>
<channels>
<channel id="current" typeId="current"/>
<channel id="voltage" typeId="level">
<label>Battery Level</label>
<description>Battery level</description>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="doors">
<label>Doors</label>
<description>State of the doors</description>
<category>Door</category>
<channels>
<channel id="locked" typeId="doorLock"/>
<channel id="driver" typeId="doorOpen">
<label>Driver Door</label>
<description>Driver door open state</description>
</channel>
</channels>
</channel-group-type>
<channel-group-type id="environment">
<label>Environment</label>
<description>Environmental conditions</description>
<channels>
<channel id="temperature" typeId="temperature">
<label>Outside Temperature</label>
<description>Outside Temperature</description>
</channel>
<channel id="daytime" typeId="daytime"/>
</channels>
</channel-group-type>
<channel-group-type id="motion">
<label>Motion</label>
<description>Motion information</description>
<channels>
<channel id="ignition" typeId="ignition"/>
<channel id="acceleration" typeId="acceleration"/>
<channel id="moving" typeId="moving"/>
<channel id="speed" typeId="speed"/>
<channel id="mileage" typeId="mileage"/>
</channels>
</channel-group-type>
<channel-group-type id="position">
<label>Position</label>
<description>Location information</description>
<channels>
<channel id="position" typeId="position"/>
<channel id="heading" typeId="heading"/>
<channel id="type" typeId="type"/>
<channel id="signal" typeId="system.signal-strength"/>
</channels>
</channel-group-type>
<channel-group-type id="various">
<label>Various</label>
<description>Miscellaneous information</description>
<channels>
<channel id="lastUpdated" typeId="lastUpdated"/>
<channel id="privacy" typeId="privacy"/>
<channel id="belt" typeId="belt"/>
<channel id="emergency" typeId="emergency"/>
<channel id="service" typeId="service"/>
<channel id="preconditioning" typeId="preconditioning"/>
<channel id="preconditioningFailure" typeId="preconditioningFailure"/>
</channels>
</channel-group-type>
<channel-type id="current">
<item-type>Number:ElectricCurrent</item-type>
<label>Current</label>
<description>Electrical current</description>
<category>Energy</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="voltage">
<item-type>Number:ElectricPotential</item-type>
<label>Voltage</label>
<description>Voltage</description>
<category>Energy</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Temperature</description>
<category>Temperature</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="daytime">
<item-type>Contact</item-type>
<label>Daytime</label>
<description>Enabled if it is daytime</description>
</channel-type>
<channel-type id="doorLock">
<item-type>String</item-type>
<label>Door Lock</label>
<description>Door lock state</description>
<category>Door</category>
<state readOnly="true">
<options>
<option value="Unlocked">Unlocked</option>
<option value="Locked">Locked</option>
<option value="SuperLocked">Super Locked</option>
<option value="DriverDoorUnlocked">Driver Door Unlocked</option>
<option value="CabinDoorsUnlocked">Cabin Doors Unlocked</option>
<option value="CargoDoorsLocked">Cargo Doors Locked</option>
<option value="CargoDoorsUnlocked">Cargo Doors Unlocked</option>
<option value="RearDoorsLocked">Rear Doors Locked</option>
<option value="RearDoorsUnlocked">Rear Doors Unlocked</option>
</options>
</state>
</channel-type>
<channel-type id="doorOpen">
<item-type>Contact</item-type>
<label>Door Open</label>
<description>Door is open</description>
<category>Door</category>
</channel-type>
<channel-type id="ignition">
<item-type>String</item-type>
<label>Ignition</label>
<description>Ignition state</description>
<state readOnly="true">
<options>
<option value="Stop">Stop</option>
<option value="StartUp">Start up</option>
<option value="Start">Start</option>
<option value="Free">Free</option>
</options>
</state>
</channel-type>
<channel-type id="moving">
<item-type>Contact</item-type>
<label>Moving</label>
<description>Vehicle is moving</description>
</channel-type>
<channel-type id="acceleration">
<item-type>Number:Acceleration</item-type>
<label>Acceleration</label>
<description>Current acceleration</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="speed">
<item-type>Number:Speed</item-type>
<label>Speed</label>
<description>Current speed</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="mileage">
<item-type>Number:Length</item-type>
<label>Mileage</label>
<description>Total travelled distance</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="position">
<item-type>Location</item-type>
<label>Position</label>
<description>Last known position</description>
<state pattern="%2$s°N %3$s°E %1$sm"/>
</channel-type>
<channel-type id="heading">
<item-type>Number:Angle</item-type>
<label>Heading</label>
<description>Direction of travel</description>
<state pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="type">
<item-type>String</item-type>
<label>Position Type</label>
<description>Position acquisition type</description>
<state readOnly="true">
<options>
<option value="Estimate">Estimate</option>
<option value="Acquire">Acquire</option>
</options>
</state>
</channel-type>
<channel-type id="lastUpdated">
<item-type>DateTime</item-type>
<label>Time of Last Update</label>
<description>Last time the results were updated on the server</description>
<category>Time</category>
</channel-type>
<channel-type id="privacy">
<item-type>String</item-type>
<label>Privacy Status</label>
<description>Privacy status</description>
<state readOnly="true">
<options>
<option value="None">None</option>
<option value="Geolocation">Location</option>
<option value="Full">Full</option>
</options>
</state>
</channel-type>
<channel-type id="belt">
<item-type>String</item-type>
<label>Belt Status</label>
<description>Seat belt status</description>
<state readOnly="true">
<options>
<option value="Normal">Belts on</option>
<option value="Omission">Belt open</option>
</options>
</state>
</channel-type>
<channel-type id="emergency">
<item-type>String</item-type>
<label>Emergency Call</label>
<description>Emergency call status</description>
<state readOnly="true">
<options>
<option value="NoRequest">None</option>
<option value="AirbagUnabled">Airbag Deployed</option>
<option value="Requested">Emergency Call Requested</option>
</options>
</state>
</channel-type>
<channel-type id="service">
<item-type>String</item-type>
<label>Service</label>
<description>Service Type</description>
<state readOnly="true">
<options>
<option value="Electric">Electric</option>
<option value="Hybrid">Hybrid</option>
<option value="Unknown">Unknown</option>
</options>
</state>
</channel-type>
<channel-type id="preconditioning">
<item-type>String</item-type>
<label>Air Conditioning</label>
<description>Air conditioning status</description>
<state readOnly="true">
<options>
<option value="Enabled">Enabled</option>
<option value="Disabled">Disabled</option>
<option value="Finished">Finished</option>
<option value="Failure">Failure</option>
</options>
</state>
</channel-type>
<channel-type id="preconditioningFailure">
<item-type>String</item-type>
<label>Air Conditioning Failure</label>
<description>Air conditioning failure cause</description>
<state readOnly="true">
<options>
<option value="Defect">Defect</option>
<option value="DoorOpened">Door Opened</option>
<option value="LowBattery">Low Battery</option>
<option value="LowFuelLevel">Low Fuel Level</option>
<option value="TooManyUnusedProg">Too many times unused</option>
</options>
</state>
</channel-type>
<channel-group-type id="fuel">
<label>Fuel</label>
<description>Thermic Motor Fuel Status</description>
<category>Energy</category>
<channels>
<channel id="autonomy" typeId="autonomy"/>
<channel id="consumption" typeId="consumption"/>
<channel id="level" typeId="level"/>
</channels>
</channel-group-type>
<channel-group-type id="electric">
<label>Electric Energy</label>
<description>Electric motor energy status</description>
<channels>
<channel id="autonomy" typeId="autonomy">
<label>Electric Autonomy</label>
<description>Remaining distance</description>
</channel>
<channel id="level" typeId="level">
<label>Electricity Level</label>
<description>Electricity level</description>
</channel>
<channel id="residual" typeId="residual"/>
<channel id="batteryCapacity" typeId="capacity"/>
<channel id="batteryHealthCapacity" typeId="healthCapacity"/>
<channel id="batteryHealthResistance" typeId="healthResistance"/>
<channel id="chargingStatus" typeId="chargingStatus"/>
<channel id="chargingMode" typeId="chargingMode"/>
<channel id="chargingPlugged" typeId="chargingPlugged"/>
<channel id="chargingRate" typeId="chargingRate"/>
<channel id="chargingRemainingTime" typeId="chargingRemainingTime"/>
<channel id="chargingNextDelayedTime" typeId="chargingNextDelayedTime"/>
</channels>
</channel-group-type>
<channel-type id="level">
<item-type>Number:Dimensionless</item-type>
<label>Fuel Level</label>
<description>Fuel level</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="autonomy">
<item-type>Number:Length</item-type>
<label>Fuel Autonomy</label>
<description>Remaining distance</description>
<state pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="consumption">
<item-type>Number:VolumetricFlowRate</item-type>
<label>Fuel Consumption</label>
<description>Fuel consumption</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="residual">
<item-type>Number:Energy</item-type>
<label>Remaining Charge</label>
<description>Remaining battery charge</description>
<category>BatteryLevel</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="capacity">
<item-type>Number:Energy</item-type>
<label>Battery Capacity</label>
<description>Battery capacity</description>
<category>BatteryLevel</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="healthCapacity">
<item-type>Number:Dimensionless</item-type>
<label>Battery Health - Capacity</label>
<description>Health of the battery capacity</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="healthResistance">
<item-type>Number:Dimensionless</item-type>
<label>Battery Health - Resistance</label>
<description>Health of the battery resistance</description>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="chargingStatus">
<item-type>String</item-type>
<label>Charging Status</label>
<description>Battery charging status</description>
<state readOnly="true">
<options>
<option value="Disconnected">Disconnected</option>
<option value="InProgess">In Progress</option>
<option value="Failure">Failure</option>
<option value="Stopped">Stopped</option>
<option value="Finished">Finished</option>
</options>
</state>
</channel-type>
<channel-type id="chargingMode">
<item-type>String</item-type>
<label>Charging Mode</label>
<description>Battery charging mode</description>
<state readOnly="true">
<options>
<option value="No">No</option>
<option value="Slow">Slow</option>
<option value="Quick">Fast</option>
</options>
</state>
</channel-type>
<channel-type id="chargingPlugged">
<item-type>Contact</item-type>
<label>Plugged In</label>
<description>Vehicle plugged in to charger</description>
</channel-type>
<channel-type id="chargingRate">
<item-type>Number:Speed</item-type>
<label>Charging Rate</label>
<description>Battery Charging Rate</description>
<category>BatteryLevel</category>
<state pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="chargingRemainingTime">
<item-type>Number:Time</item-type>
<label>Charging Time Remaining</label>
<description>Time remaining till charged</description>
<category>Time</category>
<state pattern="%tT"/>
</channel-type>
<channel-type id="chargingNextDelayedTime">
<item-type>Number:Time</item-type>
<label>Time Till Next Charging</label>
<description>Time till the next charging starts</description>
<category>Time</category>
<state pattern="%tT"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,159 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.groupepsa.internal.things;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.HttpResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.openhab.binding.groupepsa.internal.bridge.GroupePSABridgeHandler;
import org.openhab.binding.groupepsa.internal.rest.api.GroupePSAConnectApi;
import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
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.ThingUID;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.State;
/**
* The {@link GroupePSAHandlerTest} is responsible for testing the binding
*
* @author Arjan Mels - Initial contribution
*/
@NonNullByDefault
public class GroupePSAHandlerTest {
private @NonNullByDefault({}) AutoCloseable closeable;
private @NonNullByDefault({}) GroupePSAConnectApi api;
private @NonNullByDefault({}) GroupePSABridgeHandler bridgeHandler;
private @NonNullByDefault({}) GroupePSAHandler thingHandler;
private @NonNullByDefault({}) @Mock ThingHandlerCallback thingCallback;
private @NonNullByDefault({}) @Mock ThingHandlerCallback bridgeCallback;
private @NonNullByDefault({}) @Mock Thing thing;
private @NonNullByDefault({}) @Mock Bridge bridge;
private @NonNullByDefault({}) @Mock OAuthFactory oAuthFactory;
private @NonNullByDefault({}) @Mock HttpClient httpClient;
static String getResourceFileAsString(String fileName) throws GroupePSACommunicationException {
try (InputStream is = GroupePSAConnectApi.class.getResourceAsStream(fileName)) {
try (InputStreamReader isr = new InputStreamReader(is); BufferedReader reader = new BufferedReader(isr)) {
return reader.lines().collect(Collectors.joining(System.lineSeparator()));
}
} catch (Exception e) {
throw new GroupePSACommunicationException(e);
}
}
static HttpContentResponse createHttpResponse(String file) throws GroupePSACommunicationException {
return new HttpContentResponse(new HttpResponse(null, null).status(200),
getResourceFileAsString("/" + file).getBytes(), "json", "UTF-8");
}
@BeforeEach
@SuppressWarnings("null")
public void setUp() throws GroupePSACommunicationException {
closeable = MockitoAnnotations.openMocks(this);
// Create real objects
bridgeHandler = spy(new GroupePSABridgeHandler(bridge, oAuthFactory, httpClient));
thingHandler = spy(new GroupePSAHandler(thing));
api = spy(new GroupePSAConnectApi(httpClient, bridgeHandler, "clientId", "realm"));
// Setup API mock
doReturn(createHttpResponse("dummy_user.json")).when(api).executeRequest(contains("user"), anyString());
doReturn(createHttpResponse("dummy_vehiclestatus3.json")).when(api).executeRequest(contains("status"),
anyString());
// Setup bridge handler mock
bridgeHandler.setCallback(bridgeCallback);
doReturn(api).when(bridgeHandler).getAPI();
// Setup bridge mock
Configuration bridgeConfig = new Configuration();
bridgeConfig.put("vendor", "OPEL");
bridgeConfig.put("userName", "user");
bridgeConfig.put("password", "pwd");
bridgeConfig.put("clientId", "clientIdValue");
bridgeConfig.put("clientSecret", "clientSecretValue");
doReturn(bridgeConfig).when(bridge).getConfiguration();
doReturn(ThingStatus.ONLINE).when(bridge).getStatus();
doReturn(bridgeHandler).when(bridge).getHandler();
doReturn(new ThingUID("a:b:c")).when(bridge).getUID();
// Setup thing mock
Configuration thingConfig = new Configuration();
thingConfig.put("id", "mock_id");
doReturn(thingConfig).when(thing).getConfiguration();
doReturn(new ThingUID("a:b:c")).when(thing).getUID();
// Setup thing handler mock
thingHandler.setCallback(thingCallback);
doReturn(bridge).when(thingHandler).getBridge();
doNothing().when(thingHandler).buildDoorChannels(any());
}
@AfterEach
public void tearDown() throws Exception {
// Free any resources, like open database connections, files etc.
thingHandler.dispose();
bridgeHandler.dispose();
closeable.close();
}
@Test
public void intializeAndCheckChannels() throws InterruptedException {
// Initialize the bridge
bridgeHandler.initialize();
// check that the bridge is online
verify(bridgeCallback, timeout(2000)).statusUpdated(eq(bridge),
argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE)));
// Initialize the thing
thingHandler.initialize();
// check that the thing is offline without detail (last update time is not
// within 15 minutes)
verify(thingCallback, timeout(2000)).statusUpdated(eq(thing),
argThat(arg -> arg.getStatus().equals(ThingStatus.OFFLINE)
&& arg.getStatusDetail().equals(ThingStatusDetail.NONE)));
// check that the channels are updated
verify(thingCallback, atLeast(30)).stateUpdated(any(ChannelUID.class), any(State.class));
verify(thingCallback).stateUpdated(eq(new ChannelUID("a:b:c:electric#chargingStatus")),
eq(new StringType("Disconnected")));
verify(thingCallback).stateUpdated(eq(new ChannelUID("a:b:c:various#lastUpdated")), any(DateTimeType.class));
}
}

View File

@ -0,0 +1,71 @@
{
"_links": {
"self": {
"href": "ivuvawoj"
},
"trips": {
"href": "ugihub"
},
"lastTrip": {
"href": "culugakin"
},
"lastEcodriving": {
"href": "bagnu"
}
},
"id": "7000650216898560",
"email": "iwni@georure.kr",
"firstName": "Floyd",
"lastName": "Gilbert",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"_embedded": {
"vehicles": [
{
"_links": {
"self": {
"href": "ovoha"
},
"lastTrip": {
"href": "lotudwe"
},
"lastPosition": {
"href": "bobediirewelojbu"
},
"trips": {
"href": "vacofgutv"
},
"collisions": {
"href": "koafeu"
},
"telemetry": {
"href": "ewahzonug"
},
"maintenance": {
"href": "dovuzibwudmicel"
},
"alerts": {
"href": "izbisbiwpejunun"
},
"events": {
"href": "tece"
}
},
"id": "4336155157856256",
"vin": "mycar_vin1",
"brand": "opel",
"engine": [
{
"class": "Thermic",
"energy": "GPL"
},
{
"class": "Electric"
}
],
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
]
}
}

View File

@ -0,0 +1,89 @@
{
"lastPosition": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
2.2221432,
48.772476,
172
]
},
"properties": {
"updatedAt": "2019-08-24T14:15:22Z",
"type": "Aquire",
"signalQuality": 6
}
},
"preconditionning": {
"airConditioning": {
"updatedAt": "2019-08-24T14:15:22Z",
"status": "Disabled",
"programs": [
{
"enabled": false,
"recurrence": "Daily",
"start": "PT0S"
},
{
"enabled": false,
"recurrence": "Daily",
"start": "PT0S"
},
{
"enabled": false,
"recurrence": "Daily",
"start": "PT0S"
},
{
"enabled": false,
"recurrence": "Daily",
"start": "PT0S"
}
]
}
},
"energy": [
{
"type": "Electric",
"level": 0,
"autonomy": 0,
"residual": 0
},
{
"type": "Fuel",
"level": 0,
"residual": 0
}
],
"createdAt": "2019-08-24T14:15:22Z",
"autonomy": 2000,
"ignition": {
"createdAt": "2019-08-24T14:15:22Z"
},
"vin": "VF3A12BCDEF3456",
"privacy": {
"createdAt": "2019-08-24T14:15:22Z",
"state": "None"
},
"battery": {
"voltage": 0.55,
"current": 0.55,
"createdAt": "2019-08-24T14:15:22Z"
},
"kinetic": {
"createdAt": "2019-08-24T14:15:22Z",
"acceleration": 0.55,
"speed": 0.55,
"pace": 0.55,
"moving": false
},
"_links": {
"self": {
"href": "https://api.groupe-psa.com/connectedcar/v4/user/vehicles/{id}/status"
},
"vehicles": {
"href": "https://api.groupe-psa.com/connectedcar/v4/user/vehicles/{id}"
}
}
}

View File

@ -0,0 +1,180 @@
{
"createdAt": "2019-08-24T14:15:22Z",
"_embedded": {
"extension": {
"kinetic": {
"createdAt": "2019-08-24T14:15:22Z",
"acceleration": 0,
"moving": true,
"pace": 0,
"speed": 0
},
"odometer": {
"createdAt": "2019-08-24T14:15:22Z",
"mileage": 0
}
}
},
"_links": {
"self": {
"deprecation": "string",
"href": "string",
"hreflang": "string",
"name": "string",
"profile": "string",
"templated": true,
"title": "string",
"type": "string"
},
"vehicle": {
"deprecation": "string",
"href": "string",
"hreflang": "string",
"name": "string",
"profile": "string",
"templated": true,
"title": "string",
"type": "string"
}
},
"battery": {
"createdAt": "2019-08-24T14:15:22Z",
"current": 0,
"voltage": 0
},
"doorsState": {
"lockedState": [
"Unlocked"
],
"opening": [
{
"identifier": "Driver",
"state": "Open"
}
],
"updatedAt": "2019-08-24T14:15:22Z"
},
"energy": [
{
"updatedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"autonomy": 1,
"battery": {
"capacity": 0,
"health": {
"capacity": 0,
"resistance": 0
}
},
"charging": {
"chargingMode": "No",
"chargingRate": 0,
"nextDelayedTime": "PT15M",
"plugged": true,
"remainingTime": "PT15M",
"status": "Disconnected"
},
"consumption": 0,
"level": 0,
"residual": 0,
"type": "Fuel"
},
{
"updatedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"autonomy": 2,
"battery": {
"capacity": 0,
"health": {
"capacity": 0,
"resistance": 0
}
},
"charging": {
"chargingMode": "No",
"chargingRate": 0,
"nextDelayedTime": "PT15M",
"plugged": true,
"remainingTime": "PT15M",
"status": "Disconnected"
},
"consumption": 0,
"level": 0,
"residual": 0,
"type": "Electric"
}
],
"environment": {
"updatedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"air": {
"temp": 0
},
"luminosity": {
"day": true
}
},
"ignition": {
"updatedAt": "2019-08-24T14:15:22Z",
"type": "Stop"
},
"kinetic": {
"createdAt": "2019-08-24T14:15:22Z",
"acceleration": 0,
"moving": true,
"pace": 0,
"speed": 0
},
"lastPosition": {
"updatedAt": "2019-08-24T14:15:22Z",
"createdAt": "2019-08-24T14:15:22Z",
"geometry": {
"coordinates": [
0, 0, 0
],
"type": "Point"
},
"properties": {
"heading": 0,
"signalQuality": 0,
"type": "Estimate"
},
"type": "Feature"
},
"preconditionning": {
"airConditioning": {
"failureCause": "Defect",
"programs": [
{
"occurence": {
"day": [
"Mon"
],
"week": [
"string"
]
},
"recurrence": "Daily",
"start": "string",
"enabled": true,
"slot": 0
}
],
"status": "Enabled",
"updatedAt": "2019-08-24T14:15:22Z"
}
},
"privacy": {
"createdAt": "2019-08-24T14:15:22Z",
"state": "None"
},
"safety": {
"createdAt": "2019-08-24T14:15:22Z",
"beltWarning": "Normal",
"eCallTriggeringRequest": "AirbagUnabled"
},
"service": {
"type": "Electric",
"updatedAt": "2019-08-24T14:15:22Z"
}
}

View File

@ -0,0 +1,102 @@
{
"lastPosition": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
22.1111111,
11.2222222
]
},
"properties": {
"updatedAt": "2020-12-25T15:16:48Z",
"heading": 331,
"type": "Estimated"
}
},
"preconditionning": {
"airConditioning": {
"updatedAt": "2020-12-23T16:54:14Z",
"status": "Disabled",
"programs": [
{
"enabled": false,
"slot": 1,
"recurrence": "Daily",
"start": "PT6H15M",
"occurence": {
"day": [
"Mon",
"Tue",
"Wed",
"Thu",
"Fri"
]
}
},
{
"enabled": false,
"slot": 2,
"recurrence": "Daily",
"start": "PT8H",
"occurence": {
"day": [
"Mon",
"Tue",
"Wed",
"Thu",
"Fri"
]
}
}
]
}
},
"energy": [
{
"updatedAt": "2020-12-23T16:54:13Z",
"type": "Electric",
"level": 77,
"autonomy": 200,
"charging": {
"plugged": false,
"status": "Disconnected",
"remainingTime": "PT0S",
"chargingRate": 0,
"chargingMode": "No",
"nextDelayedTime": "PT5H"
}
}
],
"createdAt": "2020-12-25T15:16:48Z",
"battery": {
"voltage": 87,
"current": 0,
"createdAt": "2020-12-23T16:54:13Z"
},
"kinetic": {
"createdAt": "2020-12-25T15:16:48Z",
"moving": false
},
"privacy": {
"createdAt": "2020-12-25T15:16:43Z",
"state": "None"
},
"service": {
"type": "Electric",
"updatedAt": "2020-11-06T19:56:10Z"
},
"_links": {
"self": {
"href": "dummy_link/status"
},
"vehicles": {
"href": "dummy_link/vehicles"
}
},
"timed.odometer": {
"createdAt": null,
"mileage": 1539
},
"updatedAt": "2020-12-25T15:16:48Z"
}

View File

@ -142,6 +142,7 @@
<module>org.openhab.binding.globalcache</module>
<module>org.openhab.binding.gpstracker</module>
<module>org.openhab.binding.gree</module>
<module>org.openhab.binding.groupepsa</module>
<module>org.openhab.binding.groheondus</module>
<module>org.openhab.binding.guntamatic</module>
<module>org.openhab.binding.haassohnpelletstove</module>