diff --git a/CODEOWNERS b/CODEOWNERS
index 29ecd123a232d..24f2f9469df87 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -226,7 +226,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
diff --git a/bundles/org.openhab.binding.mybmw/README.md b/bundles/org.openhab.binding.mybmw/README.md
index b1d608207935f..5fc2360847601 100644
--- a/bundles/org.openhab.binding.mybmw/README.md
+++ b/bundles/org.openhab.binding.mybmw/README.md
@@ -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
-
+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
diff --git a/bundles/org.openhab.binding.mybmw/pom.xml b/bundles/org.openhab.binding.mybmw/pom.xml
index 223269ee4ec48..521d30c808daa 100644
--- a/bundles/org.openhab.binding.mybmw/pom.xml
+++ b/bundles/org.openhab.binding.mybmw/pom.xml
@@ -14,4 +14,171 @@
openHAB Add-ons :: Bundles :: MyBMW Binding
+
+
+ test-coverage
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ target/jacoco.exec
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.8
+
+
+ default-instrument
+
+ instrument
+
+
+
+ default-restore-instrumented-classes
+ test
+
+ restore-instrumented-classes
+
+
+
+ default-report
+ test
+
+ report
+
+
+
+ default-check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.20
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.jacoco
+ org.jacoco.agent
+ runtime
+ 0.8.8
+ test
+
+
+
+
+
+ integration-tests
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.0.0-M7
+
+
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
+ test-jar
+
+
+
+ maven-resources-plugin
+ 3.3.0
+
+
+ copy-resources
+
+ package
+
+ copy-resources
+
+
+ ${basedir}/target/classes
+
+
+ src/test/resources
+ true
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+ package
+
+ jar
+
+
+ testenv
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConfiguration.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWBridgeConfiguration.java
similarity index 85%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConfiguration.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWBridgeConfiguration.java
index 6a548795936d8..2d5f16272665d 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConfiguration.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWBridgeConfiguration.java
@@ -16,12 +16,13 @@
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
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConstants.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConstants.java
index db452ace139c2..e8c5afe000d20 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConstants.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConstants.java
@@ -23,20 +23,46 @@
*
* @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 String toString() {
}
}
- 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 FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
+ static final Set FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
- public static final Set ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
+ static final Set 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 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 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";
-
- public static final String REMOTE_SERVICE_COMMAND = "command";
- public static final String REMOTE_STATE = "state";
+ 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";
+
+ 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";
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWHandlerFactory.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWHandlerFactory.java
index 881d6b5a8ca41..f5c0f0e063f27 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWHandlerFactory.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWHandlerFactory.java
@@ -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.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 @@
* 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 boolean supportsThingType(ThingTypeUID thingTypeUID) {
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;
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWVehicleConfiguration.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWVehicleConfiguration.java
new file mode 100644
index 0000000000000..f37b9c273e0a5
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWVehicleConfiguration.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/VehicleConfiguration.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/VehicleConfiguration.java
deleted file mode 100644
index 5c7043ebf2f18..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/VehicleConfiguration.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/console/MyBMWCommandExtension.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/console/MyBMWCommandExtension.java
new file mode 100644
index 0000000000000..370e92b90a1e4
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/console/MyBMWCommandExtension.java
@@ -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 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 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 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 getUsages() {
+ return Arrays.asList(
+ new String[] { buildCommandUsage(FINGERPRINT, "generate fingerprint for all vehicles on all accounts"),
+ buildCommandUsage(FINGERPRINT + " ", "generate fingerprint for vehicles on account"),
+ buildCommandUsage(FINGERPRINT + " ",
+ "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 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscovery.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscovery.java
index 66a16830606e5..f2b8bdbf66c08 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscovery.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscovery.java
@@ -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.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";
- private static final int DISCOVERY_TIMEOUT = 10;
- private Optional bridgeHandler = Optional.empty();
+public class VehicleDiscovery extends AbstractDiscoveryService implements ThingHandlerService {
- public VehicleDiscovery() {
- super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
- }
+ private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
- public void onResponse(List 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 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);
- }
- }
- });
+ private static final int DISCOVERY_TIMEOUT = 10;
- // 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));
+ private Optional bridgeHandler = Optional.empty();
+ private Optional myBMWProxy = Optional.empty();
+ private Optional bridgeUid = Optional.empty();
- String vehicleLabel = vehicle.brand + " " + vehicle.model;
- Map convertedProperties = new HashMap(properties);
- thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
- .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
- .withProperties(convertedProperties).build());
- }
- });
- });
- });
+ public VehicleDiscovery() {
+ 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 void setThingHandler(ThingHandler handler) {
@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 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> 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 getObject(Object dto, Object compare) {
- List l = new ArrayList();
- for (Field field : dto.getClass().getDeclaredFields()) {
- try {
- Object value = field.get(dto);
- if (compare.equals(value)) {
- l.add(field.getName());
+ private void processVehicles(List 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 properties = generateProperties(vehicle);
+
+ boolean thingFound = false;
+ // Update Properties for already created Things
+ List 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 convertedProperties = new HashMap(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 generateProperties(Vehicle vehicle) {
+ Map 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;
}
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/AuthQueryResponse.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/AuthQueryResponse.java
index 6896ddb5c305b..0884878fc6bff 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/AuthQueryResponse.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/AuthQueryResponse.java
@@ -18,6 +18,7 @@
* 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 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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeProfile.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeProfile.java
deleted file mode 100644
index 37b033d177205..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeProfile.java
+++ /dev/null
@@ -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 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;
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSession.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSession.java
deleted file mode 100644
index f5f97f0c531de..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSession.java
+++ /dev/null
@@ -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
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessions.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessions.java
deleted file mode 100644
index eb19b3c5cd368..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessions.java
+++ /dev/null
@@ -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 sessions;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatistics.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatistics.java
deleted file mode 100644
index b64318c76adb3..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatistics.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatisticsContainer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatisticsContainer.java
deleted file mode 100644
index 954b5358b9cf3..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeStatisticsContainer.java
+++ /dev/null
@@ -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;// ": {
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingProfile.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingProfile.java
new file mode 100644
index 0000000000000..8a05f2e880140
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingProfile.java
@@ -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 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 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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSession.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSession.java
new file mode 100644
index 0000000000000..96019676dc077
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSession.java
@@ -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
+ + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessions.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessions.java
new file mode 100644
index 0000000000000..90a787523848d
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessions.java
@@ -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 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 getSessions() {
+ return sessions;
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return "ChargingSessions [total=" + total + ", numberOfSessions=" + numberOfSessions + ", chargingListState="
+ + chargingListState + ", sessions=" + sessions + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessionsContainer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessionsContainer.java
similarity index 78%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessionsContainer.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessionsContainer.java
index 93d14e0cf3195..484170411a6ac 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargeSessionsContainer.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSessionsContainer.java
@@ -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;
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSettings.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSettings.java
index 7a509e2ceb7d9..fc23005cf3177 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSettings.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingSettings.java
@@ -16,10 +16,58 @@
* 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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatistics.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatistics.java
new file mode 100644
index 0000000000000..1fa27e176d7a7
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatistics.java
@@ -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
+ + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsContainer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsContainer.java
new file mode 100644
index 0000000000000..e1507e2f5d9d4
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsContainer.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingWindow.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingWindow.java
index d2d81b8bde56f..f5515cbf43b53 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingWindow.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingWindow.java
@@ -16,8 +16,30 @@
* 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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/RemoteChargingCommands.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/RemoteChargingCommands.java
new file mode 100644
index 0000000000000..0adfff9137c01
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/RemoteChargingCommands.java
@@ -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 chargingControl = new ArrayList<>();
+ private List flapControl = new ArrayList<>();
+ private List plugControl = new ArrayList<>();
+
+ /**
+ * @return the chargingControl
+ */
+ public List getChargingControl() {
+ return chargingControl;
+ }
+
+ /**
+ * @param chargingControl the chargingControl to set
+ */
+ public void setChargingControl(List chargingControl) {
+ this.chargingControl = chargingControl;
+ }
+
+ /**
+ * @return the flapControl
+ */
+ public List getFlapControl() {
+ return flapControl;
+ }
+
+ /**
+ * @param flapControl the flapControl to set
+ */
+ public void setFlapControl(List flapControl) {
+ this.flapControl = flapControl;
+ }
+
+ /**
+ * @return the plugControl
+ */
+ public List getPlugControl() {
+ return plugControl;
+ }
+
+ /**
+ * @param plugControl the plugControl to set
+ */
+ public void setPlugControl(List plugControl) {
+ this.plugControl = plugControl;
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return "RemoteChargingCommands [chargingControl=" + chargingControl + ", flapControl=" + flapControl
+ + ", plugControl=" + plugControl + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/Time.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/Time.java
index 283c43fce6370..40c48be392d1b 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/Time.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/charge/Time.java
@@ -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 + "]";
}
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/network/NetworkError.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/network/NetworkError.java
deleted file mode 100644
index 48d2b74fdc634..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/network/NetworkError.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CBS.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CBS.java
deleted file mode 100644
index 1f11ce60f7837..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CBS.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CCM.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CCM.java
deleted file mode 100644
index 78dd306d6a88c..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/CCM.java
+++ /dev/null
@@ -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
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/ChargingState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/ChargingState.java
deleted file mode 100644
index 501a80851ed6c..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/ChargingState.java
+++ /dev/null
@@ -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
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Coordinates.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Coordinates.java
deleted file mode 100644
index 655483b1d8244..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Coordinates.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Distance.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Distance.java
deleted file mode 100644
index 9a169ad78068b..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Distance.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Doors.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Doors.java
deleted file mode 100644
index 1f1a5c62531d2..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Doors.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/DoorsWindows.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/DoorsWindows.java
deleted file mode 100644
index 7456e993b92ce..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/DoorsWindows.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/FuelLevel.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/FuelLevel.java
deleted file mode 100644
index 3ec39335f899b..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/FuelLevel.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Location.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Location.java
deleted file mode 100644
index d7e71afde191d..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Location.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Properties.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Properties.java
deleted file mode 100644
index 025f87fe0beca..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Properties.java
+++ /dev/null
@@ -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 checkControlMessages;
- public List serviceRequired;
- public Location vehicleLocation;
- public Tires tires;
- // "climateControl":{} [todo] definition currently unknown
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Range.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Range.java
deleted file mode 100644
index 32ed8072a63bd..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Range.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tire.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tire.java
deleted file mode 100644
index 678ff17648e02..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tire.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/TireStatus.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/TireStatus.java
deleted file mode 100644
index 763b693c585e2..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/TireStatus.java
+++ /dev/null
@@ -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
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tires.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tires.java
deleted file mode 100644
index c956a769f4b17..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Tires.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Windows.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Windows.java
deleted file mode 100644
index 1b08344e60bfe..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Windows.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionError.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionError.java
index f37dcf08711b0..38ed251b442ed 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionError.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionError.java
@@ -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,
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionStatusContainer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionStatusContainer.java
index 58b7c8ed27a7a..0ab6d33592dba 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionStatusContainer.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/remote/ExecutionStatusContainer.java
@@ -16,10 +16,49 @@
* 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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CBSMessage.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CBSMessage.java
deleted file mode 100644
index de2f51660ae03..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CBSMessage.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CCMMessage.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CCMMessage.java
deleted file mode 100644
index dc99ad4d7392d..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/CCMMessage.java
+++ /dev/null
@@ -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.
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/DoorWindow.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/DoorWindow.java
deleted file mode 100644
index 7994c459f77c0..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/DoorWindow.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/FuelIndicator.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/FuelIndicator.java
deleted file mode 100644
index ff44f62882c4e..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/FuelIndicator.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Issues.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Issues.java
deleted file mode 100644
index d2dff9c0f992e..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Issues.java
+++ /dev/null
@@ -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
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Mileage.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Mileage.java
deleted file mode 100644
index c1f340771cfad..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Mileage.java
+++ /dev/null
@@ -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"
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Status.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Status.java
deleted file mode 100644
index 37b47f1751de7..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/status/Status.java
+++ /dev/null
@@ -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 doorsAndWindows;// ":[
- public List checkControlMessages;//
- public List requiredServices;//
- // "recallMessages":[],
- // "recallExternalUrl":null,
- public List fuelIndicators;
- public String timestampMessage;// ":"Updated from vehicle 12/21/2021 05:46 PM",
- public ChargeProfile chargingProfile;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Address.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Address.java
similarity index 61%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Address.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Address.java
index 7041993ca1be8..7ab723ec4a9ef 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/properties/Address.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Address.java
@@ -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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Capabilities.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Capabilities.java
deleted file mode 100644
index ef5a4289a6608..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Capabilities.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CheckControlMessage.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CheckControlMessage.java
new file mode 100644
index 0000000000000..deb3ba6d2c650
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CheckControlMessage.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RemoteService.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateControlState.java
similarity index 50%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RemoteService.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateControlState.java
index 3551a70c58274..051da6da08b70 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RemoteService.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateControlState.java
@@ -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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateTimer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateTimer.java
new file mode 100644
index 0000000000000..2be87e8289453
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ClimateTimer.java
@@ -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 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 getTimerWeekDays() {
+ return timerWeekDays;
+ }
+
+ public void setTimerWeekDays(List 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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CombustionFuelLevel.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CombustionFuelLevel.java
new file mode 100644
index 0000000000000..53b91713a36d2
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/CombustionFuelLevel.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Coordinates.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Coordinates.java
new file mode 100644
index 0000000000000..3e0fe7d0186a2
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Coordinates.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DepartureTime.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DepartureTime.java
new file mode 100644
index 0000000000000..5267c1cf0b1e1
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DepartureTime.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DigitalKey.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DigitalKey.java
new file mode 100644
index 0000000000000..72b111f4336d8
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DigitalKey.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DriverPreferences.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DriverPreferences.java
new file mode 100644
index 0000000000000..b32297513d70c
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/DriverPreferences.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ElectricChargingState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ElectricChargingState.java
new file mode 100644
index 0000000000000..e49dc0aa49057
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/ElectricChargingState.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RequiredService.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RequiredService.java
new file mode 100644
index 0000000000000..604ac7539eccc
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/RequiredService.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Vehicle.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Vehicle.java
index 9392527f0fa14..f6fd2a1a51cfe 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Vehicle.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/Vehicle.java
@@ -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 + "]";
+ }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleAttributes.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleAttributes.java
new file mode 100644
index 0000000000000..fa8361f713b3c
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleAttributes.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBase.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBase.java
new file mode 100644
index 0000000000000..2cbd203f1f210
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBase.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilities.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilities.java
new file mode 100644
index 0000000000000..7241dafe13afa
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilities.java
@@ -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 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 getCapabilitiesAsStringList(String suffix, boolean compare) {
+ List 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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleDoorsState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleDoorsState.java
new file mode 100644
index 0000000000000..89db70a872fb4
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleDoorsState.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleLocation.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleLocation.java
new file mode 100644
index 0000000000000..655fe226ee612
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleLocation.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleRoofState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleRoofState.java
new file mode 100644
index 0000000000000..af9105e9916ca
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleRoofState.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleState.java
new file mode 100644
index 0000000000000..fe0f6412ed301
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleState.java
@@ -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 requiredServices = new ArrayList<>();
+ private List 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 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 getRequiredServices() {
+ return requiredServices;
+ }
+
+ /**
+ * @return the checkControlMessages
+ */
+ public List 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainer.java
new file mode 100644
index 0000000000000..775d45f08e10f
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainer.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireState.java
new file mode 100644
index 0000000000000..bd6f83f910841
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireState.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetails.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetails.java
new file mode 100644
index 0000000000000..0a60c25d3fa9f
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetails.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetailsClassification.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetailsClassification.java
new file mode 100644
index 0000000000000..75df0c7541de0
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateDetailsClassification.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateStatus.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateStatus.java
new file mode 100644
index 0000000000000..8cda59acd5d3d
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStateStatus.java
@@ -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
+ + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStates.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStates.java
new file mode 100644
index 0000000000000..46a88994d4904
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleTireStates.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleWindowsState.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleWindowsState.java
new file mode 100644
index 0000000000000..035fc427a099c
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleWindowsState.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWBridgeHandler.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWBridgeHandler.java
index 837afe8571612..ab63352c39ec1 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWBridgeHandler.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWBridgeHandler.java
@@ -15,19 +15,18 @@
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.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 {
+public class MyBMWBridgeHandler extends BaseBridgeHandler {
+
+ 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 discoveryService = Optional.empty();
- private Optional proxy = Optional.empty();
+ private Optional myBmwProxy = Optional.empty();
private Optional> initializerJob = Optional.empty();
- private Optional troubleshootFingerprint = Optional.empty();
- private String localeLanguage;
+ private Optional vehicleDiscovery = Optional.empty();
+ private LocaleProvider localeProvider;
- public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory hcf, String language) {
+ 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 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> getServices() {
- return Set.of(VehicleDiscovery.class);
- }
-
- public Optional getProxy() {
- return proxy;
+ logger.trace("MyBMWBridgeHandler.getServices");
+ return List.of(VehicleDiscovery.class);
}
- public void setDiscoveryService(VehicleDiscovery discoveryService) {
- this.discoveryService = Optional.of(discoveryService);
+ public Optional getMyBmwProxy() {
+ logger.trace("MyBMWBridgeHandler.getProxy");
+ createMyBmwProxy(getConfigAs(MyBMWBridgeConfiguration.class), ENVIRONMENT);
+ return myBmwProxy;
}
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWProxy.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWProxy.java
deleted file mode 100644
index 6bc03775149e6..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/MyBMWProxy.java
+++ /dev/null
@@ -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
- * https://github.com/bimmerconnected/bimmer_connected.
- * File defining these constants
- *
- * 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
- */
-@NonNullByDefault
-public class MyBMWProxy {
- private final Logger logger = LoggerFactory.getLogger(MyBMWProxy.class);
- private Optional 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 vehicleParams = new MultiMap();
- 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 chargeStatisticsParams = new MultiMap();
- 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 chargeSessionsParams = new MultiMap();
- 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 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, 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 loginParams = new MultiMap(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 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 = 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 codeParams = new MultiMap();
- 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 tokenMap = new MultiMap();
- 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;
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceExecutor.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceExecutor.java
new file mode 100644
index 0000000000000..9c6f478156883
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceExecutor.java
@@ -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> stateJob = Optional.empty();
+ private Optional serviceExecuting = Optional.empty();
+ private Optional 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();
+ });
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceHandler.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceHandler.java
deleted file mode 100644
index b40e480acd926..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/RemoteServiceHandler.java
+++ /dev/null
@@ -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
- * 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
- */
-@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> stateJob = Optional.empty();
- private Optional serviceExecuting = Optional.empty();
- private Optional 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 dataMap = new MultiMap();
- 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 dataMap = new MultiMap();
- 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();
- });
- }
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ResponseCallback.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ResponseCallback.java
deleted file mode 100644
index 951e79a519f4b..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ResponseCallback.java
+++ /dev/null
@@ -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);
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/StringResponseCallback.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/StringResponseCallback.java
deleted file mode 100644
index cf863ae36dd18..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/StringResponseCallback.java
+++ /dev/null
@@ -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);
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleChannelHandler.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleChannelHandler.java
deleted file mode 100644
index 396815bfb11b3..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleChannelHandler.java
+++ /dev/null
@@ -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 serviceList = new ArrayList();
- protected String selectedService = Constants.UNDEF;
- protected List checkControlList = new ArrayList();
- protected String selectedCC = Constants.UNDEF;
- protected List sessionList = new ArrayList();
- protected String selectedSession = Constants.UNDEF;
-
- protected MyBMWCommandOptionProvider commandOptionProvider;
- private LocationProvider locationProvider;
-
- // Data Caches
- protected Optional vehicleStatusCache = Optional.empty();
- protected Optional 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 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 lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
- if (lengthUnit == null) {
- return;
- }
- if (isElectric) {
- int rangeElectric = VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, v);
- QuantityType qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
- QuantityType 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 qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
- QuantityType 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 qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
- QuantityType 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 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 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 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 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 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 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 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);
- }
- }
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleHandler.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleHandler.java
index 78c104068bc2f..15b304a1822c4 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleHandler.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/VehicleHandler.java
@@ -12,167 +12,240 @@
*/
package org.openhab.binding.mybmw.internal.handler;
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ADDRESS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_PROFILE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_SESSION;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_STATISTICS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHECK_CONTROL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_DOORS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_LOCATION;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_RANGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_REMOTE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_SERVICE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_STATUS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_TIRES;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_VEHICLE_IMAGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_ENABLED;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_CLIMATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_CONTROL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_LIMIT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_MODE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_PREFERENCE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_REMAINING;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_STATUS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHECK_CONTROL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DETAILS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOORS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ENERGY;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_L_100KM;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_MPG;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.GPS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HEADING;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOME_DISTANCE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOOD;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.IMAGE_FORMAT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.IMAGE_VIEWPORT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ISSUE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_FETCHED;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_UPDATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LOCK;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.MILEAGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.NAME;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.PLUG_CONNECTION;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_ELECTRIC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_HYBRID;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_ELECTRIC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_HYBRID;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RAW;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMAINING_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_COMMAND;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_STATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_DATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_MILEAGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SESSIONS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SEVERITY;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SOC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.STATUS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUBTITLE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUNROOF;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.TITLE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.TRUNK;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOWS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_REAR;
+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 java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
+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.VehicleConfiguration;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeSessionsContainer;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
-import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
+import org.openhab.binding.mybmw.internal.MyBMWVehicleConfiguration;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSession;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.CheckControlMessage;
+import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleDoorsState;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleLocation;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleRoofState;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleState;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleTireStates;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleWindowsState;
+import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
+import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
+import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils;
+import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils.TimedChannel;
+import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper;
+import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
import org.openhab.binding.mybmw.internal.utils.Constants;
import org.openhab.binding.mybmw.internal.utils.Converter;
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
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.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpUtil;
+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.RawType;
import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.types.Command;
+import org.openhab.core.types.CommandOption;
import org.openhab.core.types.RefreshType;
-
-import com.google.gson.JsonSyntaxException;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The {@link VehicleHandler} handles responses from BMW API
*
+ * the introduction of channelToBeUpdated is ugly, but if there is a refresh of one channel, always all channels were
+ * updated
+ *
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit and send charge profile
+ * @author Martin Grassl - refactoring, merge with VehicleChannelHandler
+ * @author Mark Herwege - refactoring, V2 API charging
*/
@NonNullByDefault
-public class VehicleHandler extends VehicleChannelHandler {
+public class VehicleHandler extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
+
+ private boolean hasFuel = false;
+ private boolean isElectric = false;
+ private boolean isHybrid = false;
+
+ // List Interfaces
+ private volatile List serviceList = List.of();
+ private volatile String selectedService = Constants.UNDEF;
+ private volatile List checkControlList = List.of();
+ private volatile String selectedCC = Constants.UNDEF;
+ private volatile List sessionList = List.of();
+ private volatile String selectedSession = Constants.UNDEF;
+
+ private MyBMWCommandOptionProvider commandOptionProvider;
+ private LocationProvider locationProvider;
+ private TimeZoneProvider timeZoneProvider;
+
+ // Data Caches
+ private Optional vehicleStatusCache = Optional.empty();
+ private Optional imageCache = Optional.empty();
+
private Optional proxy = Optional.empty();
- private Optional remote = Optional.empty();
- public Optional configuration = Optional.empty();
+ private Optional remote = Optional.empty();
+ private Optional vehicleConfiguration = Optional.empty();
private Optional> refreshJob = Optional.empty();
private Optional> editTimeout = Optional.empty();
private ImageProperties imageProperties = new ImageProperties();
- VehicleStatusCallback vehicleStatusCallback = new VehicleStatusCallback();
- ChargeStatisticsCallback chargeStatisticsCallback = new ChargeStatisticsCallback();
- ChargeSessionsCallback chargeSessionCallback = new ChargeSessionsCallback();
- ByteResponseCallback imageCallback = new ImageCallback();
- public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String driveTrain) {
- super(thing, cop, lp, driveTrain);
- }
+ public VehicleHandler(Thing thing, MyBMWCommandOptionProvider commandOptionProvider,
+ LocationProvider locationProvider, TimeZoneProvider timeZoneProvider, String driveTrain) {
+ super(thing);
+ logger.trace("VehicleHandler.constructor {}, {}", thing.getUID(), driveTrain);
+ this.commandOptionProvider = commandOptionProvider;
+ this.timeZoneProvider = timeZoneProvider;
+ this.locationProvider = locationProvider;
+ if (locationProvider.getLocation() == null) {
+ logger.debug("Home location not available");
+ }
- @Override
- public void handleCommand(ChannelUID channelUID, Command command) {
- String group = channelUID.getGroupId();
+ hasFuel = driveTrain.equals(VehicleType.CONVENTIONAL.toString())
+ || driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
+ || driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
+ || driveTrain.equals(VehicleType.MILD_HYBRID.toString());
+ isElectric = driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
+ || driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
+ || driveTrain.equals(VehicleType.ELECTRIC.toString());
+ isHybrid = hasFuel && isElectric;
- // Refresh of Channels with cached values
- if (command instanceof RefreshType) {
- if (CHANNEL_GROUP_STATUS.equals(group)) {
- vehicleStatusCache.ifPresent(vehicleStatus -> vehicleStatusCallback.onResponse(vehicleStatus));
- } else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
- imageCache.ifPresent(image -> imageCallback.onResponse(image));
- }
- // Check for Channel Group and corresponding Actions
- } else if (CHANNEL_GROUP_REMOTE.equals(group)) {
- // Executing Remote Services
- if (command instanceof StringType str) {
- String serviceCommand = str.toFullString();
- remote.ifPresent(remot -> {
- switch (serviceCommand) {
- case REMOTE_SERVICE_LIGHT_FLASH:
- case REMOTE_SERVICE_DOOR_LOCK:
- case REMOTE_SERVICE_DOOR_UNLOCK:
- case REMOTE_SERVICE_HORN:
- case REMOTE_SERVICE_VEHICLE_FINDER:
- RemoteServiceUtils.getRemoteService(serviceCommand)
- .ifPresentOrElse(service -> remot.execute(service), () -> {
- logger.debug("Remote service execution {} unknown", serviceCommand);
- });
- break;
- case REMOTE_SERVICE_AIR_CONDITIONING_START:
- RemoteServiceUtils.getRemoteService(serviceCommand)
- .ifPresentOrElse(service -> remot.execute(service), () -> {
- logger.debug("Remote service execution {} unknown", serviceCommand);
- });
- break;
- case REMOTE_SERVICE_AIR_CONDITIONING_STOP:
- RemoteServiceUtils.getRemoteService(serviceCommand)
- .ifPresentOrElse(service -> remot.execute(service), () -> {
- logger.debug("Remote service execution {} unknown", serviceCommand);
- });
- break;
- default:
- logger.debug("Remote service execution {} unknown", serviceCommand);
- break;
- }
- });
- }
- } else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
- // Image Change
- configuration.ifPresent(config -> {
- if (command instanceof StringType) {
- if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
- String newViewport = command.toString();
- synchronized (imageProperties) {
- if (!imageProperties.viewport.equals(newViewport)) {
- imageProperties = new ImageProperties(newViewport);
- imageCache = Optional.empty();
- proxy.ifPresent(prox -> prox.requestImage(config, imageProperties, imageCallback));
- }
- }
- updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport));
- }
- }
- });
- } else if (CHANNEL_GROUP_SERVICE.equals(group)) {
- if (command instanceof StringType) {
- int index = Converter.getIndex(command.toFullString());
- if (index != -1) {
- selectService(index);
- } else {
- logger.debug("Cannot select Service index {}", command.toFullString());
- }
- }
- } else if (CHANNEL_GROUP_CHECK_CONTROL.equals(group)) {
- if (command instanceof StringType) {
- int index = Converter.getIndex(command.toFullString());
- if (index != -1) {
- selectCheckControl(index);
- } else {
- logger.debug("Cannot select CheckControl index {}", command.toFullString());
- }
- }
- } else if (CHANNEL_GROUP_CHARGE_SESSION.equals(group)) {
- if (command instanceof StringType) {
- int index = Converter.getIndex(command.toFullString());
- if (index != -1) {
- selectSession(index);
- } else {
- logger.debug("Cannot select Session index {}", command.toFullString());
- }
- }
- }
+ setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
+ }
+
+ private void setOptions(final String group, final String id, List options) {
+ commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
}
@Override
public void initialize() {
+ logger.trace("VehicleHandler.initialize");
updateStatus(ThingStatus.UNKNOWN);
- final VehicleConfiguration config = getConfigAs(VehicleConfiguration.class);
- configuration = Optional.of(config);
+ vehicleConfiguration = Optional.of(getConfigAs(MyBMWVehicleConfiguration.class));
+
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler != null) {
- proxy = ((MyBMWBridgeHandler) handler).getProxy();
- remote = proxy.map(prox -> prox.getRemoteServiceHandler(this));
+ proxy = ((MyBMWBridgeHandler) handler).getMyBmwProxy();
+ remote = Optional.of(new RemoteServiceExecutor(this, proxy.get()));
} else {
logger.debug("Bridge Handler null");
}
@@ -181,13 +254,15 @@ public void initialize() {
}
imageProperties = new ImageProperties();
- updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(imageProperties.viewport));
+ updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, Converter.toTitleCase(imageProperties.viewport),
+ null);
// start update schedule
- startSchedule(config.refreshInterval);
+ startSchedule(vehicleConfiguration.get().getRefreshInterval());
}
private void startSchedule(int interval) {
+ logger.trace("VehicleHandler.startSchedule");
refreshJob.ifPresentOrElse(job -> {
if (job.isCancelled()) {
refreshJob = Optional
@@ -200,21 +275,46 @@ private void startSchedule(int interval) {
@Override
public void dispose() {
+ logger.trace("VehicleHandler.dispose");
refreshJob.ifPresent(job -> job.cancel(true));
editTimeout.ifPresent(job -> job.cancel(true));
- remote.ifPresent(RemoteServiceHandler::cancel);
+ remote.ifPresent(RemoteServiceExecutor::cancel);
}
public void getData() {
+ logger.trace("VehicleHandler.getData");
proxy.ifPresentOrElse(prox -> {
- configuration.ifPresentOrElse(config -> {
- prox.requestVehicles(config.vehicleBrand, vehicleStatusCallback);
- if (isElectric) {
- prox.requestChargeStatistics(config, chargeStatisticsCallback);
- prox.requestChargeSessions(config, chargeSessionCallback);
+ vehicleConfiguration.ifPresentOrElse(config -> {
+
+ boolean stateError = false;
+ try {
+ VehicleStateContainer vehicleState = prox.requestVehicleState(config.getVin(),
+ config.getVehicleBrand());
+ triggerVehicleStatusUpdate(vehicleState, null);
+ stateError = false;
+ } catch (NetworkException e) {
+ logger.debug("{}", e.toString());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Vehicle State Update failed");
+ stateError = true;
}
- if (imageCache.isEmpty() && !imageProperties.failLimitReached()) {
- prox.requestImage(config, imageProperties, imageCallback);
+
+ if (!stateError && isElectric) {
+ try {
+ updateChargingStatistics(
+ prox.requestChargeStatistics(config.getVin(), config.getVehicleBrand()), null);
+ updateChargingSessions(prox.requestChargeSessions(config.getVin(), config.getVehicleBrand()),
+ null);
+ } catch (NetworkException e) {
+ logger.debug("{}", e.toString());
+ }
+ }
+ if (!stateError && !imageCache.isPresent() && !imageProperties.failLimitReached()) {
+ try {
+ updateImage(prox.requestImage(config.getVin(), config.getVehicleBrand(), imageProperties));
+ } catch (NetworkException e) {
+ logger.debug("{}", e.toString());
+ }
}
}, () -> {
logger.warn("MyBMW Vehicle Configuration isn't present");
@@ -224,129 +324,646 @@ public void getData() {
});
}
+ private void triggerVehicleStatusUpdate(VehicleStateContainer vehicleState, @Nullable String channelToBeUpdated) {
+ logger.trace("VehicleHandler.triggerVehicleStatusUpdate for {}", channelToBeUpdated);
+ if (vehicleConfiguration.isPresent()) {
+ vehicleStatusCache = Optional.of(vehicleState);
+ updateChannel(CHANNEL_GROUP_STATUS, RAW, vehicleState.getRawStateJson(), channelToBeUpdated);
+
+ updateVehicleStatus(vehicleState.getState(), channelToBeUpdated);
+ if (isElectric) {
+ updateChargingProfile(vehicleState.getState().getChargingProfile(), channelToBeUpdated);
+ }
+
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ logger.debug("configuration not present");
+ }
+ }
+
public void updateRemoteExecutionStatus(@Nullable String service, String status) {
updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
- StringType.valueOf((service == null ? "-" : service) + Constants.SPACE + status.toLowerCase()));
+ (service == null ? "-" : service) + Constants.SPACE + status.toLowerCase(), null);
}
- public Optional getConfiguration() {
- return configuration;
+ public Optional getVehicleConfiguration() {
+ logger.trace("VehicleHandler.getVehicleConfiguration");
+ return vehicleConfiguration;
}
public ScheduledExecutorService getScheduler() {
+ logger.trace("VehicleHandler.getScheduler");
return scheduler;
}
- public class ImageCallback implements ByteResponseCallback {
- @Override
- public void onResponse(byte[] content) {
- if (content.length > 0) {
- imageCache = Optional.of(content);
- String contentType = HttpUtil.guessContentTypeFromData(content);
- updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(content, contentType));
+ private void updateChannel(final String group, final String id, final String state,
+ @Nullable final String channelToBeUpdated) {
+ updateChannel(group, id, StringType.valueOf(state), channelToBeUpdated);
+ }
+
+ /**
+ * this method sets the state for a single channel. if a channelToBeUpdated is provided, the update will only take
+ * place for that single channel.
+ */
+ private void updateChannel(final String group, final String id, final State state,
+ @Nullable final String channelToBeUpdated) {
+ if (channelToBeUpdated == null || id.equals(channelToBeUpdated)) {
+ if (!"png".equals(id)) {
+ logger.trace("updating channel {}, {}, {}", group, id, state.toFullString());
} else {
- synchronized (imageProperties) {
- imageProperties.failed();
- }
+ logger.trace("updating channel {}, {}, {}", group, id, "not printed");
}
+
+ updateState(new ChannelUID(thing.getUID(), group, id), state);
}
+ }
- /**
- * Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
- */
- @Override
- public void onError(NetworkError error) {
- logger.debug("{}", error.toString());
- synchronized (imageProperties) {
- imageProperties.failed();
- }
+ private void updateChargingStatistics(ChargingStatisticsContainer chargingStatisticsContainer,
+ @Nullable String channelToBeUpdated) {
+ if (!"".equals(chargingStatisticsContainer.getDescription())) {
+ updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, chargingStatisticsContainer.getDescription(),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY, QuantityType
+ .valueOf(chargingStatisticsContainer.getStatistics().getTotalEnergyCharged(), Units.KILOWATT_HOUR),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
+ DecimalType.valueOf(Integer
+ .toString(chargingStatisticsContainer.getStatistics().getNumberOfChargingSessions())),
+ channelToBeUpdated);
}
}
/**
- * The VehicleStatus is supported by all Vehicle Types so it's used to reflect the Thing Status
+ * updates the channels with the current state of the vehicle
+ *
+ * @param vehicleStateState
*/
- public class VehicleStatusCallback implements StringResponseCallback {
- @Override
- public void onResponse(@Nullable String content) {
- if (content != null) {
- if (getConfiguration().isPresent()) {
- Vehicle v = Converter.getVehicle(configuration.get().vin, content);
- if (v.valid) {
- vehicleStatusCache = Optional.of(content);
- updateStatus(ThingStatus.ONLINE);
- updateChannel(CHANNEL_GROUP_STATUS, RAW,
- StringType.valueOf(Converter.getRawVehicleContent(configuration.get().vin, content)));
- updateVehicle(v);
- if (isElectric) {
- updateChargeProfile(v.status.chargingProfile);
- }
- } else {
- logger.debug("Vehicle {} not valid", configuration.get().vin);
- }
- } else {
- logger.debug("configuration not present");
- }
+ private void updateVehicleStatus(VehicleState vehicleStateState, @Nullable String channelToBeUpdated) {
+ boolean isLeftSteering = vehicleStateState.isLeftSteering();
+
+ updateVehicleOverallStatus(vehicleStateState, channelToBeUpdated);
+ updateRange(vehicleStateState, channelToBeUpdated);
+ updateDoors(vehicleStateState.getDoorsState(), isLeftSteering, channelToBeUpdated);
+ updateWindows(vehicleStateState.getWindowsState(), isLeftSteering, channelToBeUpdated);
+ updateRoof(vehicleStateState.getRoofState(), channelToBeUpdated);
+ updatePosition(vehicleStateState.getLocation(), channelToBeUpdated);
+ updateServices(vehicleStateState.getRequiredServices(), channelToBeUpdated);
+ updateCheckControls(vehicleStateState.getCheckControlMessages(), channelToBeUpdated);
+ updateTires(vehicleStateState.getTireState(), channelToBeUpdated);
+ }
+
+ private void updateTires(@Nullable VehicleTireStates vehicleTireStates, @Nullable String channelToBeUpdated) {
+ if (vehicleTireStates == null) {
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
+ } else {
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
+ calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getCurrentPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
+ calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getTargetPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
+ calculatePressure(vehicleTireStates.getFrontRight().getStatus().getCurrentPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
+ calculatePressure(vehicleTireStates.getFrontRight().getStatus().getTargetPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
+ calculatePressure(vehicleTireStates.getRearLeft().getStatus().getCurrentPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
+ calculatePressure(vehicleTireStates.getRearLeft().getStatus().getTargetPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
+ calculatePressure(vehicleTireStates.getRearRight().getStatus().getCurrentPressure()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
+ calculatePressure(vehicleTireStates.getRearRight().getStatus().getTargetPressure()),
+ channelToBeUpdated);
+ }
+ }
+
+ /**
+ * if the pressure is undef it is < 0
+ *
+ * @param pressure
+ * @return
+ */
+ private State calculatePressure(int pressure) {
+ if (pressure > 0) {
+ return QuantityType.valueOf(pressure / 100.0, Units.BAR);
+ } else {
+ return UnDefType.UNDEF;
+ }
+ }
+
+ private void updateVehicleOverallStatus(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
+ updateChannel(CHANNEL_GROUP_STATUS, LOCK,
+ Converter.toTitleCase(vehicleState.getDoorsState().getCombinedSecurityState()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
+ VehicleStatusUtils.getNextServiceDate(vehicleState.getRequiredServices()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
+ VehicleStatusUtils.getNextServiceMileage(vehicleState.getRequiredServices()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
+ Converter.toTitleCase(vehicleState.getOverallCheckControlStatus()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
+ Converter.zonedToLocalDateTime(vehicleState.getLastUpdatedAt(), timeZoneProvider.getTimeZone()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, LAST_FETCHED,
+ Converter.zonedToLocalDateTime(vehicleState.getLastFetched(), timeZoneProvider.getTimeZone()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, DOORS,
+ Converter.toTitleCase(vehicleState.getDoorsState().getCombinedState()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, WINDOWS,
+ Converter.toTitleCase(vehicleState.getWindowsState().getCombinedState()), channelToBeUpdated);
+
+ if (isElectric) {
+ updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
+ Converter.getConnectionState(vehicleState.getElectricChargingState().isChargerConnected()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
+ Converter.toTitleCase(vehicleState.getElectricChargingState().getChargingStatus()),
+ channelToBeUpdated);
+
+ int remainingTime = vehicleState.getElectricChargingState().getRemainingChargingMinutes();
+ updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING,
+ remainingTime >= 0 ? QuantityType.valueOf(remainingTime, Units.MINUTE) : UnDefType.UNDEF,
+ channelToBeUpdated);
+ }
+ }
+
+ private void updateRange(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
+ // get the right unit
+ Unit lengthUnit = Constants.KILOMETRE_UNIT;
+
+ if (isElectric) {
+ int rangeElectric = vehicleState.getElectricChargingState().getRange();
+ QuantityType qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
+ QuantityType qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
+ lengthUnit);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius, channelToBeUpdated);
+ }
+
+ if (hasFuel && !isHybrid) {
+ int rangeFuel = vehicleState.getCombustionFuelLevel().getRange();
+ QuantityType qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
+ QuantityType qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
+ }
+
+ if (isHybrid) {
+ int rangeCombined = vehicleState.getRange();
+
+ // there is a bug/feature in the API that the fuel range is the same like the combined range, hence in case
+ // of hybrid the fuel range has to be subtracted by the electric range
+ int rangeFuel = vehicleState.getCombustionFuelLevel().getRange()
+ - vehicleState.getElectricChargingState().getRange();
+
+ QuantityType qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
+ QuantityType qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
+ lengthUnit);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius, channelToBeUpdated);
+
+ QuantityType qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
+ QuantityType qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
+ }
+
+ if (vehicleState.getCurrentMileage() == Constants.INT_UNDEF) {
+ updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
+ } else {
+ updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
+ QuantityType.valueOf(vehicleState.getCurrentMileage(), lengthUnit), channelToBeUpdated);
+ }
+ if (isElectric) {
+ updateChannel(
+ CHANNEL_GROUP_RANGE, SOC, QuantityType
+ .valueOf(vehicleState.getElectricChargingState().getChargingLevelPercent(), Units.PERCENT),
+ channelToBeUpdated);
+ }
+ if (hasFuel) {
+ updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
+ QuantityType.valueOf(vehicleState.getCombustionFuelLevel().getRemainingFuelLiters(), Units.LITRE),
+ channelToBeUpdated);
+
+ if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
+ && vehicleState.getCombustionFuelLevel().getRange() > 1) {
+ double estimatedFuelConsumption = vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() * 1.0
+ / vehicleState.getCombustionFuelLevel().getRange() * 100.0;
+ updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM,
+ DecimalType.valueOf(estimatedFuelConsumption + ""), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG,
+ DecimalType.valueOf((235.214583 / estimatedFuelConsumption) + ""), channelToBeUpdated);
} else {
- updateChannel(CHANNEL_GROUP_STATUS, RAW, StringType.valueOf(Constants.EMPTY_JSON));
- logger.debug("Content not valid");
+ updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG, UnDefType.UNDEF, channelToBeUpdated);
+ }
+ }
+ }
+
+ private void updateCheckControls(List checkControlMessages,
+ @Nullable String channelToBeUpdated) {
+ if (checkControlMessages.isEmpty()) {
+ // No Check Control available - show not active
+ CheckControlMessage checkControlMessage = new CheckControlMessage();
+ checkControlMessage.setName(Constants.NO_ENTRIES);
+ checkControlMessage.setDescription(Constants.NO_ENTRIES);
+ checkControlMessage.setSeverity(Constants.NO_ENTRIES);
+ checkControlMessage.setType(Constants.NO_ENTRIES);
+ checkControlMessage.setId(-1);
+ checkControlMessages.add(checkControlMessage);
+ }
+
+ // add all elements to options
+ checkControlList = checkControlMessages;
+ List ccmDescriptionOptions = new ArrayList<>();
+ boolean isSelectedElementIn = false;
+ int index = 0;
+ for (CheckControlMessage checkControlMessage : checkControlList) {
+ ccmDescriptionOptions.add(
+ new CommandOption(Integer.toString(index), Converter.toTitleCase(checkControlMessage.getType())));
+ if (selectedCC.equals(checkControlMessage.getType())) {
+ isSelectedElementIn = true;
}
+ index++;
}
+ setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
- @Override
- public void onError(NetworkError error) {
- logger.debug("{}", error.toString());
- vehicleStatusCache = Optional.of(Converter.getGson().toJson(error));
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
+ // if current selected item isn't anymore in the list select first entry
+ if (!isSelectedElementIn) {
+ selectCheckControl(0, channelToBeUpdated);
}
}
- public class ChargeStatisticsCallback implements StringResponseCallback {
- @Override
- public void onResponse(@Nullable String content) {
- if (content != null) {
- try {
- ChargeStatisticsContainer csc = Converter.getGson().fromJson(content,
- ChargeStatisticsContainer.class);
- if (csc != null) {
- updateChargeStatistics(csc);
- }
- } catch (JsonSyntaxException jse) {
- logger.warn("{}", jse.getLocalizedMessage());
- }
+ private void selectCheckControl(int index, @Nullable String channelToBeUpdated) {
+ if (index >= 0 && index < checkControlList.size()) {
+ CheckControlMessage checkControlMessage = checkControlList.get(index);
+ selectedCC = checkControlMessage.getType();
+ updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, Converter.toTitleCase(checkControlMessage.getType()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS,
+ StringType.valueOf(checkControlMessage.getDescription()), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY,
+ Converter.toTitleCase(checkControlMessage.getSeverity()), channelToBeUpdated);
+ }
+ }
+
+ private void updateServices(List requiredServiceList, @Nullable String channelToBeUpdated) {
+ // if list is empty add "undefined" element
+ if (requiredServiceList.isEmpty()) {
+ RequiredService requiredService = new RequiredService();
+ requiredService.setType(Constants.NO_ENTRIES);
+ requiredService.setDescription(Constants.NO_ENTRIES);
+ requiredServiceList.add(requiredService);
+ }
+
+ // add all elements to options
+ serviceList = requiredServiceList;
+ List serviceNameOptions = new ArrayList<>();
+ boolean isSelectedElementIn = false;
+ int index = 0;
+ for (RequiredService requiredService : requiredServiceList) {
+ // create StateOption with "value = list index" and "label = human readable
+ // string"
+ serviceNameOptions
+ .add(new CommandOption(Integer.toString(index), Converter.toTitleCase(requiredService.getType())));
+ if (selectedService.equals(requiredService.getType())) {
+ 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, channelToBeUpdated);
+ }
+ }
+
+ private void selectService(int index, @Nullable String channelToBeUpdated) {
+ if (index >= 0 && index < serviceList.size()) {
+ RequiredService serviceEntry = serviceList.get(index);
+ selectedService = serviceEntry.getType();
+ updateChannel(CHANNEL_GROUP_SERVICE, NAME, Converter.toTitleCase(serviceEntry.getType()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_SERVICE, DETAILS, StringType.valueOf(serviceEntry.getDescription()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_SERVICE, DATE,
+ Converter.zonedToLocalDateTime(serviceEntry.getDateTime(), timeZoneProvider.getTimeZone()),
+ channelToBeUpdated);
+
+ if (serviceEntry.getMileage() > 0) {
+ updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
+ QuantityType.valueOf(serviceEntry.getMileage(), Constants.KILOMETRE_UNIT), channelToBeUpdated);
} else {
- logger.debug("Content not valid");
+ updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
+ }
+ }
+ }
+
+ private void updateChargingSessions(ChargingSessionsContainer chargeSessionsContainer,
+ @Nullable String channelToBeUpdated) {
+ List chargeSessions = new ArrayList<>();
+
+ if (chargeSessionsContainer.chargingSessions != null
+ && chargeSessionsContainer.chargingSessions.getSessions() != null
+ && !chargeSessionsContainer.chargingSessions.getSessions().isEmpty()) {
+ chargeSessions.addAll(chargeSessionsContainer.chargingSessions.getSessions());
+ } else {
+ // if list is empty add "undefined" element
+ ChargingSession cs = new ChargingSession();
+ cs.setTitle(Constants.NO_ENTRIES);
+ chargeSessions.add(cs);
+ }
+
+ // add all elements to options
+ sessionList = chargeSessions;
+ List sessionNameOptions = new ArrayList<>();
+ boolean isSelectedElementIn = false;
+ int index = 0;
+ for (ChargingSession session : sessionList) {
+ // create StateOption with "value = list index" and "label = human readable
+ // string"
+ sessionNameOptions.add(new CommandOption(Integer.toString(index), session.getTitle()));
+ if (selectedSession.equals(session.getTitle())) {
+ isSelectedElementIn = true;
}
+ index++;
}
+ setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
- @Override
- public void onError(NetworkError error) {
- logger.debug("{}", error.toString());
+ // if current selected item isn't anymore in the list select first entry
+ if (!isSelectedElementIn) {
+ selectSession(0, channelToBeUpdated);
}
}
- public class ChargeSessionsCallback implements StringResponseCallback {
- @Override
- public void onResponse(@Nullable String content) {
- if (content != null) {
- try {
- ChargeSessionsContainer csc = Converter.getGson().fromJson(content, ChargeSessionsContainer.class);
- if (csc != null) {
- if (csc.chargingSessions != null) {
- updateSessions(csc.chargingSessions.sessions);
- }
- }
- } catch (JsonSyntaxException jse) {
- logger.warn("{}", jse.getLocalizedMessage());
+ private void selectSession(int index, @Nullable String channelToBeUpdated) {
+ if (index >= 0 && index < sessionList.size()) {
+ ChargingSession sessionEntry = sessionList.get(index);
+ selectedSession = sessionEntry.getTitle();
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.getTitle()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.getSubtitle()),
+ channelToBeUpdated);
+ if (sessionEntry.getEnergyCharged() != null) {
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.getEnergyCharged()),
+ channelToBeUpdated);
+ } else {
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF),
+ channelToBeUpdated);
+ }
+ if (sessionEntry.getIssues() != null) {
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.getIssues()),
+ channelToBeUpdated);
+ } else {
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN),
+ channelToBeUpdated);
+ }
+ updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.getSessionStatus()),
+ channelToBeUpdated);
+ }
+ }
+
+ private void updateChargingProfile(ChargingProfile cp, @Nullable String channelToBeUpdated) {
+ ChargingProfileWrapper cpw = new ChargingProfileWrapper(cp);
+
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()),
+ channelToBeUpdated);
+ ChargingSettings cs = cpw.getChargingSettings();
+ if (cs != null) {
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
+ QuantityType.valueOf(cs.getTargetSoc(), Units.PERCENT), channelToBeUpdated);
+
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
+ OnOffType.from(cs.isAcCurrentLimitActive()), channelToBeUpdated);
+ }
+ final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
+ climate == null ? UnDefType.UNDEF : OnOffType.from(climate), channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.WINDOWSTART, channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.WINDOWEND, channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.TIMER1, channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.TIMER2, channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.TIMER3, channelToBeUpdated);
+ updateTimedState(cpw, ProfileKey.TIMER4, channelToBeUpdated);
+ }
+
+ private void updateTimedState(ChargingProfileWrapper profile, ProfileKey key, @Nullable String channelToBeUpdated) {
+ final TimedChannel timed = ChargingProfileUtils.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())),
+ channelToBeUpdated);
+ 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), channelToBeUpdated);
+ if (timed.hasDays) {
+ final Set days = profile.getDays(key);
+ EnumSet.allOf(DayOfWeek.class).forEach(day -> {
+ updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
+ timed.timer + ChargingProfileUtils.getDaysChannel(day),
+ days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)),
+ channelToBeUpdated);
+ });
}
+ }
+ }
+ }
+
+ private void updateDoors(VehicleDoorsState vehicleDoorsState, boolean isLeftSteering,
+ @Nullable String channelToBeUpdated) {
+ updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleDoorsState.getLeftFront() : vehicleDoorsState.getRightFront())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleDoorsState.getLeftRear() : vehicleDoorsState.getRightRear())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleDoorsState.getRightFront() : vehicleDoorsState.getLeftFront())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleDoorsState.getRightRear() : vehicleDoorsState.getLeftRear())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, TRUNK,
+ StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getTrunk())), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getHood())),
+ channelToBeUpdated);
+ }
+
+ private void updateWindows(VehicleWindowsState vehicleWindowState, boolean isLeftSteering,
+ @Nullable String channelToBeUpdated) {
+ updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleWindowState.getLeftFront() : vehicleWindowState.getRightFront())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleWindowState.getLeftRear() : vehicleWindowState.getRightRear())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleWindowState.getRightFront() : vehicleWindowState.getLeftFront())),
+ channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
+ StringType.valueOf(Converter.toTitleCase(
+ isLeftSteering ? vehicleWindowState.getRightRear() : vehicleWindowState.getLeftRear())),
+ channelToBeUpdated);
+ }
+
+ private void updateRoof(VehicleRoofState vehicleRoofState, @Nullable String channelToBeUpdated) {
+ updateChannel(CHANNEL_GROUP_DOORS, SUNROOF,
+ StringType.valueOf(Converter.toTitleCase(vehicleRoofState.getRoofState())), channelToBeUpdated);
+ }
+
+ private void updatePosition(VehicleLocation location, @Nullable String channelToBeUpdated) {
+ if (location.getCoordinates().getLatitude() < 0) {
+ updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
+ } else {
+ PointType vehicleLocation = PointType.valueOf(Double.toString(location.getCoordinates().getLatitude()) + ","
+ + Double.toString(location.getCoordinates().getLongitude()));
+ updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation, channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_LOCATION, HEADING,
+ QuantityType.valueOf(location.getHeading(), Units.DEGREE_ANGLE), channelToBeUpdated);
+ updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(location.getAddress().getFormatted()),
+ channelToBeUpdated);
+ PointType homeLocation = locationProvider.getLocation();
+ if (homeLocation != null) {
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
+ QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE),
+ channelToBeUpdated);
+ } else {
+ updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
+ }
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.trace("VehicleHandler.handleCommand {}, {}, {}", command.toFullString(), channelUID.getAsString(),
+ channelUID.getIdWithoutGroup());
+ String group = channelUID.getGroupId();
+
+ if (group == null) {
+ logger.debug("Cannot handle command {}, no group for channel {}", command.toFullString(),
+ channelUID.getAsString());
+ return;
+ }
+
+ if (command instanceof RefreshType) {
+ // Refresh of Channels with cached values
+ if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
+ imageCache.ifPresent(image -> updateImage(image));
} else {
- logger.debug("Content not valid");
+ vehicleStatusCache.ifPresent(
+ vehicleStatus -> triggerVehicleStatusUpdate(vehicleStatus, channelUID.getIdWithoutGroup()));
+ }
+ } else if (command instanceof StringType) {
+ // Check for Channel Group and corresponding Actions
+ switch (group) {
+ case CHANNEL_GROUP_REMOTE:
+ // Executing Remote Services
+ String serviceCommand = ((StringType) command).toFullString();
+ remote.ifPresent(remot -> {
+ RemoteServiceUtils.getRemoteServiceFromCommand(serviceCommand)
+ .ifPresentOrElse(service -> remot.execute(service), () -> {
+ logger.debug("Remote service execution {} unknown", serviceCommand);
+ });
+ });
+ break;
+ case CHANNEL_GROUP_VEHICLE_IMAGE:
+ // Image Change
+ vehicleConfiguration.ifPresent(config -> {
+ if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
+ String newViewport = command.toString();
+ synchronized (imageProperties) {
+ if (!imageProperties.viewport.equals(newViewport)) {
+ imageProperties = new ImageProperties(newViewport);
+ imageCache = Optional.empty();
+ Optional imageContent = proxy.map(prox -> {
+ try {
+ return prox.requestImage(config.getVin(), config.getVehicleBrand(),
+ imageProperties);
+ } catch (NetworkException e) {
+ logger.debug("{}", e.toString());
+ return "".getBytes();
+ }
+ });
+ imageContent.ifPresent(imageContentData -> updateImage(imageContentData));
+ }
+ }
+ updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport),
+ IMAGE_VIEWPORT);
+ }
+ });
+ break;
+ case CHANNEL_GROUP_SERVICE:
+ int serviceIndex = Converter.parseIntegerString(command.toFullString());
+ if (serviceIndex != -1) {
+ selectService(serviceIndex, null);
+ } else {
+ logger.debug("Cannot select Service index {}", command.toFullString());
+ }
+ break;
+ case CHANNEL_GROUP_CHECK_CONTROL:
+ int checkControlIndex = Converter.parseIntegerString(command.toFullString());
+ if (checkControlIndex != -1) {
+ selectCheckControl(checkControlIndex, null);
+ } else {
+ logger.debug("Cannot select CheckControl index {}", command.toFullString());
+ }
+ break;
+ case CHANNEL_GROUP_CHARGE_SESSION:
+ int sessionIndex = Converter.parseIntegerString(command.toFullString());
+ if (sessionIndex != -1) {
+ selectSession(sessionIndex, null);
+ } else {
+ logger.debug("Cannot select Session index {}", command.toFullString());
+ }
+ break;
+ default:
+ logger.debug("Cannot handle command {}, channel {} in group {} not a command channel",
+ command.toFullString(), channelUID.getAsString(), group);
}
}
+ }
- @Override
- public void onError(NetworkError error) {
- logger.debug("{}", error.toString());
+ private void updateImage(byte[] imageContent) {
+ if (imageContent.length > 0) {
+ imageCache = Optional.of(imageContent);
+ String contentType = HttpUtil.guessContentTypeFromData(imageContent);
+ updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(imageContent, contentType),
+ IMAGE_FORMAT);
+ } else {
+ synchronized (imageProperties) {
+ imageProperties.failed();
+ }
}
}
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java
new file mode 100644
index 0000000000000..9164c38e019ee
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/Token.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/Token.java
similarity index 96%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/Token.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/Token.java
index 7f286ec055768..45bd6adac584f 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/Token.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/Token.java
@@ -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;
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializer.java
new file mode 100644
index 0000000000000..f9addb18ae506
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializer.java
@@ -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 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();
+ }
+ }
+
+ 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 deserializeString(String toBeDeserialized, Class deserializedClass) {
+ return GSON.fromJson(toBeDeserialized, deserializedClass);
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWFileProxy.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWFileProxy.java
new file mode 100644
index 0000000000000..4dd16cf400647
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWFileProxy.java
@@ -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 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 requestVehiclesBase() throws NetworkException {
+ List 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 "";
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxy.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxy.java
new file mode 100644
index 0000000000000..debad84e2ba22
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxy.java
@@ -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 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 requestVehiclesBase() throws NetworkException {
+ List 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 ######");
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWProxy.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWProxy.java
new file mode 100644
index 0000000000000..a18004112c3b1
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWProxy.java
@@ -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 requestVehiclesBase(String brand) throws NetworkException;
+
+ String requestVehiclesBaseJson(String brand) throws NetworkException;
+
+ /**
+ * request vehicles for all possible brands
+ *
+ * @param callback
+ */
+ List 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;
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/NetworkException.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/NetworkException.java
new file mode 100644
index 0000000000000..ed5fa1f310f22
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/NetworkException.java
@@ -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 + "]";
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizer.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizer.java
new file mode 100644
index 0000000000000..d6ce50c9c7566
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizer.java
@@ -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
+ *
+ * - vin
+ *
+ *
+ * - location
+ *
+ *
+ * @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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ByteResponseCallback.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/ExecutionState.java
similarity index 61%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ByteResponseCallback.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/ExecutionState.java
index 14712a14d0a78..6d3ed01a71717 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/ByteResponseCallback.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/ExecutionState.java
@@ -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
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/RemoteService.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/RemoteService.java
new file mode 100644
index 0000000000000..0436c2555ea6c
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/enums/RemoteService.java
@@ -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;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/simulation/Injector.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/simulation/Injector.java
deleted file mode 100644
index 4b9299ff5ed73..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/simulation/Injector.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java
index 7284c6411b95a..f43a751ef5f0d 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java
@@ -27,49 +27,58 @@
* https://customer.bmwgroup.com/one/app/oauth.js
*
* @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 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 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 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 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 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 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 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";
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileWrapper.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileWrapper.java
deleted file mode 100644
index 620861fbf87e0..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileWrapper.java
+++ /dev/null
@@ -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 mode = Optional.empty();
- private Optional preference = Optional.empty();
- private Optional controlType = Optional.empty();
- private Optional chargeSettings = Optional.empty();
-
- private final Map enabled = new HashMap<>();
- private final Map times = new HashMap<>();
- private final Map> 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 getDays(final ProfileKey key) {
- return daysOfWeek.get(key);
- }
-
- public void setDays(final ProfileKey key, final @Nullable Set 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 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();
- 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 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 days = daysOfWeek.get(key);
- if (days != null) {
- timer.timerWeekDays = new ArrayList<>();
- for (DayOfWeek day : days) {
- timer.timerWeekDays.add(day.name().toLowerCase());
- }
- }
- return timer;
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileUtils.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileUtils.java
similarity index 96%
rename from bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileUtils.java
rename to bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileUtils.java
index 3df2c701c9d18..194bf6732b8a9 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargeProfileUtils.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileUtils.java
@@ -22,15 +22,15 @@
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 {
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileWrapper.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileWrapper.java
new file mode 100644
index 0000000000000..ae25430dcb3b4
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ChargingProfileWrapper.java
@@ -0,0 +1,202 @@
+/**
+ * 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.ChargingProfileWrapper.ProfileKey.TIMER1;
+import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER2;
+import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER3;
+import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER4;
+import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.WINDOWEND;
+import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.WINDOWSTART;
+import static org.openhab.binding.mybmw.internal.utils.Constants.NULL_LOCAL_TIME;
+import static org.openhab.binding.mybmw.internal.utils.Constants.TIME_FORMATER;
+
+import java.time.DayOfWeek;
+import java.time.LocalTime;
+import java.time.format.DateTimeParseException;
+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.ChargingProfile;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
+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 ChargingProfileWrapper} Wrapper for ChargingProfiles
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Norbert Truchsess - add ChargeProfileActions
+ * @author Martin Grassl - refactoring
+ */
+@NonNullByDefault
+public class ChargingProfileWrapper {
+ private final Logger logger = LoggerFactory.getLogger(ChargingProfileWrapper.class);
+
+ private static final String CHARGING_WINDOW = "CHARGING_WINDOW";
+ private static final String WEEKLY_PLANNER = "WEEKLY_PLANNER";
+ private static final String ACTIVATE = "ACTIVATE";
+ // not used private static final String DEACTIVATE = "DEACTIVATE";
+
+ public enum ProfileKey {
+ CLIMATE,
+ TIMER1,
+ TIMER2,
+ TIMER3,
+ TIMER4,
+ WINDOWSTART,
+ WINDOWEND
+ }
+
+ private Optional mode = Optional.empty();
+ private Optional preference = Optional.empty();
+ private Optional controlType = Optional.empty();
+ private Optional chargeSettings = Optional.empty();
+
+ private final Map enabled = new HashMap<>();
+ private final Map times = new HashMap<>();
+ private final Map> daysOfWeek = new HashMap<>();
+
+ public ChargingProfileWrapper(final ChargingProfile profile) {
+ setPreference(profile.getChargingPreference());
+ setMode(profile.getChargingMode());
+ controlType = Optional.of(profile.getChargingControlType());
+ chargeSettings = Optional.of(profile.getChargingSettings());
+ setEnabled(ProfileKey.CLIMATE, profile.isClimatisationOn());
+
+ addTimer(TIMER1, profile.getTimerId(1));
+ addTimer(TIMER2, profile.getTimerId(2));
+ if (profile.getChargingControlType().equals(WEEKLY_PLANNER)) {
+ addTimer(TIMER3, profile.getTimerId(3));
+ addTimer(TIMER4, profile.getTimerId(4));
+ }
+
+ if (CHARGING_WINDOW.equals(profile.getChargingPreference())) {
+ addTime(WINDOWSTART, profile.getReductionOfChargeCurrent().getStart());
+ addTime(WINDOWEND, profile.getReductionOfChargeCurrent().getEnd());
+ } else {
+ preference.ifPresent(pref -> {
+ if (ChargingPreference.CHARGING_WINDOW.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 getChargingSettings() {
+ 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 getDays(final ProfileKey key) {
+ return daysOfWeek.get(key);
+ }
+
+ 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;
+ }
+ }
+
+ 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 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);
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Constants.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Constants.java
index 8580f65670f9b..b7ca759e8a3ed 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Constants.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Constants.java
@@ -32,6 +32,7 @@
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - contributor
+ * @author Martin Grassl - rename drivetrain options
*/
@NonNullByDefault
public class Constants {
@@ -92,11 +93,11 @@ public class Constants {
};
// Drive Train definitions from json
- public static final String BEV = "ELECTRIC";
- public static final String REX_EXTENSION = "(+ REX)";
- public static final String HYBRID = "HYBRID";
- public static final String CONV = "COMBUSTION";
- public static final String PHEV = "PLUGIN_HYBRID";
+ public static final String DRIVETRAIN_BEV = "ELECTRIC";
+ public static final String DRIVETRAIN_REX_EXTENSION = "(+ REX)";
+ public static final String DRIVETRAIN_MILD_HYBRID = "MILD_HYBRID";
+ public static final String DRIVETRAIN_CONV = "COMBUSTION";
+ public static final String DRIVETRAIN_PHEV = "PLUGIN_HYBRID";
// Carging States
public static final String DEFAULT = "DEFAULT";
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Converter.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Converter.java
index 672a9bb836e13..69cdd02711010 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Converter.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/Converter.java
@@ -12,89 +12,68 @@
*/
package org.openhab.binding.mybmw.internal.utils;
-import java.lang.reflect.Type;
-import java.text.SimpleDateFormat;
-import java.time.LocalTime;
+import java.time.Instant;
import java.time.ZoneId;
-import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
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.charge.Time;
-import org.openhab.binding.mybmw.internal.dto.properties.Address;
-import org.openhab.binding.mybmw.internal.dto.properties.Coordinates;
-import org.openhab.binding.mybmw.internal.dto.properties.Distance;
-import org.openhab.binding.mybmw.internal.dto.properties.Location;
-import org.openhab.binding.mybmw.internal.dto.properties.Range;
-import org.openhab.binding.mybmw.internal.dto.status.Mileage;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
-
/**
* The {@link Converter} Conversion Helpers
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - extract some methods to other classes
*/
@NonNullByDefault
-public class Converter {
- public static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
-
- public static final String DATE_INPUT_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ss";
- public static final DateTimeFormatter DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern(DATE_INPUT_PATTERN_STRING);
- public static final DateTimeFormatter LOCALE_ENGLISH_TIMEFORMATTER = DateTimeFormatter.ofPattern("hh:mm a",
- Locale.ENGLISH);
- public static final SimpleDateFormat ISO_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
-
- private static final Gson GSON = new Gson();
- private static final Vehicle INVALID_VEHICLE = new Vehicle();
- private static final String SPLIT_HYPHEN = "-";
- private static final String SPLIT_BRACKET = "\\(";
- private static final String VIN_PATTERN = "\"vin\":";
- private static final String VEHICLE_LOCATION_PATTERN = "\"vehicleLocation\":";
- private static final String VEHICLE_LOCATION_REPLACEMENT = "\"vehicleLocation\": {\"coordinates\": {\"latitude\": 1.1,\"longitude\": 2.2},\"address\": {\"formatted\": \"anonymous\"},\"heading\": -1}";
- private static final char OPEN_BRACKET = "{".charAt(0);
- private static final char CLOSING_BRACKET = "}".charAt(0);
+public interface Converter {
+ static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
- // https://www.baeldung.com/gson-list
- public static final Type VEHICLE_LIST_TYPE = new TypeToken>() {
- }.getType();
- public static int offsetMinutes = -1;
+ static final String SPLIT_HYPHEN = "-";
+ static final String SPLIT_BRACKET = "\\(";
- public static String zonedToLocalDateTime(String input) {
- try {
- ZonedDateTime d = ZonedDateTime.parse(input).withZoneSameInstant(ZoneId.systemDefault());
- return d.toLocalDateTime().format(Converter.DATE_INPUT_PATTERN);
- } catch (Exception e) {
- LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
+ static State zonedToLocalDateTime(@Nullable String input, ZoneId timezone) {
+ if (input != null && !input.isEmpty()) {
+ try {
+ String localTimeString = Instant.parse(input).atZone(timezone)
+ .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+ return DateTimeType.valueOf(localTimeString);
+ } catch (Exception e) {
+ LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
+ return UnDefType.UNDEF;
+ }
+ } else {
+ return UnDefType.UNDEF;
}
- return input;
}
- public static String toTitleCase(@Nullable String input) {
- if (input == null) {
+ /**
+ * converts a string into a unified format
+ * - string is Capitalized
+ * - null is empty string
+ * - single character remains
+ *
+ * @param input
+ * @return
+ */
+ static String toTitleCase(@Nullable String input) {
+ if (input == null || input.isEmpty()) {
return toTitleCase(Constants.UNDEF);
} else if (input.length() == 1) {
return input;
} else {
+ // first, replace all underscores with spaces and make it lower case
String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
+
+ //
String converted = toTitleCase(lower, Constants.SPACE);
converted = toTitleCase(converted, SPLIT_HYPHEN);
converted = toTitleCase(converted, SPLIT_BRACKET);
@@ -103,7 +82,9 @@ public static String toTitleCase(@Nullable String input) {
}
private static String toTitleCase(String input, String splitter) {
+ // first, split all parts by the splitting string
String[] arr = input.split(splitter);
+
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
@@ -114,14 +95,6 @@ private static String toTitleCase(String input, String splitter) {
return sb.toString().trim();
}
- public static String capitalizeFirst(String str) {
- return str.substring(0, 1).toUpperCase() + str.substring(1);
- }
-
- public static Gson getGson() {
- return GSON;
- }
-
/**
* Measure distance between 2 coordinates
*
@@ -131,7 +104,7 @@ public static Gson getGson() {
* @param destinationLongitude
* @return distance
*/
- public static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
+ static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
double destinationLongitude) {
double earthRadius = 6378.137; // Radius of earth in KM
double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
@@ -144,26 +117,38 @@ public static double measureDistance(double sourceLatitude, double sourceLongitu
/**
* Easy function but there's some measures behind:
- * Guessing the range of the Vehicle on Map. If you can drive x kilometers with your Vehicle it's not feasible to
- * project this x km Radius on Map. The roads to be taken are causing some overhead because they are not a straight
+ * Guessing the range of the Vehicle on Map. If you can drive x kilometers with
+ * your Vehicle it's not feasible to
+ * project this x km Radius on Map. The roads to be taken are causing some
+ * overhead because they are not a straight
* line from Location A to B.
- * I've taken some measurements to calculate the overhead factor based on Google Maps
+ * I've taken some measurements to calculate the overhead factor based on Google
+ * Maps
* Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
* Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
- * After measuring more distances you'll find out that the outcome is between 70% and 90%. So
+ * After measuring more distances you'll find out that the outcome is between
+ * 70% and 90%. So
*
- * This depends also on the roads of a concrete route but this is only a guess without any Route Navigation behind
+ * This depends also on the roads of a concrete route but this is only a guess
+ * without any Route Navigation behind
*
- * In future it's foreseen to replace this with BMW RangeMap Service which isn't running at the moment.
+ * In future it's foreseen to replace this with BMW RangeMap Service which isn't
+ * running at the moment.
*
* @param range
* @return mapping from air-line distance to "real road" distance
*/
- public static int guessRangeRadius(double range) {
+ static int guessRangeRadius(double range) {
return (int) (range * 0.8);
}
- public static int getIndex(String fullString) {
+ /**
+ * checks if a string is a valid integer
+ *
+ * @param fullString
+ * @return
+ */
+ static int parseIntegerString(String fullString) {
int index = -1;
try {
index = Integer.parseInt(fullString);
@@ -172,98 +157,7 @@ public static int getIndex(String fullString) {
return index;
}
- /**
- * Returns list of found vehicles
- * In case of errors return empty list
- *
- * @param json
- * @return
- */
- public static List getVehicleList(String json) {
- try {
- List l = GSON.fromJson(json, VEHICLE_LIST_TYPE);
- if (l != null) {
- return l;
- } else {
- return new ArrayList();
- }
- } catch (JsonSyntaxException e) {
- LOGGER.warn("JsonSyntaxException {}", e.getMessage());
- return new ArrayList();
- }
- }
-
- public static Vehicle getVehicle(String vin, String json) {
- List l = getVehicleList(json);
- for (Vehicle vehicle : l) {
- if (vin.equals(vehicle.vin)) {
- // declare vehicle as valid
- vehicle.valid = true;
- return getConsistentVehcile(vehicle);
- }
- }
- return INVALID_VEHICLE;
- }
-
- public static String getRawVehicleContent(String vin, String json) {
- JsonArray jArr = JsonParser.parseString(json).getAsJsonArray();
- for (int i = 0; i < jArr.size(); i++) {
- JsonObject jo = jArr.get(i).getAsJsonObject();
- String jsonVin = jo.getAsJsonPrimitive(MyBMWConstants.VIN).getAsString();
- if (vin.equals(jsonVin)) {
- return jo.toString();
- }
- }
- return Constants.EMPTY_JSON;
- }
-
- /**
- * ensure basic data like mileage and location data are available every time
- *
- * @param v
- * @return
- */
- public static Vehicle getConsistentVehcile(Vehicle v) {
- if (v.status.currentMileage == null) {
- v.status.currentMileage = new Mileage();
- v.status.currentMileage.mileage = -1;
- v.status.currentMileage.units = "km";
- }
- if (v.properties.combustionRange == null) {
- v.properties.combustionRange = new Range();
- v.properties.combustionRange.distance = new Distance();
- v.properties.combustionRange.distance.value = -1;
- v.properties.combustionRange.distance.units = Constants.EMPTY;
- }
- if (v.properties.vehicleLocation == null) {
- v.properties.vehicleLocation = new Location();
- v.properties.vehicleLocation.heading = Constants.INT_UNDEF;
- v.properties.vehicleLocation.coordinates = new Coordinates();
- v.properties.vehicleLocation.coordinates.latitude = Constants.INT_UNDEF;
- v.properties.vehicleLocation.coordinates.longitude = Constants.INT_UNDEF;
- v.properties.vehicleLocation.address = new Address();
- v.properties.vehicleLocation.address.formatted = Constants.UNDEF;
- }
- return v;
- }
-
- public static State getLockState(boolean lock) {
- if (lock) {
- return StringType.valueOf(Constants.LOCKED);
- } else {
- return StringType.valueOf(Constants.UNLOCKED);
- }
- }
-
- public static State getClosedState(boolean close) {
- if (close) {
- return StringType.valueOf(Constants.CLOSED);
- } else {
- return StringType.valueOf(Constants.OPEN);
- }
- }
-
- public static State getConnectionState(boolean connected) {
+ static State getConnectionState(boolean connected) {
if (connected) {
return StringType.valueOf(Constants.CONNECTED);
} else {
@@ -271,89 +165,20 @@ public static State getConnectionState(boolean connected) {
}
}
- public static String getCurrentISOTime() {
- Date date = new Date(System.currentTimeMillis());
- synchronized (ISO_FORMATTER) {
- ISO_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
- return ISO_FORMATTER.format(date);
- }
+ static String getCurrentISOTime() {
+ return ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
}
- public static String getTime(Time t) {
+ static String getTime(Time t) {
StringBuffer time = new StringBuffer();
- if (t.hour < 10) {
+ if (t.getHour() < 10) {
time.append("0");
}
- time.append(Integer.toString(t.hour)).append(":");
- if (t.minute < 10) {
+ time.append(Integer.toString(t.getHour())).append(":");
+ if (t.getMinute() < 10) {
time.append("0");
}
- time.append(Integer.toString(t.minute));
+ time.append(Integer.toString(t.getMinute()));
return time.toString();
}
-
- public static int getOffsetMinutes() {
- if (offsetMinutes == -1) {
- ZoneOffset zo = ZonedDateTime.now().getOffset();
- offsetMinutes = zo.getTotalSeconds() / 60;
- }
- return offsetMinutes;
- }
-
- public static int stringToInt(String intStr) {
- int integer = Constants.INT_UNDEF;
- try {
- integer = Integer.parseInt(intStr);
-
- } catch (Exception e) {
- LOGGER.debug("Unable to convert range {} into int value", intStr);
- }
- return integer;
- }
-
- public static String getLocalTime(String chrageInfoLabel) {
- String[] timeSplit = chrageInfoLabel.split(Constants.TILDE);
- if (timeSplit.length == 2) {
- try {
- LocalTime timeL = LocalTime.parse(timeSplit[1].trim(), LOCALE_ENGLISH_TIMEFORMATTER);
- return timeSplit[0] + Constants.TILDE + timeL.toString();
- } catch (Exception e) {
- LOGGER.debug("Unable to parse date {} - {}", timeSplit[1], e.getMessage());
- }
- }
- return chrageInfoLabel;
- }
-
- public static String anonymousFingerprint(String raw) {
- String anonymousFingerprintString = raw;
- int vinStartIndex = raw.indexOf(VIN_PATTERN);
- if (vinStartIndex != -1) {
- String[] arr = raw.substring(vinStartIndex + VIN_PATTERN.length()).trim().split("\"");
- String vin = arr[1].trim();
- anonymousFingerprintString = raw.replace(vin, "anonymous");
- }
-
- int locationStartIndex = raw.indexOf(VEHICLE_LOCATION_PATTERN);
- int bracketCounter = -1;
- if (locationStartIndex != -1) {
- int endLocationIndex = 0;
- for (int i = locationStartIndex; i < raw.length() && bracketCounter != 0; i++) {
- endLocationIndex = i;
- if (raw.charAt(i) == OPEN_BRACKET) {
- if (bracketCounter == -1) {
- // start point
- bracketCounter = 1;
- } else {
- bracketCounter++;
- }
- } else if (raw.charAt(i) == CLOSING_BRACKET) {
- bracketCounter--;
- }
- }
- String locationReplacement = raw.substring(locationStartIndex, endLocationIndex + 1);
- anonymousFingerprintString = anonymousFingerprintString.replace(locationReplacement,
- VEHICLE_LOCATION_REPLACEMENT);
- }
- return anonymousFingerprintString;
- }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/HTTPConstants.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/HTTPConstants.java
index 38e8fba7729aa..294450a21bb75 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/HTTPConstants.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/HTTPConstants.java
@@ -15,39 +15,46 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
- * The {@link HTTPConstants} class contains fields mapping thing configuration parameters.
+ * The {@link HTTPConstants} interface contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - added image content type
*/
@NonNullByDefault
-public class HTTPConstants {
- public static final int HTTP_TIMEOUT_SEC = 10;
+public interface HTTPConstants {
+ static final int HTTP_TIMEOUT_SEC = 10;
- public static final String CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
- public static final String CONTENT_TYPE_JSON_ENCODED = "application/json";
- public static final String KEEP_ALIVE = "Keep-Alive";
- public static final String CLIENT_ID = "client_id";
- public static final String RESPONSE_TYPE = "response_type";
- public static final String TOKEN = "token";
- public static final String CODE = "code";
- public static final String CODE_VERIFIER = "code_verifier";
- public static final String STATE = "state";
- public static final String NONCE = "nonce";
- public static final String REDIRECT_URI = "redirect_uri";
- public static final String AUTHORIZATION = "authorization";
- public static final String GRANT_TYPE = "grant_type";
- public static final String SCOPE = "scope";
- public static final String CREDENTIALS = "Credentials";
- public static final String USERNAME = "username";
- public static final String PASSWORD = "password";
- public static final String CONTENT_LENGTH = "Content-Length";
- public static final String CODE_CHALLENGE = "code_challenge";
- public static final String CODE_CHALLENGE_METHOD = "code_challenge_method";
- public static final String ACCESS_TOKEN = "access_token";
- public static final String TOKEN_TYPE = "token_type";
- public static final String EXPIRES_IN = "expires_in";
- public static final String CHUNKED = "chunked";
+ static final String CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
+ static final String CONTENT_TYPE_JSON = "application/json";
+ static final String CONTENT_TYPE_IMAGE = "image/png";
+ static final String KEEP_ALIVE = "Keep-Alive";
+ static final String CLIENT_ID = "client_id";
+ static final String RESPONSE_TYPE = "response_type";
+ static final String TOKEN = "token";
+ static final String CODE = "code";
+ static final String CODE_VERIFIER = "code_verifier";
+ static final String STATE = "state";
+ static final String NONCE = "nonce";
+ static final String REDIRECT_URI = "redirect_uri";
+ static final String AUTHORIZATION = "authorization";
+ static final String GRANT_TYPE = "grant_type";
+ static final String SCOPE = "scope";
+ static final String CREDENTIALS = "Credentials";
+ static final String USERNAME = "username";
+ static final String PASSWORD = "password";
+ static final String CONTENT_LENGTH = "Content-Length";
+ static final String CODE_CHALLENGE = "code_challenge";
+ static final String CODE_CHALLENGE_METHOD = "code_challenge_method";
+ static final String ACCESS_TOKEN = "access_token";
+ static final String TOKEN_TYPE = "token_type";
+ static final String EXPIRES_IN = "expires_in";
+ static final String CHUNKED = "chunked";
- public static final String ACP_SUBSCRIPTION_KEY = "ocp-apim-subscription-key";
- public static final String X_USER_AGENT = "x-user-agent";
+ // HTTP headers for BMW API
+ static final String HEADER_ACP_SUBSCRIPTION_KEY = "ocp-apim-subscription-key";
+ static final String HEADER_X_USER_AGENT = "x-user-agent";
+ static final String HEADER_X_IDENTITY_PROVIDER = "x-identity-provider";
+ static final String HEADER_X_CORRELATION_ID = "x-correlation-id";
+ static final String HEADER_BMW_CORRELATION_ID = "bmw-correlation-id";
+ static final String HEADER_BMW_VIN = "bmw-vin";
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ImageProperties.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ImageProperties.java
index cabdcc665495e..b1a519028eca4 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ImageProperties.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/ImageProperties.java
@@ -18,12 +18,13 @@
* The {@link ImageProperties} Properties of current Vehicle Image
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - fix default viewport as "default" is not available anymore
*/
@NonNullByDefault
public class ImageProperties {
public static final int RETRY_COUNTER = 5;
public int failCounter = 0;
- public String viewport = "Default";
+ public String viewport = "VehicleStatus";
public ImageProperties(String viewport) {
this.viewport = viewport;
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationChecker.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationChecker.java
new file mode 100644
index 0000000000000..d4b9d8b29b0cf
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationChecker.java
@@ -0,0 +1,34 @@
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
+
+/**
+ *
+ * checks if the configuration is valid
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - extracted to own class
+ */
+@NonNullByDefault
+public final class MyBMWConfigurationChecker {
+ public static boolean checkConfiguration(MyBMWBridgeConfiguration config) {
+ if (config.userName.isBlank() || config.password.isBlank()) {
+ return false;
+ } else {
+ return BimmerConstants.EADRAX_SERVER_MAP.containsKey(config.region);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/RemoteServiceUtils.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/RemoteServiceUtils.java
index 2b9ff08074991..29cc02a72464b 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/RemoteServiceUtils.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/RemoteServiceUtils.java
@@ -19,13 +19,14 @@
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.mybmw.internal.handler.RemoteServiceHandler.RemoteService;
+import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
import org.openhab.core.types.CommandOption;
/**
* Helper class for Remote Service Commands
*
* @author Norbert Truchsess - Initial contribution
+ * @author Martin Grassl - small refactoring
*/
@NonNullByDefault
public class RemoteServiceUtils {
@@ -33,7 +34,7 @@ public class RemoteServiceUtils {
private static final Map COMMAND_SERVICES = Stream.of(RemoteService.values())
.collect(Collectors.toUnmodifiableMap(RemoteService::getId, service -> service));
- public static Optional getRemoteService(final String command) {
+ public static Optional getRemoteServiceFromCommand(final String command) {
return Optional.ofNullable(COMMAND_SERVICES.get(command));
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/VehicleStatusUtils.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/VehicleStatusUtils.java
index 8b7cc0a08f8fb..aa3a97ab4cb24 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/VehicleStatusUtils.java
+++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/VehicleStatusUtils.java
@@ -13,20 +13,14 @@
package org.openhab.binding.mybmw.internal.utils;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.List;
-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.properties.CBS;
-import org.openhab.binding.mybmw.internal.dto.status.FuelIndicator;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
@@ -36,17 +30,24 @@
* The {@link VehicleStatusUtils} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - refactor for v2 and extract some methods to other classes
*/
@NonNullByDefault
public class VehicleStatusUtils {
public static final Logger LOGGER = LoggerFactory.getLogger(VehicleStatusUtils.class);
- public static State getNextServiceDate(List cbsMessageList) {
+ /**
+ * the date can be empty
+ *
+ * @param requiredServices
+ * @return
+ */
+ public static State getNextServiceDate(List requiredServices) {
ZonedDateTime farFuture = ZonedDateTime.now().plusYears(100);
ZonedDateTime serviceDate = farFuture;
- for (CBS service : cbsMessageList) {
- if (service.dateTime != null) {
- ZonedDateTime d = ZonedDateTime.parse(service.dateTime);
+ for (RequiredService requiredService : requiredServices) {
+ if (requiredService.getDateTime() != null && !requiredService.getDateTime().isEmpty()) {
+ ZonedDateTime d = ZonedDateTime.parse(requiredService.getDateTime());
if (d.isBefore(serviceDate)) {
serviceDate = d;
} // else skip
@@ -55,27 +56,27 @@ public static State getNextServiceDate(List cbsMessageList) {
if (serviceDate.equals(farFuture)) {
return UnDefType.UNDEF;
} else {
- return DateTimeType.valueOf(serviceDate.format(Converter.DATE_INPUT_PATTERN));
+ return DateTimeType.valueOf(serviceDate.format(DateTimeFormatter.ISO_INSTANT));
}
}
- public static State getNextServiceMileage(List cbsMessageList) {
- boolean imperial = false;
+ /**
+ * the mileage can be empty
+ *
+ * @param requiredServices
+ * @return
+ */
+ public static State getNextServiceMileage(List requiredServices) {
int serviceMileage = Integer.MAX_VALUE;
- for (CBS service : cbsMessageList) {
- if (service.distance != null) {
- if (service.distance.value < serviceMileage) {
- serviceMileage = service.distance.value;
- imperial = !Constants.KILOMETERS_JSON.equals(service.distance.units);
+ for (RequiredService requiredService : requiredServices) {
+ if (requiredService.getMileage() > 0) {
+ if (requiredService.getMileage() < serviceMileage) {
+ serviceMileage = requiredService.getMileage();
}
}
}
if (serviceMileage != Integer.MAX_VALUE) {
- if (imperial) {
- return QuantityType.valueOf(serviceMileage, ImperialUnits.MILE);
- } else {
- return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
- }
+ return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
} else {
return UnDefType.UNDEF;
}
@@ -89,152 +90,19 @@ public static State getNextServiceMileage(List cbsMessageList) {
* @return
*/
public static VehicleType vehicleType(String driveTrain, String model) {
- if (Constants.BEV.equals(driveTrain)) {
- if (model.endsWith(Constants.REX_EXTENSION)) {
+ if (Constants.DRIVETRAIN_BEV.equals(driveTrain)) {
+ if (model.endsWith(Constants.DRIVETRAIN_REX_EXTENSION)) {
return VehicleType.ELECTRIC_REX;
} else {
return VehicleType.ELECTRIC;
}
- } else if (Constants.PHEV.equals(driveTrain)) {
+ } else if (Constants.DRIVETRAIN_PHEV.equals(driveTrain)) {
return VehicleType.PLUGIN_HYBRID;
- } else if (Constants.CONV.equals(driveTrain) || Constants.HYBRID.equals(driveTrain)) {
+ } else if (Constants.DRIVETRAIN_CONV.equals(driveTrain)
+ || Constants.DRIVETRAIN_MILD_HYBRID.equals(driveTrain)) {
return VehicleType.CONVENTIONAL;
}
LOGGER.warn("Unknown Vehicle Type: {} | {}", model, driveTrain);
return VehicleType.UNKNOWN;
}
-
- public static @Nullable Unit getLengthUnit(List indicators) {
- Unit ret = null;
- for (FuelIndicator fuelIndicator : indicators) {
- String unitAbbrev = fuelIndicator.rangeUnits;
- switch (unitAbbrev) {
- case Constants.KM_JSON:
- if (ret != null) {
- if (!ret.equals(Constants.KILOMETRE_UNIT)) {
- LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.KM_JSON);
- } // else - fine!
- } else {
- ret = Constants.KILOMETRE_UNIT;
- }
- break;
- case Constants.MI_JSON:
- if (ret != null) {
- if (!ret.equals(ImperialUnits.MILE)) {
- LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.MI_JSON);
- } // else - fine!
- } else {
- ret = ImperialUnits.MILE;
- }
- break;
- default:
- LOGGER.debug("Cannot evaluate Unit for {}", unitAbbrev);
- break;
- }
- }
- return ret;
- }
-
- /**
- * The range values delivered by BMW are quite ambiguous!
- * - status fuel indicators are missing a unique identifier
- * - properties ranges delivering wrong values for hybrid and fuel range
- * - properties ranges are not reflecting mi / km - every time km
- *
- * So getRange will try
- * 1) fuel indicator
- * 2) ranges from properties, except combined range
- * 3) take a guess from fuel indicators
- *
- * @param unitJson
- * @param vehicle
- * @return
- */
- public static int getRange(String unitJson, Vehicle vehicle) {
- if (vehicle.status.fuelIndicators.size() == 1) {
- return Converter.stringToInt(vehicle.status.fuelIndicators.get(0).rangeValue);
- } else {
- return guessRange(unitJson, vehicle);
- }
- }
-
- /**
- * Guesses the range from 3 fuelindicators
- * - electric range calculation is correct
- * - for the 2 other values:
- * -- smaller one is assigned to fuel range
- * -- bigger one is assigned to hybrid range
- *
- * @see org.openhab.binding.mybmw.internal.dto.VehicleStatusTest testGuessRange
- *
- * @param unitJson
- * @param vehicle
- * @return
- */
- public static int guessRange(String unitJson, Vehicle vehicle) {
- int electricGuess = Constants.INT_UNDEF;
- int fuelGuess = Constants.INT_UNDEF;
- int hybridGuess = Constants.INT_UNDEF;
- for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
- // electric range - this fits 100%
- if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
- && fuelIndicator.chargingStatusType != null) {
- // found electric
- electricGuess = Converter.stringToInt(fuelIndicator.rangeValue);
- } else {
- if (fuelGuess == Constants.INT_UNDEF) {
- // fuel not set? then assume it's fuel
- fuelGuess = Converter.stringToInt(fuelIndicator.rangeValue);
- } else {
- // fuel already guessed - take smaller value for fuel, bigger for hybrid
- int newGuess = Converter.stringToInt(fuelIndicator.rangeValue);
- hybridGuess = Math.max(fuelGuess, newGuess);
- fuelGuess = Math.min(fuelGuess, newGuess);
- }
- }
- }
- switch (unitJson) {
- case Constants.UNIT_PRECENT_JSON:
- return electricGuess;
- case Constants.UNIT_LITER_JSON:
- return fuelGuess;
- case Constants.PHEV:
- return hybridGuess;
- default:
- return Constants.INT_UNDEF;
- }
- }
-
- public static String getChargStatus(Vehicle vehicle) {
- FuelIndicator fi = getElectricFuelIndicator(vehicle);
- if (fi.chargingStatusType != null) {
- if (fi.chargingStatusType.equals(Constants.DEFAULT)) {
- return Constants.NOT_CHARGING_STATE;
- } else {
- return fi.chargingStatusType;
- }
- }
- return Constants.UNDEF;
- }
-
- public static String getChargeInfo(Vehicle vehicle) {
- FuelIndicator fi = getElectricFuelIndicator(vehicle);
- if (fi.chargingStatusType != null && fi.infoLabel != null) {
- if (fi.chargingStatusType.equals(Constants.CHARGING_STATE)
- || fi.chargingStatusType.equals(Constants.PLUGGED_STATE)) {
- return fi.infoLabel;
- }
- }
- return Constants.HYPHEN;
- }
-
- private static FuelIndicator getElectricFuelIndicator(Vehicle vehicle) {
- for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
- if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
- && fuelIndicator.chargingStatusType != null) {
- return fuelIndicator;
- }
- }
- return new FuelIndicator();
- }
}
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties
index 2cbf443a25180..efe4407fc345c 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties
@@ -4,102 +4,104 @@ addon.mybmw.name = MyBMW
addon.mybmw.description = Provides access to your Vehicle Data like MyBMW App
# thing types
-
-thing-type.mybmw.account.label = MyBMW Account
-thing-type.mybmw.account.description = Your BMW account data
-thing-type.mybmw.bev.label = Electric Vehicle
-thing-type.mybmw.bev.description = Battery Electric Vehicle (BEV)
-thing-type.mybmw.bev_rex.label = Electric Vehicle with REX
-thing-type.mybmw.bev_rex.description = Battery Electric Vehicle with Range Extender (BEV_REX)
-thing-type.mybmw.conv.label = Conventional Vehicle
-thing-type.mybmw.conv.description = Conventional Fuel Vehicle (CONV)
-thing-type.mybmw.phev.label = Plug-In-Hybrid Electric Vehicle
-thing-type.mybmw.phev.description = Conventional Fuel Vehicle with supporting Electric Engine (PHEV)
-
-# thing types config
-
-thing-type.config.mybmw.bridge.language.label = Language Settings
thing-type.config.mybmw.bridge.language.description = Channel data can be returned in the desired language like en, de, fr ...
-thing-type.config.mybmw.bridge.password.label = Password
+thing-type.config.mybmw.bridge.language.label = Language Settings
thing-type.config.mybmw.bridge.password.description = MyBMW Password
-thing-type.config.mybmw.bridge.region.label = Region
+thing-type.config.mybmw.bridge.password.label = Password
thing-type.config.mybmw.bridge.region.description = Select Region in order to connect to the appropriate BMW Server
-thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = North America
+thing-type.config.mybmw.bridge.region.label = Region
thing-type.config.mybmw.bridge.region.option.CHINA = China
+thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = North America
thing-type.config.mybmw.bridge.region.option.ROW = Rest of the World
-thing-type.config.mybmw.bridge.userName.label = Username
thing-type.config.mybmw.bridge.userName.description = MyBMW Username
-thing-type.config.mybmw.vehicle.refreshInterval.label = Refresh Interval
+thing-type.config.mybmw.bridge.userName.label = Username
+
thing-type.config.mybmw.vehicle.refreshInterval.description = Data refresh rate for your vehicle data
-thing-type.config.mybmw.vehicle.vehicleBrand.label = Brand of the Vehicle
+thing-type.config.mybmw.vehicle.refreshInterval.label = Refresh Interval
thing-type.config.mybmw.vehicle.vehicleBrand.description = Vehicle brand like BMW or Mini
-thing-type.config.mybmw.vehicle.vin.label = Vehicle Identification Number (VIN)
+thing-type.config.mybmw.vehicle.vehicleBrand.label = Brand of the Vehicle
thing-type.config.mybmw.vehicle.vin.description = Unique VIN given by BMW
+thing-type.config.mybmw.vehicle.vin.label = Vehicle Identification Number (VIN)
+thing-type.mybmw.account.description = Your BMW account data
+thing-type.mybmw.account.label = MyBMW Account
+thing-type.mybmw.bev_rex.description = Battery Electric Vehicle with Range Extender (BEV_REX)
+thing-type.mybmw.bev_rex.label = Electric Vehicle with REX
+thing-type.mybmw.bev.description = Battery Electric Vehicle (BEV)
+thing-type.mybmw.bev.label = Electric Vehicle
+thing-type.mybmw.conv.description = Conventional Fuel Vehicle (CONV)
+thing-type.mybmw.conv.label = Conventional Vehicle
+thing-type.mybmw.phev.description = Conventional Fuel Vehicle with supporting Electric Engine (PHEV)
+thing-type.mybmw.phev.label = Plug-In-Hybrid Electric Vehicle
# channel group types
-
-channel-group-type.mybmw.charge-statistic.label = Charging Statistics
channel-group-type.mybmw.charge-statistic.description = Charging statistics of current month
-channel-group-type.mybmw.check-control-values.label = Check Control Messages
+channel-group-type.mybmw.charge-statistic.label = Charging Statistics
channel-group-type.mybmw.check-control-values.description = Shows current active CheckControl messages
-channel-group-type.mybmw.conv-range-values.label = Range and Fuel Data
+channel-group-type.mybmw.check-control-values.label = Check Control Messages
channel-group-type.mybmw.conv-range-values.description = Provides Mileage, remaining range and fuel level values
-channel-group-type.mybmw.door-values.label = Detailed Door Status
+channel-group-type.mybmw.conv-range-values.label = Range and Fuel Data
channel-group-type.mybmw.door-values.description = Detailed Status of all Doors and Windows
-channel-group-type.mybmw.ev-range-values.label = Range and Charge Data
+channel-group-type.mybmw.door-values.label = Detailed Door Status
channel-group-type.mybmw.ev-range-values.description = Provides Mileage, remaining range and charge level values
-channel-group-type.mybmw.ev-vehicle-status.label = Vehicle Status
+channel-group-type.mybmw.ev-range-values.label = Range and Charge Data
channel-group-type.mybmw.ev-vehicle-status.description = Overall vehicle status
-channel-group-type.mybmw.hybrid-range-values.label = Range, Charge / Fuel Data
+channel-group-type.mybmw.ev-vehicle-status.label = Vehicle Status
channel-group-type.mybmw.hybrid-range-values.description = Provides mileage, remaining fuel and range data for hybrid vehicles
-channel-group-type.mybmw.image-values.label = Vehicle Image
+channel-group-type.mybmw.hybrid-range-values.label = Range, Charge / Fuel Data
channel-group-type.mybmw.image-values.description = Provides an image of your vehicle
-channel-group-type.mybmw.location-values.label = Vehicle Location
+channel-group-type.mybmw.image-values.label = Vehicle Image
+
channel-group-type.mybmw.location-values.description = Coordinates and heading of the vehicle
-channel-group-type.mybmw.profile-values.label = Electric Charging Profile
+channel-group-type.mybmw.location-values.label = Vehicle Location
channel-group-type.mybmw.profile-values.description = Scheduled charging profiles
-channel-group-type.mybmw.remote-services.label = Remote Services
+channel-group-type.mybmw.profile-values.label = Electric Charging Profile
channel-group-type.mybmw.remote-services.description = Remote control of the vehicle
-channel-group-type.mybmw.service-values.label = Vehicle Services
+channel-group-type.mybmw.remote-services.label = Remote Services
channel-group-type.mybmw.service-values.description = Future vehicle service schedules
-channel-group-type.mybmw.session-values.label = Electric Charging Sessions
+channel-group-type.mybmw.service-values.label = Vehicle Services
channel-group-type.mybmw.session-values.description = Past charging sessions
-channel-group-type.mybmw.tire-pressures.label = Tire Pressure
+channel-group-type.mybmw.session-values.label = Electric Charging Sessions
channel-group-type.mybmw.tire-pressures.description = Current and wanted pressure for all tires
-channel-group-type.mybmw.vehicle-status.label = Vehicle Status
+channel-group-type.mybmw.tire-pressures.label = Tire Pressure
channel-group-type.mybmw.vehicle-status.description = Overall vehicle status
+channel-group-type.mybmw.vehicle-status.label = Vehicle Status
# channel types
-
channel-type.mybmw.address-channel.label = Address
channel-type.mybmw.charging-info-channel.label = Charging Information
+channel-type.mybmw.charging-remaining-channel.label = Remaining Charging Time
channel-type.mybmw.charging-status-channel.label = Charging Status
channel-type.mybmw.check-control-channel.label = Check Control
channel-type.mybmw.checkcontrol-details-channel.label = CheckControl Details
channel-type.mybmw.checkcontrol-name-channel.label = CheckControl Description
channel-type.mybmw.checkcontrol-severity-channel.label = Severity Level
+
channel-type.mybmw.doors-channel.label = Overall Door Status
channel-type.mybmw.driver-front-channel.label = Driver Door
channel-type.mybmw.driver-rear-channel.label = Driver Door Rear
+channel-type.mybmw.estimated-fuel-l-100km-channel.label = Estimated consumption l/100km
+channel-type.mybmw.estimated-fuel-mpg-channel.label = Estimated consumption mpg
channel-type.mybmw.front-left-current-channel.label = Tire Pressure Front Left
channel-type.mybmw.front-left-target-channel.label = Tire Pressure Front Left Target
channel-type.mybmw.front-right-current-channel.label = Tire Pressure Front Right
channel-type.mybmw.front-right-target-channel.label = Tire Pressure Front Right Target
channel-type.mybmw.gps-channel.label = GPS Coordinates
channel-type.mybmw.heading-channel.label = Heading Angle
-channel-type.mybmw.home-distance-channel.label = Distance from Home
channel-type.mybmw.home-distance-channel.description = Computed distance between vehicle and home location
+channel-type.mybmw.home-distance-channel.label = Distance From Home
channel-type.mybmw.hood-channel.label = Hood
-channel-type.mybmw.image-view-channel.label = Image Viewport
+
+channel-type.mybmw.image-view-channel.command.option.FrontLeft = Left Side View
+channel-type.mybmw.image-view-channel.command.option.FrontRight = Right Side View
+channel-type.mybmw.image-view-channel.command.option.FrontView = Front View
+channel-type.mybmw.image-view-channel.command.option.RearView = Rear View
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Side View
-channel-type.mybmw.image-view-channel.command.option.VehicleInfo = Front View
-channel-type.mybmw.image-view-channel.command.option.ChargingHistory = Side View
-channel-type.mybmw.image-view-channel.command.option.Default = Default View
-channel-type.mybmw.last-update-channel.label = Last Status Timestamp
-channel-type.mybmw.last-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
+channel-type.mybmw.image-view-channel.label = Image Viewport
+channel-type.mybmw.last-fetched-channel.label = Last Openhab Update Timestamp
+channel-type.mybmw.last-update-channel.label = Last Car Status Timestamp
channel-type.mybmw.lock-channel.label = Doors Locked
channel-type.mybmw.mileage-channel.label = Total Distance Driven
-channel-type.mybmw.motion-channel.label = Motion Status
+
channel-type.mybmw.next-service-date-channel.label = Next Service Date
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.next-service-mileage-channel.label = Mileage Till Next Service
@@ -107,22 +109,24 @@ channel-type.mybmw.passenger-front-channel.label = Passenger Door
channel-type.mybmw.passenger-rear-channel.label = Passenger Door Rear
channel-type.mybmw.plug-connection-channel.label = Plug Connection Status
channel-type.mybmw.png-channel.label = Rendered Vehicle Image
+
channel-type.mybmw.profile-climate-channel.label = A/C at Departure Time
-channel-type.mybmw.profile-control-channel.label = Charging Plan
-channel-type.mybmw.profile-control-channel.description = Charging plan selection
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Weekly Schedule
-channel-type.mybmw.profile-limit-channel.label = Charging Energy Limited
+channel-type.mybmw.profile-control-channel.description = Charging plan selection
+channel-type.mybmw.profile-control-channel.label = Charging Plan
channel-type.mybmw.profile-limit-channel.description = Limited charging activated
-channel-type.mybmw.profile-mode-channel.label = Charge Mode
-channel-type.mybmw.profile-mode-channel.description = Mode for selecting immediate or delyed charging
-channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Immediate Charging
+channel-type.mybmw.profile-limit-channel.label = Charging Energy Limited
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Use Charging Preference
-channel-type.mybmw.profile-prefs-channel.label = Charge Preferences
-channel-type.mybmw.profile-prefs-channel.description = Preferences for delayed charging
-channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = No Selection
+channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Immediate Charging
+channel-type.mybmw.profile-mode-channel.description = Mode for selecting immediate or delyed charging
+channel-type.mybmw.profile-mode-channel.label = Charge Mode
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Charging Window
-channel-type.mybmw.profile-target-channel.label = SOC Target
+channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = No Selection
+channel-type.mybmw.profile-prefs-channel.description = Preferences for delayed charging
+channel-type.mybmw.profile-prefs-channel.label = Charge Preferences
channel-type.mybmw.profile-target-channel.description = SOC charging target
+channel-type.mybmw.profile-target-channel.label = SOC Target
+
channel-type.mybmw.range-electric-channel.label = Electric Range
channel-type.mybmw.range-fuel-channel.label = Fuel Range
channel-type.mybmw.range-hybrid-channel.label = Hybrid Range
@@ -130,17 +134,25 @@ channel-type.mybmw.range-radius-electric-channel.label = Electric Range Radius
channel-type.mybmw.range-radius-fuel-channel.label = Fuel Range Radius
channel-type.mybmw.range-radius-hybrid-channel.label = Hybrid Range Radius
channel-type.mybmw.raw-channel.label = Raw Data
+
channel-type.mybmw.rear-left-current-channel.label = Tire Pressure Rear Left
channel-type.mybmw.rear-left-target-channel.label = Tire Pressure Rear Left Target
channel-type.mybmw.rear-right-current-channel.label = Tire Pressure Rear Right
channel-type.mybmw.rear-right-target-channel.label = Tire Pressure Rear Right Target
channel-type.mybmw.remaining-fuel-channel.label = Remaining Fuel
+channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Start Climate now
+channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Stop Climate now
+channel-type.mybmw.remote-command-channel.command.option.door-lock = Lock vehicle
+channel-type.mybmw.remote-command-channel.command.option.door-unlock = Unlock vehicle
+channel-type.mybmw.remote-command-channel.command.option.horn-blow = Blow horn
+channel-type.mybmw.remote-command-channel.command.option.light-flash = Flash lights
+channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Find vehicle
channel-type.mybmw.remote-command-channel.label = Remote Command
channel-type.mybmw.remote-state-channel.label = Service Execution State
channel-type.mybmw.service-date-channel.label = Service Date
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.service-details-channel.label = Service Details
-channel-type.mybmw.service-mileage-channel.label = Mileage till Service
+channel-type.mybmw.service-mileage-channel.label = Mileage until Service
channel-type.mybmw.service-name-channel.label = Service Name
channel-type.mybmw.session-energy-channel.label = Charged Energy in Session
channel-type.mybmw.session-issue-channel.label = Issues during Session
@@ -148,97 +160,99 @@ channel-type.mybmw.session-status-channel.label = Session Status
channel-type.mybmw.session-subtitle-channel.label = Session Details
channel-type.mybmw.session-title-channel.label = Session Title
channel-type.mybmw.soc-channel.label = Battery Charge Level
-channel-type.mybmw.statistic-energy-channel.label = Energy Charged
+
channel-type.mybmw.statistic-energy-channel.description = Total energy charged in current month
-channel-type.mybmw.statistic-sessions-channel.label = Charge Sessions
+channel-type.mybmw.statistic-energy-channel.label = Energy Charged
channel-type.mybmw.statistic-sessions-channel.description = Number of charging sessions this month
+channel-type.mybmw.statistic-sessions-channel.label = Charge Sessions
channel-type.mybmw.statistic-title-channel.label = Charge Statistic Month
channel-type.mybmw.sunroof-channel.label = Sunroof
-channel-type.mybmw.timer1-day-fri-channel.label = T1 Friday
+
channel-type.mybmw.timer1-day-fri-channel.description = Friday scheduled for timer 1
-channel-type.mybmw.timer1-day-mon-channel.label = T1 Monday
+channel-type.mybmw.timer1-day-fri-channel.label = T1 Friday
channel-type.mybmw.timer1-day-mon-channel.description = Monday scheduled for timer 1
-channel-type.mybmw.timer1-day-sat-channel.label = T1 Saturday
+channel-type.mybmw.timer1-day-mon-channel.label = T1 Monday
channel-type.mybmw.timer1-day-sat-channel.description = Saturday scheduled for timer 1
-channel-type.mybmw.timer1-day-sun-channel.label = T1 Sunday
+channel-type.mybmw.timer1-day-sat-channel.label = T1 Saturday
channel-type.mybmw.timer1-day-sun-channel.description = Sunday scheduled for timer 1
-channel-type.mybmw.timer1-day-thu-channel.label = T1 Thursday
+channel-type.mybmw.timer1-day-sun-channel.label = T1 Sunday
channel-type.mybmw.timer1-day-thu-channel.description = Thursday scheduled for timer 1
-channel-type.mybmw.timer1-day-tue-channel.label = T1 Tuesday
+channel-type.mybmw.timer1-day-thu-channel.label = T1 Thursday
channel-type.mybmw.timer1-day-tue-channel.description = Tuesday scheduled for timer 1
-channel-type.mybmw.timer1-day-wed-channel.label = T1 Wednesday
+channel-type.mybmw.timer1-day-tue-channel.label = T1 Tuesday
channel-type.mybmw.timer1-day-wed-channel.description = Wednesday scheduled for timer 1
-channel-type.mybmw.timer1-departure-channel.label = T1 Departure Time
+channel-type.mybmw.timer1-day-wed-channel.label = T1 Wednesday
channel-type.mybmw.timer1-departure-channel.description = Departure time for regular schedule timer 1
+channel-type.mybmw.timer1-departure-channel.label = T1 Departure Time
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH:%1$tM
-channel-type.mybmw.timer1-enabled-channel.label = T1 Enabled
channel-type.mybmw.timer1-enabled-channel.description = Timer 1 enabled
-channel-type.mybmw.timer2-day-fri-channel.label = T2 Friday
+channel-type.mybmw.timer1-enabled-channel.label = T1 Enabled
channel-type.mybmw.timer2-day-fri-channel.description = Friday scheduled for timer 2
-channel-type.mybmw.timer2-day-mon-channel.label = T2 Monday
+channel-type.mybmw.timer2-day-fri-channel.label = T2 Friday
channel-type.mybmw.timer2-day-mon-channel.description = Monday scheduled for timer 2
-channel-type.mybmw.timer2-day-sat-channel.label = T2 Saturday
+channel-type.mybmw.timer2-day-mon-channel.label = T2 Monday
channel-type.mybmw.timer2-day-sat-channel.description = Saturday scheduled for timer 2
-channel-type.mybmw.timer2-day-sun-channel.label = T2 Sunday
+channel-type.mybmw.timer2-day-sat-channel.label = T2 Saturday
channel-type.mybmw.timer2-day-sun-channel.description = Sunday scheduled for timer 2
-channel-type.mybmw.timer2-day-thu-channel.label = T2 Thursday
+channel-type.mybmw.timer2-day-sun-channel.label = T2 Sunday
channel-type.mybmw.timer2-day-thu-channel.description = Thursday scheduled for timer 2
-channel-type.mybmw.timer2-day-tue-channel.label = T2 Tuesday
+channel-type.mybmw.timer2-day-thu-channel.label = T2 Thursday
channel-type.mybmw.timer2-day-tue-channel.description = Tuesday scheduled for timer 2
-channel-type.mybmw.timer2-day-wed-channel.label = T2 Wednesday
+channel-type.mybmw.timer2-day-tue-channel.label = T2 Tuesday
channel-type.mybmw.timer2-day-wed-channel.description = Wednesday scheduled for timer 2
-channel-type.mybmw.timer2-departure-channel.label = T2 Departure Time
+channel-type.mybmw.timer2-day-wed-channel.label = T2 Wednesday
channel-type.mybmw.timer2-departure-channel.description = Departure time for regular schedule timer 2
+channel-type.mybmw.timer2-departure-channel.label = T2 Departure Time
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH:%1$tM
-channel-type.mybmw.timer2-enabled-channel.label = T2 Enabled
channel-type.mybmw.timer2-enabled-channel.description = Timer 2 enabled
-channel-type.mybmw.timer3-day-fri-channel.label = T3 Friday
+channel-type.mybmw.timer2-enabled-channel.label = T2 Enabled
channel-type.mybmw.timer3-day-fri-channel.description = Friday scheduled for timer 3
-channel-type.mybmw.timer3-day-mon-channel.label = T3 Monday
+channel-type.mybmw.timer3-day-fri-channel.label = T3 Friday
channel-type.mybmw.timer3-day-mon-channel.description = Monday scheduled for timer 3
-channel-type.mybmw.timer3-day-sat-channel.label = T3 Saturday
+channel-type.mybmw.timer3-day-mon-channel.label = T3 Monday
channel-type.mybmw.timer3-day-sat-channel.description = Saturday scheduled for timer 3
-channel-type.mybmw.timer3-day-sun-channel.label = T3 Sunday
+channel-type.mybmw.timer3-day-sat-channel.label = T3 Saturday
channel-type.mybmw.timer3-day-sun-channel.description = Sunday scheduled for timer 3
-channel-type.mybmw.timer3-day-thu-channel.label = T3 Thursday
+channel-type.mybmw.timer3-day-sun-channel.label = T3 Sunday
channel-type.mybmw.timer3-day-thu-channel.description = Thursday scheduled for timer 3
-channel-type.mybmw.timer3-day-tue-channel.label = T3 Tuesday
+channel-type.mybmw.timer3-day-thu-channel.label = T3 Thursday
channel-type.mybmw.timer3-day-tue-channel.description = Tuesday scheduled for timer 3
-channel-type.mybmw.timer3-day-wed-channel.label = T3 Wednesday
+channel-type.mybmw.timer3-day-tue-channel.label = T3 Tuesday
channel-type.mybmw.timer3-day-wed-channel.description = Wednesday scheduled for timer 3
-channel-type.mybmw.timer3-departure-channel.label = T3 Departure Time
+channel-type.mybmw.timer3-day-wed-channel.label = T3 Wednesday
channel-type.mybmw.timer3-departure-channel.description = Departure time for regular schedule timer 3
+channel-type.mybmw.timer3-departure-channel.label = T3 Departure Time
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH:%1$tM
-channel-type.mybmw.timer3-enabled-channel.label = T3 Enabled
channel-type.mybmw.timer3-enabled-channel.description = Timer 3 enabled
-channel-type.mybmw.timer4-day-fri-channel.label = T4 Friday
+channel-type.mybmw.timer3-enabled-channel.label = T3 Enabled
channel-type.mybmw.timer4-day-fri-channel.description = Friday scheduled for timer 4
-channel-type.mybmw.timer4-day-mon-channel.label = T4 Monday
+channel-type.mybmw.timer4-day-fri-channel.label = T4 Friday
channel-type.mybmw.timer4-day-mon-channel.description = Monday scheduled for timer 4
-channel-type.mybmw.timer4-day-sat-channel.label = T4 Saturday
+channel-type.mybmw.timer4-day-mon-channel.label = T4 Monday
channel-type.mybmw.timer4-day-sat-channel.description = Saturday scheduled for timer 4
-channel-type.mybmw.timer4-day-sun-channel.label = T4 Sunday
+channel-type.mybmw.timer4-day-sat-channel.label = T4 Saturday
channel-type.mybmw.timer4-day-sun-channel.description = Sunday scheduled for timer 4
-channel-type.mybmw.timer4-day-thu-channel.label = T4 Thursday
+channel-type.mybmw.timer4-day-sun-channel.label = T4 Sunday
channel-type.mybmw.timer4-day-thu-channel.description = Thursday scheduled for timer 4
-channel-type.mybmw.timer4-day-tue-channel.label = T4 Tuesday
+channel-type.mybmw.timer4-day-thu-channel.label = T4 Thursday
channel-type.mybmw.timer4-day-tue-channel.description = Tuesday scheduled for timer 4
-channel-type.mybmw.timer4-day-wed-channel.label = T4 Wednesday
+channel-type.mybmw.timer4-day-tue-channel.label = T4 Tuesday
channel-type.mybmw.timer4-day-wed-channel.description = Wednesday scheduled for timer 4
-channel-type.mybmw.timer4-departure-channel.label = T4 Departure Time
+channel-type.mybmw.timer4-day-wed-channel.label = T4 Wednesday
channel-type.mybmw.timer4-departure-channel.description = Departure time for regular schedule timer 4
+channel-type.mybmw.timer4-departure-channel.label = T4 Departure Time
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH:%1$tM
-channel-type.mybmw.timer4-enabled-channel.label = T4 Enabled
channel-type.mybmw.timer4-enabled-channel.description = Timer 4 enabled
+channel-type.mybmw.timer4-enabled-channel.label = T4 Enabled
channel-type.mybmw.trunk-channel.label = Trunk
channel-type.mybmw.window-driver-front-channel.label = Driver Window
channel-type.mybmw.window-driver-rear-channel.label = Driver Rear Window
-channel-type.mybmw.window-end-channel.label = Window End Time
channel-type.mybmw.window-end-channel.description = End time of charging window
+channel-type.mybmw.window-end-channel.label = Window End Time
channel-type.mybmw.window-end-channel.state.pattern = %1$tH:%1$tM
channel-type.mybmw.window-passenger-front-channel.label = Passenger Window
channel-type.mybmw.window-passenger-rear-channel.label = Passenger Rear Window
-channel-type.mybmw.window-start-channel.label = Window Start Time
channel-type.mybmw.window-start-channel.description = Start time of charging window
+channel-type.mybmw.window-start-channel.label = Window Start Time
channel-type.mybmw.window-start-channel.state.pattern = %1$tH:%1$tM
channel-type.mybmw.windows-channel.label = Overall Window Status
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/conv-range-channel-group.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/conv-range-channel-group.xml
index c3a79423fa0eb..72992e7846b54 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/conv-range-channel-group.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/conv-range-channel-group.xml
@@ -10,6 +10,8 @@
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/ev-vehicle-status-group.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/ev-vehicle-status-group.xml
index 39d9ca4e8fbc1..2c525f57d86a8 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/ev-vehicle-status-group.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/ev-vehicle-status-group.xml
@@ -15,9 +15,9 @@
-
-
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/hybrid-range-channel-group.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/hybrid-range-channel-group.xml
index b65089d6925ad..2490b56255abf 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/hybrid-range-channel-group.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/hybrid-range-channel-group.xml
@@ -16,6 +16,8 @@
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/image-channel-types.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/image-channel-types.xml
index b758b425ac876..6caf17952e62b 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/image-channel-types.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/image-channel-types.xml
@@ -14,9 +14,10 @@
-
-
-
+
+
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/profile-channel-types.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/profile-channel-types.xml
index c677f62056eda..3341a943d3526 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/profile-channel-types.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/profile-channel-types.xml
@@ -40,9 +40,10 @@
- Number
+ Number:Dimensionless
SOC charging target
+
Switch
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/range-channel-types.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/range-channel-types.xml
index 7bcfadde4ac99..946700e4f2c33 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/range-channel-types.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/range-channel-types.xml
@@ -26,26 +26,36 @@
Number:Dimensionless
-
+
Number:Volume
+
+ Number
+
+
+
+
+ Number
+
+
+
Number:Length
-
+
Number:Length
-
+
Number:Length
-
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev.xml
index 593b9f516cc73..79231853b0291 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev.xml
@@ -27,6 +27,10 @@
+
+ 1
+
+
vin
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev_rex.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev_rex.xml
index 5e45f36d9712a..1053f4772b7f4 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev_rex.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-bev_rex.xml
@@ -27,6 +27,10 @@
+
+ 1
+
+
vin
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-conv.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-conv.xml
index b95df69f01bb9..52117d02a7a6f 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-conv.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-conv.xml
@@ -24,6 +24,10 @@
+
+ 1
+
+
vin
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-phev.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-phev.xml
index 35be350bfa522..9777d60d467a9 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-phev.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/thing-phev.xml
@@ -27,6 +27,10 @@
+
+ 1
+
+
vin
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/tires-channel-groups.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/tires-channel-groups.xml
index cb2a01615534b..3e651c02afa6c 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/tires-channel-groups.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/tires-channel-groups.xml
@@ -5,7 +5,7 @@
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
- Current and wanted pressure for all tires
+ Current and target pressure for all tires
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-channel-types.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-channel-types.xml
index 72087c0ad7300..cc0bf7f5d97a9 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-channel-types.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-channel-types.xml
@@ -37,24 +37,24 @@
-
- String
-
-
+
+ Number:Time
+
+
String
-
- Switch
-
-
-
DateTime
-
+
+
+
+
+ DateTime
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-group.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-group.xml
index 0c3982aa68961..318b0bad0602e 100644
--- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-group.xml
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/thing/vehicle-status-group.xml
@@ -13,8 +13,8 @@
-
+
diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/update/thing-update.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/update/thing-update.xml
new file mode 100644
index 0000000000000..d7b93817dda94
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/update/thing-update.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+ mybmw:charging-remaining-channel
+
+
+
+ mybmw:last-fetched-channel
+
+
+
+ mybmw:estimated-fuel-l-100km-channel
+
+
+
+ mybmw:estimated-fuel-mpg-channel
+
+
+
+
+
+
+
+
+
+
+
+
+ mybmw:charging-remaining-channel
+
+
+
+ mybmw:last-fetched-channel
+
+
+
+
+
+
+
+
+
+
+ mybmw:last-fetched-channel
+
+
+
+ mybmw:estimated-fuel-l-100km-channel
+
+
+
+ mybmw:estimated-fuel-mpg-channel
+
+
+
+
+
+
+
+
+
+
+
+
+ mybmw:charging-remaining-channel
+
+
+
+ mybmw:last-fetched-channel
+
+
+
+ mybmw:estimated-fuel-l-100km-channel
+
+
+
+ mybmw:estimated-fuel-mpg-channel
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/DiscoveryTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/DiscoveryTest.java
deleted file mode 100644
index edbc86ae0805d..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/DiscoveryTest.java
+++ /dev/null
@@ -1,116 +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.discovery;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
-import java.util.List;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
-import org.openhab.binding.mybmw.internal.util.FileReader;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-import org.openhab.core.config.discovery.DiscoveryListener;
-import org.openhab.core.config.discovery.DiscoveryResult;
-import org.openhab.core.config.discovery.DiscoveryService;
-import org.openhab.core.io.net.http.HttpClientFactory;
-import org.openhab.core.thing.Bridge;
-import org.openhab.core.thing.ThingUID;
-
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-
-/**
- * The {@link DiscoveryTest} Test Discovery Results
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public class DiscoveryTest {
-
- @Test
- public void testDiscovery() {
- String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
- Bridge b = mock(Bridge.class);
- MyBMWBridgeHandler bh = new MyBMWBridgeHandler(b, mock(HttpClientFactory.class), "en");
- when(b.getUID()).thenReturn(new ThingUID("mybmw", "account", "abc"));
- VehicleDiscovery discovery = new VehicleDiscovery();
- discovery.setThingHandler(bh);
- DiscoveryListener listener = mock(DiscoveryListener.class);
- discovery.addDiscoveryListener(listener);
- List vl = Converter.getVehicleList(content);
- assertEquals(1, vl.size(), "Vehicles found");
- ArgumentCaptor discoveries = ArgumentCaptor.forClass(DiscoveryResult.class);
- ArgumentCaptor services = ArgumentCaptor.forClass(DiscoveryService.class);
- bh.onResponse(content);
- verify(listener, times(1)).thingDiscovered(services.capture(), discoveries.capture());
- List results = discoveries.getAllValues();
- assertEquals(1, results.size(), "Found Vehicles");
- DiscoveryResult result = results.get(0);
- assertEquals("mybmw:bev_rex:abc:anonymous", result.getThingUID().getAsString(), "Thing UID");
- }
-
- @Test
- public void testProperties() {
- String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
- Vehicle vehicle = Converter.getVehicle(Constants.ANONYMOUS, content);
- String servicesSuppoertedReference = "RemoteHistory;ChargingHistory;ScanAndCharge;DCSContractManagement;BmwCharging;ChargeNowForBusiness;ChargingPlan";
- String servicesUnsuppoertedReference = "MiniCharging;EvGoCharging;CustomerEsim;CarSharing;EasyCharge";
- String servicesEnabledReference = "FindCharging;";
- String servicesDisabledReference = "DataPrivacy;ChargingSettings;ChargingHospitality;ChargingPowerLimit;ChargingTargetSoc;ChargingLoudness";
- assertEquals(servicesSuppoertedReference,
- VehicleDiscovery.getServices(vehicle, VehicleDiscovery.SUPPORTED_SUFFIX, true), "Services supported");
- assertEquals(servicesUnsuppoertedReference,
- VehicleDiscovery.getServices(vehicle, VehicleDiscovery.SUPPORTED_SUFFIX, false),
- "Services unsupported");
-
- String servicesEnabled = VehicleDiscovery.getServices(vehicle, VehicleDiscovery.ENABLED_SUFFIX, true)
- + Constants.SEMICOLON + VehicleDiscovery.getServices(vehicle, VehicleDiscovery.ENABLE_SUFFIX, true);
- assertEquals(servicesEnabledReference, servicesEnabled, "Services enabled");
- String servicesDisabled = VehicleDiscovery.getServices(vehicle, VehicleDiscovery.ENABLED_SUFFIX, false)
- + Constants.SEMICOLON + VehicleDiscovery.getServices(vehicle, VehicleDiscovery.ENABLE_SUFFIX, false);
- assertEquals(servicesDisabledReference, servicesDisabled, "Services disabled");
- }
-
- @Test
- public void testAnonymousFingerPrint() {
- String content = FileReader.readFileInString("src/test/resources/responses/fingerprint-raw.json");
- String anonymous = Converter.anonymousFingerprint(content);
- assertFalse(anonymous.contains("ABC45678"), "VIN deleted");
-
- anonymous = Converter.anonymousFingerprint(Constants.EMPTY);
- assertEquals(Constants.EMPTY, anonymous, "Equal Fingerprint if Empty");
-
- anonymous = Converter.anonymousFingerprint(Constants.EMPTY_JSON);
- assertEquals(Constants.EMPTY_JSON, anonymous, "Equal Fingerprint if Empty JSon");
- }
-
- @Test
- public void testRawVehicleData() {
- String content = FileReader.readFileInString("src/test/resources/responses/TwoVehicles/two-vehicles.json");
- String anonymousVehicle = Converter.getRawVehicleContent("anonymous", content);
- String contentAnon = FileReader.readFileInString("src/test/resources/responses/TwoVehicles/anonymous-raw.json");
- // remove formatting
- JsonObject jo = JsonParser.parseString(contentAnon).getAsJsonObject();
- assertEquals(jo.toString(), anonymousVehicle, "Anonymous VIN raw data");
- String contentF11 = FileReader.readFileInString("src/test/resources/responses/TwoVehicles/f11-raw.json");
- String f11Vehicle = Converter.getRawVehicleContent("some_vin_F11", content);
- jo = JsonParser.parseString(contentF11).getAsJsonObject();
- assertEquals(jo.toString(), f11Vehicle, "F11 VIN raw data");
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscoveryTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscoveryTest.java
new file mode 100644
index 0000000000000..2bdb1fabf5962
--- /dev/null
+++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscoveryTest.java
@@ -0,0 +1,128 @@
+/**
+ * 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.discovery;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.openhab.binding.mybmw.internal.MyBMWConstants;
+import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
+import org.openhab.binding.mybmw.internal.handler.backend.MyBMWHttpProxy;
+import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
+import org.openhab.binding.mybmw.internal.util.FileReader;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.config.discovery.DiscoveryListener;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
+
+import com.google.gson.Gson;
+
+/**
+ * The {@link VehicleDiscoveryTest} Test Discovery Results
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - updates
+ */
+@NonNullByDefault
+public class VehicleDiscoveryTest {
+
+ @Test
+ public void testDiscovery() {
+ String content = FileReader.fileToString("responses/vehicles.json");
+ List vehicleList = Arrays.asList(new Gson().fromJson(content, Vehicle[].class));
+
+ VehicleDiscovery vehicleDiscovery = new VehicleDiscovery();
+
+ MyBMWBridgeHandler bridgeHandler = mock(MyBMWBridgeHandler.class);
+
+ List things = new ArrayList<>();
+
+ Thing thing1 = mock(Thing.class);
+ when(thing1.getConfiguration()).thenReturn(createConfiguration("VIN1234567"));
+ things.add(thing1);
+ Thing thing2 = mock(Thing.class);
+ when(thing2.getConfiguration()).thenReturn(createConfiguration("VIN1234568"));
+ things.add(thing2);
+
+ Bridge bridge = mock(Bridge.class);
+ when(bridge.getUID()).thenReturn(new ThingUID("mybmw", "account", "abc"));
+
+ when(bridgeHandler.getThing()).thenReturn(bridge);
+
+ MyBMWHttpProxy myBMWProxy = mock(MyBMWHttpProxy.class);
+ try {
+ when(myBMWProxy.requestVehicles()).thenReturn(vehicleList);
+ } catch (NetworkException e) {
+ }
+
+ when(bridgeHandler.getMyBmwProxy()).thenReturn(Optional.of(myBMWProxy));
+
+ vehicleDiscovery.setThingHandler(bridgeHandler);
+ assertNotNull(vehicleDiscovery.getThingHandler());
+
+ DiscoveryListener listener = mock(DiscoveryListener.class);
+ vehicleDiscovery.addDiscoveryListener(listener);
+
+ assertEquals(2, vehicleList.size(), "Vehicles not found");
+ ArgumentCaptor discoveries = ArgumentCaptor.forClass(DiscoveryResult.class);
+ ArgumentCaptor services = ArgumentCaptor.forClass(DiscoveryService.class);
+
+ // call the discovery
+ vehicleDiscovery.startScan();
+
+ Mockito.verify(listener, Mockito.times(2)).thingDiscovered(services.capture(), discoveries.capture());
+ List results = discoveries.getAllValues();
+ assertEquals(2, results.size(), "Vehicles Not Found");
+
+ assertEquals("mybmw:conv:abc:VIN1234567", results.get(0).getThingUID().getAsString(), "Thing UID");
+ assertEquals("mybmw:conv:abc:VIN1234568", results.get(1).getThingUID().getAsString(), "Thing UID");
+
+ // call the discovery again to check if the vehicle is already known -> no newly created vehicles should be
+ // found
+ when(bridge.getThings()).thenReturn(things);
+
+ ArgumentCaptor discoveries2 = ArgumentCaptor.forClass(DiscoveryResult.class);
+ ArgumentCaptor services2 = ArgumentCaptor.forClass(DiscoveryService.class);
+
+ // call the discovery
+ vehicleDiscovery.startScan();
+
+ Mockito.verify(listener, Mockito.times(2)).thingDiscovered(services2.capture(), discoveries2.capture());
+ results = discoveries2.getAllValues();
+
+ vehicleDiscovery.deactivate();
+ assertEquals(2, results.size(), "Vehicles Not Found");
+ }
+
+ private Configuration createConfiguration(String vin) {
+ Configuration configuration = new Configuration();
+ configuration.put(MyBMWConstants.VIN, vin);
+
+ return configuration;
+ }
+}
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeProfileTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeProfileTest.java
deleted file mode 100644
index 817ff63def8a4..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeProfileTest.java
+++ /dev/null
@@ -1,54 +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;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.util.FileReader;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-
-/**
- * The {@link ChargeProfileTest} is testing locale settings
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public class ChargeProfileTest {
-
- @Test
- public void testWeeklyPlanner() {
- String json = FileReader
- .readFileInString("src/test/resources/responses/chargingprofile/weekly-planner-t2-active.json");
- Vehicle v = Converter.getVehicle(Constants.ANONYMOUS, json);
- ChargeProfile cp = v.status.chargingProfile;
- String cpJson = Converter.getGson().toJson(cp);
- ChargeProfileWrapper cpw = new ChargeProfileWrapper(v.status.chargingProfile);
- assertEquals(cpJson, cpw.getJson(), "JSON comparison");
- }
-
- @Test
- public void testTwoWeeksPlanner() {
- String json = FileReader.readFileInString("src/test/resources/responses/chargingprofile/two-weeks-timer.json");
- Vehicle v = Converter.getVehicle(Constants.ANONYMOUS, json);
- ChargeProfile cp = v.status.chargingProfile;
- String cpJson = Converter.getGson().toJson(cp);
- ChargeProfileWrapper cpw = new ChargeProfileWrapper(v.status.chargingProfile);
- assertEquals(cpJson, cpw.getJson(), "JSON comparison");
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeStatisticWrapper.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargingStatisticsWrapper.java
similarity index 67%
rename from bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeStatisticWrapper.java
rename to bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargingStatisticsWrapper.java
index b582e1d3a96dc..977b6d90b126f 100644
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargeStatisticWrapper.java
+++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/ChargingStatisticsWrapper.java
@@ -12,8 +12,14 @@
*/
package org.openhab.binding.mybmw.internal.dto;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_STATISTICS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ENERGY;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SESSIONS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.TITLE;
import java.util.List;
@@ -21,8 +27,8 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
-import org.openhab.binding.mybmw.internal.utils.Converter;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
@@ -31,22 +37,18 @@
import org.openhab.core.types.State;
/**
- * The {@link ChargeStatisticWrapper} tests stored fingerprint responses from BMW API
+ * The {@link ChargingStatisticsWrapper} tests stored fingerprint responses from BMW API
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - small import change
*/
@NonNullByDefault
@SuppressWarnings("null")
-public class ChargeStatisticWrapper {
- private ChargeStatisticsContainer chargeStatisticContainer;
+public class ChargingStatisticsWrapper {
+ private ChargingStatisticsContainer chargeStatisticContainer;
- public ChargeStatisticWrapper(String content) {
- ChargeStatisticsContainer fromJson = Converter.getGson().fromJson(content, ChargeStatisticsContainer.class);
- if (fromJson != null) {
- chargeStatisticContainer = fromJson;
- } else {
- chargeStatisticContainer = new ChargeStatisticsContainer();
- }
+ public ChargingStatisticsWrapper(String content) {
+ chargeStatisticContainer = JsonStringDeserializer.getChargingStatistics(content);
}
/**
@@ -79,7 +81,7 @@ private void checkResult(ChannelUID channelUID, State state) {
st = (StringType) state;
switch (gUid) {
case CHANNEL_GROUP_CHARGE_STATISTICS:
- assertEquals(chargeStatisticContainer.description, st.toString(), "Statistics name");
+ assertEquals(chargeStatisticContainer.getDescription(), st.toString(), "Statistics name");
break;
default:
assertFalse(true, "Channel " + channelUID + " " + state + " not found");
@@ -89,14 +91,15 @@ private void checkResult(ChannelUID channelUID, State state) {
case SESSIONS:
assertTrue(state instanceof DecimalType);
dt = ((DecimalType) state);
- assertEquals(chargeStatisticContainer.statistics.numberOfChargingSessions, dt.intValue(),
+ assertEquals(chargeStatisticContainer.getStatistics().getNumberOfChargingSessions(), dt.intValue(),
"Charge Sessions");
break;
case ENERGY:
assertTrue(state instanceof QuantityType);
qte = ((QuantityType) state);
assertEquals(Units.KILOWATT_HOUR, qte.getUnit(), "kwh");
- assertEquals(chargeStatisticContainer.statistics.totalEnergyCharged, qte.intValue(), "Energy");
+ assertEquals(chargeStatisticContainer.getStatistics().getTotalEnergyCharged(), qte.intValue(),
+ "Energy");
break;
default:
// fail in case of unknown update
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/StatusWrapper.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/StatusWrapper.java
index 9f51a318674b3..afd79fc233af4 100644
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/StatusWrapper.java
+++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/StatusWrapper.java
@@ -12,9 +12,67 @@
*/
package org.openhab.binding.mybmw.internal.dto;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ADDRESS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_PROFILE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHECK_CONTROL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_RANGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_SERVICE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_STATUS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_REMAINING;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_STATUS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHECK_CONTROL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DETAILS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOORS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_L_100KM;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_MPG;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.GPS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HEADING;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOME_DISTANCE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOOD;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_FETCHED;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_UPDATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.LOCK;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.MILEAGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.NAME;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.PLUG_CONNECTION;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_ELECTRIC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_HYBRID;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_ELECTRIC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_HYBRID;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.RAW;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_CURRENT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_TARGET;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMAINING_FUEL;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_DATE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_MILEAGE;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SEVERITY;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SOC;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUNROOF;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.TRUNK;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOWS;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_REAR;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_FRONT;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_REAR;
+import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -25,18 +83,19 @@
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.properties.CBS;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.handler.VehicleTests;
+import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleState;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.openhab.binding.mybmw.internal.handler.VehicleHandlerTest;
+import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
import org.openhab.binding.mybmw.internal.utils.Constants;
import org.openhab.binding.mybmw.internal.utils.Converter;
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.DecimalType;
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;
@@ -47,13 +106,15 @@
* The {@link StatusWrapper} tests stored fingerprint responses from BMW API
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - updates for v2 API
+ * @author Mark Herwege - remaining charging time test
*/
@NonNullByDefault
@SuppressWarnings("null")
public class StatusWrapper {
private static final Unit KILOMETRE = Constants.KILOMETRE_UNIT;
- private Vehicle vehicle;
+ private VehicleState vehicleState;
private boolean isElectric;
private boolean hasFuel;
private boolean isHybrid;
@@ -66,13 +127,12 @@ public StatusWrapper(String type, String statusJson) {
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
isHybrid = hasFuel && isElectric;
- List vl = Converter.getVehicleList(statusJson);
- assertEquals(1, vl.size(), "Vehciles found");
- vehicle = Converter.getConsistentVehcile(vl.get(0));
+ VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(statusJson);
+ vehicleState = vehicleStateContainer.getState();
}
/**
- * Test results auctomatically against json values
+ * Test results automatically against json values
*
* @param channels
* @param states
@@ -108,8 +168,8 @@ private void checkResult(ChannelUID channelUID, State state) {
StringType wanted;
DateTimeType dtt;
PointType pt;
- OnOffType oot;
- Unit wantedUnit;
+ DecimalType dt;
+ Unit wantedUnit = KILOMETRE;
switch (cUid) {
case MILEAGE:
switch (gUid) {
@@ -117,31 +177,20 @@ private void checkResult(ChannelUID channelUID, State state) {
if (!state.equals(UnDefType.UNDEF)) {
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
- if (Constants.KM_JSON.equals(vehicle.status.currentMileage.units)) {
- assertEquals(KILOMETRE, qt.getUnit(), "KM");
- } else {
- assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
- }
- assertEquals(qt.intValue(), vehicle.status.currentMileage.mileage, "Mileage");
+ assertEquals(qt.intValue(), vehicleState.getCurrentMileage(), "Mileage");
} else {
- assertEquals(Constants.INT_UNDEF, vehicle.status.currentMileage.mileage,
- "Mileage undefined");
+ assertEquals(Constants.INT_UNDEF, vehicleState.getCurrentMileage(), "Mileage undefined");
}
break;
case CHANNEL_GROUP_SERVICE:
- State wantedMileage = QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT);
- if (!vehicle.properties.serviceRequired.isEmpty()) {
- if (vehicle.properties.serviceRequired.get(0).distance != null) {
- if (vehicle.properties.serviceRequired.get(0).distance.units
- .equals(Constants.KILOMETERS_JSON)) {
- wantedMileage = QuantityType.valueOf(
- vehicle.properties.serviceRequired.get(0).distance.value,
- Constants.KILOMETRE_UNIT);
- } else {
- wantedMileage = QuantityType.valueOf(
- vehicle.properties.serviceRequired.get(0).distance.value,
- ImperialUnits.MILE);
- }
+ State wantedMileage = UnDefType.UNDEF;
+ if (!vehicleState.getRequiredServices().isEmpty()) {
+ if (vehicleState.getRequiredServices().get(0).getMileage() > 0) {
+ wantedMileage = QuantityType.valueOf(
+ vehicleState.getRequiredServices().get(0).getMileage(),
+ Constants.KILOMETRE_UNIT);
+ } else {
+ wantedMileage = UnDefType.UNDEF;
}
}
assertEquals(wantedMileage, state, "Service Mileage");
@@ -155,51 +204,87 @@ private void checkResult(ChannelUID channelUID, State state) {
assertTrue(isElectric, "Is Electric");
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
assertEquals(wantedUnit, qt.getUnit());
- assertEquals(VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle), qt.intValue(),
- "Range Electric");
+ assertEquals(vehicleState.getElectricChargingState().getRange(), qt.intValue(), "Range Electric");
break;
- case RANGE_FUEL:
- assertTrue(hasFuel, "Has Fuel");
+ case RANGE_HYBRID:
+ assertTrue(isHybrid, "Is hybrid");
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
assertEquals(wantedUnit, qt.getUnit());
- assertEquals(VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle), qt.intValue(),
- "Range Combustion");
+ assertEquals(vehicleState.getRange(), qt.intValue(), "Range combined hybrid");
break;
- case RANGE_HYBRID:
- assertTrue(isHybrid, "Is Hybrid");
+ case RANGE_FUEL:
+ assertTrue(hasFuel, "Has Fuel");
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
- assertEquals(wantedUnit, qt.getUnit());
- assertEquals(VehicleStatusUtils.getRange(Constants.PHEV, vehicle), qt.intValue(), "Range Combined");
+ if (!isHybrid) {
+ assertEquals(vehicleState.getCombustionFuelLevel().getRange(), qt.intValue(), "Range Combustion");
+ } else {
+ assertEquals(
+ vehicleState.getCombustionFuelLevel().getRange()
+ - vehicleState.getElectricChargingState().getRange(),
+ qt.intValue(), "Range Combustion");
+ }
break;
case REMAINING_FUEL:
assertTrue(hasFuel, "Has Fuel");
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
assertEquals(Units.LITRE, qt.getUnit(), "Liter Unit");
- assertEquals(vehicle.properties.fuelLevel.value, qt.intValue(), "Fuel Level");
+ assertEquals(vehicleState.getCombustionFuelLevel().getRemainingFuelLiters(), qt.intValue(),
+ "Fuel Level");
+ break;
+ case ESTIMATED_FUEL_L_100KM:
+ assertTrue(hasFuel, "Has Fuel");
+
+ if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
+ && vehicleState.getCombustionFuelLevel().getRange() > 0) {
+ assertTrue(state instanceof DecimalType);
+ dt = ((DecimalType) state);
+ double estimatedFuelConsumptionL100km = vehicleState.getCombustionFuelLevel()
+ .getRemainingFuelLiters() * 1.0 / vehicleState.getCombustionFuelLevel().getRange() * 100.0;
+ assertEquals(estimatedFuelConsumptionL100km, dt.doubleValue(),
+ "Estimated Fuel Consumption l/100km");
+ } else {
+ assertTrue(state instanceof UnDefType);
+ }
+ break;
+ case ESTIMATED_FUEL_MPG:
+ assertTrue(hasFuel, "Has Fuel");
+
+ if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
+ && vehicleState.getCombustionFuelLevel().getRange() > 0) {
+ assertTrue(state instanceof DecimalType);
+ dt = ((DecimalType) state);
+ double estimatedFuelConsumptionMpg = 235.214583
+ / (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() * 1.0
+ / vehicleState.getCombustionFuelLevel().getRange() * 100.0);
+ assertEquals(estimatedFuelConsumptionMpg, dt.doubleValue(), "Estimated Fuel Consumption mpg");
+ } else {
+ assertTrue(state instanceof UnDefType);
+ }
break;
case SOC:
- assertTrue(isElectric, "Is Ee wantedQt = (QuantityType) VehicleStatusUtils
- .getNextServiceMileage(vehicle.properties.serviceRequired);
+ .getNextServiceMileage(vehicleState.getRequiredServices());
assertEquals(wantedQt.getUnit(), qt.getUnit(), "Next Service Miles");
assertEquals(wantedQt.intValue(), qt.intValue(), "Mileage");
} else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
- assertEquals(vehicle.properties.serviceRequired.get(0).distance.units, qt.getUnit(),
- "First Service Unit");
- assertEquals(vehicle.properties.serviceRequired.get(0).distance.value, qt.intValue(),
+ assertEquals(vehicleState.getRequiredServices().get(0).getMileage(), qt.intValue(),
"First Service Mileage");
}
}
@@ -410,16 +501,17 @@ private void checkResult(ChannelUID channelUID, State state) {
switch (gUid) {
case CHANNEL_GROUP_SERVICE:
wanted = StringType.valueOf(Constants.NO_ENTRIES);
- if (!vehicle.properties.serviceRequired.isEmpty()) {
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.serviceRequired.get(0).type));
+ if (!vehicleState.getRequiredServices().isEmpty()) {
+ wanted = StringType.valueOf(
+ Converter.toTitleCase(vehicleState.getRequiredServices().get(0).getType()));
}
assertEquals(wanted.toString(), st.toString(), "Service Name");
break;
case CHANNEL_GROUP_CHECK_CONTROL:
wanted = StringType.valueOf(Constants.NO_ENTRIES);
- if (!vehicle.status.checkControlMessages.isEmpty()) {
- wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).title);
+ if (!vehicleState.getCheckControlMessages().isEmpty()) {
+ wanted = StringType.valueOf(
+ Converter.toTitleCase(vehicleState.getCheckControlMessages().get(0).getType()));
}
assertEquals(wanted.toString(), st.toString(), "CheckControl Name");
break;
@@ -434,16 +526,15 @@ private void checkResult(ChannelUID channelUID, State state) {
switch (gUid) {
case CHANNEL_GROUP_SERVICE:
wanted = StringType.valueOf(Converter.toTitleCase(Constants.NO_ENTRIES));
- if (!vehicle.properties.serviceRequired.isEmpty()) {
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.serviceRequired.get(0).type));
+ if (!vehicleState.getRequiredServices().isEmpty()) {
+ wanted = StringType.valueOf(vehicleState.getRequiredServices().get(0).getDescription());
}
assertEquals(wanted.toString(), st.toString(), "Service Details");
break;
case CHANNEL_GROUP_CHECK_CONTROL:
wanted = StringType.valueOf(Constants.NO_ENTRIES);
- if (!vehicle.status.checkControlMessages.isEmpty()) {
- wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).longDescription);
+ if (!vehicleState.getCheckControlMessages().isEmpty()) {
+ wanted = StringType.valueOf(vehicleState.getCheckControlMessages().get(0).getDescription());
}
assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
break;
@@ -456,24 +547,24 @@ private void checkResult(ChannelUID channelUID, State state) {
assertTrue(state instanceof StringType);
st = (StringType) state;
wanted = StringType.valueOf(Constants.NO_ENTRIES);
- if (!vehicle.status.checkControlMessages.isEmpty()) {
- wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).state);
+ if (!vehicleState.getCheckControlMessages().isEmpty()) {
+ wanted = StringType.valueOf(
+ Converter.toTitleCase(vehicleState.getCheckControlMessages().get(0).getSeverity()));
}
- assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
+ assertEquals(wanted.toString(), st.toString(), "CheckControl Severity");
break;
case DATE:
if (state.equals(UnDefType.UNDEF)) {
- for (CBS serviceEntry : vehicle.properties.serviceRequired) {
- assertTrue(serviceEntry.dateTime == null, "No Service Date available");
+ for (RequiredService serviceEntry : vehicleState.getRequiredServices()) {
+ assertTrue(serviceEntry.getDateTime() == null, "No Service Date available");
}
} else {
assertTrue(state instanceof DateTimeType);
dtt = (DateTimeType) state;
switch (gUid) {
case CHANNEL_GROUP_SERVICE:
- String dueDateString = vehicle.properties.serviceRequired.get(0).dateTime;
- DateTimeType expectedDTT = DateTimeType
- .valueOf(Converter.zonedToLocalDateTime(dueDateString));
+ String dueDateString = vehicleState.getRequiredServices().get(0).getDateTime();
+ State expectedDTT = Converter.zonedToLocalDateTime(dueDateString, ZoneId.systemDefault());
assertEquals(expectedDTT.toString(), dtt.toString(), "ServiceSate");
break;
default:
@@ -483,98 +574,89 @@ private void checkResult(ChannelUID channelUID, State state) {
}
break;
case FRONT_LEFT_CURRENT:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getFrontLeft().getStatus().getCurrentPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.frontLeft.status.currentPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getFrontLeft().getStatus().getCurrentPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
- assertTrue(state.equals(UnDefType.UNDEF));
+ assertEquals(state, UnDefType.UNDEF);
}
break;
case FRONT_LEFT_TARGET:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getFrontLeft().getStatus().getTargetPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.frontLeft.status.targetPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getFrontLeft().getStatus().getTargetPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case FRONT_RIGHT_CURRENT:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getFrontRight().getStatus().getCurrentPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.frontRight.status.currentPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getFrontRight().getStatus().getCurrentPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case FRONT_RIGHT_TARGET:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getFrontRight().getStatus().getTargetPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.frontRight.status.targetPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getFrontRight().getStatus().getTargetPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case REAR_LEFT_CURRENT:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getRearLeft().getStatus().getCurrentPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.rearLeft.status.currentPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getRearLeft().getStatus().getCurrentPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case REAR_LEFT_TARGET:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getRearLeft().getStatus().getTargetPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.rearLeft.status.targetPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getRearLeft().getStatus().getTargetPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case REAR_RIGHT_CURRENT:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getRearRight().getStatus().getCurrentPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.rearRight.status.currentPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getRearRight().getStatus().getCurrentPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
case REAR_RIGHT_TARGET:
- if (vehicle.properties.tires != null) {
+ if (vehicleState.getTireState().getRearRight().getStatus().getTargetPressure() > 0) {
assertTrue(state instanceof QuantityType);
qt = (QuantityType) state;
- assertEquals(vehicle.properties.tires.rearRight.status.targetPressure / 100, qt.doubleValue(),
- "Fron Left Current");
+ assertEquals(vehicleState.getTireState().getRearRight().getStatus().getTargetPressure() / 100.0,
+ qt.doubleValue(), "Fron Left Current");
} else {
assertTrue(state.equals(UnDefType.UNDEF));
}
break;
- case MOTION:
- assertTrue(state instanceof OnOffType);
- oot = (OnOffType) state;
- if (vehicle.properties.inMotion) {
- assertEquals(oot.toFullString(), OnOffType.ON.toFullString(), "Vehicle Driving");
- } else {
- assertEquals(oot.toFullString(), OnOffType.OFF.toFullString(), "Vehicle Stationary");
- }
- break;
case ADDRESS:
- if (state instanceof StringType str) {
- st = str;
- assertEquals(st.toFullString(), vehicle.properties.vehicleLocation.address.formatted,
+ if (state instanceof StringType) {
+ st = (StringType) state;
+ assertEquals(st.toFullString(), vehicleState.getLocation().getAddress().getFormatted(),
"Location Address");
} // else no check needed
break;
@@ -582,9 +664,9 @@ private void checkResult(ChannelUID channelUID, State state) {
if (state instanceof QuantityType quantity) {
qt = quantity;
PointType vehicleLocation = PointType
- .valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
- + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude));
- int distance = vehicleLocation.distanceFrom(VehicleTests.HOME_LOCATION).intValue();
+ .valueOf(Double.toString(vehicleState.getLocation().getCoordinates().getLatitude()) + ","
+ + Double.toString(vehicleState.getLocation().getCoordinates().getLongitude()));
+ int distance = vehicleLocation.distanceFrom(VehicleHandlerTest.HOME_LOCATION).intValue();
assertEquals(qt.intValue(), distance, "Distance from Home");
assertEquals(qt.getUnit(), SIUnits.METRE, "Distance from Home Unit");
} // else no check needed
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehiclePropertiesTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehiclePropertiesTest.java
index ece7d6778dcfb..c909f82361657 100644
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehiclePropertiesTest.java
+++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehiclePropertiesTest.java
@@ -18,10 +18,6 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.util.FileReader;
-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.core.thing.ThingTypeUID;
import org.openhab.core.types.CommandOption;
@@ -30,24 +26,11 @@
* The {@link VehiclePropertiesTest} tests stored fingerprint responses from BMW API
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - move test to myBMWProxyTest
*/
@NonNullByDefault
public class VehiclePropertiesTest {
- @Test
- public void testUserInfo() {
- String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
- List vl = Converter.getVehicleList(content);
-
- assertEquals(1, vl.size(), "Number of Vehicles");
- Vehicle v = vl.get(0);
- assertEquals(Constants.ANONYMOUS, v.vin, "VIN");
- assertEquals("i3 94 (+ REX)", v.model, "Model");
- assertEquals(Constants.BEV, v.driveTrain, "DriveTrain");
- assertEquals("BMW", v.brand, "Brand");
- assertEquals(2017, v.year, "Year of Construction");
- }
-
@Test
public void testChannelUID() {
ThingTypeUID thingTypePHEV = new ThingTypeUID("mybmw", "plugin-hybrid-vehicle");
@@ -56,7 +39,7 @@ public void testChannelUID() {
@Test
public void testRemoteServiceOptions() {
- String commandReference = "[CommandOption [command=light-flash, label=Flash Lights], CommandOption [command=vehicle-finder, label=Vehicle Finder], CommandOption [command=door-lock, label=Door Lock], CommandOption [command=door-unlock, label=Door Unlock], CommandOption [command=horn-blow, label=Horn Blow], CommandOption [command=climate-now-start, label=Start Climate], CommandOption [command=climate-now-stop, label=Stop Climate]]";
+ String commandReference = "[CommandOption [command=light-flash, label=Flash Lights], CommandOption [command=vehicle-finder, label=Vehicle Finder], CommandOption [command=door-lock, label=Door Lock], CommandOption [command=door-unlock, label=Door Unlock], CommandOption [command=horn-blow, label=Horn Blow], CommandOption [command=climate-now-start, label=Start Climate], CommandOption [command=climate-now-stop, label=Stop Climate], CommandOption [command=charge-now, label=Charge]]";
List l = RemoteServiceUtils.getOptions(true);
assertEquals(commandReference, l.toString(), "Commad Options");
}
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehicleStatusTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehicleStatusTest.java
deleted file mode 100644
index 3de394d8305b5..0000000000000
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/VehicleStatusTest.java
+++ /dev/null
@@ -1,115 +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;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.List;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.util.FileReader;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
-import org.openhab.core.library.types.DateTimeType;
-
-/**
- * The {@link VehicleStatusTest} tests stored fingerprint responses from BMW API
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-@SuppressWarnings("null")
-public class VehicleStatusTest {
-
- @Test
- public void testServiceDate() {
- String json = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
- Vehicle v = Converter.getVehicle(Constants.ANONYMOUS, json);
- assertEquals(Constants.ANONYMOUS, v.vin, "VIN check");
- assertEquals("2023-11-01T00:00",
- ((DateTimeType) VehicleStatusUtils.getNextServiceDate(v.properties.serviceRequired)).getZonedDateTime()
- .toLocalDateTime().toString(),
- "Service Date");
-
- ZonedDateTime zdt = ZonedDateTime.parse("2021-12-21T16:46:02Z").withZoneSameInstant(ZoneId.systemDefault());
- LocalDateTime ldt = zdt.toLocalDateTime();
- assertEquals(ldt.format(Converter.DATE_INPUT_PATTERN),
- Converter.zonedToLocalDateTime(v.properties.lastUpdatedAt), "Last update time");
- }
-
- @Test
- public void testBevRexValues() {
- String vehiclesJSON = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json");
- List vehicleList = Converter.getVehicleList(vehiclesJSON);
- assertEquals(1, vehicleList.size(), "Vehicles found");
- Vehicle v = vehicleList.get(0);
- assertEquals("BMW", v.brand, "Car brand");
- assertEquals(true, v.properties.areDoorsClosed, "Doors Closed");
- assertEquals(76, v.properties.electricRange.distance.value, "Electric Range");
- assertEquals(9.876, v.properties.vehicleLocation.coordinates.longitude, 0.1, "Location lon");
- assertEquals("immediateCharging", v.status.chargingProfile.chargingMode, "Charging Mode");
- assertEquals(2, v.status.chargingProfile.getTimerId(2).id, "Timer ID");
- assertEquals("[sunday]", v.status.chargingProfile.getTimerId(2).timerWeekDays.toString(), "Timer Weekdays");
- }
-
- @Test
- public void testGuessRange() {
- /**
- * PHEV G01
- * fuelIndicator electric unit = %
- * fuelIndicator fuel unit = l
- * fuelIndicator hybrid unit = null
- */
- String vehiclesJSON = FileReader.readFileInString("src/test/resources/responses/G01/vehicles_v2_bmw_0.json");
- List vehicleList = Converter.getVehicleList(vehiclesJSON);
- assertEquals(1, vehicleList.size(), "Vehicles found");
- Vehicle vehicle = vehicleList.get(0);
- assertEquals(2, VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle), "Electric Range");
- assertEquals(437, VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle), "Fuel Range");
- assertEquals(439, VehicleStatusUtils.getRange(Constants.PHEV, vehicle), "Hybrid Range");
-
- /**
- * Electric REX I01
- * fuelIndicator electric unit = %
- * fuelIndicator fuel unit = null
- * fuelIndicator hybrid unit = null
- */
- vehiclesJSON = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles_v2_bmw_0.json");
- vehicleList = Converter.getVehicleList(vehiclesJSON);
- assertEquals(1, vehicleList.size(), "Vehicles found");
- vehicle = vehicleList.get(0);
- assertEquals(164, VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle), "Electric Range");
- assertEquals(64, VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle), "Fuel Range");
- assertEquals(228, VehicleStatusUtils.getRange(Constants.PHEV, vehicle), "Hybrid Range");
-
- /**
- * PHEV G05
- * fuelIndicator electric unit = %
- * fuelIndicator fuel unit = %
- * fuelIndicator hybrid unit = null
- */
- vehiclesJSON = FileReader.readFileInString("src/test/resources/responses/G05/vehicles_v2_bmw_0.json");
- vehicleList = Converter.getVehicleList(vehiclesJSON);
- assertEquals(1, vehicleList.size(), "Vehicles found");
- vehicle = vehicleList.get(0);
- assertEquals(48, VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle), "Electric Range");
- assertEquals(418, VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle), "Fuel Range");
- assertEquals(466, VehicleStatusUtils.getRange(Constants.PHEV, vehicle), "Hybrid Range");
- }
-}
diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ChargeStatisticsTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsTest.java
similarity index 50%
rename from bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ChargeStatisticsTest.java
rename to bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsTest.java
index a1d4a8816110d..26e82da10c5e0 100644
--- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ChargeStatisticsTest.java
+++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/charge/ChargingStatisticsTest.java
@@ -10,11 +10,19 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.mybmw.internal.handler;
+package org.openhab.binding.mybmw.internal.dto.charge;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -24,11 +32,15 @@
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
-import org.openhab.binding.mybmw.internal.VehicleConfiguration;
-import org.openhab.binding.mybmw.internal.dto.ChargeStatisticWrapper;
+import org.openhab.binding.mybmw.internal.MyBMWVehicleConfiguration;
+import org.openhab.binding.mybmw.internal.dto.ChargingStatisticsWrapper;
+import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
+import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
+import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
import org.openhab.binding.mybmw.internal.util.FileReader;
import org.openhab.binding.mybmw.internal.utils.Constants;
import org.openhab.core.i18n.LocationProvider;
+import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
@@ -38,14 +50,16 @@
import org.slf4j.LoggerFactory;
/**
- * The {@link ChargeStatisticsTest} is responsible for handling commands, which are
+ * The {@link ChargingStatisticsTest} is responsible for handling commands, which
+ * are
* sent to one of the channels.
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - updated to new vehicles
*/
@NonNullByDefault
@SuppressWarnings("null")
-public class ChargeStatisticsTest {
+public class ChargingStatisticsTest {
private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
private static final int EXPECTED_UPDATE_COUNT = 3;
@@ -55,9 +69,9 @@ public class ChargeStatisticsTest {
@Nullable
ArgumentCaptor stateCaptor;
@Nullable
- ThingHandlerCallback tc;
+ ThingHandlerCallback thingHandlerCallback;
@Nullable
- VehicleHandler cch;
+ VehicleHandler vehicleHandler;
@Nullable
List allChannels;
@Nullable
@@ -73,15 +87,25 @@ public void setup(String type, boolean imperial) {
this.imperial = imperial;
Thing thing = mock(Thing.class);
when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test"));
- MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class);
+ MyBMWCommandOptionProvider myBmwCommandOptionProvider = mock(MyBMWCommandOptionProvider.class);
LocationProvider locationProvider = mock(LocationProvider.class);
- cch = new VehicleHandler(thing, cop, locationProvider, type);
- VehicleConfiguration vc = new VehicleConfiguration();
- vc.vin = Constants.ANONYMOUS;
- Optional ovc = Optional.of(vc);
- cch.configuration = ovc;
- tc = mock(ThingHandlerCallback.class);
- cch.setCallback(tc);
+ TimeZoneProvider timeZoneProvider = mock(TimeZoneProvider.class);
+ when(timeZoneProvider.getTimeZone()).thenReturn(ZoneId.systemDefault());
+ vehicleHandler = new VehicleHandler(thing, myBmwCommandOptionProvider, locationProvider, timeZoneProvider,
+ type);
+ MyBMWVehicleConfiguration vc = new MyBMWVehicleConfiguration();
+ vc.setVin(Constants.ANONYMOUS);
+
+ try {
+ Field vehicleConfigurationField = VehicleHandler.class.getDeclaredField("vehicleConfiguration");
+ vehicleConfigurationField.setAccessible(true);
+ vehicleConfigurationField.set(vehicleHandler, Optional.of(vc));
+ } catch (Exception e) {
+ logger.error("vehicleConfiguration could not be set", e);
+ fail("vehicleConfiguration could not be set", e);
+ }
+ thingHandlerCallback = mock(ThingHandlerCallback.class);
+ vehicleHandler.setCallback(thingHandlerCallback);
channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
stateCaptor = ArgumentCaptor.forClass(State.class);
}
@@ -89,13 +113,24 @@ public void setup(String type, boolean imperial) {
private boolean testVehicle(String statusContent, int callbacksExpected,
Optional