mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[mybmw] Upgrade to new BMW API (#14452)
* [mybmw] fix not working binding due to API update to make it work the code has been refactored and due to API changes some improvements could be made. These include: - (improvement) fingerprint generation: You can take a look at the README how to create a fingerprint more conveniently. - (change) changed channel: charge-info has been renamed to charge-remaining - (improvement) added channels: estimated-fuel-l-100km and estimated-fuel-mpg which calculates the estimated fuel consumption based on the range and remaining fuel liters - unfortunately such a calculation is not available for EVs as there is no information about the capacity of the battery. - (improvement) added channel last-fetched: the last-updated timestamp is showing by when the last update of the vehicle happened. As right now you can not see from the channels if a thing is offline due to connection issues, you can check now if last-fetched is more than 5 minutes ago to identify an issue - (fixed) remote command typos fixed Fixes #14065 Also-by: Mark Herwege <mark.herwege@telenet.be> Signed-off-by: Martin Grassl <martin.grassl@digital-filestore.de>
This commit is contained in:
parent
6d2b8bc92f
commit
4f84c48b21
@ -225,7 +225,7 @@
|
||||
/bundles/org.openhab.binding.mqtt.homie/ @ccutrer
|
||||
/bundles/org.openhab.binding.mqtt.ruuvigateway/ @ssalonen
|
||||
/bundles/org.openhab.binding.mycroft/ @dalgwen
|
||||
/bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess
|
||||
/bundles/org.openhab.binding.mybmw/ @ntruchsess @mherwege @martingrassl
|
||||
/bundles/org.openhab.binding.mynice/ @clinique
|
||||
/bundles/org.openhab.binding.mystrom/ @pail23
|
||||
/bundles/org.openhab.binding.nanoleaf/ @stefan-hoehn
|
||||
|
@ -163,9 +163,9 @@ Reflects overall status of the vehicle.
|
||||
| Check Control | check-control | String | Presence of active warning messages | X | X | X | X |
|
||||
| Plug Connection Status | plug-connection | String | Plug is _Connected_ or _Not connected_ | | X | X | X |
|
||||
| Charging Status | charge | String | Current charging status | | X | X | X |
|
||||
| Charging Information | charge-info | String | Information regarding current charging session | | X | X | X |
|
||||
| Motion Status | motion | Switch | Driving state - depends on vehicle hardware | X | X | X | X |
|
||||
| Remaining Charging Time | charge-remaining | Number:Time | Remaining time for current charging session | | X | X | X |
|
||||
| Last Status Timestamp | last-update | DateTime | Date and time of last status update | X | X | X | X |
|
||||
| Last Fetched Timestamp | last-fetched | DateTime | Date and time of last time status fetched | X | X | X | X |
|
||||
|
||||
Overall Door Status values
|
||||
|
||||
@ -239,17 +239,19 @@ See description [Range vs Range Radius](#range-vs-range-radius) to get more info
|
||||
- Availability according to table
|
||||
- Read-only values
|
||||
|
||||
| Channel Label | Channel ID | Type | conv | phev | bev_rex | bev |
|
||||
|---------------------------|-------------------------|----------------------|------|------|---------|-----|
|
||||
| Mileage | mileage | Number:Length | X | X | X | X |
|
||||
| Fuel Range | range-fuel | Number:Length | X | X | X | |
|
||||
| Electric Range | range-electric | Number:Length | | X | X | X |
|
||||
| Hybrid Range | range-hybrid | Number:Length | | X | X | |
|
||||
| Battery Charge Level | soc | Number:Dimensionless | | X | X | X |
|
||||
| Remaining Fuel | remaining-fuel | Number:Volume | X | X | X | |
|
||||
| Fuel Range Radius | range-radius-fuel | Number:Length | X | X | X | |
|
||||
| Electric Range Radius | range-radius-electric | Number:Length | | X | X | X |
|
||||
| Hybrid Range Radius | range-radius-hybrid | Number:Length | | X | X | |
|
||||
| Channel Label | Channel ID | Type | conv | phev | bev_rex | bev |
|
||||
|------------------------------------|----------------------------|----------------------|------|------|---------|-----|
|
||||
| Mileage | mileage | Number:Length | X | X | X | X |
|
||||
| Fuel Range | range-fuel | Number:Length | X | X | X | |
|
||||
| Electric Range | range-electric | Number:Length | | X | X | X |
|
||||
| Hybrid Range | range-hybrid | Number:Length | | X | X | |
|
||||
| Battery Charge Level | soc | Number:Dimensionless | | X | X | X |
|
||||
| Remaining Fuel | remaining-fuel | Number:Volume | X | X | X | |
|
||||
| Estimated Fuel Consumption l/100km | estimated-fuel-l-100km | Number | X | X | X | |
|
||||
| Estimated Fuel Consumption mpg | estimated-fuel-mpg | Number | X | X | X | |
|
||||
| Fuel Range Radius | range-radius-fuel | Number:Length | X | X | X | |
|
||||
| Electric Range Radius | range-radius-electric | Number:Length | | X | X | X |
|
||||
| Hybrid Range Radius | range-radius-hybrid | Number:Length | | X | X | |
|
||||
|
||||
#### Doors Details
|
||||
|
||||
@ -359,6 +361,7 @@ The channel _command_ provides options
|
||||
- _horn-blow_
|
||||
- _climate-now-start_
|
||||
- _climate-now-stop_
|
||||
- _charge-now_
|
||||
|
||||
The channel _state_ shows the progress of the command execution in the following order
|
||||
|
||||
@ -471,10 +474,11 @@ Image representation of the vehicle.
|
||||
|
||||
Possible view ports:
|
||||
|
||||
- _VehicleStatus_ Front Side View
|
||||
- _VehicleInfo_ Front View
|
||||
- _ChargingHistory_ Side View
|
||||
- _Default_ Front Side View
|
||||
- _VehicleStatus_ Front Left Side View
|
||||
- _FrontView_ Front View
|
||||
- _FrontLeft_ Front Left Side View
|
||||
- _FrontRight_ Front Right Side View
|
||||
- _RearView_ Rear View
|
||||
|
||||
## Further Descriptions
|
||||
|
||||
@ -491,7 +495,8 @@ There are 3 occurrences of dynamic data delivered
|
||||
The channel id _name_ shows the first element as default.
|
||||
All other possibilities are attached as options.
|
||||
The picture on the right shows the _Session Title_ item and 3 possible options.
|
||||
Select the desired service and the corresponding Charge Session with _Energy Charged_, _Session Status_ and _Session Issues_ will be shown.
|
||||
Select the desired service and the corresponding Charge Session with _Energy Charged_, _Session Status_ and
|
||||
_Session Issues_ will be shown.
|
||||
|
||||
### TroubleShooting
|
||||
|
||||
@ -507,32 +512,34 @@ If these preconditions are fulfilled proceed with the fingerprint generation.
|
||||
|
||||
#### Generate Debug Fingerprint
|
||||
|
||||
<img align="right" src="./doc/DiscoveryScan.png" width="400" height="350"/>
|
||||
Login to the openHAB console and use the `mybmw fingerprint` command.
|
||||
|
||||
First [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) for the binding.
|
||||
Fingerprint information on your account and vehicle(s) will show in the console and can be copiedfrom there.
|
||||
A zip file with fingerprint information for your vehicle(s) will also be generated and put into the `mybmw` folder in the userdata folder.
|
||||
This fingerprint information is valuable for the developers to better support your vehicle.
|
||||
|
||||
```shell
|
||||
log:set DEBUG org.openhab.binding.mybmw
|
||||
```
|
||||
You can restrict the accounts and vehicles for the fingerprint generation.
|
||||
Full syntax is available through the `mybmw help` console command.
|
||||
|
||||
The debug fingerprint is generated every time the discovery is executed.
|
||||
To force a new fingerprint perform a _Scan_ for MyBMW things.
|
||||
Personal data is eliminated from the log entries so it should be possible to share them in public.
|
||||
Personal data is eliminated from fingerprints so it should be possible to share them in public.
|
||||
Data like
|
||||
|
||||
- Vehicle Identification Number (VIN)
|
||||
- Location data
|
||||
|
||||
are anonymized.
|
||||
You'll find the fingerprint in the logs with the command
|
||||
are anonymized in the JSON response and URL's.
|
||||
|
||||
```shell
|
||||
grep "Discovery Fingerprint Data" openhab.log
|
||||
```
|
||||
After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint!
|
||||
|
||||
After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint data!
|
||||
Your feedback is highly appreciated!
|
||||
|
||||
#### Debug Logging
|
||||
|
||||
You can [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) to get more information on the behaviour of the binding.
|
||||
The package.subpackage in this case would be "org.openhab.binding.mybmw".
|
||||
|
||||
As with fingerprint data, personal data is eliminated from logs.
|
||||
|
||||
### Range vs Range Radius
|
||||
|
||||
<img align="right" src="./doc/range-radius.png" width="400" height="350"/>
|
||||
|
@ -14,4 +14,171 @@
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: MyBMW Binding</name>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>test-coverage</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-instrument</id>
|
||||
<goals>
|
||||
<goal>instrument</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-restore-instrumented-classes</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>restore-instrumented-classes</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-report</id>
|
||||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.20</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>BRANCH</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.20</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- must be on the classpath -->
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>org.jacoco.agent</artifactId>
|
||||
<classifier>runtime</classifier>
|
||||
<version>0.8.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</profile>
|
||||
<profile>
|
||||
<!--
|
||||
If you activate this profile, the MyBmwProxyIT is executed which means real
|
||||
backend requests. The test is only successful if you provide CONNECTED_USER and
|
||||
CONNECTED_PASSWORD as environment variable of the Maven command.
|
||||
-->
|
||||
<id>integration-tests</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.0.0-M7</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!--
|
||||
This profile generates a jar file <regular-jar-file-name>-testenv.jar in the target folder. This
|
||||
testenv jar contains the regular classes and in addition all responses from
|
||||
src/test/resources. If you copy this jar file to your addons folder, you can simulate all
|
||||
accounts which are available as fingerprints in the responses folder. This can be done like
|
||||
this:
|
||||
1. start openhab with the environment variable "ENVIRONMENT=test"
|
||||
|
||||
2. configure the connected account with username "testuser"
|
||||
|
||||
3. configure as connected password the folder which you want to test, e.g. "BEV", "BEV2", "PHEV", "ICE", "ICE2",
|
||||
"MILD_HYBRID"
|
||||
|
||||
after that you should get the vehicles loaded properly so you can check if the channels are populated with data properly.
|
||||
-->
|
||||
<id>test-jar</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<!-- here the phase you need -->
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/target/classes</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/test/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<classifier>testenv</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
@ -16,12 +16,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWConfiguration} class contains fields mapping thing configuration parameters.
|
||||
* The {@link MyBMWBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - renamed
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWConfiguration {
|
||||
public class MyBMWBridgeConfiguration {
|
||||
|
||||
/**
|
||||
* Depending on the location the correct server needs to be called
|
@ -23,20 +23,46 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - updated enum values
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWConstants {
|
||||
public interface MyBMWConstants {
|
||||
|
||||
private static final String BINDING_ID = "mybmw";
|
||||
static final String BINDING_ID = "mybmw";
|
||||
|
||||
public static final String VIN = "vin";
|
||||
static final String VIN = "vin";
|
||||
|
||||
public static final int DEFAULT_IMAGE_SIZE_PX = 1024;
|
||||
public static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 5;
|
||||
static final String REFRESH_INTERVAL = "refreshInterval";
|
||||
|
||||
static final String VEHICLE_BRAND = "vehicleBrand";
|
||||
|
||||
static final String REMOTE_SERVICES_DISABLED = "remoteServicesDisabled";
|
||||
|
||||
static final String REMOTE_SERVICES_ENABLED = "remoteServicesEnabled";
|
||||
|
||||
static final String SERVICES_DISABLED = "servicesDisabled";
|
||||
|
||||
static final String SERVICES_ENABLED = "servicesEnabled";
|
||||
|
||||
static final String SERVICES_UNSUPPORTED = "servicesUnsupported";
|
||||
|
||||
static final String SERVICES_SUPPORTED = "servicesSupported";
|
||||
|
||||
static final String VEHICLE_BODYTYPE = "vehicleBodytype";
|
||||
|
||||
static final String VEHICLE_CONSTRUCTION_YEAR = "vehicleConstructionYear";
|
||||
|
||||
static final String VEHICLE_DRIVE_TRAIN = "vehicleDriveTrain";
|
||||
|
||||
static final String VEHICLE_MODEL = "vehicleModel";
|
||||
|
||||
static final int DEFAULT_IMAGE_SIZE_PX = 1024;
|
||||
|
||||
static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 5;
|
||||
|
||||
// See constants from bimmer-connected
|
||||
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
|
||||
public enum VehicleType {
|
||||
enum VehicleType {
|
||||
CONVENTIONAL("conv"),
|
||||
PLUGIN_HYBRID("phev"),
|
||||
MILD_HYBRID("hybrid"),
|
||||
@ -56,150 +82,150 @@ public class MyBMWConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChargingMode {
|
||||
immediateCharging,
|
||||
delayedCharging
|
||||
enum ChargingMode {
|
||||
IMMEDIATE_CHARGING,
|
||||
DELAYED_CHARGING
|
||||
}
|
||||
|
||||
public enum ChargingPreference {
|
||||
noPreSelection,
|
||||
chargingWindow
|
||||
enum ChargingPreference {
|
||||
NO_PRESELECTION,
|
||||
CHARGING_WINDOW
|
||||
}
|
||||
|
||||
public static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
|
||||
static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
|
||||
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||
public static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
|
||||
static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
|
||||
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID,
|
||||
VehicleType.CONVENTIONAL.toString());
|
||||
public static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID,
|
||||
VehicleType.PLUGIN_HYBRID.toString());
|
||||
public static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID,
|
||||
VehicleType.ELECTRIC_REX.toString());
|
||||
public static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT,
|
||||
THING_TYPE_CONV, THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
|
||||
static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID, VehicleType.CONVENTIONAL.toString());
|
||||
static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID, VehicleType.PLUGIN_HYBRID.toString());
|
||||
static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC_REX.toString());
|
||||
static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
|
||||
static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT, THING_TYPE_CONV,
|
||||
THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
|
||||
|
||||
// Thing Group definitions
|
||||
public static final String CHANNEL_GROUP_STATUS = "status";
|
||||
public static final String CHANNEL_GROUP_SERVICE = "service";
|
||||
public static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
|
||||
public static final String CHANNEL_GROUP_DOORS = "doors";
|
||||
public static final String CHANNEL_GROUP_RANGE = "range";
|
||||
public static final String CHANNEL_GROUP_LOCATION = "location";
|
||||
public static final String CHANNEL_GROUP_REMOTE = "remote";
|
||||
public static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
|
||||
public static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
|
||||
public static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
|
||||
public static final String CHANNEL_GROUP_TIRES = "tires";
|
||||
public static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
|
||||
static final String CHANNEL_GROUP_STATUS = "status";
|
||||
static final String CHANNEL_GROUP_SERVICE = "service";
|
||||
static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
|
||||
static final String CHANNEL_GROUP_DOORS = "doors";
|
||||
static final String CHANNEL_GROUP_RANGE = "range";
|
||||
static final String CHANNEL_GROUP_LOCATION = "location";
|
||||
static final String CHANNEL_GROUP_REMOTE = "remote";
|
||||
static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
|
||||
static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
|
||||
static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
|
||||
static final String CHANNEL_GROUP_TIRES = "tires";
|
||||
static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
|
||||
|
||||
// Charge Statistics & Sessions
|
||||
public static final String SESSIONS = "sessions";
|
||||
public static final String ENERGY = "energy";
|
||||
public static final String TITLE = "title";
|
||||
public static final String SUBTITLE = "subtitle";
|
||||
public static final String ISSUE = "issue";
|
||||
public static final String STATUS = "status";
|
||||
static final String SESSIONS = "sessions";
|
||||
static final String ENERGY = "energy";
|
||||
static final String TITLE = "title";
|
||||
static final String SUBTITLE = "subtitle";
|
||||
static final String ISSUE = "issue";
|
||||
static final String STATUS = "status";
|
||||
|
||||
// Generic Constants for several groups
|
||||
public static final String NAME = "name";
|
||||
public static final String DETAILS = "details";
|
||||
public static final String SEVERITY = "severity";
|
||||
public static final String DATE = "date";
|
||||
public static final String MILEAGE = "mileage";
|
||||
public static final String GPS = "gps";
|
||||
public static final String HEADING = "heading";
|
||||
public static final String ADDRESS = "address";
|
||||
public static final String HOME_DISTANCE = "home-distance";
|
||||
static final String NAME = "name";
|
||||
static final String DETAILS = "details";
|
||||
static final String SEVERITY = "severity";
|
||||
static final String DATE = "date";
|
||||
static final String MILEAGE = "mileage";
|
||||
static final String GPS = "gps";
|
||||
static final String HEADING = "heading";
|
||||
static final String ADDRESS = "address";
|
||||
static final String HOME_DISTANCE = "home-distance";
|
||||
|
||||
// Status
|
||||
public static final String DOORS = "doors";
|
||||
public static final String WINDOWS = "windows";
|
||||
public static final String LOCK = "lock";
|
||||
public static final String SERVICE_DATE = "service-date";
|
||||
public static final String SERVICE_MILEAGE = "service-mileage";
|
||||
public static final String CHECK_CONTROL = "check-control";
|
||||
public static final String PLUG_CONNECTION = "plug-connection";
|
||||
public static final String CHARGE_STATUS = "charge";
|
||||
public static final String CHARGE_INFO = "charge-info";
|
||||
public static final String MOTION = "motion";
|
||||
public static final String LAST_UPDATE = "last-update";
|
||||
public static final String RAW = "raw";
|
||||
static final String DOORS = "doors";
|
||||
static final String WINDOWS = "windows";
|
||||
static final String LOCK = "lock";
|
||||
static final String SERVICE_DATE = "service-date";
|
||||
static final String SERVICE_MILEAGE = "service-mileage";
|
||||
static final String CHECK_CONTROL = "check-control";
|
||||
static final String PLUG_CONNECTION = "plug-connection";
|
||||
static final String CHARGE_STATUS = "charge";
|
||||
static final String CHARGE_REMAINING = "charge-remaining";
|
||||
static final String LAST_UPDATE = "last-update";
|
||||
static final String LAST_FETCHED = "last-fetched";
|
||||
static final String RAW = "raw";
|
||||
|
||||
// Door Details
|
||||
public static final String DOOR_DRIVER_FRONT = "driver-front";
|
||||
public static final String DOOR_DRIVER_REAR = "driver-rear";
|
||||
public static final String DOOR_PASSENGER_FRONT = "passenger-front";
|
||||
public static final String DOOR_PASSENGER_REAR = "passenger-rear";
|
||||
public static final String HOOD = "hood";
|
||||
public static final String TRUNK = "trunk";
|
||||
public static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
|
||||
public static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
|
||||
public static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
|
||||
public static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
|
||||
public static final String WINDOW_REAR = "win-rear";
|
||||
public static final String SUNROOF = "sunroof";
|
||||
static final String DOOR_DRIVER_FRONT = "driver-front";
|
||||
static final String DOOR_DRIVER_REAR = "driver-rear";
|
||||
static final String DOOR_PASSENGER_FRONT = "passenger-front";
|
||||
static final String DOOR_PASSENGER_REAR = "passenger-rear";
|
||||
static final String HOOD = "hood";
|
||||
static final String TRUNK = "trunk";
|
||||
static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
|
||||
static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
|
||||
static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
|
||||
static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
|
||||
static final String WINDOW_REAR = "win-rear";
|
||||
static final String SUNROOF = "sunroof";
|
||||
|
||||
// Charge Profile
|
||||
public static final String CHARGE_PROFILE_CLIMATE = "climate";
|
||||
public static final String CHARGE_PROFILE_MODE = "mode";
|
||||
public static final String CHARGE_PROFILE_PREFERENCE = "prefs";
|
||||
public static final String CHARGE_PROFILE_CONTROL = "control";
|
||||
public static final String CHARGE_PROFILE_TARGET = "target";
|
||||
public static final String CHARGE_PROFILE_LIMIT = "limit";
|
||||
public static final String CHARGE_WINDOW_START = "window-start";
|
||||
public static final String CHARGE_WINDOW_END = "window-end";
|
||||
public static final String CHARGE_TIMER1 = "timer1";
|
||||
public static final String CHARGE_TIMER2 = "timer2";
|
||||
public static final String CHARGE_TIMER3 = "timer3";
|
||||
public static final String CHARGE_TIMER4 = "timer4";
|
||||
public static final String CHARGE_DEPARTURE = "-departure";
|
||||
public static final String CHARGE_ENABLED = "-enabled";
|
||||
public static final String CHARGE_DAY_MON = "-day-mon";
|
||||
public static final String CHARGE_DAY_TUE = "-day-tue";
|
||||
public static final String CHARGE_DAY_WED = "-day-wed";
|
||||
public static final String CHARGE_DAY_THU = "-day-thu";
|
||||
public static final String CHARGE_DAY_FRI = "-day-fri";
|
||||
public static final String CHARGE_DAY_SAT = "-day-sat";
|
||||
public static final String CHARGE_DAY_SUN = "-day-sun";
|
||||
static final String CHARGE_PROFILE_CLIMATE = "climate";
|
||||
static final String CHARGE_PROFILE_MODE = "mode";
|
||||
static final String CHARGE_PROFILE_PREFERENCE = "prefs";
|
||||
static final String CHARGE_PROFILE_CONTROL = "control";
|
||||
static final String CHARGE_PROFILE_TARGET = "target";
|
||||
static final String CHARGE_PROFILE_LIMIT = "limit";
|
||||
static final String CHARGE_WINDOW_START = "window-start";
|
||||
static final String CHARGE_WINDOW_END = "window-end";
|
||||
static final String CHARGE_TIMER1 = "timer1";
|
||||
static final String CHARGE_TIMER2 = "timer2";
|
||||
static final String CHARGE_TIMER3 = "timer3";
|
||||
static final String CHARGE_TIMER4 = "timer4";
|
||||
static final String CHARGE_DEPARTURE = "-departure";
|
||||
static final String CHARGE_ENABLED = "-enabled";
|
||||
static final String CHARGE_DAY_MON = "-day-mon";
|
||||
static final String CHARGE_DAY_TUE = "-day-tue";
|
||||
static final String CHARGE_DAY_WED = "-day-wed";
|
||||
static final String CHARGE_DAY_THU = "-day-thu";
|
||||
static final String CHARGE_DAY_FRI = "-day-fri";
|
||||
static final String CHARGE_DAY_SAT = "-day-sat";
|
||||
static final String CHARGE_DAY_SUN = "-day-sun";
|
||||
|
||||
// Range
|
||||
public static final String RANGE_ELECTRIC = "electric";
|
||||
public static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
|
||||
public static final String RANGE_FUEL = "fuel";
|
||||
public static final String RANGE_RADIUS_FUEL = "radius-fuel";
|
||||
public static final String RANGE_HYBRID = "hybrid";
|
||||
public static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
|
||||
public static final String REMAINING_FUEL = "remaining-fuel";
|
||||
public static final String SOC = "soc";
|
||||
static final String RANGE_ELECTRIC = "electric";
|
||||
static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
|
||||
static final String RANGE_FUEL = "fuel";
|
||||
static final String RANGE_RADIUS_FUEL = "radius-fuel";
|
||||
static final String RANGE_HYBRID = "hybrid";
|
||||
static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
|
||||
static final String REMAINING_FUEL = "remaining-fuel";
|
||||
static final String ESTIMATED_FUEL_L_100KM = "estimated-fuel-l-100km";
|
||||
static final String ESTIMATED_FUEL_MPG = "estimated-fuel-mpg";
|
||||
static final String SOC = "soc";
|
||||
|
||||
// Image
|
||||
public static final String IMAGE_FORMAT = "png";
|
||||
public static final String IMAGE_VIEWPORT = "view";
|
||||
static final String IMAGE_FORMAT = "png";
|
||||
static final String IMAGE_VIEWPORT = "view";
|
||||
|
||||
// Remote Services
|
||||
public static final String REMOTE_SERVICE_LIGHT_FLASH = "light-flash";
|
||||
public static final String REMOTE_SERVICE_VEHICLE_FINDER = "vehicle-finder";
|
||||
public static final String REMOTE_SERVICE_DOOR_LOCK = "door-lock";
|
||||
public static final String REMOTE_SERVICE_DOOR_UNLOCK = "door-unlock";
|
||||
public static final String REMOTE_SERVICE_HORN = "horn-blow";
|
||||
public static final String REMOTE_SERVICE_AIR_CONDITIONING_START = "climate-now-start";
|
||||
public static final String REMOTE_SERVICE_AIR_CONDITIONING_STOP = "climate-now-stop";
|
||||
static final String REMOTE_SERVICE_LIGHT_FLASH = "light-flash";
|
||||
static final String REMOTE_SERVICE_VEHICLE_FINDER = "vehicle-finder";
|
||||
static final String REMOTE_SERVICE_DOOR_LOCK = "door-lock";
|
||||
static final String REMOTE_SERVICE_DOOR_UNLOCK = "door-unlock";
|
||||
static final String REMOTE_SERVICE_HORN = "horn-blow";
|
||||
static final String REMOTE_SERVICE_AIR_CONDITIONING_START = "climate-now-start";
|
||||
static final String REMOTE_SERVICE_AIR_CONDITIONING_STOP = "climate-now-stop";
|
||||
static final String REMOTE_SERVICE_CHARGE = "charge-now";
|
||||
|
||||
public static final String REMOTE_SERVICE_COMMAND = "command";
|
||||
public static final String REMOTE_STATE = "state";
|
||||
static final String REMOTE_SERVICE_COMMAND = "command";
|
||||
static final String REMOTE_STATE = "state";
|
||||
|
||||
// TIRES
|
||||
public static final String FRONT_LEFT_CURRENT = "fl-current";
|
||||
public static final String FRONT_LEFT_TARGET = "fl-target";
|
||||
public static final String FRONT_RIGHT_CURRENT = "fr-current";
|
||||
public static final String FRONT_RIGHT_TARGET = "fr-target";
|
||||
public static final String REAR_LEFT_CURRENT = "rl-current";
|
||||
public static final String REAR_LEFT_TARGET = "rl-target";
|
||||
public static final String REAR_RIGHT_CURRENT = "rr-current";
|
||||
public static final String REAR_RIGHT_TARGET = "rr-target";
|
||||
static final String FRONT_LEFT_CURRENT = "fl-current";
|
||||
static final String FRONT_LEFT_TARGET = "fl-target";
|
||||
static final String FRONT_RIGHT_CURRENT = "fr-current";
|
||||
static final String FRONT_RIGHT_TARGET = "fr-target";
|
||||
static final String REAR_LEFT_CURRENT = "rl-current";
|
||||
static final String REAR_LEFT_TARGET = "rl-target";
|
||||
static final String REAR_RIGHT_CURRENT = "rr-current";
|
||||
static final String REAR_RIGHT_TARGET = "rr-target";
|
||||
}
|
||||
|
@ -12,7 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUPPORTED_THING_SET;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.THING_TYPE_CONNECTED_DRIVE_ACCOUNT;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -21,6 +22,7 @@ import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
|
||||
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@ -37,6 +39,7 @@ import org.osgi.service.component.annotations.Reference;
|
||||
* handlers.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - changed localeProvider handling
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
|
||||
@ -44,15 +47,19 @@ public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private final LocationProvider locationProvider;
|
||||
private String localeLanguage;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
|
||||
@Activate
|
||||
public MyBMWHandlerFactory(final @Reference HttpClientFactory hcf, final @Reference MyBMWCommandOptionProvider cop,
|
||||
final @Reference LocaleProvider localeP, final @Reference LocationProvider locationP) {
|
||||
httpClientFactory = hcf;
|
||||
commandOptionProvider = cop;
|
||||
locationProvider = locationP;
|
||||
localeLanguage = localeP.getLocale().getLanguage().toLowerCase();
|
||||
public MyBMWHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference MyBMWCommandOptionProvider commandOptionProvider,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference LocationProvider locationProvider,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.commandOptionProvider = commandOptionProvider;
|
||||
this.locationProvider = locationProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,9 +71,10 @@ public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(thingTypeUID)) {
|
||||
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, localeLanguage);
|
||||
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, localeProvider);
|
||||
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
|
||||
return new VehicleHandler(thing, commandOptionProvider, locationProvider, thingTypeUID.getId());
|
||||
return new VehicleHandler(thing, commandOptionProvider, locationProvider, timeZoneProvider,
|
||||
thingTypeUID.getId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWVehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - renaming and refactoring to Java Beans
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWVehicleConfiguration {
|
||||
/**
|
||||
* Vehicle Identification Number (VIN)
|
||||
*/
|
||||
private String vin = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Vehicle brand
|
||||
* - bmw
|
||||
* - bmw_i
|
||||
* - mini
|
||||
*/
|
||||
private String vehicleBrand = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Data refresh rate in minutes
|
||||
*/
|
||||
private int refreshInterval = MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
|
||||
|
||||
/**
|
||||
* @return the vin
|
||||
*/
|
||||
public String getVin() {
|
||||
return vin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vin the vin to set
|
||||
*/
|
||||
public void setVin(String vin) {
|
||||
this.vin = vin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vehicleBrand
|
||||
*/
|
||||
public String getVehicleBrand() {
|
||||
return vehicleBrand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vehicleBrand the vehicleBrand to set
|
||||
*/
|
||||
public void setVehicleBrand(String vehicleBrand) {
|
||||
this.vehicleBrand = vehicleBrand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the refreshInterval
|
||||
*/
|
||||
public int getRefreshInterval() {
|
||||
return refreshInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refreshInterval the refreshInterval to set
|
||||
*/
|
||||
public void setRefreshInterval(int refreshInterval) {
|
||||
this.refreshInterval = refreshInterval;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MyBMWVehicleConfiguration [vin=" + vin + ", vehicleBrand=" + vehicleBrand + ", refreshInterval="
|
||||
+ refreshInterval + "]";
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link VehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleConfiguration {
|
||||
/**
|
||||
* Vehicle Identification Number (VIN)
|
||||
*/
|
||||
public String vin = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Vehicle brand
|
||||
* - bmw
|
||||
* - mini
|
||||
*/
|
||||
public String vehicleBrand = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Data refresh rate in minutes
|
||||
*/
|
||||
public int refreshInterval = MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
|
||||
}
|
@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.console;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.BINDING_ID;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.THING_TYPE_CONNECTED_DRIVE_ACCOUNT;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.ResponseContentAnonymizer;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWCommandExtension} is responsible for handling console commands
|
||||
*
|
||||
* @author Mark Herwege - Initial contribution
|
||||
* @author Martin Grassl - improved exception handling
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class MyBMWCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private static final String FINGERPRINT_ROOT_PATH = System.getProperty("user.home") + File.separator + BINDING_ID;
|
||||
|
||||
private static final String FINGERPRINT = "fingerprint";
|
||||
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(FINGERPRINT), false);
|
||||
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public MyBMWCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
||||
super("mybmw", "Interact with the MyBMW binding");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if ((args.length < 1) || (args.length > 3)) {
|
||||
console.println("Invalid number of arguments");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
|
||||
List<MyBMWBridgeHandler> bridgeHandlers = thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
|
||||
.map(b -> ((MyBMWBridgeHandler) b.getHandler())).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (bridgeHandlers.isEmpty()) {
|
||||
console.println("No account bridges configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FINGERPRINT.equalsIgnoreCase(args[0])) {
|
||||
console.println("Unsupported command '" + args[0] + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
|
||||
List<MyBMWBridgeHandler> handlers;
|
||||
if (args.length > 1) {
|
||||
handlers = bridgeHandlers.stream()
|
||||
.filter(b -> args[1].equalsIgnoreCase(b.getThing().getConfiguration().get("userName").toString()))
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (handlers.isEmpty()) {
|
||||
console.println("No myBMW account bridge for user '" + args[1] + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
handlers = bridgeHandlers;
|
||||
}
|
||||
|
||||
String basePath = FINGERPRINT_ROOT_PATH + File.separator
|
||||
+ LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
String path = nextPath(basePath, null);
|
||||
|
||||
console.println("# Start fingerprint");
|
||||
int accountNdx = 0;
|
||||
for (MyBMWBridgeHandler handler : handlers) {
|
||||
accountNdx++;
|
||||
console.println("### Account " + String.valueOf(accountNdx));
|
||||
if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
|
||||
console.println("MyBMW bridge for account not online, cannot create fingerprint");
|
||||
} else {
|
||||
String accountPath = path + File.separator + "Account-" + String.valueOf(accountNdx);
|
||||
handler.getMyBmwProxy().ifPresentOrElse(prox -> {
|
||||
// get list of vehicles
|
||||
List<@NonNull VehicleBase> vehicles = null;
|
||||
try {
|
||||
vehicles = prox.requestVehiclesBase();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
console.println("###### Vehicles base for brand " + brand);
|
||||
printAndSave(console, accountPath, "VehicleBase_" + brand,
|
||||
prox.requestVehiclesBaseJson(brand));
|
||||
}
|
||||
|
||||
if (args.length == 3) {
|
||||
Optional<VehicleBase> vehicleOptional = vehicles.stream()
|
||||
.filter(v -> v.getVin().equalsIgnoreCase(args[2])).findAny();
|
||||
if (vehicleOptional.isEmpty()) {
|
||||
console.println("'" + args[2] + "' is not a valid vin on the account bridge with id '"
|
||||
+ handler.getThing().getUID().getId() + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
vehicles = List.of(vehicleOptional.get());
|
||||
}
|
||||
|
||||
int vinNdx = 0;
|
||||
for (VehicleBase vehicleBase : vehicles) {
|
||||
vinNdx++;
|
||||
String vinPath = accountPath + File.separator + "Vin-" + String.valueOf(vinNdx);
|
||||
console.println("###### Vehicle " + String.valueOf(vinNdx));
|
||||
|
||||
// get state
|
||||
console.println("######## Vehicle state");
|
||||
printAndSave(console, vinPath, "VehicleState", prox.requestVehicleStateJson(
|
||||
vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
// get charge statistics -> only successful for electric vehicles
|
||||
console.println("######### Vehicle charging statistics");
|
||||
printAndSave(console, vinPath, "VehicleChargingStatistics",
|
||||
prox.requestChargeStatisticsJson(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
// get charge sessions -> only successful for electric vehicles
|
||||
console.println("######### Vehicle charging sessions");
|
||||
printAndSave(console, vinPath, "VehicleChargingSessions", prox.requestChargeSessionsJson(
|
||||
vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
console.println("###### End vehicle " + String.valueOf(vinNdx));
|
||||
}
|
||||
} catch (NetworkException e) {
|
||||
console.println("Fingerprint failed, network exception: " + e.getReason());
|
||||
}
|
||||
}, () -> {
|
||||
console.println("MyBMW bridge with id '" + handler.getThing().getUID().getId()
|
||||
+ "', communication not started, cannot retrieve fingerprint");
|
||||
});
|
||||
}
|
||||
console.println("### End account " + String.valueOf(accountNdx));
|
||||
}
|
||||
|
||||
try {
|
||||
String zipfile = nextPath(basePath, "zip");
|
||||
zipDirectory(Paths.get(path), Paths.get(zipfile));
|
||||
deleteDirectory(path);
|
||||
console.println("### Fingerprint has been written to zipfile: " + zipfile);
|
||||
} catch (IOException e) {
|
||||
console.println("Exception zipping fingerprint: " + e.getMessage());
|
||||
console.println("### Fingerprint has been written to files in directory: " + path);
|
||||
}
|
||||
|
||||
console.println("# End fingerprint");
|
||||
}
|
||||
|
||||
private void printAndSave(Console console, String path, String filename, String content) throws NetworkException {
|
||||
String json = prettyJson(ResponseContentAnonymizer.anonymizeResponseContent(content));
|
||||
console.println(json);
|
||||
try {
|
||||
writeJsonToFile(path, filename, json);
|
||||
} catch (IOException e) {
|
||||
console.println("Exception writing to file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String nextPath(String pathString, @Nullable String extension) {
|
||||
String path = pathString + ((extension != null) ? ("." + extension) : "");
|
||||
int pathNdx = 1;
|
||||
while (Files.exists(Paths.get(path))) {
|
||||
path = pathString + "_" + String.valueOf(pathNdx) + ((extension != null) ? ("." + extension) : "");
|
||||
pathNdx++;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String prettyJson(String json) {
|
||||
try {
|
||||
return GSON.toJson(JsonParser.parseString(json));
|
||||
} catch (JsonSyntaxException e) {
|
||||
// Keep the unformatted json if there is a syntax exception
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeJsonToFile(String pathString, String filename, String json) throws IOException {
|
||||
try {
|
||||
JsonElement element = JsonParser.parseString(json);
|
||||
if (element.isJsonNull() || (element.isJsonArray() && ((JsonArray) element).size() == 0)) {
|
||||
// Don't write a file if empty
|
||||
return;
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
// Just continue and write the file with non-valid json anyway
|
||||
}
|
||||
|
||||
String path = nextPath(pathString + File.separator + filename, "json");
|
||||
|
||||
// ensure full path exists
|
||||
File file = new File(path);
|
||||
file.getParentFile().mkdirs();
|
||||
|
||||
final byte[] contents = json.getBytes(StandardCharsets.UTF_8);
|
||||
Files.write(file.toPath(), contents);
|
||||
}
|
||||
|
||||
// Stackoverflow:
|
||||
// https://stackoverflow.com/questions/57997257/how-can-i-zip-a-complete-directory-with-all-subfolders-in-java
|
||||
private void zipDirectory(Path sourceDirectoryPath, Path zipPath) throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
Files.walkFileTree(sourceDirectoryPath, new SimpleFileVisitor<@Nullable Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(@Nullable Path file, @Nullable BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
zos.putNextEntry(new ZipEntry(sourceDirectoryPath.relativize(file).toString()));
|
||||
Files.copy(file, zos);
|
||||
zos.closeEntry();
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteDirectory(String path) throws IOException {
|
||||
Files.walk(Paths.get(path)).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(
|
||||
new String[] { buildCommandUsage(FINGERPRINT, "generate fingerprint for all vehicles on all accounts"),
|
||||
buildCommandUsage(FINGERPRINT + " <userName>", "generate fingerprint for vehicles on account"),
|
||||
buildCommandUsage(FINGERPRINT + " <userName> <vin>",
|
||||
"generate fingerprint for vehicle with vin on account") });
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
try {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 1) {
|
||||
return new StringsCompleter(
|
||||
thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
|
||||
.map(t -> t.getConfiguration().get("userName").toString()).collect(Collectors.toList()),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 2) {
|
||||
MyBMWBridgeHandler handler = (MyBMWBridgeHandler) thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID())
|
||||
&& args[1].equals(t.getConfiguration().get("userName")))
|
||||
.map(t -> t.getHandler()).findAny().get();
|
||||
List<VehicleBase> vehicles = handler.getMyBmwProxy().get().requestVehiclesBase();
|
||||
return new StringsCompleter(
|
||||
vehicles.stream().map(v -> v.getVin()).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
} catch (NoSuchElementException | NetworkException e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -12,27 +12,28 @@
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUPPORTED_THING_SET;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleAttributes;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleCapabilities;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.RemoteServiceHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
@ -40,130 +41,34 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleDiscovery} requests data from BMW API and is identifying the Vehicles after response
|
||||
* The {@link VehicleDiscovery} requests data from BMW API and is identifying
|
||||
* the Vehicles after response
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(VehicleDiscovery.class);
|
||||
public static final String SUPPORTED_SUFFIX = "Supported";
|
||||
public static final String ENABLE_SUFFIX = "Enable";
|
||||
public static final String ENABLED_SUFFIX = "Enabled";
|
||||
public class VehicleDiscovery extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
|
||||
|
||||
private static final int DISCOVERY_TIMEOUT = 10;
|
||||
|
||||
private Optional<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
|
||||
private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
|
||||
private Optional<ThingUID> bridgeUid = Optional.empty();
|
||||
|
||||
public VehicleDiscovery() {
|
||||
super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
|
||||
}
|
||||
|
||||
public void onResponse(List<Vehicle> vehicleList) {
|
||||
bridgeHandler.ifPresent(bridge -> {
|
||||
final ThingUID bridgeUID = bridge.getThing().getUID();
|
||||
vehicleList.forEach(vehicle -> {
|
||||
// the DriveTrain field in the delivered json is defining the Vehicle Type
|
||||
String vehicleType = VehicleStatusUtils.vehicleType(vehicle.driveTrain, vehicle.model).toString();
|
||||
SUPPORTED_THING_SET.forEach(entry -> {
|
||||
if (entry.getId().equals(vehicleType)) {
|
||||
ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
// Vehicle Properties
|
||||
properties.put("vehicleModel", vehicle.model);
|
||||
properties.put("vehicleDriveTrain", vehicle.driveTrain);
|
||||
properties.put("vehicleConstructionYear", Integer.toString(vehicle.year));
|
||||
properties.put("vehicleBodytype", vehicle.bodyType);
|
||||
|
||||
properties.put("servicesSupported", getServices(vehicle, SUPPORTED_SUFFIX, true));
|
||||
properties.put("servicesUnsupported", getServices(vehicle, SUPPORTED_SUFFIX, false));
|
||||
String servicesEnabled = getServices(vehicle, ENABLED_SUFFIX, true) + Constants.SEMICOLON
|
||||
+ getServices(vehicle, ENABLE_SUFFIX, true);
|
||||
properties.put("servicesEnabled", servicesEnabled.trim());
|
||||
String servicesDisabled = getServices(vehicle, ENABLED_SUFFIX, false) + Constants.SEMICOLON
|
||||
+ getServices(vehicle, ENABLE_SUFFIX, false);
|
||||
properties.put("servicesDisabled", servicesDisabled.trim());
|
||||
|
||||
// For RemoteServices we need to do it step-by-step
|
||||
StringBuffer remoteServicesEnabled = new StringBuffer();
|
||||
StringBuffer remoteServicesDisabled = new StringBuffer();
|
||||
if (vehicle.capabilities.lock.isEnabled) {
|
||||
remoteServicesEnabled.append(
|
||||
RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(
|
||||
RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicle.capabilities.unlock.isEnabled) {
|
||||
remoteServicesEnabled.append(
|
||||
RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(
|
||||
RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicle.capabilities.lights.isEnabled) {
|
||||
remoteServicesEnabled.append(
|
||||
RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(
|
||||
RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicle.capabilities.horn.isEnabled) {
|
||||
remoteServicesEnabled.append(
|
||||
RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(
|
||||
RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicle.capabilities.vehicleFinder.isEnabled) {
|
||||
remoteServicesEnabled.append(
|
||||
RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(
|
||||
RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicle.capabilities.climateNow.isEnabled) {
|
||||
remoteServicesEnabled.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
|
||||
+ Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled
|
||||
.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
|
||||
+ Constants.SEMICOLON);
|
||||
}
|
||||
properties.put("remoteServicesEnabled", remoteServicesEnabled.toString().trim());
|
||||
properties.put("remoteServicesDisabled", remoteServicesDisabled.toString().trim());
|
||||
|
||||
// Update Properties for already created Things
|
||||
bridge.getThing().getThings().forEach(vehicleThing -> {
|
||||
Configuration c = vehicleThing.getConfiguration();
|
||||
if (c.containsKey(MyBMWConstants.VIN)) {
|
||||
String thingVIN = c.get(MyBMWConstants.VIN).toString();
|
||||
if (vehicle.vin.equals(thingVIN)) {
|
||||
vehicleThing.setProperties(properties);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Properties needed for functional Thing
|
||||
properties.put(MyBMWConstants.VIN, vehicle.vin);
|
||||
properties.put("vehicleBrand", vehicle.brand);
|
||||
properties.put("refreshInterval",
|
||||
Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
|
||||
|
||||
String vehicleLabel = vehicle.brand + " " + vehicle.model;
|
||||
Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
|
||||
.withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
|
||||
.withProperties(convertedProperties).build());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
super(MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof MyBMWBridgeHandler bmwBridgeHandler) {
|
||||
logger.trace("VehicleDiscovery.setThingHandler for MybmwBridge");
|
||||
bridgeHandler = Optional.of(bmwBridgeHandler);
|
||||
bridgeHandler.get().setDiscoveryService(this);
|
||||
bridgeHandler.get().setVehicleDiscovery(this);
|
||||
bridgeUid = Optional.of(bridgeHandler.get().getThing().getUID());
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,50 +79,154 @@ public class VehicleDiscovery extends AbstractDiscoveryService implements Discov
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
bridgeHandler.ifPresent(MyBMWBridgeHandler::requestVehicles);
|
||||
logger.trace("VehicleDiscovery.startScan");
|
||||
discoverVehicles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
logger.trace("VehicleDiscovery.deactivate");
|
||||
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
public static String getServices(Vehicle vehicle, String suffix, boolean enabled) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
List<String> l = getObject(vehicle.capabilities, enabled);
|
||||
for (String capEntry : l) {
|
||||
// remove "is" prefix
|
||||
String cut = capEntry.substring(2);
|
||||
if (cut.endsWith(suffix)) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(Constants.SEMICOLON);
|
||||
public void discoverVehicles() {
|
||||
logger.trace("VehicleDiscovery.discoverVehicles");
|
||||
|
||||
myBMWProxy = bridgeHandler.get().getMyBmwProxy();
|
||||
|
||||
try {
|
||||
Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
|
||||
try {
|
||||
return prox.requestVehicles();
|
||||
} catch (NetworkException e) {
|
||||
throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
|
||||
}
|
||||
sb.append(cut.substring(0, cut.length() - suffix.length()));
|
||||
}
|
||||
});
|
||||
vehicleList.ifPresentOrElse(vehicles -> {
|
||||
bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoverySuccess());
|
||||
processVehicles(vehicles);
|
||||
}, () -> bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError()));
|
||||
} catch (IllegalStateException ex) {
|
||||
bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all field names from a DTO with a specific value
|
||||
* Used to get e.g. all services which are "ACTIVATED"
|
||||
*
|
||||
* @param dto Object
|
||||
* @param compare String which needs to map with the value
|
||||
* @return String with all field names matching this value separated with Spaces
|
||||
* this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
|
||||
*
|
||||
* it iterates through the list of existing things and checks if the vehicles found via the API
|
||||
* call are already known to OH. If not, it creates a new thing and puts it into the inbox
|
||||
*
|
||||
* @param vehicleList
|
||||
*/
|
||||
public static List<String> getObject(Object dto, Object compare) {
|
||||
List<String> l = new ArrayList<String>();
|
||||
for (Field field : dto.getClass().getDeclaredFields()) {
|
||||
try {
|
||||
Object value = field.get(dto);
|
||||
if (compare.equals(value)) {
|
||||
l.add(field.getName());
|
||||
private void processVehicles(List<Vehicle> vehicleList) {
|
||||
logger.trace("VehicleDiscovery.processVehicles");
|
||||
|
||||
vehicleList.forEach(vehicle -> {
|
||||
// the DriveTrain field in the delivered json is defining the Vehicle Type
|
||||
String vehicleType = VehicleStatusUtils
|
||||
.vehicleType(vehicle.getVehicleBase().getAttributes().getDriveTrain(),
|
||||
vehicle.getVehicleBase().getAttributes().getModel())
|
||||
.toString();
|
||||
MyBMWConstants.SUPPORTED_THING_SET.forEach(entry -> {
|
||||
if (entry.getId().equals(vehicleType)) {
|
||||
ThingUID uid = new ThingUID(entry, vehicle.getVehicleBase().getVin(), bridgeUid.get().getId());
|
||||
|
||||
Map<String, String> properties = generateProperties(vehicle);
|
||||
|
||||
boolean thingFound = false;
|
||||
// Update Properties for already created Things
|
||||
List<Thing> vehicleThings = bridgeHandler.get().getThing().getThings();
|
||||
for (Thing vehicleThing : vehicleThings) {
|
||||
Configuration configuration = vehicleThing.getConfiguration();
|
||||
|
||||
if (configuration.containsKey(MyBMWConstants.VIN)) {
|
||||
String thingVIN = configuration.get(MyBMWConstants.VIN).toString();
|
||||
if (vehicle.getVehicleBase().getVin().equals(thingVIN)) {
|
||||
vehicleThing.setProperties(properties);
|
||||
thingFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the vehicle found is not yet known to OH, so put it into the inbox
|
||||
if (!thingFound) {
|
||||
// Properties needed for functional Thing
|
||||
VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
|
||||
Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
|
||||
convertedProperties.put(MyBMWConstants.VIN, vehicle.getVehicleBase().getVin());
|
||||
convertedProperties.put(MyBMWConstants.VEHICLE_BRAND, vehicleAttributes.getBrand());
|
||||
convertedProperties.put(MyBMWConstants.REFRESH_INTERVAL,
|
||||
Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
|
||||
|
||||
String vehicleLabel = vehicleAttributes.getBrand() + " " + vehicleAttributes.getModel();
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUid.get())
|
||||
.withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
|
||||
.withProperties(convertedProperties).build());
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
LOGGER.debug("Field {} not found {}", compare, e.getMessage());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, String> generateProperties(Vehicle vehicle) {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
// Vehicle Properties
|
||||
VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
|
||||
properties.put(MyBMWConstants.VEHICLE_MODEL, vehicleAttributes.getModel());
|
||||
properties.put(MyBMWConstants.VEHICLE_DRIVE_TRAIN, vehicleAttributes.getDriveTrain());
|
||||
properties.put(MyBMWConstants.VEHICLE_CONSTRUCTION_YEAR, Integer.toString(vehicleAttributes.getYear()));
|
||||
properties.put(MyBMWConstants.VEHICLE_BODYTYPE, vehicleAttributes.getBodyType());
|
||||
|
||||
VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
|
||||
|
||||
properties.put(MyBMWConstants.SERVICES_SUPPORTED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, true));
|
||||
properties.put(MyBMWConstants.SERVICES_UNSUPPORTED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, false));
|
||||
properties.put(MyBMWConstants.SERVICES_ENABLED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, true));
|
||||
properties.put(MyBMWConstants.SERVICES_DISABLED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, false));
|
||||
|
||||
// For RemoteServices we need to do it step-by-step
|
||||
StringBuffer remoteServicesEnabled = new StringBuffer();
|
||||
StringBuffer remoteServicesDisabled = new StringBuffer();
|
||||
if (vehicleCapabilities.isLock()) {
|
||||
remoteServicesEnabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
return l;
|
||||
if (vehicleCapabilities.isUnlock()) {
|
||||
remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isLights()) {
|
||||
remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isHorn()) {
|
||||
remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isVehicleFinder()) {
|
||||
remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isVehicleFinder()) {
|
||||
remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
|
||||
properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.List;
|
||||
* The {@link AuthQueryResponse} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - add toString for debugging
|
||||
*/
|
||||
public class AuthQueryResponse {
|
||||
public String clientName;// ": "mybmwapp",
|
||||
@ -47,4 +48,17 @@ public class AuthQueryResponse {
|
||||
// "authenticate_user"
|
||||
// ],
|
||||
public List<String> promptValues; // ": ["login"]
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthQueryResponse [clientName=" + clientName + ", clientSecret=" + clientSecret + ", clientId="
|
||||
+ clientId + ", gcdmBaseUrl=" + gcdmBaseUrl + ", returnUrl=" + returnUrl + ", brand=" + brand
|
||||
+ ", language=" + language + ", country=" + country + ", authorizationEndpoint=" + authorizationEndpoint
|
||||
+ ", tokenEndpoint=" + tokenEndpoint + ", scopes=" + scopes + ", promptValues=" + promptValues + "]";
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargeProfile} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
*/
|
||||
public class ChargeProfile {
|
||||
public static final Timer INVALID_TIMER = new Timer();
|
||||
|
||||
public ChargingWindow reductionOfChargeCurrent;
|
||||
public String chargingMode;// ": "immediateCharging",
|
||||
public String chargingPreference;// ": "chargingWindow",
|
||||
public String chargingControlType;// ": "weeklyPlanner",
|
||||
public List<Timer> departureTimes;
|
||||
public boolean climatisationOn;// ": false,
|
||||
public ChargingSettings chargingSettings;
|
||||
|
||||
public Timer getTimerId(int id) {
|
||||
if (departureTimes != null) {
|
||||
for (Timer t : departureTimes) {
|
||||
if (t.id == id) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return INVALID_TIMER;
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargeSession} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargeSession {
|
||||
public String id;// ": "2021-12-26T16:57:20Z_128fa4af",
|
||||
public String title;// ": "Gestern 17:57",
|
||||
public String subtitle;// ": "Uferstraße 4B • 7h 45min • -- EUR",
|
||||
public String energyCharged;// ": "~ 31 kWh",
|
||||
public String sessionStatus;// ": "FINISHED",
|
||||
public String issues;// ": "2 Probleme",
|
||||
public String isPublic;// ": false
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargeSessions} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargeSessions {
|
||||
public String total;// ": "~ 218 kWh",
|
||||
public String numberOfSessions;// ": "17",
|
||||
public String chargingListState;// ": "HAS_SESSIONS",
|
||||
public List<ChargeSession> sessions;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargeStatistics} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargeStatistics {
|
||||
public int totalEnergyCharged;// ": 173,
|
||||
public String totalEnergyChargedSemantics;// ": "Insgesamt circa 173 Kilowattstunden geladen",
|
||||
public String symbol;// ": "~",
|
||||
public int numberOfChargingSessions;// ": 13,
|
||||
public String numberOfChargingSessionsSemantics;// ": "13 Ladevorgänge"
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargeStatisticsContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargeStatisticsContainer {
|
||||
public String description;// ": "Dezember 2021",
|
||||
public String optStateType;// ": "OPT_IN_WITH_SESSIONS",
|
||||
public ChargeStatistics statistics;// ": {
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargingProfile} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingProfile {
|
||||
private ChargingWindow reductionOfChargeCurrent = new ChargingWindow();
|
||||
private String chargingMode = "";// ": "immediateCharging",
|
||||
private String chargingPreference = "";// ": "chargingWindow",
|
||||
private String chargingControlType = "";// ": "weeklyPlanner",
|
||||
private List<Timer> departureTimes = new ArrayList<>();
|
||||
private boolean climatisationOn = false;// ": false,
|
||||
private ChargingSettings chargingSettings = new ChargingSettings();
|
||||
|
||||
public Timer getTimerId(int id) {
|
||||
if (departureTimes != null) {
|
||||
for (Timer t : departureTimes) {
|
||||
if (t.id == id) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Timer();
|
||||
}
|
||||
|
||||
public ChargingWindow getReductionOfChargeCurrent() {
|
||||
return reductionOfChargeCurrent;
|
||||
}
|
||||
|
||||
public String getChargingMode() {
|
||||
return chargingMode;
|
||||
}
|
||||
|
||||
public String getChargingPreference() {
|
||||
return chargingPreference;
|
||||
}
|
||||
|
||||
public String getChargingControlType() {
|
||||
return chargingControlType;
|
||||
}
|
||||
|
||||
public List<Timer> getDepartureTimes() {
|
||||
return departureTimes;
|
||||
}
|
||||
|
||||
public boolean isClimatisationOn() {
|
||||
return climatisationOn;
|
||||
}
|
||||
|
||||
public ChargingSettings getChargingSettings() {
|
||||
return chargingSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingProfile [reductionOfChargeCurrent=" + reductionOfChargeCurrent + ", chargingMode="
|
||||
+ chargingMode + ", chargingPreference=" + chargingPreference + ", chargingControlType="
|
||||
+ chargingControlType + ", departureTimes=" + departureTimes + ", climatisationOn=" + climatisationOn
|
||||
+ ", chargingSettings=" + chargingSettings + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSession} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingSession {
|
||||
private String id;// ": "2021-12-26T16:57:20Z_128fa4af",
|
||||
private String title;// ": "Gestern 17:57",
|
||||
private String subtitle;// ": "Uferstraße 4B • 7h 45min • -- EUR",
|
||||
private String energyCharged;// ": "~ 31 kWh",
|
||||
private String sessionStatus;// ": "FINISHED",
|
||||
private String issues;// ": "2 Probleme",
|
||||
private String isPublic;// ": false
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title the title to set
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the subtitle
|
||||
*/
|
||||
public String getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the energyCharged
|
||||
*/
|
||||
public String getEnergyCharged() {
|
||||
return energyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sessionStatus
|
||||
*/
|
||||
public String getSessionStatus() {
|
||||
return sessionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the issues
|
||||
*/
|
||||
public String getIssues() {
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isPublic
|
||||
*/
|
||||
public String getIsPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSession [id=" + id + ", title=" + title + ", subtitle=" + subtitle + ", energyCharged="
|
||||
+ energyCharged + ", sessionStatus=" + sessionStatus + ", issues=" + issues + ", isPublic=" + isPublic
|
||||
+ "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSessions} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingSessions {
|
||||
private String total;// ": "~ 218 kWh",
|
||||
private String numberOfSessions;// ": "17",
|
||||
private String chargingListState;// ": "HAS_SESSIONS",
|
||||
private List<ChargingSession> sessions;
|
||||
|
||||
/**
|
||||
* @return the total
|
||||
*/
|
||||
public String getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfSessions
|
||||
*/
|
||||
public String getNumberOfSessions() {
|
||||
return numberOfSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingListState
|
||||
*/
|
||||
public String getChargingListState() {
|
||||
return chargingListState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sessions
|
||||
*/
|
||||
public List<ChargingSession> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSessions [total=" + total + ", numberOfSessions=" + numberOfSessions + ", chargingListState="
|
||||
+ chargingListState + ", sessions=" + sessions + "]";
|
||||
}
|
||||
}
|
@ -13,11 +13,11 @@
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargeSessionsContainer} Data Transfer Object
|
||||
* The {@link ChargingSessionsContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargeSessionsContainer {
|
||||
public class ChargingSessionsContainer {
|
||||
public Object paginationInfo;
|
||||
public ChargeSessions chargingSessions;
|
||||
public ChargingSessions chargingSessions;
|
||||
}
|
@ -16,10 +16,58 @@ package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
* The {@link ChargingSettings} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingSettings {
|
||||
public int targetSoc;// ": 100,
|
||||
public boolean isAcCurrentLimitActive;// ": false,
|
||||
public String hospitality;// ": "NO_ACTION",
|
||||
public String idcc;// ": "NO_ACTION"
|
||||
private int acCurrentLimit = -1; // 32,
|
||||
private String hospitality = ""; // HOSP_INACTIVE,
|
||||
private String idcc = ""; // AUTOMATIC_INTELLIGENT,
|
||||
private boolean isAcCurrentLimitActive = false; // false,
|
||||
private int targetSoc = -1; // 80
|
||||
|
||||
public int getAcCurrentLimit() {
|
||||
return acCurrentLimit;
|
||||
}
|
||||
|
||||
public void setAcCurrentLimit(int acCurrentLimit) {
|
||||
this.acCurrentLimit = acCurrentLimit;
|
||||
}
|
||||
|
||||
public String getHospitality() {
|
||||
return hospitality;
|
||||
}
|
||||
|
||||
public void setHospitality(String hospitality) {
|
||||
this.hospitality = hospitality;
|
||||
}
|
||||
|
||||
public String getIdcc() {
|
||||
return idcc;
|
||||
}
|
||||
|
||||
public void setIdcc(String idcc) {
|
||||
this.idcc = idcc;
|
||||
}
|
||||
|
||||
public boolean isAcCurrentLimitActive() {
|
||||
return isAcCurrentLimitActive;
|
||||
}
|
||||
|
||||
public void setAcCurrentLimitActive(boolean isAcCurrentLimitActive) {
|
||||
this.isAcCurrentLimitActive = isAcCurrentLimitActive;
|
||||
}
|
||||
|
||||
public int getTargetSoc() {
|
||||
return targetSoc;
|
||||
}
|
||||
|
||||
public void setTargetSoc(int targetSoc) {
|
||||
this.targetSoc = targetSoc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSettings [acCurrentLimit=" + acCurrentLimit + ", hospitality=" + hospitality + ", idcc=" + idcc
|
||||
+ ", isAcCurrentLimitActive=" + isAcCurrentLimitActive + ", targetSoc=" + targetSoc + "]";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingStatistics} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
public class ChargingStatistics {
|
||||
private int totalEnergyCharged;// ": 173,
|
||||
private String totalEnergyChargedSemantics;// ": "Insgesamt circa 173 Kilowattstunden geladen",
|
||||
private String symbol;// ": "~",
|
||||
private int numberOfChargingSessions;// ": 13,
|
||||
private String numberOfChargingSessionsSemantics;// ": "13 Ladevorgänge"
|
||||
|
||||
/**
|
||||
* @return the totalEnergyCharged
|
||||
*/
|
||||
public int getTotalEnergyCharged() {
|
||||
return totalEnergyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalEnergyCharged the totalEnergyCharged to set
|
||||
*/
|
||||
public void setTotalEnergyCharged(int totalEnergyCharged) {
|
||||
this.totalEnergyCharged = totalEnergyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the totalEnergyChargedSemantics
|
||||
*/
|
||||
public String getTotalEnergyChargedSemantics() {
|
||||
return totalEnergyChargedSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalEnergyChargedSemantics the totalEnergyChargedSemantics to set
|
||||
*/
|
||||
public void setTotalEnergyChargedSemantics(String totalEnergyChargedSemantics) {
|
||||
this.totalEnergyChargedSemantics = totalEnergyChargedSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the symbol
|
||||
*/
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param symbol the symbol to set
|
||||
*/
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfChargingSessions
|
||||
*/
|
||||
public int getNumberOfChargingSessions() {
|
||||
return numberOfChargingSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numberOfChargingSessions the numberOfChargingSessions to set
|
||||
*/
|
||||
public void setNumberOfChargingSessions(int numberOfChargingSessions) {
|
||||
this.numberOfChargingSessions = numberOfChargingSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfChargingSessionsSemantics
|
||||
*/
|
||||
public String getNumberOfChargingSessionsSemantics() {
|
||||
return numberOfChargingSessionsSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numberOfChargingSessionsSemantics the numberOfChargingSessionsSemantics to set
|
||||
*/
|
||||
public void setNumberOfChargingSessionsSemantics(String numberOfChargingSessionsSemantics) {
|
||||
this.numberOfChargingSessionsSemantics = numberOfChargingSessionsSemantics;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingStatistics [totalEnergyCharged=" + totalEnergyCharged + ", totalEnergyChargedSemantics="
|
||||
+ totalEnergyChargedSemantics + ", symbol=" + symbol + ", numberOfChargingSessions="
|
||||
+ numberOfChargingSessions + ", numberOfChargingSessionsSemantics=" + numberOfChargingSessionsSemantics
|
||||
+ "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingStatisticsContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingStatisticsContainer {
|
||||
private String description;// ": "Dezember 2021",
|
||||
private String optStateType;// ": "OPT_IN_WITH_SESSIONS",
|
||||
private ChargingStatistics statistics;// ": {
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the optStateType
|
||||
*/
|
||||
public String getOptStateType() {
|
||||
return optStateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param optStateType the optStateType to set
|
||||
*/
|
||||
public void setOptStateType(String optStateType) {
|
||||
this.optStateType = optStateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the statistics
|
||||
*/
|
||||
public ChargingStatistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param statistics the statistics to set
|
||||
*/
|
||||
public void setStatistics(ChargingStatistics statistics) {
|
||||
this.statistics = statistics;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingStatisticsContainer [description=" + description + ", optStateType=" + optStateType
|
||||
+ ", statistics=" + statistics + "]";
|
||||
}
|
||||
}
|
@ -16,8 +16,30 @@ package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
* The {@link ChargingWindow} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingWindow {
|
||||
public Time start;
|
||||
public Time end;
|
||||
private Time start = new Time();
|
||||
private Time end = new Time();
|
||||
|
||||
public Time getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Time start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Time getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Time end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingWindow [start=" + start + ", end=" + end + "]";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class RemoteChargingCommands {
|
||||
private List<String> chargingControl = new ArrayList<>();
|
||||
private List<String> flapControl = new ArrayList<>();
|
||||
private List<String> plugControl = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @return the chargingControl
|
||||
*/
|
||||
public List<String> getChargingControl() {
|
||||
return chargingControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingControl the chargingControl to set
|
||||
*/
|
||||
public void setChargingControl(List<String> chargingControl) {
|
||||
this.chargingControl = chargingControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the flapControl
|
||||
*/
|
||||
public List<String> getFlapControl() {
|
||||
return flapControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param flapControl the flapControl to set
|
||||
*/
|
||||
public void setFlapControl(List<String> flapControl) {
|
||||
this.flapControl = flapControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the plugControl
|
||||
*/
|
||||
public List<String> getPlugControl() {
|
||||
return plugControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param plugControl the plugControl to set
|
||||
*/
|
||||
public void setPlugControl(List<String> plugControl) {
|
||||
this.plugControl = plugControl;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoteChargingCommands [chargingControl=" + chargingControl + ", flapControl=" + flapControl
|
||||
+ ", plugControl=" + plugControl + "]";
|
||||
}
|
||||
}
|
@ -12,20 +12,35 @@
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
|
||||
/**
|
||||
* The {@link Time} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Time {
|
||||
public int hour;// ": 11,
|
||||
public int minute;// ": 0
|
||||
private int hour = -1;// ": 11,
|
||||
private int minute = -1;// ": 0
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Converter.getTime(this);
|
||||
return "Time [hour=" + hour + ", minute=" + minute + "]";
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.network;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
|
||||
/**
|
||||
* The {@link NetworkError} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class NetworkError {
|
||||
public String url;
|
||||
public int status;
|
||||
public String reason;
|
||||
public String params;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder(url).append(Constants.HYPHEN).append(status).append(Constants.HYPHEN).append(reason)
|
||||
.append(params).toString();
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return Converter.getGson().toJson(this);
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link CBS} Data Transfer Object ConditionBasedService
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class CBS {
|
||||
public String type = Constants.NO_ENTRIES;// ": "BRAKE_FLUID",
|
||||
public String status = Constants.NO_ENTRIES;// ": "OK",
|
||||
public String dateTime;// ": "2023-11-01T00:00:00.000Z"
|
||||
public Distance distance;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link CCM} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class CCM {
|
||||
// [todo] [todo] definition currently unknown
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link ChargingState} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingState {
|
||||
public int chargePercentage;// ": 74,
|
||||
public String state;// ": "NOT_CHARGING",
|
||||
public String type;// ": "NOT_AVAILABLE",
|
||||
public boolean isChargerConnected;// ": false
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Coordinates} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Coordinates {
|
||||
public double latitude;
|
||||
public double longitude;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Distance} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Distance {
|
||||
public int value;// ": 31,
|
||||
public String units;// ": "KILOMETERS"
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Doors} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Doors {
|
||||
public String driverFront;// ": "CLOSED",
|
||||
public String driverRear;// ": "CLOSED",
|
||||
public String passengerFront;// ": "CLOSED",
|
||||
public String passengerRear;// ": "CLOSED"
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link DoorsWindows} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class DoorsWindows {
|
||||
public Doors doors;
|
||||
public Windows windows;
|
||||
public String trunk;// ": "CLOSED",
|
||||
public String hood;// ": "CLOSED",
|
||||
public String moonroof;// ": "CLOSED"
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link FuelLevel} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class FuelLevel {
|
||||
public int value;// ": 4,
|
||||
public String units;// ": "LITERS"
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Location} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Location {
|
||||
public Coordinates coordinates;
|
||||
public Address address;
|
||||
public int heading;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link Properties} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Properties {
|
||||
public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
|
||||
public boolean inMotion;// ": false,
|
||||
public boolean areDoorsLocked;// ": true,
|
||||
public String originCountryISO;// ": "DE",
|
||||
public boolean areDoorsClosed;// ": true,
|
||||
public boolean areDoorsOpen;// ": false,
|
||||
public boolean areWindowsClosed;// ": true,
|
||||
public DoorsWindows doorsAndWindows;// ":
|
||||
public boolean isServiceRequired;// ":false
|
||||
public FuelLevel fuelLevel;
|
||||
public ChargingState chargingState;// ":
|
||||
public Range combustionRange;
|
||||
public Range combinedRange;
|
||||
public Range electricRange;
|
||||
public Range electricRangeAndStatus;
|
||||
public List<CCM> checkControlMessages;
|
||||
public List<CBS> serviceRequired;
|
||||
public Location vehicleLocation;
|
||||
public Tires tires;
|
||||
// "climateControl":{} [todo] definition currently unknown
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Range} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Range {
|
||||
public int chargePercentage;
|
||||
public Distance distance;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Tire} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Tire {
|
||||
public TireStatus status;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link TireStatus} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class TireStatus {
|
||||
public double currentPressure;// ": 220,
|
||||
public String localizedCurrentPressure;// ": "2.2 bar",
|
||||
public String localizedTargetPressure;// ": "2.3 bar",
|
||||
public double targetPressure;// ": 230
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Tires} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Tires {
|
||||
public Tire frontLeft;
|
||||
public Tire frontRight;
|
||||
public Tire rearLeft;
|
||||
public Tire rearRight;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
|
||||
/**
|
||||
* The {@link Windows} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Windows {
|
||||
public String driverFront;// ": "CLOSED",
|
||||
public String driverRear;// ": "CLOSED",
|
||||
public String passengerFront;// ": "CLOSED",
|
||||
public String passengerRear;// ": "CLOSED"
|
||||
}
|
@ -22,7 +22,7 @@ public class ExecutionError {
|
||||
public String description;// ": "Die folgenden Einschränkungen verbieten die Ausführung von Remote Services: Aus
|
||||
// Sicherheitsgründen sind Remote Services nicht verfügbar, wenn die Fahrbereitschaft
|
||||
// eingeschaltet ist. Remote Services können nur mit einem ausreichenden Ladezustand
|
||||
// durchgeführt werden. Die Remote Services „Verriegeln“ und „Entriegeln“ können nur
|
||||
// durchgeführt werden. Die Remote Services „Verriegeln" und „Entriegeln" können nur
|
||||
// ausgeführt werden, wenn die Fahrertür geschlossen und der Türstatus bekannt ist.",
|
||||
public String presentationType;// ": "PAGE",
|
||||
public int iconId;// ": 60217,
|
||||
|
@ -16,10 +16,49 @@ package org.openhab.binding.mybmw.internal.dto.remote;
|
||||
* The {@link ExecutionStatusContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ExecutionStatusContainer {
|
||||
public String eventId;
|
||||
public String creationTime;
|
||||
public String eventStatus;
|
||||
public ExecutionError errorDetails;
|
||||
private String eventId = "";
|
||||
private String creationTime = "";
|
||||
private String eventStatus = "";
|
||||
private ExecutionError errorDetails = null;
|
||||
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
public String getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public void setCreationTime(String creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public String getEventStatus() {
|
||||
return eventStatus;
|
||||
}
|
||||
|
||||
public void setEventStatus(String eventStatus) {
|
||||
this.eventStatus = eventStatus;
|
||||
}
|
||||
|
||||
public ExecutionError getErrorDetails() {
|
||||
return errorDetails;
|
||||
}
|
||||
|
||||
public void setErrorDetails(ExecutionError errorDetails) {
|
||||
this.errorDetails = errorDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExecutionStatusContainer [eventId=" + eventId + ", creationTime=" + creationTime + ", eventStatus="
|
||||
+ eventStatus + ", errorDetails=" + errorDetails + "]";
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
/**
|
||||
* The {@link CBSMessage} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class CBSMessage {
|
||||
public String id;// ": "BrakeFluid",
|
||||
public String title;// ": "Brake fluid",
|
||||
public int iconId;// ": 60223,
|
||||
public String longDescription;// ": "Next service due by the specified date.",
|
||||
public String subtitle;// ": "Due in November 2023",
|
||||
public String criticalness;// ": "nonCritical"
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link CCMMessage} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class CCMMessage {
|
||||
public String criticalness;// ": "semiCritical",
|
||||
public int iconId;// ": 60217,
|
||||
public String state = Constants.NO_ENTRIES;// ": "Medium",
|
||||
public String title = Constants.NO_ENTRIES;// ": "Battery discharged: Start engine"
|
||||
public String id;// ": "229",
|
||||
public String longDescription = Constants.NO_ENTRIES;// ": "Charge by driving for longer periods or use external
|
||||
// charger. Functions requiring battery will be switched off.
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
/**
|
||||
* The {@link DoorWindow} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class DoorWindow {
|
||||
public int iconId;// ": 59757,
|
||||
public String title;// ": "Lock status",
|
||||
public String state;// ": "Locked",
|
||||
public String criticalness;// ": "nonCritical"
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
/**
|
||||
* The {@link FuelIndicator} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class FuelIndicator {
|
||||
public int mainBarValue;// ": 74,
|
||||
public String rangeUnits;// ": "km",
|
||||
public String rangeValue;// ": "76",
|
||||
public String levelUnits;// ": "%",
|
||||
public String levelValue;// ": "74",
|
||||
|
||||
public int secondaryBarValue;// ": 0,
|
||||
public int infoIconId;// ": 59694,
|
||||
public int rangeIconId;// ": 59683,
|
||||
public int levelIconId;// ": 59694,
|
||||
public boolean showsBar;// ": true,
|
||||
public boolean showBarGoal;// ": false,
|
||||
public String barType;// ": null,
|
||||
public String infoLabel;// ": "State of Charge",
|
||||
public boolean isInaccurate;// ": false,
|
||||
public boolean isCircleIcon;// ": false,
|
||||
public String iconOpacity;// ": "high",
|
||||
public String chargingType;// ": null,
|
||||
public String chargingStatusType;// ": "DEFAULT",
|
||||
public String chargingStatusIndicatorType;// ": "DEFAULT"
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
/**
|
||||
* The {@link Issues} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Issues {
|
||||
// [todo] definition currently unknown
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
/**
|
||||
* The {@link Mileage} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Mileage {
|
||||
public int mileage;// ": 31537,
|
||||
public String units;// ": "km",
|
||||
public String formattedMileage;// ": "31537"
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.status;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||
|
||||
/**
|
||||
* The {@link Status} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class Status {
|
||||
public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
|
||||
public Mileage currentMileage;
|
||||
public Issues issues;
|
||||
public String doorsGeneralState;// ":"Locked",
|
||||
public String checkControlMessagesGeneralState;// ":"No Issues",
|
||||
public List<DoorWindow> doorsAndWindows;// ":[
|
||||
public List<CCMMessage> checkControlMessages;//
|
||||
public List<CBSMessage> requiredServices;//
|
||||
// "recallMessages":[],
|
||||
// "recallExternalUrl":null,
|
||||
public List<FuelIndicator> fuelIndicators;
|
||||
public String timestampMessage;// ":"Updated from vehicle 12/21/2021 05:46 PM",
|
||||
public ChargeProfile chargingProfile;
|
||||
}
|
@ -10,13 +10,23 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Address} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Address {
|
||||
public String formatted;
|
||||
private String formatted = "";
|
||||
|
||||
public String getFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Address [formatted=" + formatted + "]";
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Capabilities} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
|
||||
public class Capabilities {
|
||||
public boolean isRemoteServicesBookingRequired;
|
||||
public boolean isRemoteServicesActivationRequired;
|
||||
public boolean isRemoteHistorySupported;
|
||||
public boolean canRemoteHistoryBeDeleted;
|
||||
public boolean isChargingHistorySupported;
|
||||
public boolean isScanAndChargeSupported;
|
||||
public boolean isDCSContractManagementSupported;
|
||||
public boolean isBmwChargingSupported;
|
||||
public boolean isMiniChargingSupported;
|
||||
public boolean isChargeNowForBusinessSupported;
|
||||
public boolean isDataPrivacyEnabled;
|
||||
public boolean isChargingPlanSupported;
|
||||
public boolean isChargingPowerLimitEnable;
|
||||
public boolean isChargingTargetSocEnable;
|
||||
public boolean isChargingLoudnessEnable;
|
||||
public boolean isChargingSettingsEnabled;
|
||||
public boolean isChargingHospitalityEnabled;
|
||||
public boolean isEvGoChargingSupported;
|
||||
public boolean isFindChargingEnabled;
|
||||
public boolean isCustomerEsimSupported;
|
||||
public boolean isCarSharingSupported;
|
||||
public boolean isEasyChargeSupported;
|
||||
|
||||
public RemoteService lock;
|
||||
public RemoteService unlock;
|
||||
public RemoteService lights;
|
||||
public RemoteService horn;
|
||||
public RemoteService vehicleFinder;
|
||||
public RemoteService sendPoi;
|
||||
public RemoteService climateNow;
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class CheckControlMessage {
|
||||
private String type = ""; // TIRE_PRESSURE,
|
||||
private String severity = ""; // LOW
|
||||
private int id = -1; // 955,
|
||||
private String description = ""; // Tire pressure notification: You can continue driving. Check tire pressure when
|
||||
// the tires are cold and adjust if necessary. Perform reset after adjustment. See
|
||||
// Owner's Manual for further information.
|
||||
private String name = ""; // Tire pressure notification
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public void setSeverity(String severity) {
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckControlMessage [type=" + type + ", severity=" + severity + ", id=" + id + ", description="
|
||||
+ description + ", name=" + name + "]";
|
||||
}
|
||||
}
|
@ -13,12 +13,24 @@
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link RemoteService} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class RemoteService {
|
||||
public boolean isEnabled;// ": true,
|
||||
public boolean isPinAuthenticationRequired;// ": false,
|
||||
public String executionMessage;// ": "Lock your vehicle now? Remote functions may take a few seconds."
|
||||
public class ClimateControlState {
|
||||
private String activity = ""; // INACTIVE
|
||||
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
public void setActivity(String activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClimateControlState [activity=" + activity + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class ClimateTimer {
|
||||
private boolean isWeeklyTimer = false; // true,
|
||||
private String timerAction = ""; // DEACTIVATE,
|
||||
private List<String> timerWeekDays = new ArrayList<>(); // [ MONDAY ]
|
||||
private DepartureTime departureTime = new DepartureTime();
|
||||
|
||||
public boolean isWeeklyTimer() {
|
||||
return isWeeklyTimer;
|
||||
}
|
||||
|
||||
public void setWeeklyTimer(boolean isWeeklyTimer) {
|
||||
this.isWeeklyTimer = isWeeklyTimer;
|
||||
}
|
||||
|
||||
public String getTimerAction() {
|
||||
return timerAction;
|
||||
}
|
||||
|
||||
public void setTimerAction(String timerAction) {
|
||||
this.timerAction = timerAction;
|
||||
}
|
||||
|
||||
public List<String> getTimerWeekDays() {
|
||||
return timerWeekDays;
|
||||
}
|
||||
|
||||
public void setTimerWeekDays(List<String> timerWeekDays) {
|
||||
this.timerWeekDays = timerWeekDays;
|
||||
}
|
||||
|
||||
public DepartureTime getDepartureTime() {
|
||||
return departureTime;
|
||||
}
|
||||
|
||||
public void setDepartureTime(DepartureTime departureTime) {
|
||||
this.departureTime = departureTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClimateTimer [isWeeklyTimer=" + isWeeklyTimer + ", timerAction=" + timerAction + ", timerWeekDays="
|
||||
+ timerWeekDays + ", departureTime=" + departureTime + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class CombustionFuelLevel {
|
||||
private int remainingFuelPercent = -1; // 65,
|
||||
private int remainingFuelLiters = -1; // 34,
|
||||
private int range = -1; // 435
|
||||
|
||||
public int getRemainingFuelPercent() {
|
||||
return remainingFuelPercent;
|
||||
}
|
||||
|
||||
public void setRemainingFuelPercent(int remainingFuelPercent) {
|
||||
this.remainingFuelPercent = remainingFuelPercent;
|
||||
}
|
||||
|
||||
public int getRemainingFuelLiters() {
|
||||
return remainingFuelLiters;
|
||||
}
|
||||
|
||||
public void setRemainingFuelLiters(int remainingFuelLiters) {
|
||||
this.remainingFuelLiters = remainingFuelLiters;
|
||||
}
|
||||
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public void setRange(int range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CombustionFuelLevel [remainingFuelPercent=" + remainingFuelPercent + ", remainingFuelLiters="
|
||||
+ remainingFuelLiters + ", range=" + range + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Coordinates} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Coordinates {
|
||||
private double latitude = -1.0;
|
||||
private double longitude = -1.0;
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Coordinates [latitude=" + latitude + ", longitude=" + longitude + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DepartureTime {
|
||||
private int hour = -1; // 7,
|
||||
private int minute = -1; // 0
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepartureTime [hour=" + hour + ", minute=" + minute + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DigitalKey {
|
||||
private String bookedServicePackage = ""; // NONE,
|
||||
private String readerGraphics = "";
|
||||
private String state = ""; // NOT_AVAILABLE
|
||||
|
||||
public String getBookedServicePackage() {
|
||||
return bookedServicePackage;
|
||||
}
|
||||
|
||||
public void setBookedServicePackage(String bookedServicePackage) {
|
||||
this.bookedServicePackage = bookedServicePackage;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getReaderGraphics() {
|
||||
return readerGraphics;
|
||||
}
|
||||
|
||||
public void setReaderGraphics(String readerGraphics) {
|
||||
this.readerGraphics = readerGraphics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DigitalKey [bookedServicePackage=" + bookedServicePackage + ", readerGraphics=" + readerGraphics
|
||||
+ ", state=" + state + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DriverPreferences {
|
||||
private String lscPrivacyMode = ""; // OFF
|
||||
|
||||
public String getLscPrivacyMode() {
|
||||
return lscPrivacyMode;
|
||||
}
|
||||
|
||||
public void setLscPrivacyMode(String lscPrivacyMode) {
|
||||
this.lscPrivacyMode = lscPrivacyMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DriverPreferences [lscPrivacyMode=" + lscPrivacyMode + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - refactoring, V2 API charging
|
||||
*/
|
||||
public class ElectricChargingState {
|
||||
private String chargingConnectionType = ""; // UNKNOWN,
|
||||
private String chargingStatus = ""; // FINISHED_FULLY_CHARGED,
|
||||
private boolean isChargerConnected = false; // true,
|
||||
private int chargingTarget = -1; // 80,
|
||||
private int chargingLevelPercent = -1; // 80,
|
||||
private int remainingChargingMinutes = -1; // 178
|
||||
private int range = -1; // 286
|
||||
|
||||
/**
|
||||
* @return the chargingConnectionType
|
||||
*/
|
||||
public String getChargingConnectionType() {
|
||||
return chargingConnectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingConnectionType the chargingConnectionType to set
|
||||
*/
|
||||
public void setChargingConnectionType(String chargingConnectionType) {
|
||||
this.chargingConnectionType = chargingConnectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingStatus
|
||||
*/
|
||||
public String getChargingStatus() {
|
||||
return chargingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingStatus the chargingStatus to set
|
||||
*/
|
||||
public void setChargingStatus(String chargingStatus) {
|
||||
this.chargingStatus = chargingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isChargerConnected
|
||||
*/
|
||||
public boolean isChargerConnected() {
|
||||
return isChargerConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isChargerConnected the isChargerConnected to set
|
||||
*/
|
||||
public void setChargerConnected(boolean isChargerConnected) {
|
||||
this.isChargerConnected = isChargerConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingTarget
|
||||
*/
|
||||
public int getChargingTarget() {
|
||||
return chargingTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingTarget the chargingTarget to set
|
||||
*/
|
||||
public void setChargingTarget(int chargingTarget) {
|
||||
this.chargingTarget = chargingTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingLevelPercent
|
||||
*/
|
||||
public int getChargingLevelPercent() {
|
||||
return chargingLevelPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingLevelPercent the chargingLevelPercent to set
|
||||
*/
|
||||
public void setChargingLevelPercent(int chargingLevelPercent) {
|
||||
this.chargingLevelPercent = chargingLevelPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remainingChargingMinutes
|
||||
*/
|
||||
public int getRemainingChargingMinutes() {
|
||||
return remainingChargingMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remainingChargingMinutes the remainingChargingMinutes to set
|
||||
*/
|
||||
public void setRemainingChargingMinutes(int remainingChargingMinutes) {
|
||||
this.remainingChargingMinutes = remainingChargingMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range the range to set
|
||||
*/
|
||||
public void setRange(int range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ElectricChargingState [chargingConnectionType=" + chargingConnectionType + ", chargingStatus="
|
||||
+ chargingStatus + ", isChargerConnected=" + isChargerConnected + ", chargingTarget=" + chargingTarget
|
||||
+ ", chargingLevelPercent=" + chargingLevelPercent + ", remainingChargingMinutes="
|
||||
+ remainingChargingMinutes + ", range=" + range + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class RequiredService {
|
||||
private String dateTime = ""; // 2024-06-01T00:00:00.000Z,
|
||||
private int mileage = -1; // 29000,
|
||||
private String type = ""; // OIL,
|
||||
private String status = ""; // OK,
|
||||
private String description = ""; // Next service due after the specified distance or date.
|
||||
|
||||
public String getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
public void setDateTime(String dateTime) {
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
public int getMileage() {
|
||||
return mileage;
|
||||
}
|
||||
|
||||
public void setMileage(int mileage) {
|
||||
this.mileage = mileage;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RequiredService [dateTime=" + dateTime + ", mileage=" + mileage + ", type=" + type + ", status="
|
||||
+ status + ", description=" + description + "]";
|
||||
}
|
||||
}
|
@ -12,35 +12,34 @@
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.Properties;
|
||||
import org.openhab.binding.mybmw.internal.dto.status.Status;
|
||||
|
||||
/**
|
||||
* The {@link Vehicle} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored for v2 API
|
||||
*/
|
||||
public class Vehicle {
|
||||
public String vin;// ": "WBY1Z81040V905639",
|
||||
public String model;// ": "i3 94 (+ REX)",
|
||||
public int year;// ": 2017,
|
||||
public String brand;// ": "BMW",
|
||||
public String headUnit;// ": "ID5",
|
||||
public boolean isLscSupported;// ": true,
|
||||
public String driveTrain;// ": "ELECTRIC",
|
||||
public String puStep;// ": "0321",
|
||||
public String iStep;// ": "I001-21-03-530",
|
||||
public String telematicsUnit;// ": "TCB1",
|
||||
public String hmiVersion;// ": "ID4",
|
||||
public String bodyType;// ": "I01",
|
||||
public String a4aType;// ": "USB_ONLY",
|
||||
public String exFactoryPUStep;// ": "0717",
|
||||
public String exFactoryILevel;// ": "I001-17-07-500"
|
||||
public Capabilities capabilities;
|
||||
// "connectedDriveServices": [] currently no clue how to resolve,
|
||||
public Properties properties;
|
||||
public boolean isMappingPending;// ":false,"
|
||||
public boolean isMappingUnconfirmed;// ":false,
|
||||
public Status status;
|
||||
public boolean valid = false;
|
||||
private VehicleBase vehicleBase = new VehicleBase();
|
||||
private VehicleStateContainer vehicleState = new VehicleStateContainer();
|
||||
|
||||
public VehicleBase getVehicleBase() {
|
||||
return vehicleBase;
|
||||
}
|
||||
|
||||
public void setVehicleBase(VehicleBase vehicleBase) {
|
||||
this.vehicleBase = vehicleBase;
|
||||
}
|
||||
|
||||
public VehicleStateContainer getVehicleState() {
|
||||
return vehicleState;
|
||||
}
|
||||
|
||||
public void setVehicleState(VehicleStateContainer vehicleState) {
|
||||
this.vehicleState = vehicleState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vehicle [vehicleBase=" + vehicleBase + ", vehicleState=" + vehicleState + "]";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - fix brand BMW_I
|
||||
*/
|
||||
public class VehicleAttributes {
|
||||
private String lastFetched = ""; // "2022-12-21T17:30:40.363Z"
|
||||
private String model = "";// ": "i3 94 (+ REX)",
|
||||
private int year = -1;// ": 2017,
|
||||
private long color = -1;// ": 4284572001,
|
||||
private String brand = "";// ": "BMW",
|
||||
private String driveTrain = "";// ": "ELECTRIC",
|
||||
private String headUnitType = "";// ": "ID5",
|
||||
private String headUnitRaw = "";// ": "ID5",
|
||||
private String hmiVersion = "";// ": "ID4",
|
||||
// softwareVersionCurrent - needed?
|
||||
// softwareVersionExFactory - needed?
|
||||
private String telematicsUnit = "";// ": "TCB1",
|
||||
private String bodyType = "";// ": "I01",
|
||||
private String countryOfOrigin = ""; // "DE"
|
||||
// driverGuideInfo - needed?
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(int year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public long getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(long color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
if (BimmerConstants.BRAND_BMWI.equals(brand.toLowerCase())) {
|
||||
return BimmerConstants.BRAND_BMW;
|
||||
} else {
|
||||
return brand.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBrand(String brand) {
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getDriveTrain() {
|
||||
return driveTrain;
|
||||
}
|
||||
|
||||
public void setDriveTrain(String driveTrain) {
|
||||
this.driveTrain = driveTrain;
|
||||
}
|
||||
|
||||
public String getHeadUnitType() {
|
||||
return headUnitType;
|
||||
}
|
||||
|
||||
public void setHeadUnitType(String headUnitType) {
|
||||
this.headUnitType = headUnitType;
|
||||
}
|
||||
|
||||
public String getHeadUnitRaw() {
|
||||
return headUnitRaw;
|
||||
}
|
||||
|
||||
public void setHeadUnitRaw(String headUnitRaw) {
|
||||
this.headUnitRaw = headUnitRaw;
|
||||
}
|
||||
|
||||
public String getHmiVersion() {
|
||||
return hmiVersion;
|
||||
}
|
||||
|
||||
public void setHmiVersion(String hmiVersion) {
|
||||
this.hmiVersion = hmiVersion;
|
||||
}
|
||||
|
||||
public String getTelematicsUnit() {
|
||||
return telematicsUnit;
|
||||
}
|
||||
|
||||
public void setTelematicsUnit(String telematicsUnit) {
|
||||
this.telematicsUnit = telematicsUnit;
|
||||
}
|
||||
|
||||
public String getBodyType() {
|
||||
return bodyType;
|
||||
}
|
||||
|
||||
public void setBodyType(String bodyType) {
|
||||
this.bodyType = bodyType;
|
||||
}
|
||||
|
||||
public String getCountryOfOrigin() {
|
||||
return countryOfOrigin;
|
||||
}
|
||||
|
||||
public void setCountryOfOrigin(String countryOfOrigin) {
|
||||
this.countryOfOrigin = countryOfOrigin;
|
||||
}
|
||||
|
||||
public String getLastFetched() {
|
||||
return lastFetched;
|
||||
}
|
||||
|
||||
public void setLastFetched(String lastFetched) {
|
||||
this.lastFetched = lastFetched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleAttributes [lastFetched=" + lastFetched + ", model=" + model + ", year=" + year + ", color="
|
||||
+ color + ", brand=" + brand + ", driveTrain=" + driveTrain + ", headUnitType=" + headUnitType
|
||||
+ ", headUnitRaw=" + headUnitRaw + ", hmiVersion=" + hmiVersion + ", telematicsUnit=" + telematicsUnit
|
||||
+ ", bodyType=" + bodyType + ", countryOfOrigin=" + countryOfOrigin + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link VehicleBase} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class VehicleBase {
|
||||
private String vin = "";// ": "WBY1Z81040V905639",
|
||||
// mappingInfo - needed?
|
||||
// appVehicleType - needed?
|
||||
private VehicleAttributes attributes = new VehicleAttributes();
|
||||
|
||||
public String getVin() {
|
||||
return vin;
|
||||
}
|
||||
|
||||
public void setVin(String vin) {
|
||||
this.vin = vin;
|
||||
}
|
||||
|
||||
public VehicleAttributes getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(VehicleAttributes attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleBase [vin=" + vin + ", attributes=" + attributes + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.RemoteChargingCommands;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleCapabilities} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
|
||||
public class VehicleCapabilities {
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleCapabilities.class);
|
||||
|
||||
private static final String PREFIX_IS = "is";
|
||||
public static final String SUPPORTED_SUFFIX = "Supported";
|
||||
public static final String ENABLED_SUFFIX = "Enabled";
|
||||
|
||||
private boolean checkSustainabilityDPP = false;
|
||||
private boolean climateNow = false;
|
||||
private boolean horn = false;
|
||||
private boolean isBmwChargingSupported = false;
|
||||
private boolean isCarSharingSupported = false;
|
||||
private boolean isChargeNowForBusinessSupported = false;
|
||||
private boolean isChargingHistorySupported = false;
|
||||
private boolean isChargingHospitalityEnabled = false;
|
||||
private boolean isChargingLoudnessEnabled = false;
|
||||
private boolean isChargingPlanSupported = false;
|
||||
private boolean isChargingPowerLimitEnabled = false;
|
||||
private boolean isChargingSettingsEnabled = false;
|
||||
private boolean isChargingTargetSocEnabled = false;
|
||||
private boolean isClimateTimerSupported = false;
|
||||
private boolean isClimateTimerWeeklyActive = false;
|
||||
private boolean isCustomerEsimSupported = false;
|
||||
private boolean isDataPrivacyEnabled = false;
|
||||
private boolean isDCSContractManagementSupported = false;
|
||||
private boolean isEasyChargeEnabled = false;
|
||||
private boolean isEvGoChargingSupported = false;
|
||||
private boolean isMiniChargingSupported = false;
|
||||
private boolean isNonLscFeatureEnabled = false;
|
||||
private boolean isRemoteEngineStartSupported = false;
|
||||
private boolean isRemoteHistoryDeletionSupported = false;
|
||||
private boolean isRemoteHistorySupported = false;
|
||||
private boolean isRemoteParkingSupported = false;
|
||||
private boolean isRemoteServicesActivationRequired = false;
|
||||
private boolean isRemoteServicesBookingRequired = false;
|
||||
private boolean isScanAndChargeSupported = false;
|
||||
private boolean isSustainabilityAccumulatedViewEnabled = false;
|
||||
private boolean isSustainabilitySupported = false;
|
||||
private boolean isWifiHotspotServiceSupported = false;
|
||||
private boolean lights = false;
|
||||
private boolean lock = false;
|
||||
private boolean remote360 = false;
|
||||
private RemoteChargingCommands remoteChargingCommands = new RemoteChargingCommands();
|
||||
private boolean remoteSoftwareUpgrade = false;
|
||||
private boolean sendPoi = false;
|
||||
private boolean speechThirdPartyAlexa = false;
|
||||
private boolean speechThirdPartyAlexaSDK = false;
|
||||
private boolean unlock = false;
|
||||
private boolean vehicleFinder = false;
|
||||
private DigitalKey digitalKey = new DigitalKey();
|
||||
private String a4aType = ""; // NOT_SUPPORTED,
|
||||
private String climateFunction = ""; // VENTILATION,
|
||||
private String climateTimerTrigger = ""; // DEPARTURE_TIMER,
|
||||
private String lastStateCallState = ""; // ACTIVATED,
|
||||
private String vehicleStateSource = ""; // LAST_STATE_CALL,
|
||||
|
||||
/**
|
||||
* @return the climateNow
|
||||
*/
|
||||
public boolean isClimateNow() {
|
||||
return climateNow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the horn
|
||||
*/
|
||||
public boolean isHorn() {
|
||||
return horn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lights
|
||||
*/
|
||||
public boolean isLights() {
|
||||
return lights;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lock
|
||||
*/
|
||||
public boolean isLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remote360
|
||||
*/
|
||||
public boolean isRemote360() {
|
||||
return remote360;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sendPoi
|
||||
*/
|
||||
public boolean isSendPoi() {
|
||||
return sendPoi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unlock
|
||||
*/
|
||||
public boolean isUnlock() {
|
||||
return unlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vehicleFinder
|
||||
*/
|
||||
public boolean isVehicleFinder() {
|
||||
return vehicleFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the digitalKey
|
||||
*/
|
||||
public DigitalKey getDigitalKey() {
|
||||
return digitalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of capabilities filtered by the provided suffix and the enabled requirement
|
||||
*
|
||||
* @param suffix the suffix of the capability
|
||||
* @param enabled if it should return only enabled or disabled capabilities
|
||||
* @return the list of capabilities as single string
|
||||
*/
|
||||
public String getCapabilitiesAsString(String suffix, boolean enabled) {
|
||||
StringBuffer capabilitiesAsString = new StringBuffer();
|
||||
List<String> capabilitiesAsStringList = getCapabilitiesAsStringList(suffix, enabled);
|
||||
|
||||
for (String capEntry : capabilitiesAsStringList) {
|
||||
// remove "is" prefix and provided suffix
|
||||
String cut = capEntry.substring(2);
|
||||
if (cut.endsWith(suffix)) {
|
||||
if (capabilitiesAsString.length() > 0) {
|
||||
capabilitiesAsString.append(Constants.SEMICOLON);
|
||||
}
|
||||
capabilitiesAsString.append(cut.substring(0, cut.length() - suffix.length()));
|
||||
}
|
||||
}
|
||||
return capabilitiesAsString.toString();
|
||||
}
|
||||
|
||||
private List<String> getCapabilitiesAsStringList(String suffix, boolean compare) {
|
||||
List<String> l = new ArrayList<>();
|
||||
|
||||
Arrays.asList(VehicleCapabilities.class.getDeclaredFields()).stream()
|
||||
.filter(field -> field.getName().startsWith(PREFIX_IS) && field.getName().endsWith(suffix))
|
||||
.forEach(field -> {
|
||||
try {
|
||||
boolean value = field.getBoolean(this);
|
||||
if (compare == value) {
|
||||
l.add(field.getName());
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
logger.trace("field {} not usable: ", field.getName());
|
||||
}
|
||||
});
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleCapabilities [checkSustainabilityDPP=" + checkSustainabilityDPP + ", climateNow=" + climateNow
|
||||
+ ", horn=" + horn + ", isBmwChargingSupported=" + isBmwChargingSupported + ", isCarSharingSupported="
|
||||
+ isCarSharingSupported + ", isChargeNowForBusinessSupported=" + isChargeNowForBusinessSupported
|
||||
+ ", isChargingHistorySupported=" + isChargingHistorySupported + ", isChargingHospitalityEnabled="
|
||||
+ isChargingHospitalityEnabled + ", isChargingLoudnessEnabled=" + isChargingLoudnessEnabled
|
||||
+ ", isChargingPlanSupported=" + isChargingPlanSupported + ", isChargingPowerLimitEnabled="
|
||||
+ isChargingPowerLimitEnabled + ", isChargingSettingsEnabled=" + isChargingSettingsEnabled
|
||||
+ ", isChargingTargetSocEnabled=" + isChargingTargetSocEnabled + ", isClimateTimerSupported="
|
||||
+ isClimateTimerSupported + ", isClimateTimerWeeklyActive=" + isClimateTimerWeeklyActive
|
||||
+ ", isCustomerEsimSupported=" + isCustomerEsimSupported + ", isDataPrivacyEnabled="
|
||||
+ isDataPrivacyEnabled + ", isDCSContractManagementSupported=" + isDCSContractManagementSupported
|
||||
+ ", isEasyChargeEnabled=" + isEasyChargeEnabled + ", isEvGoChargingSupported="
|
||||
+ isEvGoChargingSupported + ", isMiniChargingSupported=" + isMiniChargingSupported
|
||||
+ ", isNonLscFeatureEnabled=" + isNonLscFeatureEnabled + ", isRemoteEngineStartSupported="
|
||||
+ isRemoteEngineStartSupported + ", isRemoteHistoryDeletionSupported="
|
||||
+ isRemoteHistoryDeletionSupported + ", isRemoteHistorySupported=" + isRemoteHistorySupported
|
||||
+ ", isRemoteParkingSupported=" + isRemoteParkingSupported + ", isRemoteServicesActivationRequired="
|
||||
+ isRemoteServicesActivationRequired + ", isRemoteServicesBookingRequired="
|
||||
+ isRemoteServicesBookingRequired + ", isScanAndChargeSupported=" + isScanAndChargeSupported
|
||||
+ ", isSustainabilityAccumulatedViewEnabled=" + isSustainabilityAccumulatedViewEnabled
|
||||
+ ", isSustainabilitySupported=" + isSustainabilitySupported + ", isWifiHotspotServiceSupported="
|
||||
+ isWifiHotspotServiceSupported + ", lights=" + lights + ", lock=" + lock + ", remote360=" + remote360
|
||||
+ ", remoteChargingCommands=" + remoteChargingCommands + ", remoteSoftwareUpgrade="
|
||||
+ remoteSoftwareUpgrade + ", sendPoi=" + sendPoi + ", speechThirdPartyAlexa=" + speechThirdPartyAlexa
|
||||
+ ", speechThirdPartyAlexaSDK=" + speechThirdPartyAlexaSDK + ", unlock=" + unlock + ", vehicleFinder="
|
||||
+ vehicleFinder + ", digitalKey=" + digitalKey + ", a4aType=" + a4aType + ", climateFunction="
|
||||
+ climateFunction + ", climateTimerTrigger=" + climateTimerTrigger + ", lastStateCallState="
|
||||
+ lastStateCallState + ", vehicleStateSource=" + vehicleStateSource + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleDoorsState {
|
||||
private String combinedSecurityState = ""; // SECURED,
|
||||
private String leftFront = ""; // CLOSED
|
||||
private String leftRear = ""; // CLOSED
|
||||
private String rightFront = ""; // CLOSED
|
||||
private String rightRear = ""; // CLOSED
|
||||
private String combinedState = ""; // CLOSED
|
||||
private String hood = ""; // CLOSED
|
||||
private String trunk = ""; // CLOSED
|
||||
|
||||
public String getCombinedSecurityState() {
|
||||
return combinedSecurityState;
|
||||
}
|
||||
|
||||
public void setCombinedSecurityState(String combinedSecurityState) {
|
||||
this.combinedSecurityState = combinedSecurityState;
|
||||
}
|
||||
|
||||
public String getLeftFront() {
|
||||
return leftFront;
|
||||
}
|
||||
|
||||
public void setLeftFront(String leftFront) {
|
||||
this.leftFront = leftFront;
|
||||
}
|
||||
|
||||
public String getLeftRear() {
|
||||
return leftRear;
|
||||
}
|
||||
|
||||
public void setLeftRear(String leftRear) {
|
||||
this.leftRear = leftRear;
|
||||
}
|
||||
|
||||
public String getRightFront() {
|
||||
return rightFront;
|
||||
}
|
||||
|
||||
public void setRightFront(String rightFront) {
|
||||
this.rightFront = rightFront;
|
||||
}
|
||||
|
||||
public String getRightRear() {
|
||||
return rightRear;
|
||||
}
|
||||
|
||||
public void setRightRear(String rightRear) {
|
||||
this.rightRear = rightRear;
|
||||
}
|
||||
|
||||
public String getCombinedState() {
|
||||
return combinedState;
|
||||
}
|
||||
|
||||
public void setCombinedState(String combinedState) {
|
||||
this.combinedState = combinedState;
|
||||
}
|
||||
|
||||
public String getHood() {
|
||||
return hood;
|
||||
}
|
||||
|
||||
public void setHood(String hood) {
|
||||
this.hood = hood;
|
||||
}
|
||||
|
||||
public String getTrunk() {
|
||||
return trunk;
|
||||
}
|
||||
|
||||
public void setTrunk(String trunk) {
|
||||
this.trunk = trunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleDoorsState [combinedSecurityState=" + combinedSecurityState + ", leftFront=" + leftFront
|
||||
+ ", leftRear=" + leftRear + ", rightFront=" + rightFront + ", rightRear=" + rightRear
|
||||
+ ", combinedState=" + combinedState + ", hood=" + hood + ", trunk=" + trunk + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link VehicleLocation} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class VehicleLocation {
|
||||
private Coordinates coordinates = new Coordinates();
|
||||
private Address address = new Address();
|
||||
private int heading = -1;
|
||||
|
||||
public Coordinates getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public void setCoordinates(Coordinates coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public int getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public void setHeading(int heading) {
|
||||
this.heading = heading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleLocation [coordinates=" + coordinates + ", address=" + address + ", heading=" + heading + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleRoofState {
|
||||
private String roofState = ""; // CLOSED,
|
||||
private String roofStateType = ""; // SUN_ROOF
|
||||
|
||||
public String getRoofState() {
|
||||
return roofState;
|
||||
}
|
||||
|
||||
public void setRoofState(String roofState) {
|
||||
this.roofState = roofState;
|
||||
}
|
||||
|
||||
public String getRoofStateType() {
|
||||
return roofStateType;
|
||||
}
|
||||
|
||||
public void setRoofStateType(String roofStateType) {
|
||||
this.roofStateType = roofStateType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleRoofState [roofState=" + roofState + ", roofStateType=" + roofStateType + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleState {
|
||||
|
||||
public static final String CHECK_CONTROL_OVERALL_MESSAGE_OK = "No Issues";
|
||||
|
||||
private boolean isLeftSteering = false;
|
||||
private String lastFetched = ""; // 2022-12-21T17:31:26.560Z,
|
||||
private String lastUpdatedAt = ""; // 2022-12-21T15:41:23Z,
|
||||
private boolean isLscSupported = false; // true,
|
||||
private int range = -1; // 435,
|
||||
private VehicleDoorsState doorsState = new VehicleDoorsState();
|
||||
private VehicleWindowsState windowsState = new VehicleWindowsState();
|
||||
private VehicleRoofState roofState = new VehicleRoofState();
|
||||
private VehicleTireStates tireState = new VehicleTireStates();
|
||||
|
||||
private VehicleLocation location = new VehicleLocation();
|
||||
private int currentMileage = -1;
|
||||
private ClimateControlState climateControlState = new ClimateControlState();
|
||||
private List<RequiredService> requiredServices = new ArrayList<>();
|
||||
private List<CheckControlMessage> checkControlMessages = new ArrayList<>();
|
||||
private CombustionFuelLevel combustionFuelLevel = new CombustionFuelLevel();
|
||||
private DriverPreferences driverPreferences = new DriverPreferences();
|
||||
private ElectricChargingState electricChargingState = new ElectricChargingState();
|
||||
private boolean isDeepSleepModeActive = false; // false
|
||||
private List<ClimateTimer> climateTimers = new ArrayList<>();
|
||||
private ChargingProfile chargingProfile = new ChargingProfile();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return the isLeftSteering
|
||||
*/
|
||||
public boolean isLeftSteering() {
|
||||
return isLeftSteering;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastFetched
|
||||
*/
|
||||
public String getLastFetched() {
|
||||
return lastFetched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastUpdatedAt
|
||||
*/
|
||||
public String getLastUpdatedAt() {
|
||||
return lastUpdatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isLscSupported
|
||||
*/
|
||||
public boolean isLscSupported() {
|
||||
return isLscSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the doorsState
|
||||
*/
|
||||
public VehicleDoorsState getDoorsState() {
|
||||
return doorsState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the windowsState
|
||||
*/
|
||||
public VehicleWindowsState getWindowsState() {
|
||||
return windowsState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the roofState
|
||||
*/
|
||||
public VehicleRoofState getRoofState() {
|
||||
return roofState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tireState
|
||||
*/
|
||||
public VehicleTireStates getTireState() {
|
||||
return tireState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the location
|
||||
*/
|
||||
public VehicleLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the currentMileage
|
||||
*/
|
||||
public int getCurrentMileage() {
|
||||
return currentMileage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the climateControlState
|
||||
*/
|
||||
public ClimateControlState getClimateControlState() {
|
||||
return climateControlState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the requiredServices
|
||||
*/
|
||||
public List<RequiredService> getRequiredServices() {
|
||||
return requiredServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the checkControlMessages
|
||||
*/
|
||||
public List<CheckControlMessage> getCheckControlMessages() {
|
||||
return checkControlMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the combustionFuelLevel
|
||||
*/
|
||||
public CombustionFuelLevel getCombustionFuelLevel() {
|
||||
return combustionFuelLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the driverPreferences
|
||||
*/
|
||||
public DriverPreferences getDriverPreferences() {
|
||||
return driverPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the electricChargingState
|
||||
*/
|
||||
public ElectricChargingState getElectricChargingState() {
|
||||
return electricChargingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isDeepSleepModeActive
|
||||
*/
|
||||
public boolean isDeepSleepModeActive() {
|
||||
return isDeepSleepModeActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the climateTimers
|
||||
*/
|
||||
public List<ClimateTimer> getClimateTimers() {
|
||||
return climateTimers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingProfile
|
||||
*/
|
||||
public ChargingProfile getChargingProfile() {
|
||||
return chargingProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleState [isLeftSteering=" + isLeftSteering + ", lastFetched=" + lastFetched + ", lastUpdatedAt="
|
||||
+ lastUpdatedAt + ", isLscSupported=" + isLscSupported + ", range=" + range + ", doorsState="
|
||||
+ doorsState + ", windowsState=" + windowsState + ", roofState=" + roofState + ", tireState="
|
||||
+ tireState + ", location=" + location + ", currentMileage=" + currentMileage + ", climateControlState="
|
||||
+ climateControlState + ", requiredServices=" + requiredServices + ", checkControlMessages="
|
||||
+ checkControlMessages + ", combustionFuelLevel=" + combustionFuelLevel + ", driverPreferences="
|
||||
+ driverPreferences + ", electricChargingState=" + electricChargingState + ", isDeepSleepModeActive="
|
||||
+ isDeepSleepModeActive + ", climateTimers=" + climateTimers + ", chargingProfile=" + chargingProfile
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* helper methods
|
||||
*/
|
||||
public String getOverallCheckControlStatus() {
|
||||
StringBuilder overallMessage = new StringBuilder();
|
||||
|
||||
for (CheckControlMessage checkControlMessage : checkControlMessages) {
|
||||
if (checkControlMessage.getId() > 0) {
|
||||
overallMessage.append(checkControlMessage.getName() + "; ");
|
||||
}
|
||||
}
|
||||
|
||||
String overallMessageString = overallMessage.toString();
|
||||
|
||||
if (overallMessageString.isEmpty()) {
|
||||
overallMessageString = CHECK_CONTROL_OVERALL_MESSAGE_OK;
|
||||
}
|
||||
|
||||
return overallMessageString;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleStateContainer {
|
||||
private VehicleState state = new VehicleState();
|
||||
private VehicleCapabilities capabilities = new VehicleCapabilities();
|
||||
|
||||
private String rawStateJson = "";
|
||||
|
||||
public VehicleState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(VehicleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public VehicleCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(VehicleCapabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleState [state=" + state + ", capabilities=" + capabilities + "]";
|
||||
}
|
||||
|
||||
public String getRawStateJson() {
|
||||
return rawStateJson;
|
||||
}
|
||||
|
||||
public void setRawStateJson(String rawStateJson) {
|
||||
this.rawStateJson = rawStateJson;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireState {
|
||||
private VehicleTireStateDetails details = new VehicleTireStateDetails();
|
||||
private VehicleTireStateStatus status = new VehicleTireStateStatus();
|
||||
|
||||
public VehicleTireStateDetails getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(VehicleTireStateDetails details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public VehicleTireStateStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(VehicleTireStateStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireState [details=" + details + ", status=" + status + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateDetails {
|
||||
private String dimension = ""; // 225/45 R18 95V XL,
|
||||
private String treadDesign = ""; // Winter Contact TS 860 S SSR,
|
||||
private String manufacturer = ""; // Continental,
|
||||
private int manufacturingWeek = -1; // 5299,
|
||||
private boolean isOptimizedForOemBmw = false; // true,
|
||||
private String partNumber = ""; // 2471558,
|
||||
private VehicleTireStateDetailsClassification speedClassification; //
|
||||
private String mountingDate = ""; // 2022-10-06T00:00:00.000Z,
|
||||
private int season = -1; // 4,
|
||||
private boolean identificationInProgress = false; // false
|
||||
|
||||
public String getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void setDimension(String dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public String getTreadDesign() {
|
||||
return treadDesign;
|
||||
}
|
||||
|
||||
public void setTreadDesign(String treadDesign) {
|
||||
this.treadDesign = treadDesign;
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
}
|
||||
|
||||
public int getManufacturingWeek() {
|
||||
return manufacturingWeek;
|
||||
}
|
||||
|
||||
public void setManufacturingWeek(int manufacturingWeek) {
|
||||
this.manufacturingWeek = manufacturingWeek;
|
||||
}
|
||||
|
||||
public boolean isOptimizedForOemBmw() {
|
||||
return isOptimizedForOemBmw;
|
||||
}
|
||||
|
||||
public void setOptimizedForOemBmw(boolean isOptimizedForOemBmw) {
|
||||
this.isOptimizedForOemBmw = isOptimizedForOemBmw;
|
||||
}
|
||||
|
||||
public String getPartNumber() {
|
||||
return partNumber;
|
||||
}
|
||||
|
||||
public void setPartNumber(String partNumber) {
|
||||
this.partNumber = partNumber;
|
||||
}
|
||||
|
||||
public VehicleTireStateDetailsClassification getSpeedClassification() {
|
||||
return speedClassification;
|
||||
}
|
||||
|
||||
public void setSpeedClassification(VehicleTireStateDetailsClassification speedClassification) {
|
||||
this.speedClassification = speedClassification;
|
||||
}
|
||||
|
||||
public String getMountingDate() {
|
||||
return mountingDate;
|
||||
}
|
||||
|
||||
public void setMountingDate(String mountingDate) {
|
||||
this.mountingDate = mountingDate;
|
||||
}
|
||||
|
||||
public int getSeason() {
|
||||
return season;
|
||||
}
|
||||
|
||||
public void setSeason(int season) {
|
||||
this.season = season;
|
||||
}
|
||||
|
||||
public boolean isIdentificationInProgress() {
|
||||
return identificationInProgress;
|
||||
}
|
||||
|
||||
public void setIdentificationInProgress(boolean identificationInProgress) {
|
||||
this.identificationInProgress = identificationInProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateDetails [dimension=" + dimension + ", treadDesign=" + treadDesign + ", manufacturer="
|
||||
+ manufacturer + ", manufacturingWeek=" + manufacturingWeek + ", isOptimizedForOemBmw="
|
||||
+ isOptimizedForOemBmw + ", partNumber=" + partNumber + ", speedClassification=" + speedClassification
|
||||
+ ", mountingDate=" + mountingDate + ", season=" + season + ", identificationInProgress="
|
||||
+ identificationInProgress + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateDetailsClassification {
|
||||
private int speedRating = -1; // 240,
|
||||
private boolean atLeast = false; // false
|
||||
|
||||
public int getSpeedRating() {
|
||||
return speedRating;
|
||||
}
|
||||
|
||||
public void setSpeedRating(int speedRating) {
|
||||
this.speedRating = speedRating;
|
||||
}
|
||||
|
||||
public boolean isAtLeast() {
|
||||
return atLeast;
|
||||
}
|
||||
|
||||
public void setAtLeast(boolean atLeast) {
|
||||
this.atLeast = atLeast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateDetailsClassification [speedRating=" + speedRating + ", atLeast=" + atLeast + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from API response
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateStatus {
|
||||
private int currentPressure = -1; // 280,
|
||||
private int targetPressure = -1; // 290
|
||||
|
||||
public int getCurrentPressure() {
|
||||
return currentPressure;
|
||||
}
|
||||
|
||||
public void setCurrentPressure(int currentPressure) {
|
||||
this.currentPressure = currentPressure;
|
||||
}
|
||||
|
||||
public int getTargetPressure() {
|
||||
return targetPressure;
|
||||
}
|
||||
|
||||
public void setTargetPressure(int targetPressure) {
|
||||
this.targetPressure = targetPressure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateStatus [currentPressure=" + currentPressure + ", targetPressure=" + targetPressure
|
||||
+ "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStates {
|
||||
private VehicleTireState frontLeft = new VehicleTireState();
|
||||
private VehicleTireState frontRight = new VehicleTireState();
|
||||
private VehicleTireState rearLeft = new VehicleTireState();
|
||||
private VehicleTireState rearRight = new VehicleTireState();
|
||||
|
||||
public VehicleTireState getFrontLeft() {
|
||||
return frontLeft;
|
||||
}
|
||||
|
||||
public void setFrontLeft(VehicleTireState frontLeft) {
|
||||
this.frontLeft = frontLeft;
|
||||
}
|
||||
|
||||
public VehicleTireState getFrontRight() {
|
||||
return frontRight;
|
||||
}
|
||||
|
||||
public void setFrontRight(VehicleTireState frontRight) {
|
||||
this.frontRight = frontRight;
|
||||
}
|
||||
|
||||
public VehicleTireState getRearLeft() {
|
||||
return rearLeft;
|
||||
}
|
||||
|
||||
public void setRearLeft(VehicleTireState rearLeft) {
|
||||
this.rearLeft = rearLeft;
|
||||
}
|
||||
|
||||
public VehicleTireState getRearRight() {
|
||||
return rearRight;
|
||||
}
|
||||
|
||||
public void setRearRight(VehicleTireState rearRight) {
|
||||
this.rearRight = rearRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStates [frontLeft=" + frontLeft + ", frontRight=" + frontRight + ", rearLeft=" + rearLeft
|
||||
+ ", rearRight=" + rearRight + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleWindowsState {
|
||||
private String leftFront = ""; // CLOSED,
|
||||
private String leftRear = ""; // CLOSED,
|
||||
private String rightFront = ""; // CLOSED,
|
||||
private String rightRear = ""; // CLOSED,
|
||||
private String rear = ""; // CLOSED,
|
||||
private String combinedState = ""; // CLOSED
|
||||
|
||||
public String getLeftFront() {
|
||||
return leftFront;
|
||||
}
|
||||
|
||||
public void setLeftFront(String leftFront) {
|
||||
this.leftFront = leftFront;
|
||||
}
|
||||
|
||||
public String getLeftRear() {
|
||||
return leftRear;
|
||||
}
|
||||
|
||||
public void setLeftRear(String leftRear) {
|
||||
this.leftRear = leftRear;
|
||||
}
|
||||
|
||||
public String getRightFront() {
|
||||
return rightFront;
|
||||
}
|
||||
|
||||
public void setRightFront(String rightFront) {
|
||||
this.rightFront = rightFront;
|
||||
}
|
||||
|
||||
public String getRightRear() {
|
||||
return rightRear;
|
||||
}
|
||||
|
||||
public void setRightRear(String rightRear) {
|
||||
this.rightRear = rightRear;
|
||||
}
|
||||
|
||||
public String getRear() {
|
||||
return rear;
|
||||
}
|
||||
|
||||
public void setRear(String rear) {
|
||||
this.rear = rear;
|
||||
}
|
||||
|
||||
public String getCombinedState() {
|
||||
return combinedState;
|
||||
}
|
||||
|
||||
public void setCombinedState(String combinedState) {
|
||||
this.combinedState = combinedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleWindowsState [leftFront=" + leftFront + ", leftRear=" + leftRear + ", rightFront=" + rightFront
|
||||
+ ", rightRear=" + rightRear + ", rear=" + rear + ", combinedState=" + combinedState + "]";
|
||||
}
|
||||
}
|
@ -15,19 +15,18 @@ package org.openhab.binding.mybmw.internal.handler;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.openhab.binding.mybmw.internal.MyBMWConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.discovery.VehicleDiscovery;
|
||||
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWFileProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWHttpProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.MyBMWConfigurationChecker;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -40,102 +39,118 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWBridgeHandler} is responsible for handling commands, which are
|
||||
* The {@link MyBMWBridgeHandler} is responsible for handling commands, which
|
||||
* are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored, all discovery functionality moved to VehicleDiscovery
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWBridgeHandler extends BaseBridgeHandler implements StringResponseCallback {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWBridgeHandler.class);
|
||||
private HttpClientFactory httpClientFactory;
|
||||
private Optional<VehicleDiscovery> discoveryService = Optional.empty();
|
||||
private Optional<MyBMWProxy> proxy = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
|
||||
private Optional<String> troubleshootFingerprint = Optional.empty();
|
||||
private String localeLanguage;
|
||||
public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory hcf, String language) {
|
||||
private static final String ENVIRONMENT = "ENVIRONMENT";
|
||||
private static final String TEST = "test";
|
||||
private static final String TESTUSER = "testuser";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWBridgeHandler.class);
|
||||
|
||||
private HttpClientFactory httpClientFactory;
|
||||
private Optional<MyBMWProxy> myBmwProxy = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
|
||||
private Optional<VehicleDiscovery> vehicleDiscovery = Optional.empty();
|
||||
private LocaleProvider localeProvider;
|
||||
|
||||
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory hcf, LocaleProvider localeProvider) {
|
||||
super(bridge);
|
||||
httpClientFactory = hcf;
|
||||
localeLanguage = language;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public void setVehicleDiscovery(VehicleDiscovery vehicleDiscovery) {
|
||||
logger.trace("MyBMWBridgeHandler.setVehicleDiscovery");
|
||||
this.vehicleDiscovery = Optional.of(vehicleDiscovery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// no commands available
|
||||
logger.trace("MyBMWBridgeHandler.handleCommand");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
troubleshootFingerprint = Optional.empty();
|
||||
logger.trace("MyBMWBridgeHandler.initialize");
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
MyBMWConfiguration config = getConfigAs(MyBMWConfiguration.class);
|
||||
MyBMWBridgeConfiguration config = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
if (config.language.equals(Constants.LANGUAGE_AUTODETECT)) {
|
||||
config.language = localeLanguage;
|
||||
config.language = localeProvider.getLocale().getLanguage().toLowerCase();
|
||||
}
|
||||
if (!checkConfiguration(config)) {
|
||||
if (!MyBMWConfigurationChecker.checkConfiguration(config)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
} else {
|
||||
proxy = Optional.of(new MyBMWProxy(httpClientFactory, config));
|
||||
initializerJob = Optional.of(scheduler.schedule(this::requestVehicles, 2, TimeUnit.SECONDS));
|
||||
// there is no risk in this functionality as several steps have to happen to get the file proxy working:
|
||||
// 1. environment variable ENVIRONMENT has to be available
|
||||
// 2. username of the myBMW account must be set to "testuser" which is anyhow no valid username
|
||||
// 3. the jar file must contain the fingerprints which will only happen if it has been built with the
|
||||
// test-jar profile
|
||||
String environment = System.getenv(ENVIRONMENT);
|
||||
|
||||
if (environment == null) {
|
||||
environment = "";
|
||||
}
|
||||
|
||||
createMyBmwProxy(config, environment);
|
||||
initializerJob = Optional.of(scheduler.schedule(this::discoverVehicles, 2, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkConfiguration(MyBMWConfiguration config) {
|
||||
if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
|
||||
return false;
|
||||
} else {
|
||||
return BimmerConstants.EADRAX_SERVER_MAP.containsKey(config.region);
|
||||
private synchronized void createMyBmwProxy(MyBMWBridgeConfiguration config, String environment) {
|
||||
if (!myBmwProxy.isPresent()) {
|
||||
if (!(TEST.equals(environment) && TESTUSER.equals(config.userName))) {
|
||||
myBmwProxy = Optional.of(new MyBMWHttpProxy(httpClientFactory, config));
|
||||
} else {
|
||||
myBmwProxy = Optional.of(new MyBMWFileProxy(httpClientFactory, config));
|
||||
}
|
||||
logger.trace("MyBMWBridgeHandler proxy set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.trace("MyBMWBridgeHandler.dispose");
|
||||
initializerJob.ifPresent(job -> job.cancel(true));
|
||||
}
|
||||
|
||||
public void requestVehicles() {
|
||||
proxy.ifPresent(prox -> prox.requestVehicles(this));
|
||||
public void vehicleDiscoveryError() {
|
||||
logger.trace("MyBMWBridgeHandler.vehicleDiscoveryError");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Request vehicles failed");
|
||||
}
|
||||
|
||||
private void logFingerPrint() {
|
||||
logger.debug("###### Discovery Fingerprint Data - BEGIN ######");
|
||||
logger.debug("{}", troubleshootFingerprint.get());
|
||||
logger.debug("###### Discovery Fingerprint Data - END ######");
|
||||
public void vehicleDiscoverySuccess() {
|
||||
logger.trace("MyBMWBridgeHandler.vehicleDiscoverySuccess");
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Response for vehicle request
|
||||
*/
|
||||
@Override
|
||||
public synchronized void onResponse(@Nullable String response) {
|
||||
if (response != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
List<Vehicle> vehicleList = Converter.getVehicleList(response);
|
||||
discoveryService.get().onResponse(vehicleList);
|
||||
troubleshootFingerprint = Optional.of(Converter.anonymousFingerprint(response));
|
||||
logFingerPrint();
|
||||
}
|
||||
}
|
||||
private void discoverVehicles() {
|
||||
logger.trace("MyBMWBridgeHandler.requestVehicles");
|
||||
|
||||
@Override
|
||||
public void onError(NetworkError error) {
|
||||
troubleshootFingerprint = Optional.of(error.toJson());
|
||||
logFingerPrint();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
|
||||
MyBMWBridgeConfiguration config = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
|
||||
myBmwProxy.ifPresent(proxy -> proxy.setBridgeConfiguration(config));
|
||||
|
||||
vehicleDiscovery.ifPresent(discovery -> discovery.discoverVehicles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Set.of(VehicleDiscovery.class);
|
||||
logger.trace("MyBMWBridgeHandler.getServices");
|
||||
return List.of(VehicleDiscovery.class);
|
||||
}
|
||||
|
||||
public Optional<MyBMWProxy> getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public void setDiscoveryService(VehicleDiscovery discoveryService) {
|
||||
this.discoveryService = Optional.of(discoveryService);
|
||||
public Optional<MyBMWProxy> getMyBmwProxy() {
|
||||
logger.trace("MyBMWBridgeHandler.getProxy");
|
||||
createMyBmwProxy(getConfigAs(MyBMWBridgeConfiguration.class), ENVIRONMENT);
|
||||
return myBmwProxy;
|
||||
}
|
||||
}
|
||||
|
@ -1,519 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.AuthQueryResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.AuthResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||
import org.openhab.binding.mybmw.internal.handler.simulation.Injector;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWProxy} This class holds the important constants for the BMW Connected Drive Authorization.
|
||||
* They are taken from the Bimmercode from github <a href="https://github.com/bimmerconnected/bimmer_connected">
|
||||
* https://github.com/bimmerconnected/bimmer_connected</a>.
|
||||
* File defining these constants
|
||||
* <a href="https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py">
|
||||
* https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py</a>
|
||||
* <a href="https://customer.bmwgroup.com/one/app/oauth.js">https://customer.bmwgroup.com/one/app/oauth.js</a>
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWProxy.class);
|
||||
private Optional<RemoteServiceHandler> remoteServiceHandler = Optional.empty();
|
||||
private final Token token = new Token();
|
||||
private final HttpClient httpClient;
|
||||
private final MyBMWConfiguration configuration;
|
||||
|
||||
/**
|
||||
* URLs taken from https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
|
||||
*/
|
||||
final String vehicleUrl;
|
||||
final String remoteCommandUrl;
|
||||
final String remoteStatusUrl;
|
||||
final String serviceExecutionAPI = "/executeService";
|
||||
final String serviceExecutionStateAPI = "/serviceExecutionStatus";
|
||||
final String remoteServiceEADRXstatusUrl = BimmerConstants.API_REMOTE_SERVICE_BASE_URL
|
||||
+ "eventStatus?eventId={event_id}";
|
||||
|
||||
public MyBMWProxy(HttpClientFactory httpClientFactory, MyBMWConfiguration config) {
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
configuration = config;
|
||||
|
||||
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ BimmerConstants.API_VEHICLES;
|
||||
|
||||
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
|
||||
remoteStatusUrl = remoteCommandUrl + "eventStatus";
|
||||
}
|
||||
|
||||
public synchronized void call(final String url, final boolean post, final @Nullable String encoding,
|
||||
final @Nullable String params, final String brand, final ResponseCallback callback) {
|
||||
// only executed in "simulation mode"
|
||||
// SimulationTest.testSimulationOff() assures Injector is off when releasing
|
||||
if (Injector.isActive()) {
|
||||
if (url.equals(vehicleUrl)) {
|
||||
((StringResponseCallback) callback).onResponse(Injector.getDiscovery());
|
||||
} else if (url.endsWith(vehicleUrl)) {
|
||||
((StringResponseCallback) callback).onResponse(Injector.getStatus());
|
||||
} else {
|
||||
logger.debug("Simulation of {} not supported", url);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// return in case of unknown brand
|
||||
if (!BimmerConstants.ALL_BRANDS.contains(brand.toLowerCase())) {
|
||||
logger.warn("Unknown Brand {}", brand);
|
||||
return;
|
||||
}
|
||||
|
||||
final Request req;
|
||||
final String completeUrl;
|
||||
|
||||
if (post) {
|
||||
completeUrl = url;
|
||||
req = httpClient.POST(url);
|
||||
if (encoding != null) {
|
||||
req.header(HttpHeader.CONTENT_TYPE, encoding);
|
||||
if (CONTENT_TYPE_URL_ENCODED.equals(encoding)) {
|
||||
req.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
|
||||
} else if (CONTENT_TYPE_JSON_ENCODED.equals(encoding)) {
|
||||
req.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completeUrl = params == null ? url : url + Constants.QUESTION + params;
|
||||
req = httpClient.newRequest(completeUrl);
|
||||
}
|
||||
req.header(HttpHeader.AUTHORIZATION, getToken().getBearerToken());
|
||||
req.header(HTTPConstants.X_USER_AGENT,
|
||||
String.format(BimmerConstants.X_USER_AGENT, brand, configuration.region));
|
||||
req.header(HttpHeader.ACCEPT_LANGUAGE, configuration.language);
|
||||
if (callback instanceof ByteResponseCallback) {
|
||||
req.header(HttpHeader.ACCEPT, "image/png");
|
||||
} else {
|
||||
req.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON_ENCODED);
|
||||
}
|
||||
|
||||
req.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send(new BufferingResponseListener() {
|
||||
@NonNullByDefault({})
|
||||
@Override
|
||||
public void onComplete(Result result) {
|
||||
if (result.getResponse().getStatus() != 200) {
|
||||
NetworkError error = new NetworkError();
|
||||
error.url = completeUrl;
|
||||
error.status = result.getResponse().getStatus();
|
||||
if (result.getResponse().getReason() != null) {
|
||||
error.reason = result.getResponse().getReason();
|
||||
} else {
|
||||
error.reason = result.getFailure().getMessage();
|
||||
}
|
||||
error.params = result.getRequest().getParams().toString();
|
||||
logger.debug("HTTP Error {}", error.toString());
|
||||
callback.onError(error);
|
||||
} else {
|
||||
if (callback instanceof StringResponseCallback responseCallback) {
|
||||
responseCallback.onResponse(getContentAsString());
|
||||
} else if (callback instanceof ByteResponseCallback responseCallback) {
|
||||
responseCallback.onResponse(getContent());
|
||||
} else {
|
||||
logger.error("unexpected reponse type {}", callback.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void get(String url, @Nullable String coding, @Nullable String params, final String brand,
|
||||
ResponseCallback callback) {
|
||||
call(url, false, coding, params, brand, callback);
|
||||
}
|
||||
|
||||
public void post(String url, @Nullable String coding, @Nullable String params, final String brand,
|
||||
ResponseCallback callback) {
|
||||
call(url, true, coding, params, brand, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand
|
||||
*
|
||||
* @param brand
|
||||
* @param callback
|
||||
*/
|
||||
public void requestVehicles(String brand, StringResponseCallback callback) {
|
||||
// calculate necessary parameters for query
|
||||
MultiMap<String> vehicleParams = new MultiMap<String>();
|
||||
vehicleParams.put(BimmerConstants.TIRE_GUARD_MODE, Constants.ENABLED);
|
||||
vehicleParams.put(BimmerConstants.APP_DATE_TIME, Long.toString(System.currentTimeMillis()));
|
||||
vehicleParams.put(BimmerConstants.APP_TIMEZONE, Integer.toString(Converter.getOffsetMinutes()));
|
||||
String params = UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false);
|
||||
get(vehicleUrl + "?" + params, null, null, brand, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public void requestVehicles(StringResponseCallback callback) {
|
||||
BimmerConstants.ALL_BRANDS.forEach(brand -> {
|
||||
requestVehicles(brand, callback);
|
||||
});
|
||||
}
|
||||
|
||||
public void requestImage(VehicleConfiguration config, ImageProperties props, ByteResponseCallback callback) {
|
||||
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ "/eadrax-ics/v3/presentation/vehicles/" + config.vin + "/images?carView=" + props.viewport;
|
||||
get(localImageUrl, null, null, config.vehicleBrand, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public void requestChargeStatistics(VehicleConfiguration config, StringResponseCallback callback) {
|
||||
MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
|
||||
chargeStatisticsParams.put("vin", config.vin);
|
||||
chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
|
||||
String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ "/eadrax-chs/v1/charging-statistics?" + params;
|
||||
get(chargeStatisticsUrl, null, null, config.vehicleBrand, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public void requestChargeSessions(VehicleConfiguration config, StringResponseCallback callback) {
|
||||
MultiMap<String> chargeSessionsParams = new MultiMap<String>();
|
||||
chargeSessionsParams.put("vin", "WBY1Z81040V905639");
|
||||
chargeSessionsParams.put("maxResults", "40");
|
||||
chargeSessionsParams.put("include_date_picker", "true");
|
||||
String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ "/eadrax-chs/v1/charging-sessions?" + params;
|
||||
|
||||
get(chargeSessionsUrl, null, null, config.vehicleBrand, callback);
|
||||
}
|
||||
|
||||
RemoteServiceHandler getRemoteServiceHandler(VehicleHandler vehicleHandler) {
|
||||
remoteServiceHandler = Optional.of(new RemoteServiceHandler(vehicleHandler, this));
|
||||
return remoteServiceHandler.get();
|
||||
}
|
||||
|
||||
// Token handling
|
||||
|
||||
/**
|
||||
* Gets new token if old one is expired or invalid. In case of error the token remains.
|
||||
* So if token refresh fails the corresponding requests will also fail and update the
|
||||
* Thing status accordingly.
|
||||
*
|
||||
* @return token
|
||||
*/
|
||||
public Token getToken() {
|
||||
if (!token.isValid()) {
|
||||
boolean tokenUpdateSuccess = false;
|
||||
switch (configuration.region) {
|
||||
case BimmerConstants.REGION_CHINA:
|
||||
tokenUpdateSuccess = updateTokenChina();
|
||||
break;
|
||||
case BimmerConstants.REGION_NORTH_AMERICA:
|
||||
tokenUpdateSuccess = updateToken();
|
||||
break;
|
||||
case BimmerConstants.REGION_ROW:
|
||||
tokenUpdateSuccess = updateToken();
|
||||
break;
|
||||
default:
|
||||
logger.warn("Region {} not supported", configuration.region);
|
||||
break;
|
||||
}
|
||||
if (!tokenUpdateSuccess) {
|
||||
logger.debug("Authorization failed!");
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything is catched by surroundig try catch
|
||||
* - HTTP Exceptions
|
||||
* - JSONSyntax Exceptions
|
||||
* - potential NullPointer Exceptions
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
public synchronized boolean updateToken() {
|
||||
try {
|
||||
/*
|
||||
* Step 1) Get basic values for further queries
|
||||
*/
|
||||
String authValuesUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||
+ BimmerConstants.API_OAUTH_CONFIG;
|
||||
Request authValuesRequest = httpClient.newRequest(authValuesUrl).timeout(HTTP_TIMEOUT_SEC,
|
||||
TimeUnit.SECONDS);
|
||||
authValuesRequest.header(ACP_SUBSCRIPTION_KEY, BimmerConstants.OCP_APIM_KEYS.get(configuration.region));
|
||||
authValuesRequest.header(X_USER_AGENT,
|
||||
String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
|
||||
|
||||
ContentResponse authValuesResponse = authValuesRequest.send();
|
||||
if (authValuesResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
|
||||
+ authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
|
||||
authValuesResponse);
|
||||
}
|
||||
AuthQueryResponse aqr = Converter.getGson().fromJson(authValuesResponse.getContentAsString(),
|
||||
AuthQueryResponse.class);
|
||||
|
||||
/*
|
||||
* Step 2) Calculate values for base parameters
|
||||
*/
|
||||
String verfifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
|
||||
String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verfifierBytes.getBytes());
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
|
||||
String codeChallange = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
||||
String stateBytes = StringUtils.getRandomAlphabetic(16).toLowerCase();
|
||||
String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
|
||||
|
||||
MultiMap<String> baseParams = new MultiMap<String>();
|
||||
baseParams.put(CLIENT_ID, aqr.clientId);
|
||||
baseParams.put(RESPONSE_TYPE, CODE);
|
||||
baseParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
baseParams.put(STATE, state);
|
||||
baseParams.put(NONCE, BimmerConstants.LOGIN_NONCE);
|
||||
baseParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
|
||||
baseParams.put(CODE_CHALLENGE, codeChallange);
|
||||
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
|
||||
|
||||
/**
|
||||
* Step 3) Authorization with username and password
|
||||
*/
|
||||
String loginUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
|
||||
Request loginRequest = httpClient.POST(loginUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
|
||||
MultiMap<String> loginParams = new MultiMap<String>(baseParams);
|
||||
loginParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
|
||||
loginParams.put(USERNAME, configuration.userName);
|
||||
loginParams.put(PASSWORD, configuration.password);
|
||||
loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse loginResponse = loginRequest.send();
|
||||
if (loginResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||
+ loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
|
||||
loginResponse);
|
||||
}
|
||||
String authCode = getAuthCode(loginResponse.getContentAsString());
|
||||
|
||||
/**
|
||||
* Step 4) Authorize with code
|
||||
*/
|
||||
Request authRequest = httpClient.POST(loginUrl).followRedirects(false).timeout(HTTP_TIMEOUT_SEC,
|
||||
TimeUnit.SECONDS);
|
||||
MultiMap<String> authParams = new MultiMap<String>(baseParams);
|
||||
authParams.put(AUTHORIZATION, authCode);
|
||||
authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse authResponse = authRequest.send();
|
||||
if (authResponse.getStatus() != 302) {
|
||||
throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
|
||||
+ ", Message: " + authResponse.getContentAsString(), authResponse);
|
||||
}
|
||||
String code = MyBMWProxy.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
|
||||
|
||||
/**
|
||||
* Step 5) Request token
|
||||
*/
|
||||
Request codeRequest = httpClient.POST(aqr.tokenEndpoint).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||
String basicAuth = "Basic "
|
||||
+ Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
|
||||
codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
codeRequest.header(AUTHORIZATION, basicAuth);
|
||||
|
||||
MultiMap<String> codeParams = new MultiMap<String>();
|
||||
codeParams.put(CODE, code);
|
||||
codeParams.put(CODE_VERIFIER, codeVerifier);
|
||||
codeParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
codeParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
|
||||
codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse codeResponse = codeRequest.send();
|
||||
if (codeResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
|
||||
+ ", Message: " + codeResponse.getContentAsString(), codeResponse);
|
||||
}
|
||||
AuthResponse ar = Converter.getGson().fromJson(codeResponse.getContentAsString(), AuthResponse.class);
|
||||
token.setType(ar.tokenType);
|
||||
token.setToken(ar.accessToken);
|
||||
token.setExpiration(ar.expiresIn);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getAuthCode(String response) {
|
||||
String[] keys = response.split("&");
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i].startsWith(AUTHORIZATION)) {
|
||||
String authCode = keys[i].split("=")[1];
|
||||
authCode = authCode.split("\"")[0];
|
||||
return authCode;
|
||||
}
|
||||
}
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
|
||||
public static String codeFromUrl(String encodedUrl) {
|
||||
final MultiMap<String> tokenMap = new MultiMap<String>();
|
||||
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
|
||||
final StringBuilder codeFound = new StringBuilder();
|
||||
tokenMap.forEach((key, value) -> {
|
||||
if (!value.isEmpty()) {
|
||||
String val = value.get(0);
|
||||
if (key.endsWith(CODE)) {
|
||||
codeFound.append(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
return codeFound.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
public synchronized boolean updateTokenChina() {
|
||||
try {
|
||||
/**
|
||||
* Step 1) get public key
|
||||
*/
|
||||
String publicKeyUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
|
||||
+ BimmerConstants.CHINA_PUBLIC_KEY;
|
||||
Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||
oauthQueryRequest.header(HttpHeader.USER_AGENT, BimmerConstants.USER_AGENT);
|
||||
oauthQueryRequest.header(X_USER_AGENT,
|
||||
String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
|
||||
ContentResponse publicKeyResponse = oauthQueryRequest.send();
|
||||
if (publicKeyResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
|
||||
+ publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(),
|
||||
publicKeyResponse);
|
||||
}
|
||||
ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponse.getContentAsString(),
|
||||
ChinaPublicKeyResponse.class);
|
||||
|
||||
/**
|
||||
* Step 2) Encode password with public key
|
||||
*/
|
||||
// https://www.baeldung.com/java-read-pem-file-keys
|
||||
String publicKeyStr = pkr.data.value;
|
||||
String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "")
|
||||
.replace("\\n", "").trim();
|
||||
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
|
||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||
PublicKey publicKey = kf.generatePublic(spec);
|
||||
// https://www.thexcoders.net/java-ciphers-rsa/
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
|
||||
String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
|
||||
/**
|
||||
* Step 3) Send Auth with encoded password
|
||||
*/
|
||||
String tokenUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
|
||||
+ BimmerConstants.CHINA_LOGIN;
|
||||
Request loginRequest = httpClient.POST(tokenUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
|
||||
loginRequest.header(X_USER_AGENT,
|
||||
String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
|
||||
String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
|
||||
+ "\"}";
|
||||
loginRequest.content(new StringContentProvider(jsonContent));
|
||||
ContentResponse tokenResponse = loginRequest.send();
|
||||
if (tokenResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||
+ tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(),
|
||||
tokenResponse);
|
||||
}
|
||||
String authCode = getAuthCode(tokenResponse.getContentAsString());
|
||||
|
||||
/**
|
||||
* Step 4) Decode access token
|
||||
*/
|
||||
ChinaTokenResponse cat = Converter.getGson().fromJson(authCode, ChinaTokenResponse.class);
|
||||
String token = cat.data.accessToken;
|
||||
// https://www.baeldung.com/java-jwt-token-decode
|
||||
String[] chunks = token.split("\\.");
|
||||
String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1]));
|
||||
ChinaTokenExpiration cte = Converter.getGson().fromJson(tokenJwtDecodeStr, ChinaTokenExpiration.class);
|
||||
Token t = new Token();
|
||||
t.setToken(token);
|
||||
t.setType(cat.data.tokenType);
|
||||
t.setExpirationTotal(cte.exp);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.ExecutionState;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RemoteServiceExecutor} handles executions of remote services
|
||||
* towards your Vehicle
|
||||
*
|
||||
* @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - rename and refactor for v2
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteServiceExecutor {
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteServiceExecutor.class);
|
||||
|
||||
private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
|
||||
private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
|
||||
|
||||
private final MyBMWProxy proxy;
|
||||
private final VehicleHandler handler;
|
||||
|
||||
private int counter = 0;
|
||||
private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
|
||||
private Optional<String> serviceExecuting = Optional.empty();
|
||||
private Optional<String> executingEventId = Optional.empty();
|
||||
|
||||
public RemoteServiceExecutor(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
|
||||
handler = vehicleHandler;
|
||||
proxy = myBmwProxy;
|
||||
}
|
||||
|
||||
public boolean execute(RemoteService service) {
|
||||
synchronized (this) {
|
||||
if (serviceExecuting.isPresent()) {
|
||||
logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
|
||||
// only one service executing
|
||||
return false;
|
||||
}
|
||||
serviceExecuting = Optional.of(service.getId());
|
||||
}
|
||||
try {
|
||||
ExecutionStatusContainer executionStatus = proxy.executeRemoteServiceCall(
|
||||
handler.getVehicleConfiguration().get().getVin(),
|
||||
handler.getVehicleConfiguration().get().getVehicleBrand(), service);
|
||||
handleRemoteExecution(executionStatus);
|
||||
} catch (NetworkException e) {
|
||||
handleRemoteServiceException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void getState() {
|
||||
synchronized (this) {
|
||||
serviceExecuting.ifPresentOrElse(service -> {
|
||||
if (counter >= GIVEUP_COUNTER) {
|
||||
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.TIMEOUT.name().toLowerCase());
|
||||
reset();
|
||||
// immediately refresh data
|
||||
handler.getData();
|
||||
} else {
|
||||
counter++;
|
||||
try {
|
||||
ExecutionStatusContainer executionStatusContainer = proxy.executeRemoteServiceStatusCall(
|
||||
handler.getVehicleConfiguration().get().getVehicleBrand(), executingEventId.get());
|
||||
handleRemoteExecution(executionStatusContainer);
|
||||
} catch (NetworkException e) {
|
||||
handleRemoteServiceException(e);
|
||||
}
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("No Service executed to get state");
|
||||
});
|
||||
stateJob = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoteServiceException(NetworkException e) {
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(e.getStatus()));
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoteExecution(ExecutionStatusContainer executionStatusContainer) {
|
||||
if (!executionStatusContainer.getEventId().isEmpty()) {
|
||||
// service initiated - store event id for further MyBMW updates
|
||||
executingEventId = Optional.of(executionStatusContainer.getEventId());
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.INITIATED.name().toLowerCase());
|
||||
} else if (!executionStatusContainer.getEventStatus().isEmpty()) {
|
||||
// service status updated
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
executionStatusContainer.getEventStatus().toLowerCase());
|
||||
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
|
||||
|| ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
|
||||
// refresh loop ends - update of status handled in the normal refreshInterval.
|
||||
// Earlier update doesn't show better results!
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// schedule even if no result is present until retries exceeded
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(job -> {
|
||||
if (!job.isDone()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
});
|
||||
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
serviceExecuting = Optional.empty();
|
||||
executingEventId = Optional.empty();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(action -> {
|
||||
if (!action.isDone()) {
|
||||
action.cancel(true);
|
||||
}
|
||||
stateJob = Optional.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_JSON_ENCODED;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
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.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link RemoteServiceHandler} handles executions of remote services towards your Vehicle
|
||||
*
|
||||
* @see <a href="https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py">
|
||||
* https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py</a>
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteServiceHandler implements StringResponseCallback {
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteServiceHandler.class);
|
||||
|
||||
private static final String EVENT_ID = "eventId";
|
||||
private static final String DATA = "data";
|
||||
private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
|
||||
private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
|
||||
|
||||
private final MyBMWProxy proxy;
|
||||
private final VehicleHandler handler;
|
||||
private final String serviceExecutionAPI;
|
||||
private final String serviceExecutionStateAPI;
|
||||
|
||||
private int counter = 0;
|
||||
private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
|
||||
private Optional<String> serviceExecuting = Optional.empty();
|
||||
private Optional<String> executingEventId = Optional.empty();
|
||||
|
||||
public enum ExecutionState {
|
||||
READY,
|
||||
INITIATED,
|
||||
PENDING,
|
||||
DELIVERED,
|
||||
EXECUTED,
|
||||
ERROR,
|
||||
TIMEOUT
|
||||
}
|
||||
|
||||
public enum RemoteService {
|
||||
LIGHT_FLASH("Flash Lights", REMOTE_SERVICE_LIGHT_FLASH, REMOTE_SERVICE_LIGHT_FLASH),
|
||||
VEHICLE_FINDER("Vehicle Finder", REMOTE_SERVICE_VEHICLE_FINDER, REMOTE_SERVICE_VEHICLE_FINDER),
|
||||
DOOR_LOCK("Door Lock", REMOTE_SERVICE_DOOR_LOCK, REMOTE_SERVICE_DOOR_LOCK),
|
||||
DOOR_UNLOCK("Door Unlock", REMOTE_SERVICE_DOOR_UNLOCK, REMOTE_SERVICE_DOOR_UNLOCK),
|
||||
HORN_BLOW("Horn Blow", REMOTE_SERVICE_HORN, REMOTE_SERVICE_HORN),
|
||||
CLIMATE_NOW_START("Start Climate", REMOTE_SERVICE_AIR_CONDITIONING_START, "climate-now?action=START"),
|
||||
CLIMATE_NOW_STOP("Stop Climate", REMOTE_SERVICE_AIR_CONDITIONING_STOP, "climate-now?action=STOP");
|
||||
|
||||
private final String label;
|
||||
private final String id;
|
||||
private final String command;
|
||||
|
||||
RemoteService(final String label, final String id, String command) {
|
||||
this.label = label;
|
||||
this.id = id;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteServiceHandler(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
|
||||
handler = vehicleHandler;
|
||||
proxy = myBmwProxy;
|
||||
final VehicleConfiguration config = handler.getConfiguration().get();
|
||||
serviceExecutionAPI = proxy.remoteCommandUrl + config.vin + "/";
|
||||
serviceExecutionStateAPI = proxy.remoteStatusUrl;
|
||||
}
|
||||
|
||||
boolean execute(RemoteService service, String... data) {
|
||||
synchronized (this) {
|
||||
if (serviceExecuting.isPresent()) {
|
||||
logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
|
||||
// only one service executing
|
||||
return false;
|
||||
}
|
||||
serviceExecuting = Optional.of(service.getId());
|
||||
}
|
||||
final MultiMap<String> dataMap = new MultiMap<String>();
|
||||
if (data.length > 0) {
|
||||
dataMap.add(DATA, data[0]);
|
||||
proxy.post(serviceExecutionAPI + service.getCommand(), CONTENT_TYPE_JSON_ENCODED, data[0],
|
||||
handler.getConfiguration().get().vehicleBrand, this);
|
||||
} else {
|
||||
proxy.post(serviceExecutionAPI + service.getCommand(), null, null,
|
||||
handler.getConfiguration().get().vehicleBrand, this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void getState() {
|
||||
synchronized (this) {
|
||||
serviceExecuting.ifPresentOrElse(service -> {
|
||||
if (counter >= GIVEUP_COUNTER) {
|
||||
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.TIMEOUT.name().toLowerCase());
|
||||
reset();
|
||||
// immediately refresh data
|
||||
handler.getData();
|
||||
} else {
|
||||
counter++;
|
||||
final MultiMap<String> dataMap = new MultiMap<String>();
|
||||
dataMap.add(EVENT_ID, executingEventId.get());
|
||||
final String encoded = UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false);
|
||||
proxy.post(serviceExecutionStateAPI + Constants.QUESTION + encoded, null, null,
|
||||
handler.getConfiguration().get().vehicleBrand, this);
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("No Service executed to get state");
|
||||
});
|
||||
stateJob = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@Nullable String result) {
|
||||
if (result != null) {
|
||||
try {
|
||||
ExecutionStatusContainer esc = Converter.getGson().fromJson(result, ExecutionStatusContainer.class);
|
||||
if (esc != null) {
|
||||
if (esc.eventId != null) {
|
||||
// service initiated - store event id for further MyBMW updates
|
||||
executingEventId = Optional.of(esc.eventId);
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.INITIATED.name().toLowerCase());
|
||||
} else if (esc.eventStatus != null) {
|
||||
// service status updated
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
esc.eventStatus.toLowerCase());
|
||||
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(esc.eventStatus)
|
||||
|| ExecutionState.ERROR.name().equalsIgnoreCase(esc.eventStatus)) {
|
||||
// refresh loop ends - update of status handled in the normal refreshInterval.
|
||||
// Earlier update doesn't show better results!
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException jse) {
|
||||
logger.debug("RemoteService response is unparseable: {} {}", result, jse.getMessage());
|
||||
}
|
||||
}
|
||||
// schedule even if no result is present until retries exceeded
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(job -> {
|
||||
if (!job.isDone()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
});
|
||||
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(NetworkError error) {
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(error.status));
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
serviceExecuting = Optional.empty();
|
||||
executingEventId = Optional.empty();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(action -> {
|
||||
if (!action.isDone()) {
|
||||
action.cancel(true);
|
||||
}
|
||||
stateJob = Optional.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||
|
||||
/**
|
||||
* The {@link ResponseCallback} Marker Interface for all ASYNC REST API callbacks
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ResponseCallback {
|
||||
void onError(NetworkError error);
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link StringResponseCallback} Interface for all String results from ASYNC REST API
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StringResponseCallback extends ResponseCallback {
|
||||
|
||||
void onResponse(@Nullable String result);
|
||||
}
|
@ -1,478 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Length;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargeSession;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.CBS;
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.DoorsWindows;
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.Location;
|
||||
import org.openhab.binding.mybmw.internal.dto.properties.Tires;
|
||||
import org.openhab.binding.mybmw.internal.dto.status.CCMMessage;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils.TimedChannel;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleChannelHandler} handles Channel updates
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class VehicleChannelHandler extends BaseThingHandler {
|
||||
protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
|
||||
protected boolean hasFuel = false;
|
||||
protected boolean isElectric = false;
|
||||
protected boolean isHybrid = false;
|
||||
|
||||
// List Interfaces
|
||||
protected List<CBS> serviceList = new ArrayList<CBS>();
|
||||
protected String selectedService = Constants.UNDEF;
|
||||
protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
|
||||
protected String selectedCC = Constants.UNDEF;
|
||||
protected List<ChargeSession> sessionList = new ArrayList<ChargeSession>();
|
||||
protected String selectedSession = Constants.UNDEF;
|
||||
|
||||
protected MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private LocationProvider locationProvider;
|
||||
|
||||
// Data Caches
|
||||
protected Optional<String> vehicleStatusCache = Optional.empty();
|
||||
protected Optional<byte[]> imageCache = Optional.empty();
|
||||
|
||||
public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
|
||||
super(thing);
|
||||
commandOptionProvider = cop;
|
||||
locationProvider = lp;
|
||||
if (lp.getLocation() == null) {
|
||||
logger.debug("Home location not available");
|
||||
}
|
||||
|
||||
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
|
||||
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
|
||||
isHybrid = hasFuel && isElectric;
|
||||
|
||||
setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
|
||||
}
|
||||
|
||||
private void setOptions(final String group, final String id, List<CommandOption> options) {
|
||||
commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
|
||||
}
|
||||
|
||||
protected void updateChannel(final String group, final String id, final State state) {
|
||||
updateState(new ChannelUID(thing.getUID(), group, id), state);
|
||||
}
|
||||
|
||||
protected void updateChargeStatistics(ChargeStatisticsContainer csc) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, StringType.valueOf(csc.description));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY,
|
||||
QuantityType.valueOf(csc.statistics.totalEnergyCharged, Units.KILOWATT_HOUR));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
|
||||
DecimalType.valueOf(Integer.toString(csc.statistics.numberOfChargingSessions)));
|
||||
}
|
||||
|
||||
protected void updateVehicle(Vehicle v) {
|
||||
updateVehicleStatus(v);
|
||||
updateRange(v);
|
||||
updateDoors(v.properties.doorsAndWindows);
|
||||
updateWindows(v.properties.doorsAndWindows);
|
||||
updatePosition(v.properties.vehicleLocation);
|
||||
updateServices(v.properties.serviceRequired);
|
||||
updateCheckControls(v.status.checkControlMessages);
|
||||
updateTires(v.properties.tires);
|
||||
}
|
||||
|
||||
private void updateTires(@Nullable Tires tires) {
|
||||
if (tires == null) {
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
|
||||
QuantityType.valueOf(tires.frontLeft.status.currentPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
|
||||
QuantityType.valueOf(tires.frontLeft.status.targetPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
|
||||
QuantityType.valueOf(tires.frontRight.status.currentPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
|
||||
QuantityType.valueOf(tires.frontRight.status.targetPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
|
||||
QuantityType.valueOf(tires.rearLeft.status.currentPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
|
||||
QuantityType.valueOf(tires.rearLeft.status.targetPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
|
||||
QuantityType.valueOf(tires.rearRight.status.currentPressure / 100, Units.BAR));
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
|
||||
QuantityType.valueOf(tires.rearRight.status.targetPressure / 100, Units.BAR));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateVehicleStatus(Vehicle v) {
|
||||
updateChannel(CHANNEL_GROUP_STATUS, LOCK, Converter.getLockState(v.properties.areDoorsLocked));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
|
||||
VehicleStatusUtils.getNextServiceDate(v.properties.serviceRequired));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
|
||||
VehicleStatusUtils.getNextServiceMileage(v.properties.serviceRequired));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
|
||||
StringType.valueOf(v.status.checkControlMessagesGeneralState));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, MOTION, OnOffType.from(v.properties.inMotion));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
|
||||
DateTimeType.valueOf(Converter.zonedToLocalDateTime(v.properties.lastUpdatedAt)));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, DOORS, Converter.getClosedState(v.properties.areDoorsClosed));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, WINDOWS, Converter.getClosedState(v.properties.areWindowsClosed));
|
||||
|
||||
if (isElectric) {
|
||||
updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
|
||||
Converter.getConnectionState(v.properties.chargingState.isChargerConnected));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
|
||||
StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(v))));
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_INFO,
|
||||
StringType.valueOf(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(v))));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateRange(Vehicle v) {
|
||||
// get the right unit
|
||||
Unit<Length> lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
|
||||
if (lengthUnit == null) {
|
||||
return;
|
||||
}
|
||||
if (isElectric) {
|
||||
int rangeElectric = VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, v);
|
||||
QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
|
||||
QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
|
||||
lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius);
|
||||
}
|
||||
if (hasFuel) {
|
||||
int rangeFuel = VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, v);
|
||||
QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
|
||||
QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius);
|
||||
}
|
||||
if (isHybrid) {
|
||||
int rangeCombined = VehicleStatusUtils.getRange(Constants.PHEV, v);
|
||||
QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
|
||||
QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
|
||||
lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius);
|
||||
}
|
||||
if (v.status.currentMileage.mileage == Constants.INT_UNDEF) {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
|
||||
QuantityType.valueOf(v.status.currentMileage.mileage, lengthUnit));
|
||||
}
|
||||
if (isElectric) {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, SOC,
|
||||
QuantityType.valueOf(v.properties.chargingState.chargePercentage, Units.PERCENT));
|
||||
}
|
||||
if (hasFuel) {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
|
||||
QuantityType.valueOf(v.properties.fuelLevel.value, Units.LITRE));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateCheckControls(List<CCMMessage> ccl) {
|
||||
if (ccl.isEmpty()) {
|
||||
// No Check Control available - show not active
|
||||
CCMMessage ccm = new CCMMessage();
|
||||
ccm.title = Constants.NO_ENTRIES;
|
||||
ccm.longDescription = Constants.NO_ENTRIES;
|
||||
ccm.state = Constants.NO_ENTRIES;
|
||||
ccl.add(ccm);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
checkControlList = ccl;
|
||||
List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (CCMMessage ccEntry : checkControlList) {
|
||||
ccmDescriptionOptions.add(new CommandOption(Integer.toString(index), ccEntry.title));
|
||||
if (selectedCC.equals(ccEntry.title)) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectCheckControl(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void selectCheckControl(int index) {
|
||||
if (index >= 0 && index < checkControlList.size()) {
|
||||
CCMMessage ccEntry = checkControlList.get(index);
|
||||
selectedCC = ccEntry.title;
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.title));
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.longDescription));
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY, StringType.valueOf(ccEntry.state));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateServices(List<CBS> sl) {
|
||||
// if list is empty add "undefined" element
|
||||
if (sl.isEmpty()) {
|
||||
CBS cbsm = new CBS();
|
||||
cbsm.type = Constants.NO_ENTRIES;
|
||||
sl.add(cbsm);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
serviceList = sl;
|
||||
List<CommandOption> serviceNameOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (CBS serviceEntry : serviceList) {
|
||||
// create StateOption with "value = list index" and "label = human readable string"
|
||||
serviceNameOptions.add(new CommandOption(Integer.toString(index), serviceEntry.type));
|
||||
if (selectedService.equals(serviceEntry.type)) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectService(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void selectService(int index) {
|
||||
if (index >= 0 && index < serviceList.size()) {
|
||||
CBS serviceEntry = serviceList.get(index);
|
||||
selectedService = serviceEntry.type;
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, NAME, StringType.valueOf(Converter.toTitleCase(serviceEntry.type)));
|
||||
if (serviceEntry.dateTime != null) {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, DATE,
|
||||
DateTimeType.valueOf(Converter.zonedToLocalDateTime(serviceEntry.dateTime)));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, DATE, UnDefType.UNDEF);
|
||||
}
|
||||
if (serviceEntry.distance != null) {
|
||||
if (Constants.KILOMETERS_JSON.equals(serviceEntry.distance.units)) {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||
QuantityType.valueOf(serviceEntry.distance.value, Constants.KILOMETRE_UNIT));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||
QuantityType.valueOf(serviceEntry.distance.value, ImperialUnits.MILE));
|
||||
}
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||
QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSessions(List<ChargeSession> sl) {
|
||||
// if list is empty add "undefined" element
|
||||
if (sl.isEmpty()) {
|
||||
ChargeSession cs = new ChargeSession();
|
||||
cs.title = Constants.NO_ENTRIES;
|
||||
sl.add(cs);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
sessionList = sl;
|
||||
List<CommandOption> sessionNameOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (ChargeSession session : sessionList) {
|
||||
// create StateOption with "value = list index" and "label = human readable string"
|
||||
sessionNameOptions.add(new CommandOption(Integer.toString(index), session.title));
|
||||
if (selectedService.equals(session.title)) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectSession(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void selectSession(int index) {
|
||||
if (index >= 0 && index < sessionList.size()) {
|
||||
ChargeSession sessionEntry = sessionList.get(index);
|
||||
selectedService = sessionEntry.title;
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.title));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.subtitle));
|
||||
if (sessionEntry.energyCharged != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.energyCharged));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF));
|
||||
}
|
||||
if (sessionEntry.issues != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.issues));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN));
|
||||
}
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.sessionStatus));
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateChargeProfile(ChargeProfile cp) {
|
||||
ChargeProfileWrapper cpw = new ChargeProfileWrapper(cp);
|
||||
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()));
|
||||
ChargingSettings cs = cpw.getChargeSettings();
|
||||
if (cs != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
|
||||
DecimalType.valueOf(Integer.toString(cs.targetSoc)));
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
|
||||
OnOffType.from(cs.isAcCurrentLimitActive));
|
||||
}
|
||||
final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
|
||||
climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
|
||||
updateTimedState(cpw, ProfileKey.WINDOWSTART);
|
||||
updateTimedState(cpw, ProfileKey.WINDOWEND);
|
||||
updateTimedState(cpw, ProfileKey.TIMER1);
|
||||
updateTimedState(cpw, ProfileKey.TIMER2);
|
||||
updateTimedState(cpw, ProfileKey.TIMER3);
|
||||
updateTimedState(cpw, ProfileKey.TIMER4);
|
||||
}
|
||||
|
||||
protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
|
||||
final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
|
||||
if (timed != null) {
|
||||
final LocalTime time = profile.getTime(key);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
|
||||
time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
|
||||
: new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
|
||||
if (timed.timer != null) {
|
||||
final Boolean enabled = profile.isEnabled(key);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
|
||||
enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
|
||||
if (timed.hasDays) {
|
||||
final Set<DayOfWeek> days = profile.getDays(key);
|
||||
EnumSet.allOf(DayOfWeek.class).forEach(day -> {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
|
||||
timed.timer + ChargeProfileUtils.getDaysChannel(day),
|
||||
days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateDoors(DoorsWindows dw) {
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.doors.driverFront)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.doors.driverRear)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.doors.passengerFront)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.doors.passengerRear)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(dw.trunk)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(dw.hood)));
|
||||
}
|
||||
|
||||
protected void updateWindows(DoorsWindows dw) {
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.windows.driverFront)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.windows.driverRear)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.windows.passengerFront)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(dw.windows.passengerRear)));
|
||||
updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(dw.moonroof)));
|
||||
}
|
||||
|
||||
protected void updatePosition(Location pos) {
|
||||
if (pos.coordinates.latitude < 0) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
|
||||
} else {
|
||||
PointType vehicleLocation = PointType.valueOf(
|
||||
Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude));
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
|
||||
PointType homeLocation = locationProvider.getLocation();
|
||||
if (homeLocation != null) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
|
||||
QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE));
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.auth;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.API_OAUTH_CONFIG;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.APP_VERSIONS;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.AUTHORIZATION_CODE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.AUTH_PROVIDER;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.BRAND_BMW;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.CHINA_LOGIN;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.CHINA_PUBLIC_KEY;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.EADRAX_SERVER_MAP;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.LOGIN_NONCE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.OAUTH_ENDPOINT;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.OCP_APIM_KEYS;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_CHINA;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_NORTH_AMERICA;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_ROW;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.USER_AGENT;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.X_USER_AGENT;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.AUTHORIZATION;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CLIENT_ID;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE_CHALLENGE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE_CHALLENGE_METHOD;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE_VERIFIER;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_URL_ENCODED;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.GRANT_TYPE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_ACP_SUBSCRIPTION_KEY;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_BMW_CORRELATION_ID;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_X_CORRELATION_ID;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_X_IDENTITY_PROVIDER;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_X_USER_AGENT;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.NONCE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.PASSWORD;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.REDIRECT_URI;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.RESPONSE_TYPE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.SCOPE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.STATE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.USERNAME;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.AuthQueryResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.AuthResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.core.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* requests the tokens for MyBMW API authorization
|
||||
*
|
||||
* thanks to bimmer_connected https://github.com/bimmerconnected/bimmer_connected
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted from myBmwProxy
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWTokenController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWTokenController.class);
|
||||
|
||||
private Token token = new Token();
|
||||
private MyBMWBridgeConfiguration configuration;
|
||||
private HttpClient httpClient;
|
||||
|
||||
public MyBMWTokenController(MyBMWBridgeConfiguration configuration, HttpClient httpClient) {
|
||||
this.configuration = configuration;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets new token if old one is expired or invalid. In case of error the token
|
||||
* remains.
|
||||
* So if token refresh fails the corresponding requests will also fail and
|
||||
* update the Thing status accordingly.
|
||||
*
|
||||
* @return token
|
||||
*/
|
||||
public Token getToken() {
|
||||
if (!token.isValid()) {
|
||||
boolean tokenUpdateSuccess = false;
|
||||
switch (configuration.region) {
|
||||
case REGION_CHINA:
|
||||
tokenUpdateSuccess = updateTokenChina();
|
||||
break;
|
||||
case REGION_NORTH_AMERICA:
|
||||
case REGION_ROW:
|
||||
tokenUpdateSuccess = updateToken();
|
||||
break;
|
||||
default:
|
||||
logger.warn("Region {} not supported", configuration.region);
|
||||
break;
|
||||
}
|
||||
if (!tokenUpdateSuccess) {
|
||||
logger.warn("Authorization failed!");
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything is caught by surrounding try catch
|
||||
* - HTTP Exceptions
|
||||
* - JSONSyntax Exceptions
|
||||
* - potential NullPointer Exceptions
|
||||
*
|
||||
* @return true if the token was successfully updated
|
||||
*/
|
||||
private synchronized boolean updateToken() {
|
||||
try {
|
||||
/*
|
||||
* Step 1) Get basic values for further queries
|
||||
*/
|
||||
String uuidString = UUID.randomUUID().toString();
|
||||
|
||||
String authValuesUrl = "https://" + EADRAX_SERVER_MAP.get(configuration.region) + API_OAUTH_CONFIG;
|
||||
Request authValuesRequest = httpClient.newRequest(authValuesUrl);
|
||||
authValuesRequest.header(HEADER_ACP_SUBSCRIPTION_KEY, OCP_APIM_KEYS.get(configuration.region));
|
||||
authValuesRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
authValuesRequest.header(HEADER_X_IDENTITY_PROVIDER, AUTH_PROVIDER);
|
||||
authValuesRequest.header(HEADER_X_CORRELATION_ID, uuidString);
|
||||
authValuesRequest.header(HEADER_BMW_CORRELATION_ID, uuidString);
|
||||
|
||||
ContentResponse authValuesResponse = authValuesRequest.send();
|
||||
if (authValuesResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
|
||||
+ authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
|
||||
authValuesResponse);
|
||||
}
|
||||
AuthQueryResponse aqr = JsonStringDeserializer.deserializeString(authValuesResponse.getContentAsString(),
|
||||
AuthQueryResponse.class);
|
||||
|
||||
logger.trace("authQueryResponse: {}", aqr);
|
||||
|
||||
/*
|
||||
* Step 2) Calculate values for oauth base parameters
|
||||
*/
|
||||
String codeVerifier = generateCodeVerifier();
|
||||
String codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
String state = generateState();
|
||||
|
||||
MultiMap<@Nullable String> baseParams = new MultiMap<>();
|
||||
baseParams.put(CLIENT_ID, aqr.clientId);
|
||||
baseParams.put(RESPONSE_TYPE, CODE);
|
||||
baseParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
baseParams.put(STATE, state);
|
||||
baseParams.put(NONCE, LOGIN_NONCE);
|
||||
baseParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
|
||||
baseParams.put(CODE_CHALLENGE, codeChallenge);
|
||||
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
|
||||
|
||||
/**
|
||||
* Step 3) Authorization with username and password
|
||||
*/
|
||||
String loginUrl = aqr.gcdmBaseUrl + OAUTH_ENDPOINT;
|
||||
Request loginRequest = httpClient.POST(loginUrl);
|
||||
|
||||
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
|
||||
MultiMap<@Nullable String> loginParams = new MultiMap<>(baseParams);
|
||||
loginParams.put(GRANT_TYPE, AUTHORIZATION_CODE);
|
||||
loginParams.put(USERNAME, configuration.userName);
|
||||
loginParams.put(PASSWORD, configuration.password);
|
||||
loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse loginResponse = loginRequest.send();
|
||||
if (loginResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||
+ loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
|
||||
loginResponse);
|
||||
}
|
||||
String authCode = getAuthCode(loginResponse.getContentAsString());
|
||||
|
||||
/**
|
||||
* Step 4) Authorize with code
|
||||
*/
|
||||
Request authRequest = httpClient.POST(loginUrl).followRedirects(false);
|
||||
MultiMap<@Nullable String> authParams = new MultiMap<>(baseParams);
|
||||
authParams.put(AUTHORIZATION, authCode);
|
||||
authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse authResponse = authRequest.send();
|
||||
if (authResponse.getStatus() != 302) {
|
||||
throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
|
||||
+ ", Message: " + authResponse.getContentAsString(), authResponse);
|
||||
}
|
||||
String code = codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
|
||||
|
||||
/**
|
||||
* Step 5) Request token
|
||||
*/
|
||||
Request codeRequest = httpClient.POST(aqr.tokenEndpoint);
|
||||
String basicAuth = "Basic "
|
||||
+ Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
|
||||
codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
codeRequest.header(AUTHORIZATION, basicAuth);
|
||||
|
||||
MultiMap<@Nullable String> codeParams = new MultiMap<>();
|
||||
codeParams.put(CODE, code);
|
||||
codeParams.put(CODE_VERIFIER, codeVerifier);
|
||||
codeParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
codeParams.put(GRANT_TYPE, AUTHORIZATION_CODE);
|
||||
codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse codeResponse = codeRequest.send();
|
||||
if (codeResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
|
||||
+ ", Message: " + codeResponse.getContentAsString(), codeResponse);
|
||||
}
|
||||
AuthResponse ar = JsonStringDeserializer.deserializeString(codeResponse.getContentAsString(),
|
||||
AuthResponse.class);
|
||||
token.setType(ar.tokenType);
|
||||
token.setToken(ar.accessToken);
|
||||
token.setExpiration(ar.expiresIn);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String generateState() {
|
||||
String stateBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
|
||||
}
|
||||
|
||||
private String generateCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
||||
}
|
||||
|
||||
private String generateCodeVerifier() {
|
||||
String verfifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(verfifierBytes.getBytes());
|
||||
}
|
||||
|
||||
private String getAuthCode(String response) {
|
||||
String[] keys = response.split("&");
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i].startsWith(AUTHORIZATION)) {
|
||||
String authCode = keys[i].split("=")[1];
|
||||
authCode = authCode.split("\"")[0];
|
||||
return authCode;
|
||||
}
|
||||
}
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
|
||||
private String codeFromUrl(String encodedUrl) {
|
||||
final MultiMap<@Nullable String> tokenMap = new MultiMap<>();
|
||||
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
|
||||
final StringBuilder codeFound = new StringBuilder();
|
||||
tokenMap.forEach((key, value) -> {
|
||||
if (value.size() > 0) {
|
||||
String val = value.get(0);
|
||||
if (key.endsWith(CODE) && (val != null)) {
|
||||
codeFound.append(val.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
return codeFound.toString();
|
||||
}
|
||||
|
||||
private synchronized boolean updateTokenChina() {
|
||||
try {
|
||||
/**
|
||||
* Step 1) get public key
|
||||
*/
|
||||
String publicKeyUrl = "https://" + EADRAX_SERVER_MAP.get(REGION_CHINA) + CHINA_PUBLIC_KEY;
|
||||
Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl);
|
||||
oauthQueryRequest.header(HttpHeader.USER_AGENT, USER_AGENT);
|
||||
oauthQueryRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
ContentResponse publicKeyResponse = oauthQueryRequest.send();
|
||||
if (publicKeyResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
|
||||
+ publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(),
|
||||
publicKeyResponse);
|
||||
}
|
||||
ChinaPublicKeyResponse pkr = JsonStringDeserializer
|
||||
.deserializeString(publicKeyResponse.getContentAsString(), ChinaPublicKeyResponse.class);
|
||||
|
||||
/**
|
||||
* Step 2) Encode password with public key
|
||||
*/
|
||||
// https://www.baeldung.com/java-read-pem-file-keys
|
||||
String publicKeyStr = pkr.data.value;
|
||||
String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "")
|
||||
.replace("\\n", "").trim();
|
||||
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
|
||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
|
||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||
PublicKey publicKey = kf.generatePublic(spec);
|
||||
// https://www.thexcoders.net/java-ciphers-rsa/
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
|
||||
String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
|
||||
/**
|
||||
* Step 3) Send Auth with encoded password
|
||||
*/
|
||||
String tokenUrl = "https://" + EADRAX_SERVER_MAP.get(REGION_CHINA) + CHINA_LOGIN;
|
||||
Request loginRequest = httpClient.POST(tokenUrl);
|
||||
loginRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
|
||||
+ "\"}";
|
||||
loginRequest.content(new StringContentProvider(jsonContent));
|
||||
ContentResponse tokenResponse = loginRequest.send();
|
||||
if (tokenResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||
+ tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(),
|
||||
tokenResponse);
|
||||
}
|
||||
String authCode = getAuthCode(tokenResponse.getContentAsString());
|
||||
|
||||
/**
|
||||
* Step 4) Decode access token
|
||||
*/
|
||||
ChinaTokenResponse cat = JsonStringDeserializer.deserializeString(authCode, ChinaTokenResponse.class);
|
||||
String token = cat.data.accessToken;
|
||||
// https://www.baeldung.com/java-jwt-token-decode
|
||||
String[] chunks = token.split("\\.");
|
||||
String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1]));
|
||||
ChinaTokenExpiration cte = JsonStringDeserializer.deserializeString(tokenJwtDecodeStr,
|
||||
ChinaTokenExpiration.class);
|
||||
Token t = new Token();
|
||||
t.setToken(token);
|
||||
t.setType(cat.data.tokenType);
|
||||
t.setExpirationTotal(cte.exp);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
package org.openhab.binding.mybmw.internal.handler.auth;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
*
|
||||
* deserialization of a JSON string to a Java Object
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface JsonStringDeserializer {
|
||||
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(JsonStringDeserializer.class);
|
||||
|
||||
static final Gson GSON = new Gson();
|
||||
|
||||
public static List<VehicleBase> getVehicleBaseList(String vehicleBaseJson) {
|
||||
try {
|
||||
VehicleBase[] vehicleBaseArray = deserializeString(vehicleBaseJson, VehicleBase[].class);
|
||||
return Arrays.asList(vehicleBaseArray);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ArrayList<VehicleBase>();
|
||||
}
|
||||
}
|
||||
|
||||
public static VehicleStateContainer getVehicleState(String vehicleStateJson) {
|
||||
try {
|
||||
VehicleStateContainer vehicleState = deserializeString(vehicleStateJson, VehicleStateContainer.class);
|
||||
vehicleState.setRawStateJson(vehicleStateJson);
|
||||
return vehicleState;
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new VehicleStateContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChargingStatisticsContainer getChargingStatistics(String chargeStatisticsJson) {
|
||||
try {
|
||||
ChargingStatisticsContainer chargeStatistics = deserializeString(chargeStatisticsJson,
|
||||
ChargingStatisticsContainer.class);
|
||||
return chargeStatistics;
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ChargingStatisticsContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChargingSessionsContainer getChargingSessions(String chargeSessionsJson) {
|
||||
try {
|
||||
return deserializeString(chargeSessionsJson, ChargingSessionsContainer.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ChargingSessionsContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ExecutionStatusContainer getExecutionStatus(String executionStatusJson) {
|
||||
try {
|
||||
return deserializeString(executionStatusJson, ExecutionStatusContainer.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ExecutionStatusContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T deserializeString(String toBeDeserialized, Class<T> deserializedClass) {
|
||||
return GSON.fromJson(toBeDeserialized, deserializedClass);
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class is for local testing. You have to configure a connected account with username = "testuser" and password =
|
||||
* vehicle to be tested (e.g. BEV, ICE, BEV2, MILD_HYBRID,...)
|
||||
* The respective files are loaded from the resources folder
|
||||
*
|
||||
* You have to set the environment variable "ENVIRONMENT" to the value "test"
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWFileProxy implements MyBMWProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWFileProxy.class);
|
||||
private String vehicleToBeTested;
|
||||
|
||||
private static final String RESPONSES = "responses" + File.separator;
|
||||
private static final String VEHICLES_BASE = File.separator + "vehicles_base.json";
|
||||
private static final String VEHICLES_STATE = File.separator + "vehicles_state.json";
|
||||
private static final String CHARGING_SESSIONS = File.separator + "charging_sessions.json";
|
||||
private static final String CHARGING_STATISTICS = File.separator + "charging_statistics.json";
|
||||
private static final String REMOTE_SERVICES_CALL = File.separator + "remote_service_call.json";
|
||||
private static final String REMOTE_SERVICES_STATE = File.separator + "remote_service_status.json";
|
||||
|
||||
public MyBMWFileProxy(HttpClientFactory httpClientFactory, MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - initialize");
|
||||
vehicleToBeTested = bridgeConfiguration.password;
|
||||
}
|
||||
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - update bridge");
|
||||
vehicleToBeTested = bridgeConfiguration.password;
|
||||
}
|
||||
|
||||
public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
|
||||
List<@NonNull Vehicle> vehicles = new ArrayList<>();
|
||||
List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
|
||||
Vehicle vehicle = new Vehicle();
|
||||
vehicle.setVehicleBase(vehicleBase);
|
||||
vehicle.setVehicleState(vehicleState);
|
||||
vehicles.add(vehicle);
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
*/
|
||||
public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
|
||||
String vehicleResponseString = requestVehiclesBaseJson(brand);
|
||||
return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
|
||||
}
|
||||
|
||||
public String requestVehiclesBaseJson(String brand) throws NetworkException {
|
||||
String vehicleResponseString = fileToString(VEHICLES_BASE);
|
||||
return vehicleResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
public List<VehicleBase> requestVehiclesBase() throws NetworkException {
|
||||
List<VehicleBase> vehicles = new ArrayList<>();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
vehicles.addAll(requestVehiclesBase(brand));
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param config
|
||||
* @param props
|
||||
* @return
|
||||
*/
|
||||
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
|
||||
return "".getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param baseVehicle
|
||||
* @return
|
||||
*/
|
||||
public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
|
||||
return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
|
||||
}
|
||||
|
||||
public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = fileToString(VEHICLES_STATE);
|
||||
return vehicleStateResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
*/
|
||||
public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
|
||||
}
|
||||
|
||||
public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = fileToString(CHARGING_STATISTICS);
|
||||
return chargeStatisticsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
*/
|
||||
public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
|
||||
}
|
||||
|
||||
public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = fileToString(CHARGING_SESSIONS);
|
||||
return chargeSessionsResponseString;
|
||||
}
|
||||
|
||||
public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException {
|
||||
return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_CALL));
|
||||
}
|
||||
|
||||
public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
|
||||
throws NetworkException {
|
||||
return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_STATE));
|
||||
}
|
||||
|
||||
private String fileToString(String filename) {
|
||||
logger.trace("reading file {}", RESPONSES + vehicleToBeTested + filename);
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(
|
||||
MyBMWFileProxy.class.getClassLoader().getResourceAsStream(RESPONSES + vehicleToBeTested + filename),
|
||||
"UTF-8"))) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String sCurrentLine;
|
||||
|
||||
while ((sCurrentLine = br.readLine()) != null) {
|
||||
buf.append(sCurrentLine);
|
||||
}
|
||||
logger.trace("successful");
|
||||
return buf.toString();
|
||||
} catch (IOException e) {
|
||||
logger.error("file {} could not be loaded: {}", filename, e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.APP_VERSIONS;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
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.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.auth.MyBMWTokenController;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWHttpProxy} This class holds the important constants for the BMW Connected Drive Authorization.
|
||||
* They are taken from the Bimmercode from github
|
||||
* {@link https://github.com/bimmerconnected/bimmer_connected}
|
||||
* File defining these constants
|
||||
* {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
|
||||
* https://customer.bmwgroup.com/one/app/oauth.js
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactoring
|
||||
* @author Mark Herwege - extended log anonymization
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxy.class);
|
||||
private final HttpClient httpClient;
|
||||
private MyBMWBridgeConfiguration bridgeConfiguration;
|
||||
private final MyBMWTokenController myBMWTokenHandler;
|
||||
|
||||
/**
|
||||
* URLs taken from
|
||||
* https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
|
||||
*/
|
||||
private final String vehicleUrl;
|
||||
private final String vehicleStateUrl;
|
||||
private final String remoteCommandUrl;
|
||||
private final String remoteStatusUrl;
|
||||
|
||||
public MyBMWHttpProxy(HttpClientFactory httpClientFactory, MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWHttpProxy - initialize");
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
|
||||
myBMWTokenHandler = new MyBMWTokenController(bridgeConfiguration, httpClient);
|
||||
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
|
||||
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
+ BimmerConstants.API_VEHICLES;
|
||||
|
||||
vehicleStateUrl = vehicleUrl + "/state";
|
||||
|
||||
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
|
||||
remoteStatusUrl = remoteCommandUrl + "eventStatus";
|
||||
logger.trace("MyBMWHttpProxy - ready");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* requests all vehicles
|
||||
*
|
||||
* @return list of vehicles
|
||||
*/
|
||||
public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
|
||||
List<@NonNull Vehicle> vehicles = new ArrayList<>();
|
||||
List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
|
||||
Vehicle vehicle = new Vehicle();
|
||||
vehicle.setVehicleBase(vehicleBase);
|
||||
vehicle.setVehicleState(vehicleState);
|
||||
vehicles.add(vehicle);
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
* @return the vehicles of one brand
|
||||
*/
|
||||
public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
|
||||
String vehicleResponseString = requestVehiclesBaseJson(brand);
|
||||
return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the raw JSON for the vehicle
|
||||
*
|
||||
* @param brand
|
||||
* @return the base vehicle information as JSON string
|
||||
*/
|
||||
public String requestVehiclesBaseJson(String brand) throws NetworkException {
|
||||
byte[] vehicleResponse = get(vehicleUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String vehicleResponseString = new String(vehicleResponse, Charset.defaultCharset());
|
||||
return vehicleResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @return the list of vehicles
|
||||
*/
|
||||
public List<VehicleBase> requestVehiclesBase() throws NetworkException {
|
||||
List<VehicleBase> vehicles = new ArrayList<>();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
try {
|
||||
vehicles.addAll(requestVehiclesBase(brand));
|
||||
|
||||
Thread.sleep(10000);
|
||||
} catch (Exception e) {
|
||||
logger.warn("error retrieving the base vehicles for brand {}: {}", brand, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param vin the vin of the vehicle
|
||||
* @param brand the brand of the vehicle
|
||||
* @param props the image properties
|
||||
* @return the image as a byte array
|
||||
*/
|
||||
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
|
||||
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
+ "/eadrax-ics/v3/presentation/vehicles/" + vin + "/images?carView=" + props.viewport;
|
||||
return get(localImageUrl, brand, vin, HTTPConstants.CONTENT_TYPE_IMAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the vehicle state
|
||||
*/
|
||||
public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
|
||||
return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the raw state as JSON for one specific vehicle
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the vehicle state as string
|
||||
*/
|
||||
public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
|
||||
byte[] vehicleStateResponse = get(vehicleStateUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String vehicleStateResponseString = new String(vehicleStateResponse, Charset.defaultCharset());
|
||||
return vehicleStateResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge statistics
|
||||
*/
|
||||
public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles as JSON
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge statistics as JSON string
|
||||
*/
|
||||
public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
|
||||
MultiMap<@Nullable String> chargeStatisticsParams = new MultiMap<>();
|
||||
chargeStatisticsParams.put("vin", vin);
|
||||
chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
|
||||
String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
+ "/eadrax-chs/v1/charging-statistics?" + params;
|
||||
byte[] chargeStatisticsResponse = get(chargeStatisticsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeStatisticsResponseString = new String(chargeStatisticsResponse);
|
||||
return chargeStatisticsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge sessions
|
||||
*/
|
||||
public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles as JSON string
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge sessions as JSON string
|
||||
*/
|
||||
public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
|
||||
MultiMap<@Nullable String> chargeSessionsParams = new MultiMap<>();
|
||||
chargeSessionsParams.put("vin", vin);
|
||||
chargeSessionsParams.put("maxResults", "40");
|
||||
chargeSessionsParams.put("include_date_picker", "true");
|
||||
String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
+ "/eadrax-chs/v1/charging-sessions?" + params;
|
||||
byte[] chargeSessionsResponse = get(chargeSessionsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeSessionsResponseString = new String(chargeSessionsResponse);
|
||||
return chargeSessionsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a remote service call
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @param service the service which should be executed
|
||||
* @return the running service execution for status checks
|
||||
*/
|
||||
public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException {
|
||||
String executionUrl = remoteCommandUrl + vin + "/" + service.getCommand();
|
||||
|
||||
byte[] response = post(executionUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON, service.getBody());
|
||||
|
||||
return JsonStringDeserializer.getExecutionStatus(new String(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* check the status of a service call
|
||||
*
|
||||
* @param brand
|
||||
* @param eventid the ID of the currently running service execution
|
||||
* @return the running service execution for status checks
|
||||
*/
|
||||
public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
|
||||
throws NetworkException {
|
||||
String executionUrl = remoteStatusUrl + Constants.QUESTION + "eventId=" + eventId;
|
||||
|
||||
byte[] response = post(executionUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON, null);
|
||||
|
||||
return JsonStringDeserializer.getExecutionStatus(new String(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares a GET request to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private byte[] get(String url, final String brand, @Nullable String vin, String contentType)
|
||||
throws NetworkException {
|
||||
return call(url, false, brand, vin, contentType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares a POST request to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @param body
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private byte[] post(String url, final String brand, @Nullable String vin, String contentType, @Nullable String body)
|
||||
throws NetworkException {
|
||||
return call(url, true, brand, vin, contentType, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the real call to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param post boolean value indicating if it is a post request
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @param body
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private synchronized byte[] call(final String url, final boolean post, final String brand,
|
||||
final @Nullable String vin, final String contentType, final @Nullable String body) throws NetworkException {
|
||||
byte[] responseByteArray = "".getBytes();
|
||||
|
||||
// return in case of unknown brand
|
||||
if (!BimmerConstants.REQUESTED_BRANDS.contains(brand.toLowerCase())) {
|
||||
logger.warn("Unknown Brand {}", brand);
|
||||
throw new NetworkException("Unknown Brand " + brand);
|
||||
}
|
||||
|
||||
final Request req;
|
||||
|
||||
if (post) {
|
||||
req = httpClient.POST(url);
|
||||
} else {
|
||||
req = httpClient.newRequest(url);
|
||||
}
|
||||
|
||||
req.header(HttpHeader.AUTHORIZATION, myBMWTokenHandler.getToken().getBearerToken());
|
||||
req.header(HTTPConstants.HEADER_X_USER_AGENT, String.format(BimmerConstants.X_USER_AGENT, brand.toLowerCase(),
|
||||
APP_VERSIONS.get(bridgeConfiguration.region), bridgeConfiguration.region));
|
||||
req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.language);
|
||||
req.header(HttpHeader.ACCEPT, contentType);
|
||||
req.header(HTTPConstants.HEADER_BMW_VIN, vin);
|
||||
|
||||
try {
|
||||
ContentResponse response = req.timeout(HTTPConstants.HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() >= 300) {
|
||||
responseByteArray = "".getBytes();
|
||||
NetworkException exception = new NetworkException(url, response.getStatus(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()), body);
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(exception.getUrl(), vin), exception.getReason(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(body));
|
||||
throw exception;
|
||||
} else {
|
||||
responseByteArray = response.getContent();
|
||||
|
||||
// don't print images
|
||||
if (!HTTPConstants.CONTENT_TYPE_IMAGE.equals(contentType)) {
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(body));
|
||||
}
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(vin));
|
||||
throw new NetworkException(url, -1, null, body, e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(vin));
|
||||
throw new NetworkException(url, -1, null, body, e);
|
||||
}
|
||||
|
||||
return responseByteArray;
|
||||
}
|
||||
|
||||
private void logResponse(@Nullable String url, @Nullable String fingerprint, @Nullable String body) {
|
||||
logger.debug("###### Request URL - BEGIN ######");
|
||||
logger.debug("{}", url);
|
||||
logger.debug("###### Request Body - BEGIN ######");
|
||||
logger.debug("{}", body);
|
||||
logger.debug("###### Response Data - BEGIN ######");
|
||||
logger.debug("{}", fingerprint);
|
||||
logger.debug("###### Response Data - END ######");
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
|
||||
/**
|
||||
* this is the interface for requesting the myBMW responses
|
||||
*
|
||||
* @author Martin Grassl - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MyBMWProxy {
|
||||
|
||||
void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration);
|
||||
|
||||
List<@NonNull Vehicle> requestVehicles() throws NetworkException;
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
*/
|
||||
List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException;
|
||||
|
||||
String requestVehiclesBaseJson(String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
List<VehicleBase> requestVehiclesBase() throws NetworkException;
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param config
|
||||
* @param props
|
||||
* @return
|
||||
*/
|
||||
byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param baseVehicle
|
||||
* @return
|
||||
*/
|
||||
VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestVehicleStateJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
*/
|
||||
ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestChargeStatisticsJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
*/
|
||||
ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestChargeSessionsJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException;
|
||||
|
||||
ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId) throws NetworkException;
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NetworkException} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extend Exception
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetworkException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 123L;
|
||||
|
||||
private String url = "";
|
||||
private int status = -1;
|
||||
private String reason = "";
|
||||
private String body = "";
|
||||
|
||||
public NetworkException() {
|
||||
}
|
||||
|
||||
public NetworkException(String url, int status, @Nullable String reason, @Nullable String body) {
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
this.reason = reason != null ? reason : "";
|
||||
this.body = body != null ? body : "";
|
||||
}
|
||||
|
||||
public NetworkException(String url, int status, @Nullable String reason, @Nullable String body, Throwable cause) {
|
||||
super(cause);
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
this.reason = reason != null ? reason : "";
|
||||
this.body = body != null ? body : "";
|
||||
}
|
||||
|
||||
public NetworkException(String message) {
|
||||
super(message);
|
||||
this.reason = message;
|
||||
}
|
||||
|
||||
public NetworkException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public NetworkException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.reason = message;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NetworkException [url=" + url + ", status=" + status + ", reason=" + reason + ", body=" + body + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* anonymizes all occurrencies of locations and vins
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring & extension for any occurrence
|
||||
* @author Mark Herwege - extended log anonymization
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ResponseContentAnonymizer {
|
||||
|
||||
static final String ANONYMOUS_VIN = "anonymousVin";
|
||||
static final String VIN_PATTERN = "\"vin\":";
|
||||
static final String VEHICLE_CHARGING_LOCATION_PATTERN = "\"subtitle\":";
|
||||
static final String VEHICLE_LOCATION_PATTERN = "\"location\":";
|
||||
static final String VEHICLE_LOCATION_LATITUDE_PATTERN = "latitude";
|
||||
static final String VEHICLE_LOCATION_LONGITUDE_PATTERN = "longitude";
|
||||
static final String VEHICLE_LOCATION_FORMATTED_PATTERN = "formatted";
|
||||
static final String VEHICLE_LOCATION_HEADING_PATTERN = "heading";
|
||||
static final String VEHICLE_LOCATION_LATITUDE = "1.1";
|
||||
static final String VEHICLE_LOCATION_LONGITUDE = "2.2";
|
||||
static final String ANONYMOUS_ADDRESS = "anonymousAddress";
|
||||
static final String VEHICLE_LOCATION_HEADING = "-1";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_START = "\\\"location\\\"";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_END = "\\\"heading\\\"";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_REPLACER = "\"location\":{\"coordinates\":{\"latitude\":"
|
||||
+ VEHICLE_LOCATION_LATITUDE + ",\"longitude\":" + VEHICLE_LOCATION_LONGITUDE
|
||||
+ "},\"address\":{\"formatted\":\"" + ANONYMOUS_ADDRESS + "\"},";
|
||||
|
||||
static final String CLOSING_BRACKET = "}";
|
||||
static final String QUOTE = "\"";
|
||||
static final String CLOSE_VALUE = "\":";
|
||||
static final String COMMA = ",";
|
||||
|
||||
/**
|
||||
* anonymizes the responseContent
|
||||
* <p>
|
||||
* - vin
|
||||
* </p>
|
||||
* <p>
|
||||
* - location
|
||||
* </p>
|
||||
*
|
||||
* @param responseContent
|
||||
* @return
|
||||
*/
|
||||
public static String anonymizeResponseContent(@Nullable String responseContent) {
|
||||
if (responseContent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String anonymizedVinString = replaceVins(responseContent);
|
||||
|
||||
String anonymizedLocationString = replaceLocations(anonymizedVinString);
|
||||
|
||||
String anonymizedRawLocationString = replaceRawLocations(anonymizedLocationString);
|
||||
|
||||
String anonymizedChargingLocationString = replaceChargingLocations(anonymizedRawLocationString);
|
||||
|
||||
return anonymizedChargingLocationString;
|
||||
}
|
||||
|
||||
static String replaceChargingLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(VEHICLE_CHARGING_LOCATION_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(VEHICLE_CHARGING_LOCATION_PATTERN);
|
||||
replacedString.append(replaceChargingLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceChargingLocation(String responseContent) {
|
||||
String[] subtitleStrings = responseContent.split(" • ", 2);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
|
||||
replacedString.append("\"");
|
||||
replacedString.append(ANONYMOUS_ADDRESS);
|
||||
if (subtitleStrings.length > 1) {
|
||||
replacedString.append(" • ");
|
||||
replacedString.append(subtitleStrings[1]);
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceRawLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_START));
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(replaceRawLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* this just replaces a string
|
||||
*
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
static String replaceRawLocation(String stringToBeReplaced) {
|
||||
String[] stringParts = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_END));
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_REPLACER);
|
||||
replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_END);
|
||||
replacedString.append(stringParts[1]);
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(VEHICLE_LOCATION_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(VEHICLE_LOCATION_PATTERN);
|
||||
replacedString.append(replaceLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceLocation(String responseContent) {
|
||||
String stringToBeReplaced = responseContent;
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
// latitude
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LATITUDE_PATTERN,
|
||||
VEHICLE_LOCATION_LATITUDE);
|
||||
|
||||
// longitude
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LONGITUDE_PATTERN,
|
||||
VEHICLE_LOCATION_LONGITUDE);
|
||||
|
||||
// formatted address
|
||||
stringToBeReplaced = replaceStringValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_FORMATTED_PATTERN,
|
||||
ANONYMOUS_ADDRESS);
|
||||
|
||||
// heading
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_HEADING_PATTERN,
|
||||
VEHICLE_LOCATION_HEADING);
|
||||
|
||||
replacedString.append(stringToBeReplaced);
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceNumberValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
|
||||
String replacerValue) {
|
||||
int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
|
||||
+ (replacerPattern.length() + CLOSE_VALUE.length());
|
||||
int endIndex = -1;
|
||||
|
||||
// in an object, the comma comes after the value or a closing bracket
|
||||
if (stringToBeReplaced.indexOf(COMMA, startIndex) < stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex)) {
|
||||
endIndex = stringToBeReplaced.indexOf(COMMA, startIndex);
|
||||
} else {
|
||||
endIndex = stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex);
|
||||
}
|
||||
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(replacerValue);
|
||||
|
||||
return stringToBeReplaced.substring(endIndex);
|
||||
}
|
||||
|
||||
static String replaceStringValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
|
||||
String replacerValue) {
|
||||
// the startIndex is the String after the first quote of the value after the key
|
||||
// detect end of key
|
||||
int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
|
||||
+ (replacerPattern.length() + CLOSE_VALUE.length());
|
||||
// detect start of value
|
||||
startIndex = stringToBeReplaced.indexOf(QUOTE, startIndex) + 1;
|
||||
|
||||
// detect end of value
|
||||
int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
|
||||
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(replacerValue);
|
||||
|
||||
return stringToBeReplaced.substring(endIndex);
|
||||
}
|
||||
|
||||
static String replaceVins(String stringToBeReplaced) {
|
||||
String[] vinStrings = stringToBeReplaced.split(VIN_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(vinStrings[0]);
|
||||
for (int i = 1; vinStrings.length > 0 && i < vinStrings.length; i++) {
|
||||
replacedString.append(VIN_PATTERN);
|
||||
replacedString.append(replaceVin(vinStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceVin(String stringToBeReplaced) {
|
||||
// the vin is between two quotes
|
||||
int startIndex = stringToBeReplaced.indexOf(QUOTE) + 1;
|
||||
int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(ANONYMOUS_VIN);
|
||||
replacedString.append(stringToBeReplaced.substring(endIndex));
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static @Nullable String replaceVin(@Nullable String stringToBeReplaced, @Nullable String vin) {
|
||||
if (stringToBeReplaced == null) {
|
||||
return null;
|
||||
}
|
||||
return vin != null ? stringToBeReplaced.replace(vin, ANONYMOUS_VIN) : stringToBeReplaced;
|
||||
}
|
||||
}
|
@ -10,17 +10,23 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
package org.openhab.binding.mybmw.internal.handler.enums;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ByteResponseCallback} Interface for all raw byte results from ASYNC REST API
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*
|
||||
* execution state of a remote command
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ByteResponseCallback extends ResponseCallback {
|
||||
|
||||
void onResponse(byte[] result);
|
||||
public enum ExecutionState {
|
||||
READY,
|
||||
INITIATED,
|
||||
PENDING,
|
||||
DELIVERED,
|
||||
EXECUTED,
|
||||
ERROR,
|
||||
TIMEOUT
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.enums;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_AIR_CONDITIONING_START;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_AIR_CONDITIONING_STOP;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_CHARGE;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_DOOR_LOCK;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_DOOR_UNLOCK;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_HORN;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_LIGHT_FLASH;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_VEHICLE_FINDER;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* possible remote services
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - electric charging commands
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum RemoteService {
|
||||
LIGHT_FLASH("Flash Lights", REMOTE_SERVICE_LIGHT_FLASH, REMOTE_SERVICE_LIGHT_FLASH, ""),
|
||||
VEHICLE_FINDER("Vehicle Finder", REMOTE_SERVICE_VEHICLE_FINDER, REMOTE_SERVICE_VEHICLE_FINDER, ""),
|
||||
DOOR_LOCK("Door Lock", REMOTE_SERVICE_DOOR_LOCK, REMOTE_SERVICE_DOOR_LOCK, ""),
|
||||
DOOR_UNLOCK("Door Unlock", REMOTE_SERVICE_DOOR_UNLOCK, REMOTE_SERVICE_DOOR_UNLOCK, ""),
|
||||
HORN_BLOW("Horn Blow", REMOTE_SERVICE_HORN, REMOTE_SERVICE_HORN, ""),
|
||||
CLIMATE_NOW_START("Start Climate", REMOTE_SERVICE_AIR_CONDITIONING_START, "climate-now", "{\"action\": \"START\"}"),
|
||||
CLIMATE_NOW_STOP("Stop Climate", REMOTE_SERVICE_AIR_CONDITIONING_STOP, "climate-now", "{\"action\": \"STOP\"}"),
|
||||
CHARGE_NOW("Charge", REMOTE_SERVICE_CHARGE, "start-charging", "");
|
||||
|
||||
private final String label;
|
||||
private final String id;
|
||||
private final String command;
|
||||
private final String body;
|
||||
|
||||
RemoteService(final String label, final String id, String command, String body) {
|
||||
this.label = label;
|
||||
this.id = id;
|
||||
this.command = command;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.simulation;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link Injector} Simulates feedback of the BMW API
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Injector {
|
||||
private static boolean active = false;
|
||||
|
||||
// copy discovery json here
|
||||
private static String discovery = "";
|
||||
|
||||
// copy vehicle status json here
|
||||
private static String status = "";
|
||||
|
||||
public static boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public static String getDiscovery() {
|
||||
return discovery;
|
||||
}
|
||||
|
||||
public static String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
@ -27,49 +27,58 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
* <a href="https://customer.bmwgroup.com/one/app/oauth.js">https://customer.bmwgroup.com/one/app/oauth.js</a>
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - update to v2 API
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BimmerConstants {
|
||||
public interface BimmerConstants {
|
||||
|
||||
public static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
|
||||
public static final String REGION_CHINA = "CHINA";
|
||||
public static final String REGION_ROW = "ROW";
|
||||
static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
|
||||
static final String REGION_CHINA = "CHINA";
|
||||
static final String REGION_ROW = "ROW";
|
||||
|
||||
public static final String BRAND_BMW = "bmw";
|
||||
public static final String BRAND_MINI = "mini";
|
||||
public static final List<String> ALL_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
|
||||
static final String BRAND_BMW = "bmw";
|
||||
static final String BRAND_BMWI = "bmw_i";
|
||||
static final String BRAND_MINI = "mini";
|
||||
static final List<String> REQUESTED_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
|
||||
|
||||
public static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
|
||||
static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
|
||||
static final String AUTH_PROVIDER = "gcdm";
|
||||
|
||||
public static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
|
||||
public static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
|
||||
public static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn";
|
||||
public static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA,
|
||||
EADRAX_SERVER_NORTH_AMERICA, REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
|
||||
static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
|
||||
static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
|
||||
static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn";
|
||||
static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, EADRAX_SERVER_NORTH_AMERICA,
|
||||
REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
|
||||
|
||||
public static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
|
||||
public static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
|
||||
public static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
|
||||
static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
|
||||
static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
|
||||
static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
|
||||
REGION_ROW, OCP_APIM_KEY_ROW);
|
||||
|
||||
public static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey";
|
||||
public static final String CHINA_LOGIN = "/eadrax-coas/v1/login/pwd";
|
||||
static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey";
|
||||
static final String CHINA_LOGIN = "/eadrax-coas/v2/login/pwd";
|
||||
|
||||
// Http variables
|
||||
public static final String USER_AGENT = "Dart/2.14 (dart:io)";
|
||||
public static final String X_USER_AGENT = "android(SP1A.210812.016.C1);%s;2.5.2(14945);%s";
|
||||
static final String APP_VERSION_NORTH_AMERICA = "2.12.0(19883)";
|
||||
static final String APP_VERSION_ROW = "2.12.0(19883)";
|
||||
static final String APP_VERSION_CHINA = "2.3.0(13603)";
|
||||
static final Map<String, String> APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW,
|
||||
APP_VERSION_ROW, REGION_CHINA, APP_VERSION_CHINA);
|
||||
static final String USER_AGENT = "Dart/2.16 (dart:io)";
|
||||
// see const.py of bimmer_constants: user-agent; brand; app_version; region
|
||||
static final String X_USER_AGENT = "android(SP1A.210812.016.C1);%s;%s;%s";
|
||||
|
||||
public static final String LOGIN_NONCE = "login_nonce";
|
||||
public static final String AUTHORIZATION_CODE = "authorization_code";
|
||||
static final String LOGIN_NONCE = "login_nonce";
|
||||
static final String AUTHORIZATION_CODE = "authorization_code";
|
||||
|
||||
// Parameters for API Requests
|
||||
public static final String TIRE_GUARD_MODE = "tireGuardMode";
|
||||
public static final String APP_DATE_TIME = "appDateTime";
|
||||
public static final String APP_TIMEZONE = "apptimezone";
|
||||
static final String TIRE_GUARD_MODE = "tireGuardMode";
|
||||
static final String APP_DATE_TIME = "appDateTime";
|
||||
static final String APP_TIMEZONE = "apptimezone";
|
||||
|
||||
// API endpoints
|
||||
public static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
|
||||
public static final String API_VEHICLES = "/eadrax-vcs/v1/vehicles";
|
||||
public static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v2/presentation/remote-commands/"; // '/{vin}/{service_type}'
|
||||
public static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
|
||||
static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
|
||||
static final String API_VEHICLES = "/eadrax-vcs/v4/vehicles";
|
||||
static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v3/presentation/remote-commands/"; // '/{vin}/{service_type}'
|
||||
static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
|
||||
}
|
||||
|
@ -1,303 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey.*;
|
||||
import static org.openhab.binding.mybmw.internal.utils.Constants.*;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingMode;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingPreference;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingWindow;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.Time;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.Timer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ChargeProfileWrapper} Wrapper for ChargeProfiles
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - add ChargeProfileActions
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargeProfileWrapper {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ChargeProfileWrapper.class);
|
||||
|
||||
private static final String CHARGING_WINDOW = "chargingWindow";
|
||||
private static final String WEEKLY_PLANNER = "weeklyPlanner";
|
||||
private static final String ACTIVATE = "activate";
|
||||
private static final String DEACTIVATE = "deactivate";
|
||||
|
||||
public enum ProfileKey {
|
||||
CLIMATE,
|
||||
TIMER1,
|
||||
TIMER2,
|
||||
TIMER3,
|
||||
TIMER4,
|
||||
WINDOWSTART,
|
||||
WINDOWEND
|
||||
}
|
||||
|
||||
private Optional<ChargingMode> mode = Optional.empty();
|
||||
private Optional<ChargingPreference> preference = Optional.empty();
|
||||
private Optional<String> controlType = Optional.empty();
|
||||
private Optional<ChargingSettings> chargeSettings = Optional.empty();
|
||||
|
||||
private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
|
||||
private final Map<ProfileKey, LocalTime> times = new HashMap<>();
|
||||
private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
|
||||
|
||||
public ChargeProfileWrapper(final ChargeProfile profile) {
|
||||
setPreference(profile.chargingPreference);
|
||||
setMode(profile.chargingMode);
|
||||
controlType = Optional.of(profile.chargingControlType);
|
||||
chargeSettings = Optional.of(profile.chargingSettings);
|
||||
setEnabled(CLIMATE, profile.climatisationOn);
|
||||
|
||||
addTimer(TIMER1, profile.getTimerId(1));
|
||||
addTimer(TIMER2, profile.getTimerId(2));
|
||||
if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
|
||||
addTimer(TIMER3, profile.getTimerId(3));
|
||||
addTimer(TIMER4, profile.getTimerId(4));
|
||||
}
|
||||
|
||||
if (CHARGING_WINDOW.equals(profile.chargingPreference)) {
|
||||
addTime(WINDOWSTART, profile.reductionOfChargeCurrent.start);
|
||||
addTime(WINDOWEND, profile.reductionOfChargeCurrent.end);
|
||||
} else {
|
||||
preference.ifPresent(pref -> {
|
||||
if (ChargingPreference.chargingWindow.equals(pref)) {
|
||||
addTime(WINDOWSTART, null);
|
||||
addTime(WINDOWEND, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Boolean isEnabled(final ProfileKey key) {
|
||||
return enabled.get(key);
|
||||
}
|
||||
|
||||
public void setEnabled(final ProfileKey key, @Nullable final Boolean enabled) {
|
||||
if (enabled == null) {
|
||||
this.enabled.remove(key);
|
||||
} else {
|
||||
this.enabled.put(key, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getMode() {
|
||||
return mode.map(m -> m.name()).orElse(null);
|
||||
}
|
||||
|
||||
public @Nullable String getControlType() {
|
||||
return controlType.get();
|
||||
}
|
||||
|
||||
public @Nullable ChargingSettings getChargeSettings() {
|
||||
return chargeSettings.get();
|
||||
}
|
||||
|
||||
public void setMode(final @Nullable String mode) {
|
||||
if (mode != null) {
|
||||
try {
|
||||
this.mode = Optional.of(ChargingMode.valueOf(mode));
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
LOGGER.warn("unexpected value for chargingMode: {}", mode);
|
||||
}
|
||||
}
|
||||
this.mode = Optional.empty();
|
||||
}
|
||||
|
||||
public @Nullable String getPreference() {
|
||||
return preference.map(pref -> pref.name()).orElse(null);
|
||||
}
|
||||
|
||||
public void setPreference(final @Nullable String preference) {
|
||||
if (preference != null) {
|
||||
try {
|
||||
this.preference = Optional.of(ChargingPreference.valueOf(preference));
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
LOGGER.warn("unexpected value for chargingPreference: {}", preference);
|
||||
}
|
||||
}
|
||||
this.preference = Optional.empty();
|
||||
}
|
||||
|
||||
public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
|
||||
return daysOfWeek.get(key);
|
||||
}
|
||||
|
||||
public void setDays(final ProfileKey key, final @Nullable Set<DayOfWeek> days) {
|
||||
if (days == null) {
|
||||
daysOfWeek.remove(key);
|
||||
} else {
|
||||
daysOfWeek.put(key, days);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDayEnabled(final ProfileKey key, final DayOfWeek day, final boolean enabled) {
|
||||
final Set<DayOfWeek> days = daysOfWeek.get(key);
|
||||
if (days == null) {
|
||||
daysOfWeek.put(key, enabled ? EnumSet.of(day) : EnumSet.noneOf(DayOfWeek.class));
|
||||
} else {
|
||||
if (enabled) {
|
||||
days.add(day);
|
||||
} else {
|
||||
days.remove(day);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LocalTime getTime(final ProfileKey key) {
|
||||
LocalTime t = times.get(key);
|
||||
if (t != null) {
|
||||
return t;
|
||||
} else {
|
||||
LOGGER.debug("Profile not valid - Key {} doesn't contain boolean value", key);
|
||||
return Constants.NULL_LOCAL_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTime(final ProfileKey key, @Nullable LocalTime time) {
|
||||
if (time == null) {
|
||||
times.remove(key);
|
||||
} else {
|
||||
times.put(key, time);
|
||||
}
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
final ChargeProfile profile = new ChargeProfile();
|
||||
|
||||
preference.ifPresent(pref -> profile.chargingPreference = pref.name());
|
||||
profile.chargingControlType = controlType.get();
|
||||
Boolean enabledBool = isEnabled(CLIMATE);
|
||||
profile.climatisationOn = enabledBool == null ? false : enabledBool;
|
||||
preference.ifPresent(pref -> {
|
||||
if (ChargingPreference.chargingWindow.equals(pref)) {
|
||||
profile.chargingMode = getMode();
|
||||
final LocalTime start = getTime(WINDOWSTART);
|
||||
final LocalTime end = getTime(WINDOWEND);
|
||||
if (!start.equals(Constants.NULL_LOCAL_TIME) && !end.equals(Constants.NULL_LOCAL_TIME)) {
|
||||
ChargingWindow cw = new ChargingWindow();
|
||||
profile.reductionOfChargeCurrent = cw;
|
||||
cw.start = new Time();
|
||||
cw.start.hour = start.getHour();
|
||||
cw.start.minute = start.getMinute();
|
||||
cw.end = new Time();
|
||||
cw.end.hour = end.getHour();
|
||||
cw.end.minute = end.getMinute();
|
||||
}
|
||||
}
|
||||
});
|
||||
profile.departureTimes = new ArrayList<Timer>();
|
||||
profile.departureTimes.add(getTimer(TIMER1));
|
||||
profile.departureTimes.add(getTimer(TIMER2));
|
||||
if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
|
||||
profile.departureTimes.add(getTimer(TIMER3));
|
||||
profile.departureTimes.add(getTimer(TIMER4));
|
||||
}
|
||||
|
||||
profile.chargingSettings = chargeSettings.get();
|
||||
return Converter.getGson().toJson(profile);
|
||||
}
|
||||
|
||||
private void addTime(final ProfileKey key, @Nullable final Time time) {
|
||||
try {
|
||||
times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(Converter.getTime(time), TIME_FORMATER));
|
||||
} catch (DateTimeParseException dtpe) {
|
||||
LOGGER.warn("unexpected value for {} time: {}", key.name(), time);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTimer(final ProfileKey key, @Nullable final Timer timer) {
|
||||
if (timer == null) {
|
||||
enabled.put(key, false);
|
||||
addTime(key, null);
|
||||
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||
} else {
|
||||
enabled.put(key, ACTIVATE.equals(timer.action));
|
||||
addTime(key, timer.timeStamp);
|
||||
final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
|
||||
if (timer.timerWeekDays != null) {
|
||||
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||
for (String day : timer.timerWeekDays) {
|
||||
try {
|
||||
daySet.add(DayOfWeek.valueOf(day.toUpperCase()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
LOGGER.warn("unexpected value for {} day: {}", key.name(), day);
|
||||
}
|
||||
daysOfWeek.put(key, daySet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Timer getTimer(final ProfileKey key) {
|
||||
final Timer timer = new Timer();
|
||||
switch (key) {
|
||||
case TIMER1:
|
||||
timer.id = 1;
|
||||
break;
|
||||
case TIMER2:
|
||||
timer.id = 2;
|
||||
break;
|
||||
case TIMER3:
|
||||
timer.id = 3;
|
||||
break;
|
||||
case TIMER4:
|
||||
timer.id = 4;
|
||||
break;
|
||||
default:
|
||||
// timer id stays -1
|
||||
break;
|
||||
}
|
||||
Boolean enabledBool = isEnabled(key);
|
||||
if (enabledBool != null) {
|
||||
timer.action = enabledBool ? ACTIVATE : DEACTIVATE;
|
||||
} else {
|
||||
timer.action = DEACTIVATE;
|
||||
}
|
||||
final LocalTime time = getTime(key);
|
||||
if (!time.equals(Constants.NULL_LOCAL_TIME)) {
|
||||
timer.timeStamp = new Time();
|
||||
timer.timeStamp.hour = time.getHour();
|
||||
timer.timeStamp.minute = time.getMinute();
|
||||
}
|
||||
final Set<DayOfWeek> days = daysOfWeek.get(key);
|
||||
if (days != null) {
|
||||
timer.timerWeekDays = new ArrayList<>();
|
||||
for (DayOfWeek day : days) {
|
||||
timer.timerWeekDays.add(day.name().toLowerCase());
|
||||
}
|
||||
}
|
||||
return timer;
|
||||
}
|
||||
}
|
@ -22,15 +22,15 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
|
||||
|
||||
/**
|
||||
* The {@link ChargeProfileUtils} utility functions for charging profiles
|
||||
* The {@link ChargingProfileUtils} utility functions for charging profiles
|
||||
*
|
||||
* @author Norbert Truchsess - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargeProfileUtils {
|
||||
public class ChargingProfileUtils {
|
||||
|
||||
// Charging
|
||||
public static class TimedChannel {
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user