diff --git a/CODEOWNERS b/CODEOWNERS index a2ee3fcf0100c..ba28549a2fb5a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -224,7 +224,7 @@ /bundles/org.openhab.binding.mqtt.homie/ @openhab/add-ons-maintainers /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.myq/ @digitaldan /bundles/org.openhab.binding.mystrom/ @pail23 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> concreteChecks) { assertNotNull(statusContent); - cch.chargeStatisticsCallback.onResponse(statusContent); - verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture()); + + try { + Method updateChargeStatisticsMethod = VehicleHandler.class.getDeclaredMethod("updateChargingStatistics", + ChargingStatisticsContainer.class, String.class); + updateChargeStatisticsMethod.setAccessible(true); + updateChargeStatisticsMethod.invoke(vehicleHandler, + JsonStringDeserializer.getChargingStatistics(statusContent), null); + } catch (Exception e) { + logger.error("chargeStatistics could not be set", e); + fail("chargeStatistics could not be set", e); + } + verify(thingHandlerCallback, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), + stateCaptor.capture()); allChannels = channelCaptor.getAllValues(); allStates = stateCaptor.getAllValues(); assertNotNull(driveTrain); - ChargeStatisticWrapper checker = new ChargeStatisticWrapper(statusContent); + ChargingStatisticsWrapper checker = new ChargingStatisticsWrapper(statusContent); trace(); return checker.checkResults(allChannels, allStates); } @@ -107,35 +142,34 @@ private void trace() { } @Test - public void testI01REX() { + public void testBevIx() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); setup(VehicleType.ELECTRIC_REX.toString(), false); - String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/charge-statistics-de.json"); + String content = FileReader.fileToString("responses/BEV/charging_statistics.json"); assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty())); } @Test - public void testG21() { + public void testBevIX3() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), false); - String content = FileReader.readFileInString("src/test/resources/responses/G21/charging-statistics_0.json"); + setup(VehicleType.ELECTRIC_REX.toString(), false); + String content = FileReader.fileToString("responses/BEV3/charging_statistics.json"); assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty())); } @Test - public void testG30() { + public void testIceX320d() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); setup(VehicleType.PLUGIN_HYBRID.toString(), false); - String content = FileReader.readFileInString("src/test/resources/responses/G30/charging-statistics_0.json"); + String content = FileReader.fileToString("responses/ICE2/charging_statistics.json"); assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty())); } @Test - public void testI01NOREX() { + public void testPhev330e() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.ELECTRIC.toString(), false); - String content = FileReader - .readFileInString("src/test/resources/responses/I01_NOREX/charging-statistics_0.json"); + setup(VehicleType.PLUGIN_HYBRID.toString(), false); + String content = FileReader.fileToString("responses/PHEV2/charging_statistics.json"); assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty())); } } diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBaseTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBaseTest.java new file mode 100644 index 0000000000000..e7d08eba6a3aa --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleBaseTest.java @@ -0,0 +1,83 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import ch.qos.logback.classic.Level; + +/** + * + * checks the vehicleBase response + * + * @author Martin Grassl - Initial contribution + */ +public class VehicleBaseTest { + + private Logger logger = LoggerFactory.getLogger(VehicleBaseTest.class); + + @BeforeEach + public void setupLogger() { + Logger root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + + if ("debug".equals(System.getenv("LOG_LEVEL"))) { + ((ch.qos.logback.classic.Logger) root).setLevel(Level.DEBUG); + } else if ("trace".equals(System.getenv("LOG_LEVEL"))) { + ((ch.qos.logback.classic.Logger) root).setLevel(Level.TRACE); + } + + logger.trace("tracing enabled"); + logger.debug("debugging enabled"); + logger.info("info enabled"); + } + + @Test + public void testVehicleBaseDeserializationByGson() { + String vehicleBaseJson = FileReader.fileToString("responses/MILD_HYBRID/vehicles_base.json"); + Gson gson = new Gson(); + + VehicleBase[] vehicleBaseArray = gson.fromJson(vehicleBaseJson, VehicleBase[].class); + + assertNotNull(vehicleBaseArray); + } + + @Test + public void testVehicleBaseDeserializationByConverter() { + String vehicleBaseJson = FileReader.fileToString("responses/MILD_HYBRID/vehicles_base.json"); + + List vehicleBaseList = JsonStringDeserializer.getVehicleBaseList(vehicleBaseJson); + + assertNotNull(vehicleBaseList); + + assertEquals(1, vehicleBaseList.size(), "Number of Vehicles"); + VehicleBase vehicle = vehicleBaseList.get(0); + assertEquals(Constants.ANONYMOUS + "MILD_HYBRID", vehicle.getVin(), "VIN"); + assertEquals("M340i xDrive", vehicle.getAttributes().getModel(), "Model"); + assertEquals(Constants.DRIVETRAIN_MILD_HYBRID, vehicle.getAttributes().getDriveTrain(), "DriveTrain"); + assertEquals("bmw", vehicle.getAttributes().getBrand(), "Brand"); + assertEquals(2022, vehicle.getAttributes().getYear(), "Year of Construction"); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilitiesTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilitiesTest.java new file mode 100644 index 0000000000000..6876a45aed395 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleCapabilitiesTest.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; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer; +import org.openhab.binding.mybmw.internal.util.FileReader; + +/** + * + * checks the transformation of the capabilities to string lists + * + * @author Bernd Weymann - Initial contribution + * @author Martin Grassl - refactoring + */ +public class VehicleCapabilitiesTest { + @Test + void testGetCapabilitiesAsString() { + String content = FileReader.fileToString("responses/BEV/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(content); + + String servicesSupportedReference = "BmwCharging;ChargingHistory;ChargingPlan;CustomerEsim;DCSContractManagement;RemoteHistory;ScanAndCharge"; + String servicesUnsupportedReference = "CarSharing;ChargeNowForBusiness;ClimateTimer;EvGoCharging;MiniCharging;RemoteEngineStart;RemoteHistoryDeletion;RemoteParking;Sustainability;WifiHotspotService"; + String servicesEnabledReference = "ChargingHospitality;ChargingLoudness;ChargingPowerLimit;ChargingSettings;ChargingTargetSoc"; + String servicesDisabledReference = "DataPrivacy;EasyCharge;NonLscFeature;SustainabilityAccumulatedView"; + assertEquals(servicesSupportedReference, vehicleStateContainer.getCapabilities() + .getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, true), "Services supported"); + assertEquals(servicesUnsupportedReference, vehicleStateContainer.getCapabilities() + .getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, false), "Services unsupported"); + assertEquals(servicesEnabledReference, vehicleStateContainer.getCapabilities() + .getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, true), "Services enabled"); + assertEquals(servicesDisabledReference, vehicleStateContainer.getCapabilities() + .getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, false), "Services disabled"); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainerTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainerTest.java new file mode 100644 index 0000000000000..db55983dfbab3 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/dto/vehicle/VehicleStateContainerTest.java @@ -0,0 +1,59 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer; +import org.openhab.binding.mybmw.internal.util.FileReader; +import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils; +import org.openhab.core.library.types.DateTimeType; + +import com.google.gson.Gson; + +/** + * + * checks basic data of state of vehicle + * + * @author Bernd Weymann - Initial contribution + * @author Martin Grassl - refactoring + */ +public class VehicleStateContainerTest { + @Test + public void testVehicleStateDeserializationByGson() { + String vehicleStateJson = FileReader.fileToString("responses/MILD_HYBRID/vehicles_state.json"); + Gson gson = new Gson(); + + VehicleStateContainer vehicle = gson.fromJson(vehicleStateJson, VehicleStateContainer.class); + + assertNotNull(vehicle); + } + + @Test + public void testVehicleStateDeserializationByConverter() { + String vehicleStateJson = FileReader.fileToString("responses/MILD_HYBRID/vehicles_state.json"); + + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(vehicleStateJson); + + assertNotNull(vehicleStateContainer); + assertEquals("2024-06-01T00:00", + ((DateTimeType) VehicleStatusUtils + .getNextServiceDate(vehicleStateContainer.getState().getRequiredServices())).getZonedDateTime() + .toLocalDateTime().toString(), + "Service Date"); + + assertEquals("2022-12-21T15:41:23Z", vehicleStateContainer.getState().getLastUpdatedAt(), "Last update time"); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ConfigurationTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ConfigurationTest.java deleted file mode 100644 index 5119966d01e0d..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ConfigurationTest.java +++ /dev/null @@ -1,47 +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.junit.jupiter.api.Assertions.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Test; -import org.openhab.binding.mybmw.internal.MyBMWConfiguration; -import org.openhab.binding.mybmw.internal.utils.BimmerConstants; - -/** - * The {@link ConfigurationTest} test different configurations - * - * @author Bernd Weymann - Initial contribution - */ -@NonNullByDefault -public class ConfigurationTest { - - @Test - public void testAuthServerMap() { - MyBMWConfiguration cdc = new MyBMWConfiguration(); - assertFalse(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.userName = "a"; - assertFalse(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.password = "b"; - assertFalse(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.region = "c"; - assertFalse(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.region = BimmerConstants.REGION_NORTH_AMERICA; - assertTrue(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.region = BimmerConstants.REGION_ROW; - assertTrue(MyBMWBridgeHandler.checkConfiguration(cdc)); - cdc.region = BimmerConstants.REGION_CHINA; - assertTrue(MyBMWBridgeHandler.checkConfiguration(cdc)); - } -} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ErrorResponseTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ErrorResponseTest.java deleted file mode 100644 index bd30e4a125c95..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/ErrorResponseTest.java +++ /dev/null @@ -1,78 +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.mockito.Mockito.*; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.openhab.core.i18n.LocationProvider; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.types.State; - -/** - * The {@link ErrorResponseTest} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Bernd Weymann - Initial contribution - */ -@NonNullByDefault -@SuppressWarnings("null") -public class ErrorResponseTest { - @Nullable - ArgumentCaptor channelCaptor; - @Nullable - ArgumentCaptor stateCaptor; - @Nullable - ThingHandlerCallback tc; - @Nullable - VehicleHandler cch; - @Nullable - List allChannels; - @Nullable - List allStates; - @Nullable - String driveTrain; - boolean imperial; - - /** - * Prepare environment for Vehicle Status Updates - */ - public void setup(String type, boolean imperial) { - driveTrain = type; - this.imperial = imperial; - Thing thing = mock(Thing.class); - when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test")); - MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class); - LocationProvider locationProvider = mock(LocationProvider.class); - cch = new VehicleHandler(thing, cop, locationProvider, type); - tc = mock(ThingHandlerCallback.class); - cch.setCallback(tc); - channelCaptor = ArgumentCaptor.forClass(ChannelUID.class); - stateCaptor = ArgumentCaptor.forClass(State.class); - } - - @Test - public void testErrorResponseCallbacks() { - String error = "{\"error\":true,\"reason\":\"offline\"}"; - setup("BEV", false); - cch.vehicleStatusCallback.onResponse(error); - } -} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/SimulationTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/SimulationTest.java deleted file mode 100644 index d6522be29268e..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/SimulationTest.java +++ /dev/null @@ -1,33 +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.junit.jupiter.api.Assertions.assertFalse; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.junit.jupiter.api.Test; -import org.openhab.binding.mybmw.internal.handler.simulation.Injector; - -/** - * The {@link SimulationTest} Assures simulation is off - * - * @author Bernd Weymann - Initial contribution - */ -@NonNullByDefault -public class SimulationTest { - - @Test - public void testSimulationOff() { - assertFalse(Injector.isActive(), "Simulation off"); - } -} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleTests.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleHandlerTest.java similarity index 61% rename from bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleTests.java rename to bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleHandlerTest.java index ff800ed45c818..7f52423900d68 100644 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleTests.java +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/VehicleHandlerTest.java @@ -12,9 +12,20 @@ */ package org.openhab.binding.mybmw.internal.handler; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +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.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,85 +35,116 @@ 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.MyBMWVehicleConfiguration; import org.openhab.binding.mybmw.internal.dto.StatusWrapper; +import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer; +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.library.types.PointType; +import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.ThingHandlerCallback; import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * The {@link VehicleTests} is responsible for handling commands, which are + * The {@link VehicleHandlerTest} is responsible for handling commands, which are * sent to one of the channels. * * @author Bernd Weymann - Initial contribution */ @NonNullByDefault -@SuppressWarnings("null") -public class VehicleTests { +public class VehicleHandlerTest { private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class); + // counters for the number of properties per section private static final int STATUS_ELECTRIC = 12; private static final int STATUS_CONV = 9; - private static final int RANGE_HYBRID = 9; - private static final int RANGE_CONV = 4; + private static final int RANGE_HYBRID = 11; + private static final int RANGE_CONV = 6; private static final int RANGE_ELECTRIC = 4; private static final int DOORS = 11; private static final int CHECK_EMPTY = 3; - private static final int CHECK_AVAILABLE = 3; - private static final int SERVICE_AVAILABLE = 3; - private static final int SERVICE_EMPTY = 3; + private static final int SERVICE_AVAILABLE = 4; + private static final int SERVICE_EMPTY = 4; private static final int LOCATION = 4; private static final int CHARGE_PROFILE = 44; private static final int TIRES = 8; public static final PointType HOME_LOCATION = new PointType("54.321,9.876"); - @Nullable - ArgumentCaptor channelCaptor; - @Nullable - ArgumentCaptor stateCaptor; - @Nullable - ThingHandlerCallback tc; - @Nullable - VehicleHandler cch; - @Nullable - List allChannels; - @Nullable - List allStates; + + // I couldn't resolve all NonNull compile errors, hence I'm initializing the values here... + ArgumentCaptor channelCaptor = ArgumentCaptor.forClass(ChannelUID.class); + ArgumentCaptor stateCaptor = ArgumentCaptor.forClass(State.class); + ThingHandlerCallback thingHandlerCallback = mock(ThingHandlerCallback.class); + VehicleHandler vehicleHandler = mock(VehicleHandler.class); + List allChannels = new ArrayList<>(); + List allStates = new ArrayList<>(); + String driveTrain = Constants.EMPTY; /** * Prepare environment for Vehicle Status Updates */ - public void setup(String type, String vin) { + private void setup(String type, String vin) { driveTrain = type; Thing thing = mock(Thing.class); when(thing.getUID()).thenReturn(new ThingUID("testbinding", "test")); MyBMWCommandOptionProvider cop = mock(MyBMWCommandOptionProvider.class); LocationProvider locationProvider = mock(LocationProvider.class); when(locationProvider.getLocation()).thenReturn(HOME_LOCATION); - cch = new VehicleHandler(thing, cop, locationProvider, type); - VehicleConfiguration vc = new VehicleConfiguration(); - vc.vin = vin; - 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, cop, locationProvider, timeZoneProvider, type); + MyBMWVehicleConfiguration vehicleConfiguration = new MyBMWVehicleConfiguration(); + vehicleConfiguration.setVin(vin); + + setVehicleConfigurationToVehicleHandler(vehicleHandler, vehicleConfiguration); + thingHandlerCallback = mock(ThingHandlerCallback.class); + try { + vehicleHandler.setCallback(thingHandlerCallback); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } channelCaptor = ArgumentCaptor.forClass(ChannelUID.class); stateCaptor = ArgumentCaptor.forClass(State.class); } + private void setVehicleConfigurationToVehicleHandler(@Nullable VehicleHandler vehicleHandler, + MyBMWVehicleConfiguration vehicleConfiguration) { + try { + Field vehicleConfigurationField = VehicleHandler.class.getDeclaredField("vehicleConfiguration"); + vehicleConfigurationField.setAccessible(true); + vehicleConfigurationField.set(vehicleHandler, Optional.of(vehicleConfiguration)); + } catch (Exception e) { + logger.error("vehicleConfiguration could not be set", e); + fail("vehicleConfiguration could not be set", e); + } + } + private boolean testVehicle(String statusContent, int callbacksExpected, Optional> concreteChecks) { assertNotNull(statusContent); - cch.vehicleStatusCallback.onResponse(statusContent); - verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture()); + + try { + Method triggerVehicleStatusUpdateMethod = VehicleHandler.class + .getDeclaredMethod("triggerVehicleStatusUpdate", VehicleStateContainer.class, String.class); + triggerVehicleStatusUpdateMethod.setAccessible(true); + triggerVehicleStatusUpdateMethod.invoke(vehicleHandler, + JsonStringDeserializer.getVehicleState(statusContent), null); + } catch (Exception e) { + logger.error("vehicleState could not be set", e); + fail("vehicleState could not be set", e); + } + + verify(thingHandlerCallback, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), + stateCaptor.capture()); allChannels = channelCaptor.getAllValues(); allStates = stateCaptor.getAllValues(); @@ -123,6 +165,25 @@ private void trace() { } } + @Test + public void testPressureConversion() { + try { + Method calculatePressureMethod = VehicleHandler.class.getDeclaredMethod("calculatePressure", int.class); + calculatePressureMethod.setAccessible(true); + State state = (State) calculatePressureMethod.invoke(vehicleHandler, 110); + assertInstanceOf(QuantityType.class, state); + assertEquals(1.1, ((QuantityType) state).doubleValue()); + state = (State) calculatePressureMethod.invoke(vehicleHandler, 280); + assertEquals(2.8, ((QuantityType) state).doubleValue()); + + state = (State) calculatePressureMethod.invoke(vehicleHandler, -1); + assertInstanceOf(UnDefType.class, state); + } catch (Exception e) { + logger.error("vehicleState could not be set", e); + fail("vehicleState could not be set", e); + } + } + /** * Test various Vehicles from users which delivered their fingerprint. * The tests are checking the chain from "JSON to Channel update". @@ -211,132 +272,112 @@ private void trace() { * Channel testbinding::test:profile#timer4-day-sat OFF * Channel testbinding::test:profile#timer4-day-sun ON */ + @Test - public void testI01Rex() { + public void testBevIx() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.ELECTRIC_REX.toString(), Constants.ANONYMOUS); - String content = FileReader.readFileInString("src/test/resources/responses/I01_REX/vehicles.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + RANGE_HYBRID + DOORS + LOCATION + SERVICE_AVAILABLE - + CHECK_EMPTY + CHARGE_PROFILE + TIRES, Optional.empty())); + setup(VehicleType.ELECTRIC.toString(), "anonymous"); + String content = FileReader.fileToString("responses/BEV/vehicles_state.json"); + assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testF11() { + public void testBevI3() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F11"); - String content = FileReader.readFileInString("src/test/resources/responses/F11/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, - STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES, - Optional.empty())); + setup(VehicleType.ELECTRIC.toString(), "anonymous"); + String content = FileReader.fileToString("responses/BEV2/vehicles_state.json"); + assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testF31() { + public void testBevIX3() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F31"); - String content = FileReader.readFileInString("src/test/resources/responses/F31/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, - STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + TIRES, - Optional.empty())); + setup(VehicleType.ELECTRIC.toString(), "anonymous"); + String content = FileReader.fileToString("responses/BEV3/vehicles_state.json"); + assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testF44() { + public void testBevI4() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F44"); - String content = FileReader.readFileInString("src/test/resources/responses/F44/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, - STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); + setup(VehicleType.ELECTRIC.toString(), "anonymous"); + String content = FileReader.fileToString("responses/BEV4/vehicles_state.json"); + assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testF45() { + public void testBevI7() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_F45"); - String content = FileReader.readFileInString("src/test/resources/responses/F45/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY + setup(VehicleType.ELECTRIC.toString(), "anonymous"); + String content = FileReader.fileToString("responses/BEV5/vehicles_state.json"); + assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testF48() { + public void testIceMiniCooper() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.CONVENTIONAL.toString(), "some_vin_F48"); - String content = FileReader.readFileInString("src/test/resources/responses/F48/vehicles_v2_bmw_0.json"); + setup(VehicleType.CONVENTIONAL.toString(), "anonymous"); + String content = FileReader.fileToString("responses/ICE/vehicles_state.json"); assertTrue(testVehicle(content, - STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_AVAILABLE + LOCATION + TIRES, - Optional.empty())); + STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); } @Test - public void testG01() { + public void testIceX320d() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G01"); - String content = FileReader.readFileInString("src/test/resources/responses/G01/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY - + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); + setup(VehicleType.CONVENTIONAL.toString(), "anonymous"); + String content = FileReader.fileToString("responses/ICE2/vehicles_state.json"); + assertTrue(testVehicle(content, + STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); } @Test - public void testG05() { + public void testIce530d() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G05"); - String content = FileReader.readFileInString("src/test/resources/responses/G05/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY - + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); + setup(VehicleType.CONVENTIONAL.toString(), "anonymous"); + String content = FileReader.fileToString("responses/ICE3/vehicles_state.json"); + assertTrue(testVehicle(content, + STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); } @Test - public void testG08() { + public void testIce435i() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.ELECTRIC.toString(), "some_vin_G08"); - String content = FileReader.readFileInString("src/test/resources/responses/G08/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY - + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); + setup(VehicleType.CONVENTIONAL.toString(), "anonymous"); + String content = FileReader.fileToString("responses/ICE4/vehicles_state.json"); + assertTrue(testVehicle(content, + STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); } @Test - public void testG21() { + public void testMildHybrid340i() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G21"); - String content = FileReader.readFileInString("src/test/resources/responses/G21/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY - + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); + setup(VehicleType.MILD_HYBRID.toString(), "anonymous"); + String content = FileReader.fileToString("responses/MILD_HYBRID/vehicles_state.json"); + assertTrue(testVehicle(content, + STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); } @Test - public void testG30() { + public void testPhev530e() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.PLUGIN_HYBRID.toString(), "some_vin_G30"); - String content = FileReader.readFileInString("src/test/resources/responses/G30/vehicles_v2_bmw_0.json"); + setup(VehicleType.PLUGIN_HYBRID.toString(), "anonymous"); + String content = FileReader.fileToString("responses/PHEV/vehicles_state.json"); assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } @Test - public void testI01NoRex() { - logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.ELECTRIC.toString(), "some_vin_I01_NOREX"); - String content = FileReader.readFileInString("src/test/resources/responses/I01_NOREX/vehicles_v2_bmw_0.json"); - assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_ELECTRIC + SERVICE_AVAILABLE + CHECK_EMPTY - + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); - } - - @Test - public void test530e() { + public void testPhev330e() { logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); setup(VehicleType.PLUGIN_HYBRID.toString(), "anonymous"); - String content = FileReader.readFileInString("src/test/resources/responses/530e/vehicles.json"); + String content = FileReader.fileToString("responses/PHEV2/vehicles_state.json"); assertTrue(testVehicle(content, STATUS_ELECTRIC + DOORS + RANGE_HYBRID + SERVICE_AVAILABLE + CHECK_EMPTY + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty())); } - - @Test - public void test340i() { - logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName()); - setup(VehicleType.MILD_HYBRID.toString(), "anonymous"); - String content = FileReader.readFileInString("src/test/resources/responses/G21/340i.json"); - assertTrue(testVehicle(content, - STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty())); - } } diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/AuthTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/auth/AuthTest.java similarity index 77% rename from bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/AuthTest.java rename to bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/auth/AuthTest.java index 5563919a0992f..b341f1acafb52 100644 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/AuthTest.java +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/auth/AuthTest.java @@ -10,11 +10,18 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.mybmw.internal.handler; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*; +package org.openhab.binding.mybmw.internal.handler.auth; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.AUTHORIZATION; +import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE; +import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_JSON; +import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_URL_ENCODED; +import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_X_USER_AGENT; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; @@ -35,12 +42,13 @@ import org.eclipse.jetty.util.UrlEncoded; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.jupiter.api.Test; -import org.openhab.binding.mybmw.internal.MyBMWConfiguration; +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.util.FileReader; import org.openhab.binding.mybmw.internal.utils.BimmerConstants; import org.openhab.binding.mybmw.internal.utils.Constants; @@ -54,12 +62,14 @@ * The {@link AuthTest} test authorization flow * * @author Bernd Weymann - Initial contribution + * @author Martin Grassl - moved to other package and updated for v2 */ @NonNullByDefault class AuthTest { private final Logger logger = LoggerFactory.getLogger(AuthTest.class); - void testAuth() { + @Test + public void testAuth() { String user = "usr"; String pwd = "pwd"; @@ -76,7 +86,7 @@ void testAuth() { ContentResponse firstResponse = firstRequest.send(); logger.info(firstResponse.getContentAsString()); - AuthQueryResponse aqr = Converter.getGson().fromJson(firstResponse.getContentAsString(), + AuthQueryResponse aqr = JsonStringDeserializer.deserializeString(firstResponse.getContentAsString(), AuthQueryResponse.class); String verifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase(); @@ -129,10 +139,7 @@ void testAuth() { logger.info("Auth"); logger.info(aqr.tokenEndpoint); - // AuthenticationStore authenticationStore = authHttpClient.getAuthenticationStore(); - // BasicAuthentication ba = new BasicAuthentication(new URI(aqr.tokenEndpoint), Authentication.ANY_REALM, - // aqr.clientId, aqr.clientSecret); - // authenticationStore.addAuthentication(ba); + Request codeRequest = authHttpClient.POST(aqr.tokenEndpoint); String basicAuth = "Basic " + Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes()); @@ -149,7 +156,8 @@ void testAuth() { UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8)); ContentResponse codeResponse = codeRequest.send(); logger.info(codeResponse.getContentAsString()); - AuthResponse ar = Converter.getGson().fromJson(codeResponse.getContentAsString(), AuthResponse.class); + AuthResponse ar = JsonStringDeserializer.deserializeString(codeResponse.getContentAsString(), + AuthResponse.class); Token t = new Token(); t.setType(ar.tokenType); t.setToken(ar.accessToken); @@ -166,30 +174,19 @@ void testAuth() { vehicleParams.put("tireGuardMode", "ENABLED"); vehicleParams.put("appDateTime", Long.toString(System.currentTimeMillis())); vehicleParams.put("apptimezone", "60"); - // vehicleRequest.param("tireGuardMode", "ENABLED"); - // vehicleRequest.param("appDateTime", Long.toString(System.currentTimeMillis())); - // vehicleRequest.param("apptimezone", "60.0"); - // vehicleRequest. - // // logger.info(vehicleParams); - // vehicleRequest.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, vehicleParams.toString(), - // StandardCharsets.UTF_8)); - // logger.info(vehicleRequest.getHeaders()); + String params = UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false); String vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW) + "/eadrax-vcs/v1/vehicles"; logger.info(vehicleUrl); - Request vehicleRequest = apiHttpClient.newRequest(vehicleUrl + "?" + params);// - // .param("tireGuardMode", "ENABLED") - // .param("appDateTime", Long.toString(System.currentTimeMillis())).param("apptimezone", "60.0"); - // vehicleRequest.header("Content-Type", "application/x-www-form-urlencoded"); + Request vehicleRequest = apiHttpClient.newRequest(vehicleUrl + "?" + params); + vehicleRequest.header(HttpHeader.AUTHORIZATION, t.getBearerToken()); vehicleRequest.header("accept", "application/json"); vehicleRequest.header("accept-language", "de"); vehicleRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)"); - // vehicleRequest.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded"); - // vehicleRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, - // UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8)); + ContentResponse vehicleResponse = vehicleRequest.send(); logger.info("Vehicle Status {} {}", vehicleResponse.getStatus(), vehicleResponse.getContentAsString()); @@ -206,17 +203,12 @@ void testAuth() { Request chargeStatisticsRequest = apiHttpClient.newRequest(chargeStatisticsUrl) .param("vin", "WBY1Z81040V905639").param("currentDate", Converter.getCurrentISOTime()); logger.info("{}", chargeStatisticsUrl); - // vehicleRequest.header("Content-Type", "application/x-www-form-urlencoded"); + chargeStatisticsRequest.header(HttpHeader.AUTHORIZATION, t.getBearerToken()); chargeStatisticsRequest.header("accept", "application/json"); chargeStatisticsRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)"); chargeStatisticsRequest.header("accept-language", "de"); - // MultiMap chargeStatisticsParams = new MultiMap(); - // chargeStatisticsParams.put("vin", "WBY1Z81040V905639"); - // chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime()); - // - // params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false); logger.info("{}", params); chargeStatisticsRequest .content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8)); @@ -240,20 +232,13 @@ void testAuth() { + "/eadrax-chs/v1/charging-sessions"; Request chargeSessionsRequest = apiHttpClient.newRequest(chargeSessionsUrl + "?" + params); logger.info("{}", chargeSessionsUrl); - // vehicleRequest.header("Content-Type", "application/x-www-form-urlencoded"); + chargeSessionsRequest.header(HttpHeader.AUTHORIZATION, t.getBearerToken()); chargeSessionsRequest.header("accept", "application/json"); chargeSessionsRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)"); chargeSessionsRequest.header("accept-language", "de"); - // MultiMap chargeStatisticsParams = new MultiMap(); - // chargeStatisticsParams.put("vin", "WBY1Z81040V905639"); - // chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime()); - // - // params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false); logger.info("{}", params); - // chargeStatisticsRequest - // .content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8)); ContentResponse chargeSessionsResponse = chargeSessionsRequest.send(); logger.info("{}", chargeSessionsResponse.getStatus()); @@ -264,31 +249,12 @@ void testAuth() { + "/eadrax-vrccs/v2/presentation/remote-commands/WBY1Z81040V905639/charging-control"; Request chargingControlRequest = apiHttpClient.POST(chargingControlUrl); logger.info("{}", chargingControlUrl); - // vehicleRequest.header("Content-Type", "application/x-www-form-urlencoded"); + chargingControlRequest.header(HttpHeader.AUTHORIZATION, t.getBearerToken()); chargingControlRequest.header("accept", "application/json"); chargingControlRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)"); chargingControlRequest.header("accept-language", "de"); - chargingControlRequest.header("Content-Type", CONTENT_TYPE_JSON_ENCODED); - - // String content = FileReader.readFileInString("src/test/resources/responses/charging-profile.json"); - // logger.info("{}", content); - // ChargeProfile cpc = Converter.getGson().fromJson(content, ChargeProfile.class); - // String contentTranfsorm = Converter.getGson().toJson(cpc); - // String profile = "{chargingProfile:" + contentTranfsorm + "}"; - // logger.info("{}", profile); - // chargingControlRequest - // .content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8)); - - // chargeStatisticsParams.put("vin", "WBY1Z81040V905639"); - // chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime()); - // - // params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false); - - // ContentResponse chargingControlResponse = chargingControlRequest.send(); - // logger.info("{}", chargingControlResponse.getStatus()); - // logger.info("{}", chargingControlResponse.getReason()); - // logger.info("{}", chargingControlResponse.getContentAsString()); + chargingControlRequest.header("Content-Type", CONTENT_TYPE_JSON); } catch (Exception e) { logger.error("{}", e.getMessage()); @@ -324,15 +290,16 @@ public static String codeFromUrl(String encodedUrl) { @Test public void testJWTDeserialze() { - String accessTokenResponseStr = FileReader - .readFileInString("src/test/resources/responses/auth/auth_cn_login_pwd.json"); - ChinaTokenResponse cat = Converter.getGson().fromJson(accessTokenResponseStr, ChinaTokenResponse.class); + String accessTokenResponseStr = FileReader.fileToString("responses/auth/auth_cn_login_pwd.json"); + ChinaTokenResponse cat = JsonStringDeserializer.deserializeString(accessTokenResponseStr, + ChinaTokenResponse.class); // https://www.baeldung.com/java-jwt-token-decode String token = cat.data.accessToken; String[] chunks = token.split("\\."); String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1])); - ChinaTokenExpiration cte = Converter.getGson().fromJson(tokenJwtDecodeStr, ChinaTokenExpiration.class); + ChinaTokenExpiration cte = JsonStringDeserializer.deserializeString(tokenJwtDecodeStr, + ChinaTokenExpiration.class); Token t = new Token(); t.setToken(token); t.setType(cat.data.tokenType); @@ -349,12 +316,14 @@ public void testChina() { authHttpClient.start(); HttpClientFactory mockHCF = mock(HttpClientFactory.class); when(mockHCF.getCommonHttpClient()).thenReturn(authHttpClient); - MyBMWConfiguration config = new MyBMWConfiguration(); + MyBMWBridgeConfiguration config = new MyBMWBridgeConfiguration(); config.region = BimmerConstants.REGION_CHINA; config.userName = "Hello User"; config.password = "Hello Password"; - MyBMWProxy bmwProxy = new MyBMWProxy(mockHCF, config); - bmwProxy.updateTokenChina(); + MyBMWTokenController tokenHandler = new MyBMWTokenController(config, authHttpClient); + Token token = tokenHandler.getToken(); + assertNotNull(token); + assertNotNull(token.getBearerToken()); } catch (Exception e) { logger.warn("Exception: " + e.getMessage()); } @@ -362,8 +331,9 @@ public void testChina() { @Test public void testPublicKey() { - String publicKeyResponseStr = FileReader.readFileInString("src/test/resources/responses/auth/china-key.json"); - ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponseStr, ChinaPublicKeyResponse.class); + String publicKeyResponseStr = FileReader.fileToString("responses/auth/china-key.json"); + ChinaPublicKeyResponse pkr = JsonStringDeserializer.deserializeString(publicKeyResponseStr, + ChinaPublicKeyResponse.class); String publicKeyStr = pkr.data.value; String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "") @@ -393,16 +363,16 @@ public void testChinaToken() { String url = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA) + BimmerConstants.CHINA_PUBLIC_KEY; Request oauthQueryRequest = authHttpClient.newRequest(url); - oauthQueryRequest.header(X_USER_AGENT, + oauthQueryRequest.header(HEADER_X_USER_AGENT, String.format(BimmerConstants.BRAND_BMW, BimmerConstants.BRAND_BMW, BimmerConstants.REGION_ROW)); ContentResponse publicKeyResponse = oauthQueryRequest.send(); - ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponse.getContentAsString(), - ChinaPublicKeyResponse.class); + ChinaPublicKeyResponse pkr = JsonStringDeserializer + .deserializeString(publicKeyResponse.getContentAsString(), ChinaPublicKeyResponse.class); // https://stackoverflow.com/questions/11410770/load-rsa-public-key-from-file String publicKeyStr = pkr.data.value; - // String cleanPublicKeyStr = pkr.data.value.replaceAll("(\r\n|\n)", Constants.EMPTY); + String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "") .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", ""); byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializerTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializerTest.java new file mode 100644 index 0000000000000..aafa5f3aa0265 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/JsonStringDeserializerTest.java @@ -0,0 +1,112 @@ +/** + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +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.openhab.binding.mybmw.internal.util.FileReader; + +import com.google.gson.Gson; + +/** + * This test checks if the file can be parsed correctly to the object. Additionally + * it can be used to check if there are properties in the files not mapped to the objects + * The output of Junit (at least in VSCode) shows the differences between the original file + * and the string generated by GSON to identify the gap. + * + * @author Martin Grassl - Initial contribution + */ +@NonNullByDefault +public class JsonStringDeserializerTest { + + Gson gson = new Gson(); + + void testGetChargeSessions() { + String content = FileReader.fileToString("responses/BEV/charging_sessions.json"); + ChargingSessionsContainer chargeSessionsContainer = JsonStringDeserializer.getChargingSessions(content); + assertNotNull(chargeSessionsContainer); + } + + @Test + void testGetChargeStatistics() { + String content = FileReader.fileToString("responses/BEV/charging_statistics.json"); + ChargingStatisticsContainer chargeStatisticsContainer = JsonStringDeserializer.getChargingStatistics(content); + assertNotNull(chargeStatisticsContainer); + } + + @Test + void testGetExecutionStatus() { + String content = FileReader.fileToString("responses/MILD_HYBRID/remote_service_status.json"); + ExecutionStatusContainer executionStatusContainer = JsonStringDeserializer.getExecutionStatus(content); + assertNotNull(executionStatusContainer); + } + + @Test + void testGetExecutionError() { + String content = FileReader.fileToString("responses/MILD_HYBRID/remote_service_error.json"); + ExecutionStatusContainer executionStatusContainer = JsonStringDeserializer.getExecutionStatus(content); + assertNotNull(executionStatusContainer); + } + + @Test + void testGetVehicleBaseList() { + String content = FileReader.fileToString("responses/BEV/vehicles_base.json"); + List vehicleBases = JsonStringDeserializer.getVehicleBaseList(content); + assertNotNull(vehicleBases); + assertFalse(vehicleBases.isEmpty()); + assertEquals(1, vehicleBases.size()); + } + + @Test + void testGetVehicleStateBEV() { + String content = FileReader.fileToString("responses/BEV/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(content); + assertNotNull(vehicleStateContainer); + + vehicleStateContainer.setRawStateJson(null); + String jsonString = gson.toJson(vehicleStateContainer); + assertNotNull(jsonString); + } + + @Test + void testGetVehicleStateMILDHYBRID() { + String content = FileReader.fileToString("responses/MILD_HYBRID/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(content); + assertNotNull(vehicleStateContainer); + } + + @Test + void testGetVehicleStatePHEV() { + String content = FileReader.fileToString("responses/PHEV/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(content); + assertNotNull(vehicleStateContainer); + } + + @Test + void testGetVehicleStateICE() { + String content = FileReader.fileToString("responses/ICE/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(content); + assertNotNull(vehicleStateContainer); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxyTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxyTest.java new file mode 100644 index 0000000000000..d49e33f37df90 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/MyBMWHttpProxyTest.java @@ -0,0 +1,221 @@ +/** + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration; +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.util.FileReader; +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; + +import ch.qos.logback.classic.Level; + +/** + * this test tests the different MyBMWProxy request types (GET, POST) and their errors (SUCCESS, other) + * + * @author Martin Grassl - initial contribution + */ +@NonNullByDefault +public class MyBMWHttpProxyTest { + + private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxyTest.class); + + @BeforeEach + public void setupLogger() { + Logger root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + + if ("debug".equals(System.getenv("LOG_LEVEL"))) { + ((ch.qos.logback.classic.Logger) root).setLevel(Level.DEBUG); + } else if ("trace".equals(System.getenv("LOG_LEVEL"))) { + ((ch.qos.logback.classic.Logger) root).setLevel(Level.TRACE); + } + + logger.trace("tracing enabled"); + logger.debug("debugging enabled"); + logger.info("info enabled"); + } + + @Test + void testWrongBrand() { + // test successful GET for vehicle state + String responseContent = FileReader.fileToString("responses/BEV/vehicles_state.json"); + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(200, responseContent); + + try { + myBMWProxy.requestVehicleState("testVin", "WRONG_BRAND"); + } catch (NetworkException e) { + assertEquals("Unknown Brand WRONG_BRAND", e.getMessage()); + } + } + + @Test + void testSuccessfulGet() { + // test successful GET for vehicle state + String responseContent = FileReader.fileToString("responses/BEV/vehicles_state.json"); + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(200, responseContent); + + try { + VehicleStateContainer vehicleStateContainer = myBMWProxy.requestVehicleState("testVin", + BimmerConstants.BRAND_BMW); + assertEquals(2686, vehicleStateContainer.getState().getCurrentMileage()); + } catch (NetworkException e) { + fail(e.toString()); + } + } + + @Test + void testErrorGet() { + // test successful GET for vehicle state + String responseContent = FileReader.fileToString("responses/BEV/vehicles_state.json"); + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(400, responseContent); + + try { + myBMWProxy.requestVehicleState("testVin", BimmerConstants.BRAND_BMW); + + fail("here an exception should be thrown"); + } catch (NetworkException e) { + assertEquals(400, e.getStatus()); + } + } + + @Test + void testSuccessfulPost() { + // test successful POST for remote service execution + String responseContent = FileReader.fileToString("responses/MILD_HYBRID/remote_service_call.json"); + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(200, responseContent); + + try { + ExecutionStatusContainer executionStatusContainer = myBMWProxy.executeRemoteServiceCall("testVin", + BimmerConstants.BRAND_BMW, RemoteService.LIGHT_FLASH); + assertNotNull(executionStatusContainer.getCreationTime()); + assertNotNull(executionStatusContainer.getEventId()); + assertEquals("", executionStatusContainer.getEventStatus()); + } catch (NetworkException e) { + fail(e.toString()); + } + } + + @Test + void testErrorPost() { + // test successful POST for remote service execution + String responseContent = FileReader.fileToString("responses/MILD_HYBRID/remote_service_call.json"); + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(400, responseContent); + + try { + myBMWProxy.executeRemoteServiceCall("testVin", BimmerConstants.BRAND_BMW, RemoteService.LIGHT_FLASH); + fail("here an exception should be thrown"); + } catch (NetworkException e) { + assertEquals(400, e.getStatus()); + } + } + + @Test + void testSuccessfulImage() { + // test successful POST for remote service execution + MyBMWHttpProxy myBMWProxy = generateMyBmwProxy(200, "test"); + + try { + byte[] image = myBMWProxy.requestImage("testVin", BimmerConstants.BRAND_BMW, new ImageProperties()); + assertNotNull(image); + } catch (NetworkException e) { + fail(e.toString()); + } + } + + @Test + void testSuccessfulGetVehicles() { + HttpClientFactory httpClientFactoryMock = Mockito.mock(HttpClientFactory.class); + HttpClient httpClientMock = Mockito.mock(HttpClient.class); + Mockito.when(httpClientFactoryMock.getCommonHttpClient()).thenReturn(httpClientMock); + + MyBMWBridgeConfiguration myBMWBridgeConfiguration = new MyBMWBridgeConfiguration(); + + MyBMWHttpProxy myBMWProxyMock = Mockito + .spy(new MyBMWHttpProxy(httpClientFactoryMock, myBMWBridgeConfiguration)); + + String vehiclesBaseString = FileReader.fileToString("responses/BEV/vehicles_base.json"); + List baseVehicles = JsonStringDeserializer.getVehicleBaseList(vehiclesBaseString); + + String vehicleStateString = FileReader.fileToString("responses/BEV/vehicles_state.json"); + VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(vehicleStateString); + + try { + doReturn(baseVehicles).when(myBMWProxyMock).requestVehiclesBase(); + doReturn(vehicleStateContainer).when(myBMWProxyMock).requestVehicleState(anyString(), anyString()); + + List vehicles = myBMWProxyMock.requestVehicles(); + + logger.debug("found vehicles {}", vehicles.toString()); + + assertNotNull(vehicles); + assertEquals(1, vehicles.size()); + assertEquals("I20", vehicles.get(0).getVehicleBase().getAttributes().getBodyType()); + + } catch (NetworkException e) { + fail("vehicles not loaded properly", e); + } + } + + MyBMWHttpProxy generateMyBmwProxy(int statuscode, String responseContent) { + HttpClientFactory httpClientFactoryMock = Mockito.mock(HttpClientFactory.class); + HttpClient httpClientMock = Mockito.mock(HttpClient.class); + Request requestMock = Mockito.mock(Request.class); + Mockito.when(httpClientMock.newRequest(Mockito.anyString())).thenReturn(requestMock); + Mockito.when(httpClientMock.POST(Mockito.anyString())).thenReturn(requestMock); + MyBMWBridgeConfiguration myBMWBridgeConfiguration = new MyBMWBridgeConfiguration(); + Mockito.when(httpClientFactoryMock.getCommonHttpClient()).thenReturn(httpClientMock); + + ContentResponse responseMock = Mockito.mock(ContentResponse.class); + Mockito.when(responseMock.getStatus()).thenReturn(statuscode); + Mockito.when(responseMock.getContent()).thenReturn(responseContent.getBytes()); + Mockito.when(responseMock.getContentAsString()).thenReturn(responseContent); + try { + Mockito.when(requestMock.timeout(anyLong(), any())).thenReturn(requestMock); + Mockito.when(requestMock.send()).thenReturn(responseMock); + } catch (InterruptedException e1) { + logger.error(e1.getMessage(), e1); + } catch (TimeoutException e1) { + logger.error(e1.getMessage(), e1); + } catch (ExecutionException e1) { + logger.error(e1.getMessage(), e1); + } + + return new MyBMWHttpProxy(httpClientFactoryMock, myBMWBridgeConfiguration); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizerTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizerTest.java new file mode 100644 index 0000000000000..25962d5e00435 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/handler/backend/ResponseContentAnonymizerTest.java @@ -0,0 +1,58 @@ +/** + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.mybmw.internal.util.FileReader; + +/** + * + * checks if the response anonymization is successful + * + * @author Martin Grassl - initial contribution + */ +@NonNullByDefault +public class ResponseContentAnonymizerTest { + @Test + void testAnonymizeResponseContent() { + String content = FileReader.fileToString("responses/vehicles.json"); + String anonymous = ResponseContentAnonymizer.anonymizeResponseContent(content); + assertFalse(anonymous.contains("VIN1234567"), "VIN not deleted!"); + assertFalse(anonymous.contains("Testort"), "Location not deleted!"); + } + + @Test + void testAnonymizeRandomString() { + String content = "asdfiulsahföauifhnasdölfam,xöasiocjfsailfunsalifnsaölfkmasdäf.ifnvaskdfnvinlocationasdfiulsdanf"; + String anonymous = ResponseContentAnonymizer.anonymizeResponseContent(content); + assertEquals(content, anonymous); + } + + @Test + void testAnonymizeEmptyString() { + String content = ""; + String anonymous = ResponseContentAnonymizer.anonymizeResponseContent(content); + assertEquals(content, anonymous); + } + + @Test + void testAnonymizeNullString() { + String content = null; + String anonymous = ResponseContentAnonymizer.anonymizeResponseContent(content); + assertEquals("", anonymous); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/util/FileReader.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/util/FileReader.java index 61b509254c5d7..bb6abaf052264 100644 --- a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/util/FileReader.java +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/util/FileReader.java @@ -12,11 +12,12 @@ */ package org.openhab.binding.mybmw.internal.util; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.io.BufferedReader; -import java.io.FileInputStream; +import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -26,23 +27,49 @@ * The {@link FileReader} Helper Util to read test resource files * * @author Bernd Weymann - Initial contribution + * @author Martin Grassl - added reading of image */ @NonNullByDefault public class FileReader { - public static String readFileInString(String filename) { - try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "UTF-8"))) { + /** + * reads a file into a string + * + * @param filename + * @return + */ + public static String fileToString(String filename) { + try (BufferedReader br = new BufferedReader( + new InputStreamReader(FileReader.class.getClassLoader().getResourceAsStream(filename), "UTF-8"))) { StringBuilder buf = new StringBuilder(); String sCurrentLine; while ((sCurrentLine = br.readLine()) != null) { buf.append(sCurrentLine); } - return buf.toString(); + return buf != null ? buf.toString() : ""; } catch (IOException e) { - // fail if file cannot be read - assertEquals(filename, Constants.EMPTY, "Read failute " + filename); + fail("Read failure " + filename, e); } return Constants.UNDEF; } + + /** + * reads a file into a byte[] + * + * @param filename + * @return + */ + public static byte[] fileToByteArray(String filename) { + File file = new File(filename); + byte[] bytes = new byte[(int) file.length()]; + + try (InputStream is = (FileReader.class.getClassLoader().getResourceAsStream(filename))) { + is.read(bytes); + } catch (IOException e) { + fail("Read failure " + filename, e); + } + + return bytes; + } } diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/ConverterTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/ConverterTest.java new file mode 100644 index 0000000000000..8d97a6c012837 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/ConverterTest.java @@ -0,0 +1,57 @@ +/** + * 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.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.time.ZoneId; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * @author Martin Grassl - initial contribution + */ +@NonNullByDefault +public class ConverterTest { + @Test + void testToTitleCase() { + assertEquals("Closed", Converter.toTitleCase("CLOSED")); + assertEquals("Secured", Converter.toTitleCase("SECURED")); + assertEquals("Undef", Converter.toTitleCase(null)); + assertEquals("Undef", Converter.toTitleCase("")); + assertEquals("Secured", Converter.toTitleCase("SECURED")); + assertEquals("Secured", Converter.toTitleCase("SECURED")); + assertEquals("Test Data", Converter.toTitleCase("test_data")); + assertEquals("Test-Data", Converter.toTitleCase("test-data")); + assertEquals("Test Data", Converter.toTitleCase("test data")); + } + + @Test + void testDateConversion() { + State state = Converter.zonedToLocalDateTime(null, ZoneId.systemDefault()); + assertTrue(state instanceof UnDefType); + state = Converter.zonedToLocalDateTime("", ZoneId.systemDefault()); + assertTrue(state instanceof UnDefType); + state = Converter.zonedToLocalDateTime("2023-01-18", ZoneId.systemDefault()); + assertTrue(state instanceof UnDefType); + state = Converter.zonedToLocalDateTime("2023-01-18T18:07:59.076Z", ZoneId.systemDefault()); + assertTrue(state instanceof DateTimeType); + state = Converter.zonedToLocalDateTime("2023-10-28T17:41:17Z", ZoneId.systemDefault()); + assertTrue(state instanceof DateTimeType); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationCheckerTest.java b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationCheckerTest.java new file mode 100644 index 0000000000000..c5133ed0ae580 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/java/org/openhab/binding/mybmw/internal/utils/MyBMWConfigurationCheckerTest.java @@ -0,0 +1,48 @@ +/** + * 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.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration; + +/** + * + * checks if the configuration checker works fine + * + * @author Bernd Weymann - Initial contribution + * @author Martin Grassl - renamed + */ +@NonNullByDefault +public class MyBMWConfigurationCheckerTest { + @Test + void testCheckConfiguration() { + MyBMWBridgeConfiguration cdc = new MyBMWBridgeConfiguration(); + assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.userName = "a"; + assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.password = "b"; + assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.region = "c"; + assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.region = BimmerConstants.REGION_NORTH_AMERICA; + assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.region = BimmerConstants.REGION_ROW; + assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc)); + cdc.region = BimmerConstants.REGION_CHINA; + assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc)); + } +} diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/530e/vehicles.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/530e/vehicles.json deleted file mode 100644 index 2d05f4f6f28af..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/530e/vehicles.json +++ /dev/null @@ -1,379 +0,0 @@ -[{ - "vin": "anonymous", - "model": "530e", - "year": 2021, - "brand": "BMW", - "headUnit": "MGU", - "isLscSupported": true, - "driveTrain": "PLUGIN_HYBRID", - "puStep": "1121", - "iStep": "S15A-21-11-530", - "telematicsUnit": "ATM02", - "hmiVersion": "id7", - "bodyType": "G30", - "a4aType": "NOT_SUPPORTED", - "exFactoryPUStep": "1121", - "exFactoryILevel": "S15A-21-11-530", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern." - } - }, - "properties": { - "lastUpdatedAt": "2022-01-06T15:59:07Z", - "inMotion": false, - "areDoorsLocked": false, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 18, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 60, - "state": "CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": true - }, - "combustionRange": { - "chargePercentage": 0, - "distance": { - "value": 251, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "chargePercentage": 0, - "distance": { - "value": 251, - "units": "KILOMETERS" - } - }, - "electricRange": { - "chargePercentage": 0, - "distance": { - "value": 27, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 60, - "distance": { - "value": 27, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-12-01T00:00:00.000Z", - "distance": { - "value": 30000, - "units": "KILOMETERS" - } - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2025-12-01T00:00:00.000Z", - "distance": { - "value": 60000, - "units": "KILOMETERS" - } - }, - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2024-12-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2024-12-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.234, - "longitude": 5.678 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 270 - }, - "tires": { - "frontLeft": { - "status": { - "currentPressure": 240.0, - "localizedCurrentPressure": "2,4 bar", - "localizedTargetPressure": "2,4 bar", - "targetPressure": 240.0 - } - }, - "frontRight": { - "status": { - "currentPressure": 240.0, - "localizedCurrentPressure": "2,4 bar", - "localizedTargetPressure": "2,4 bar", - "targetPressure": 240.0 - } - }, - "rearLeft": { - "status": { - "currentPressure": 270.0, - "localizedCurrentPressure": "2,7 bar", - "localizedTargetPressure": "2,8 bar", - "targetPressure": 280.0 - } - }, - "rearRight": { - "status": { - "currentPressure": 270.0, - "localizedCurrentPressure": "2,7 bar", - "localizedTargetPressure": "2,8 bar", - "targetPressure": 280.0 - } - } - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "status": { - "lastUpdatedAt": "2022-01-06T15:59:07Z", - "currentMileage": { - "mileage": 589, - "units": "km", - "formattedMileage": "589" - }, - "issues": null, - "doorsGeneralState": "Entriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59737, - "title": "Verriegelungsstatus", - "state": "Entriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60117, - "state": "OK", - "title": "Reifen", - "longDescription": "-" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Motoröl", - "longDescription": "-" - } - ], - "requiredServices": [ - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Service nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im Dezember 2023 oder in 30.000 km", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im Dezember 2025 oder in 60.000 km", - "criticalness": "nonCritical" - }, - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im Dezember 2024", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im Dezember 2024", - "criticalness": "nonCritical" - } - ], - "fuelIndicators": [ - { - "mainBarValue": 0, - "rangeUnits": "km", - "rangeValue": "251" - }, - { - "mainBarValue": 60, - "rangeUnits": "km", - "rangeValue": "27", - "levelUnits": "%", - "levelValue": "60" - }, - { - "mainBarValue": 42, - "rangeUnits": "km", - "rangeValue": "224", - "levelUnits": "%", - "levelValue": "42" - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 6.1.2022 04:59 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 0, - "minute": 0 - }, - "end": { - "hour": 0, - "minute": 0 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [] - }, - { - "id": 2, - "action": "deactivate", - "timerWeekDays": [] - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [] - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [] - } - ], - "climatisationOn": true, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "valid": false -} -] - diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_sessions.json new file mode 100644 index 0000000000000..2ece14b711162 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_sessions.json @@ -0,0 +1,74 @@ +{ + "paginationInfo": null, + "chargingSessions": { + "total": "~ 142 kWh", + "numberOfSessions": "7", + "chargingListState": "HAS_SESSIONS", + "sessions": [ + { + "id": "2023-01-13T20:46:46Z_20735abd", + "title": "Vrijdag 21:46", + "subtitle": "Leopoldstrasse 30 • 2u 06min • -- EUR", + "energyCharged": "~ 22 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-12T17:39:51Z_20735abd", + "title": "Donderdag 18:39", + "subtitle": "Leopoldstrasse 30 • 3u 00min • -- EUR", + "energyCharged": "~ 31 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-08T16:16:13Z_20735abd", + "title": "08-01-2023 17:16", + "subtitle": "Leopoldstrasse 30 • 2u 15min • -- EUR", + "energyCharged": "~ 24 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-08T16:16:04.000Z_20735abd", + "title": "08-01-2023 17:16", + "subtitle": "Leopoldstrasse 30 • 0 min • -- EUR", + "energyCharged": "0 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-08T15:05:06Z_20735abd", + "title": "08-01-2023 16:05", + "subtitle": "Leopoldstrasse 30 • 2 min • -- EUR", + "energyCharged": "< 2 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-05T22:46:55Z_20735abd", + "title": "05-01-2023 23:46", + "subtitle": "Leopoldstrasse 30 • 2u 30min • -- EUR", + "energyCharged": "~ 27 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-03T17:08:29Z_20735abd", + "title": "03-01-2023 18:08", + "subtitle": "Leopoldstrasse 30 • 3u 34min • -- EUR", + "energyCharged": "~ 38 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + } + ], + "costsGroupedByCurrency": [ + "--" + ] + }, + "datePicker": { + "startDate": "2022-11-25T12:15:40Z", + "selectedDate": "2023-01-13T20:46:48Z", + "endDate": "2023-01-16T15:20:49Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_statistics.json new file mode 100644 index 0000000000000..645aa3a0b3630 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/charging_statistics.json @@ -0,0 +1,11 @@ +{ + "description": "januari 2023", + "optStateType": "OPT_IN_WITH_SESSIONS", + "statistics": { + "totalEnergyCharged": 142, + "totalEnergyChargedSemantics": "In totaal circa 142 kilowattuur geladen", + "symbol": "~", + "numberOfChargingSessions": 7, + "numberOfChargingSessionsSemantics": "7 laadprocessen" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_base.json new file mode 100644 index 0000000000000..1c3b07c4ce62e --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_base.json @@ -0,0 +1,48 @@ +[ + { + "vin": "anonymousBEV", + "mappingInfo": { + "isAssociated": true, + "isLmmEnabled": true, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-16T15:41:26.664Z", + "model": "iX xDrive40", + "year": 2022, + "color": 4282345065, + "brand": "BMW_I", + "driveTrain": "ELECTRIC", + "headUnitType": "MGU", + "headUnitRaw": "HU_MGU", + "hmiVersion": "ID8", + "softwareVersionCurrent": { + "puStep": { + "month": 7, + "year": 22 + }, + "iStep": 545, + "seriesCluster": "I020" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 7, + "year": 22 + }, + "iStep": 545, + "seriesCluster": "I020" + }, + "telematicsUnit": "WAVE11", + "bodyType": "I20", + "countryOfOrigin": "BE", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_state.json new file mode 100644 index 0000000000000..5a60806b3573a --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV/vehicles_state.json @@ -0,0 +1,292 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-16T15:25:49.307Z", + "lastUpdatedAt": "2023-01-16T12:32:48Z", + "isLscSupported": true, + "range": 191, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED" + }, + "tireState": { + "frontLeft": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 230, + "targetPressure": 220, + "wearStatus": 0 + } + }, + "frontRight": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 230, + "targetPressure": 220, + "wearStatus": 0 + } + }, + "rearLeft": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 260, + "targetPressure": 260, + "wearStatus": 0 + } + }, + "rearRight": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 260, + "wearStatus": 0 + } + } + }, + "location": { + "coordinates": { + "latitude": 1.1, + "longitude": 2.2 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": -1 + }, + "currentMileage": 2686, + "climateControlState": { + "activity": "INACTIVE" + }, + "requiredServices": [ + { + "dateTime": "2024-11-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Volgende vervanging uiterlijk op het aangegeven tijdstip." + }, + { + "dateTime": "2024-11-01T00:00:00.000Z", + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Volgende visuele controle op de aangegeven datum of na afloop van de evt. aangegeven afstand." + }, + { + "type": "TIRE_WEAR_REAR", + "status": "OK" + }, + { + "type": "TIRE_WEAR_FRONT", + "status": "OK" + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW" + } + ], + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "reductionOfChargeCurrent": { + "start": { + "hour": 21, + "minute": 0 + }, + "end": { + "hour": 6, + "minute": 0 + } + }, + "chargingMode": "DELAYED_CHARGING", + "chargingPreference": "CHARGING_WINDOW", + "departureTimes": [ + { + "id": 1, + "timeStamp": { + "hour": 7, + "minute": 0 + }, + "action": "ACTIVATE", + "timerWeekDays": [ + "MONDAY", + "TUESDAY", + "WEDNESDAY", + "THURSDAY", + "FRIDAY", + "SATURDAY", + "SUNDAY" + ] + }, + { + "id": 2, + "timeStamp": { + "hour": 23, + "minute": 59 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 3, + "timeStamp": { + "hour": 23, + "minute": 59 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 4, + "timeStamp": { + "hour": 7, + "minute": 30 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "climatisationOn": false, + "chargingSettings": { + "targetSoc": 80, + "acCurrentLimit": 32, + "idcc": "AUTOMATIC_INTELLIGENT", + "hospitality": "HOSP_INACTIVE", + "isAcCurrentLimitActive": false + } + }, + "electricChargingState": { + "chargingLevelPercent": 46, + "remainingChargingMinutes": 178, + "range": 159, + "isChargerConnected": true, + "chargingConnectionType": "UNKNOWN", + "chargingStatus": "CHARGING", + "chargingTarget": 80 + }, + "combustionFuelLevel": { + "range": 191 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "NOT_SUPPORTED", + "climateNow": true, + "climateFunction": "AIR_CONDITIONING", + "horn": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": true, + "isChargingLoudnessEnabled": true, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": true, + "isChargingSettingsEnabled": true, + "isChargingTargetSocEnabled": true, + "isCustomerEsimSupported": true, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": true, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": true, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remote360": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "surroundViewRecorder": true, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": false, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": { + "chargingControl": [ + "START", + "STOP" + ], + "plugControl": [ + "NOT_SUPPORTED" + ], + "flapControl": [ + "COUPLE_FLAP", + "DECOUPLE_FLAP" + ] + }, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "SMACC_2_UWB", + "readerGraphics": "000200000001", + "state": "ACTIVATED" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV2/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV2/vehicles_state.json new file mode 100644 index 0000000000000..9c947cdb3cb0d --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV2/vehicles_state.json @@ -0,0 +1,154 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-18T18:07:59.076Z", + "lastUpdatedAt": "2023-01-18T17:50:01Z", + "isLscSupported": true, + "range": 101, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "rightFront": "CLOSED", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "location": { + "coordinates": { + "latitude": 1.1, + "longitude": 2.2 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": -1 + }, + "currentMileage": 129898, + "requiredServices": [ + { + "dateTime": "2024-07-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + }, + { + "dateTime": "2024-07-01T00:00:00.000Z", + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next vehicle check due after the specified distance or date." + } + ], + "checkControlMessages": [], + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "reductionOfChargeCurrent": { + "start": { + "hour": 0, + "minute": 0 + }, + "end": { + "hour": 8, + "minute": 0 + } + }, + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "CHARGING_WINDOW", + "departureTimes": [ + { + "id": 1, + "timeStamp": { + "hour": 8, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 2, + "timeStamp": { + "hour": 3, + "minute": 10 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 3, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 4, + "action": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "climatisationOn": false, + "chargingSettings": { + "targetSoc": 100, + "idcc": "NO_ACTION", + "hospitality": "NO_ACTION" + } + }, + "electricChargingState": { + "chargingLevelPercent": 73, + "range": 101, + "isChargerConnected": false, + "chargingConnectionType": "CONDUCTIVE", + "chargingStatus": "INVALID", + "chargingTarget": 100 + }, + "combustionFuelLevel": null, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_sessions.json new file mode 100644 index 0000000000000..41dd429add804 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_sessions.json @@ -0,0 +1,50 @@ +{ + "paginationInfo": {}, + "chargingSessions": { + "total": "~ 78 kWh", + "numberOfSessions": "4", + "chargingListState": "HAS_SESSIONS", + "sessions": [ + { + "id": "2023-01-21T09:26:09Z_c700db02", + "title": "Today 10:26", + "subtitle": "anonymousAddress • 36 min • – EUR", + "energyCharged": "~ 7 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-17T10:33:37Z_c700db02", + "title": "Tuesday 11:33", + "subtitle": "anonymousAddress • 3h 39min • – EUR", + "energyCharged": "~ 39 kWh", + "sessionStatus": "FINISHED", + "isPublic": true + }, + { + "id": "2023-01-05T14:07:49Z_c700db02", + "title": "1/5/2023 15:07", + "subtitle": "anonymousAddress • 52 min • – EUR", + "energyCharged": "~ 9 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-01-01T20:00:27Z_c700db02", + "title": "1/1/2023 21:00", + "subtitle": "anonymousAddress • 2h 38min • ~ 10,27 EUR", + "energyCharged": "~ 23 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + } + ], + "costsGroupedByCurrency": [ + "~10.27 EUR" + ] + }, + "datePicker": { + "startDate": "2022-06-13T10:29:32Z", + "selectedDate": "2023-01-21T09:26:09Z", + "endDate": "2023-01-21T18:57:40Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_statistics.json new file mode 100644 index 0000000000000..f9413c30d2492 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/charging_statistics.json @@ -0,0 +1,11 @@ +{ + "description": "January 2023", + "optStateType": "OPT_IN_WITH_SESSIONS", + "statistics": { + "totalEnergyCharged": 78, + "totalEnergyChargedSemantics": "Charged a total of approximately 78 kilowatt-hours", + "symbol": "~", + "numberOfChargingSessions": 4, + "numberOfChargingSessionsSemantics": "4 charging sessions" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_base.json new file mode 100644 index 0000000000000..d4ddb4cd55866 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_base.json @@ -0,0 +1,48 @@ +[ + { + "vin": "anonymousBEV3", + "mappingInfo": { + "isAssociated": true, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-21T18: 57: 39.114Z", + "model": "iX3 M Sport", + "year": 2022, + "color": 4280231501, + "brand": "BMW", + "driveTrain": "ELECTRIC", + "headUnitType": "MGU", + "headUnitRaw": "HU_MGU", + "hmiVersion": "ID7", + "softwareVersionCurrent": { + "puStep": { + "month": 3, + "year": 22 + }, + "iStep": 552, + "seriesCluster": "S15C" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 11, + "year": 21 + }, + "iStep": 564, + "seriesCluster": "S15C" + }, + "telematicsUnit": "ATM2", + "bodyType": "G08", + "countryOfOrigin": "AT", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide: ///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_state.json new file mode 100644 index 0000000000000..e284d44d121f2 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV3/vehicles_state.json @@ -0,0 +1,248 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-21T18:57:39.524Z", + "lastUpdatedAt": "2023-01-21T11:50:52Z", + "isLscSupported": true, + "range": 208, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "tireState": { + "frontLeft": { + "status": { + "currentPressure": 250 + } + }, + "frontRight": { + "status": { + "currentPressure": 250 + } + }, + "rearLeft": { + "status": { + "currentPressure": 280 + } + }, + "rearRight": { + "status": { + "currentPressure": 290 + } + } + }, + "location": { + "coordinates": { + "latitude": 1.1, + "longitude": 2.2 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": -1 + }, + "currentMileage": 18289, + "climateControlState": { + "activity": "INACTIVE" + }, + "requiredServices": [ + { + "dateTime": "2024-06-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + }, + { + "dateTime": "2024-06-01T00:00:00.000Z", + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next visual inspection due by specified date or, if shown, when stated distance has been reached." + }, + { + "dateTime": "2025-06-01T00:00:00.000Z", + "type": "VEHICLE_TUV", + "status": "OK", + "description": "Next state inspection due by the specified date." + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW" + } + ], + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "reductionOfChargeCurrent": { + "start": { + "hour": 0, + "minute": 0 + }, + "end": { + "hour": 0, + "minute": 0 + } + }, + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "NO_PRESELECTION", + "departureTimes": [ + { + "id": 1, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 2, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 3, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 4, + "action": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "climatisationOn": false, + "chargingSettings": { + "targetSoc": 90, + "idcc": "NO_ACTION", + "hospitality": "NO_ACTION" + } + }, + "electricChargingState": { + "chargingLevelPercent": 76, + "remainingChargingMinutes": 843, + "range": 208, + "isChargerConnected": false, + "chargingConnectionType": "UNKNOWN", + "chargingStatus": "INVALID", + "chargingTarget": 90 + }, + "combustionFuelLevel": { + "range": 208 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "NOT_SUPPORTED", + "climateNow": true, + "climateFunction": "AIR_CONDITIONING", + "horn": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": true, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": true, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": {}, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "SMACC_1_5", + "readerGraphics": "000200000000", + "state": "ACTIVATED" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_base.json new file mode 100644 index 0000000000000..1925452de0e65 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_base.json @@ -0,0 +1,50 @@ +[ + { + "appVehicleType": "DEMO", + "attributes": { + "a4aType": "NOT_SUPPORTED", + "bodyType": "G26", + "brand": "BMW", + "color": 4284245350, + "countryOfOrigin": "DE", + "driveTrain": "ELECTRIC", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + }, + "headUnitRaw": "HU_MGU", + "headUnitType": "MGU", + "hmiVersion": "ID8", + "lastFetched": "2023-01-04T14:57:06.019Z", + "model": "i4 eDrive40", + "softwareVersionCurrent": { + "iStep": 470, + "puStep": { + "month": 11, + "year": 21 + }, + "seriesCluster": "G026" + }, + "softwareVersionExFactory": { + "iStep": 470, + "puStep": { + "month": 11, + "year": 21 + }, + "seriesCluster": "G026" + }, + "telematicsUnit": "WAVE01", + "year": 2021 + }, + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "isPrimaryUser": true, + "lmmStatusReasons": [], + "mappingStatus": "CONFIRMED" + }, + "vin": "anonymousBEV4" + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_state.json new file mode 100644 index 0000000000000..6d7e1cb3d9cb6 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV4/vehicles_state.json @@ -0,0 +1,311 @@ +{ + "capabilities": { + "a4aType": "NOT_SUPPORTED", + "checkSustainabilityDPP": false, + "climateFunction": "AIR_CONDITIONING", + "climateNow": true, + "digitalKey": { + "bookedServicePackage": "SMACC_1_5", + "readerGraphics": "readerGraphics", + "state": "ACTIVATED" + }, + "horn": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": true, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": true, + "isChargingLoudnessEnabled": true, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": true, + "isChargingSettingsEnabled": true, + "isChargingTargetSocEnabled": true, + "isClimateTimerWeeklyActive": false, + "isCustomerEsimSupported": true, + "isDCSContractManagementSupported": true, + "isDataPrivacyEnabled": false, + "isEasyChargeEnabled": true, + "isEvGoChargingSupported": false, + "isMiniChargingSupported": false, + "isNonLscFeatureEnabled": false, + "isRemoteEngineStartSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteHistorySupported": true, + "isRemoteParkingSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": true, + "isSustainabilityAccumulatedViewEnabled": false, + "isSustainabilitySupported": false, + "isWifiHotspotServiceSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remote360": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "specialThemeSupport": [], + "speechThirdPartyAlexa": false, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL" + }, + "state": { + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "NO_PRESELECTION", + "chargingSettings": { + "acCurrentLimit": 16, + "hospitality": "NO_ACTION", + "idcc": "UNLIMITED_LOUD", + "targetSoc": 80 + }, + "departureTimes": [ + { + "action": "DEACTIVATE", + "id": 1, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 2, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 3, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 4, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + } + ] + }, + "checkControlMessages": [ + { + "severity": "LOW", + "type": "TIRE_PRESSURE" + } + ], + "climateControlState": { + "activity": "STANDBY" + }, + "climateTimers": [ + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "combustionFuelLevel": {}, + "currentMileage": 1121, + "doorsState": { + "combinedSecurityState": "LOCKED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "trunk": "CLOSED" + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "electricChargingState": { + "chargingConnectionType": "UNKNOWN", + "chargingLevelPercent": 80, + "chargingStatus": "INVALID", + "chargingTarget": 80, + "isChargerConnected": false, + "range": 472, + "remainingChargingMinutes": 10 + }, + "isLeftSteering": true, + "isLscSupported": true, + "lastFetched": "2023-01-04T14:57:06.386Z", + "lastUpdatedAt": "2023-01-04T14:57:06.407Z", + "location": { + "address": { + "formatted": "Am Olympiapark 1, 80809 München" + }, + "coordinates": { + "latitude": 48.177334, + "longitude": 11.556274 + }, + "heading": 180 + }, + "range": 472, + "requiredServices": [ + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "BRAKE_FLUID" + }, + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "VEHICLE_TUV" + }, + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "VEHICLE_CHECK" + }, + { + "status": "OK", + "type": "TIRE_WEAR_REAR" + }, + { + "status": "OK", + "type": "TIRE_WEAR_FRONT" + } + ], + "tireState": { + "frontLeft": { + "details": { + "dimension": "225/35 R20 90Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 4021, + "mountingDate": "2022-03-07T00:00:00.000Z", + "partNumber": "2461756", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 241, + "pressureStatus": 0, + "targetPressure": 269, + "wearStatus": 0 + } + }, + "frontRight": { + "details": { + "dimension": "225/35 R20 90Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 2419, + "mountingDate": "2022-03-07T00:00:00.000Z", + "partNumber": "2461756", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 255, + "pressureStatus": 0, + "targetPressure": 269, + "wearStatus": 0 + } + }, + "rearLeft": { + "details": { + "dimension": "255/30 R20 92Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 1219, + "mountingDate": "2022-03-07T00:00:00.000Z", + "partNumber": "2461757", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 324, + "pressureStatus": 0, + "targetPressure": 303, + "wearStatus": 0 + } + }, + "rearRight": { + "details": { + "dimension": "255/30 R20 92Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 1219, + "mountingDate": "2022-03-07T00:00:00.000Z", + "partNumber": "2461757", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 331, + "pressureStatus": 0, + "targetPressure": 303, + "wearStatus": 0 + } + } + }, + "windowsState": { + "combinedState": "CLOSED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_base.json new file mode 100644 index 0000000000000..eaea75f9de2c5 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_base.json @@ -0,0 +1,50 @@ +[ + { + "appVehicleType": "DEMO", + "attributes": { + "a4aType": "BLUETOOTH", + "bodyType": "G70", + "brand": "BMW", + "color": 4284900182, + "countryOfOrigin": "DE", + "driveTrain": "ELECTRIC", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + }, + "headUnitRaw": "MGU_02_L", + "headUnitType": "MGU", + "hmiVersion": "ID8", + "lastFetched": "2023-01-04T15:03:07.150Z", + "model": "i7 xDrive60", + "softwareVersionCurrent": { + "iStep": 505, + "puStep": { + "month": 7, + "year": 22 + }, + "seriesCluster": "G070" + }, + "softwareVersionExFactory": { + "iStep": 450, + "puStep": { + "month": 7, + "year": 22 + }, + "seriesCluster": "G070" + }, + "telematicsUnit": "WAVE01", + "year": 2022 + }, + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "isPrimaryUser": true, + "lmmStatusReasons": [], + "mappingStatus": "CONFIRMED" + }, + "vin": "anonymousBEV5" + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_state.json new file mode 100644 index 0000000000000..8c18830a0c740 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/BEV5/vehicles_state.json @@ -0,0 +1,319 @@ +{ + "capabilities": { + "a4aType": "BLUETOOTH", + "checkSustainabilityDPP": false, + "climateFunction": "AIR_CONDITIONING", + "climateNow": true, + "digitalKey": { + "bookedServicePackage": "SMACC_2_UWB", + "readerGraphics": "readerGraphics", + "state": "ACTIVATED" + }, + "horn": true, + "inCarCamera": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": true, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": true, + "isChargingLoudnessEnabled": true, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": true, + "isChargingSettingsEnabled": true, + "isChargingTargetSocEnabled": true, + "isClimateTimerWeeklyActive": false, + "isCustomerEsimSupported": true, + "isDCSContractManagementSupported": true, + "isDataPrivacyEnabled": false, + "isEasyChargeEnabled": true, + "isEvGoChargingSupported": false, + "isMiniChargingSupported": false, + "isNonLscFeatureEnabled": false, + "isRemoteEngineStartSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteHistorySupported": true, + "isRemoteParkingSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": true, + "isSustainabilityAccumulatedViewEnabled": false, + "isSustainabilitySupported": false, + "isWifiHotspotServiceSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remote360": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "specialThemeSupport": [], + "speechThirdPartyAlexa": false, + "speechThirdPartyAlexaSDK": false, + "surroundViewRecorder": true, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL" + }, + "state": { + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "NO_PRESELECTION", + "chargingSettings": { + "acCurrentLimit": 16, + "hospitality": "NO_ACTION", + "idcc": "UNLIMITED_LOUD", + "targetSoc": 80 + }, + "departureTimes": [ + { + "action": "DEACTIVATE", + "id": 1, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 2, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 3, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + }, + { + "action": "DEACTIVATE", + "id": 4, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "timerWeekDays": [] + } + ] + }, + "checkControlMessages": [ + { + "severity": "LOW", + "type": "TIRE_PRESSURE" + } + ], + "climateControlState": { + "activity": "INACTIVE" + }, + "climateTimers": [ + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "departureTime": { + "hour": 0, + "minute": 0 + }, + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "combustionFuelLevel": { + "remainingFuelPercent": 10 + }, + "currentMileage": 1121, + "doorsState": { + "combinedSecurityState": "LOCKED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "trunk": "CLOSED" + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "electricChargingState": { + "chargingConnectionType": "UNKNOWN", + "chargingLevelPercent": 70, + "chargingStatus": "CHARGING", + "chargingTarget": 80, + "isChargerConnected": true, + "range": 340, + "remainingChargingMinutes": 10 + }, + "isLeftSteering": true, + "isLscSupported": true, + "lastFetched": "2023-01-04T14:57:06.371Z", + "lastUpdatedAt": "2023-01-04T14:57:06.383Z", + "location": { + "address": { + "formatted": "Am Olympiapark 1, 80809 München" + }, + "coordinates": { + "latitude": 48.177334, + "longitude": 11.556274 + }, + "heading": 180 + }, + "range": 340, + "requiredServices": [ + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "BRAKE_FLUID" + }, + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "VEHICLE_TUV" + }, + { + "dateTime": "2024-12-01T00:00:00.000Z", + "description": "", + "mileage": 50000, + "status": "OK", + "type": "VEHICLE_CHECK" + }, + { + "status": "OK", + "type": "TIRE_WEAR_REAR" + }, + { + "status": "OK", + "type": "TIRE_WEAR_FRONT" + } + ], + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "tireState": { + "frontLeft": { + "details": { + "dimension": "275/40 R22 107Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 4021, + "mountingDate": "2022-04-20T00:00:00.000Z", + "partNumber": "5A401A1", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 241, + "pressureStatus": 0, + "targetPressure": 241, + "wearStatus": 0 + } + }, + "frontRight": { + "details": { + "dimension": "275/40 R22 107Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 4021, + "mountingDate": "2022-04-20T00:00:00.000Z", + "partNumber": "5A401A1", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 241, + "pressureStatus": 0, + "targetPressure": 241, + "wearStatus": 0 + } + }, + "rearLeft": { + "details": { + "dimension": "275/40 R22 107Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 4021, + "mountingDate": "2022-04-20T00:00:00.000Z", + "partNumber": "5A401A1", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 261, + "pressureStatus": 0, + "targetPressure": 269, + "wearStatus": 0 + } + }, + "rearRight": { + "details": { + "dimension": "275/40 R22 107Y XL", + "isOptimizedForOemBmw": true, + "manufacturer": "Pirelli", + "manufacturingWeek": 4021, + "mountingDate": "2022-04-20T00:00:00.000Z", + "partNumber": "5A401A1", + "season": 2, + "speedClassification": { + "atLeast": false, + "speedRating": 300 + }, + "treadDesign": "P-ZERO" + }, + "status": { + "currentPressure": 269, + "pressureStatus": 0, + "targetPressure": 269, + "wearStatus": 0 + } + } + }, + "windowsState": { + "combinedState": "CLOSED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F11/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F11/vehicles_v2_bmw_0.json deleted file mode 100644 index b0a48f13dd800..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F11/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,279 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "F11", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Ventilation" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true, - "page": { - "description": "By setting a start time you let the vehicle know when you plan to use it.", - "primaryButtonText": "SEND TO VEHICLE", - "secondaryButtonText": "DEACTIVATE AND SEND TO VEHICLE", - "subtitle": "Set start time", - "title": "Ventilation timer" - }, - "tile": { - "description": "Plan start time", - "iconId": 59774, - "title": "Ventilation timer" - } - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "NOT_CAPABLE" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": false, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F010-12-11-503", - "exFactoryPUStep": "1112", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F010-12-11-503", - "isLscSupported": false, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "530d", - "properties": { - "checkControlMessages": [], - "climateControl": {}, - "doorsAndWindows": { - "doors": {}, - "windows": {} - }, - "fuelLevel": { - "units": "LITERS", - "value": 24 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "originCountryISO": "GB", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 25000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 60000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ] - }, - "puStep": "1112", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - }, - { - "criticalness": "semiCritical", - "iconId": 60217, - "id": "229", - "longDescription": "Charge by driving for longer periods or use external charger. Functions requiring battery will be switched off.", - "state": "Medium", - "title": "Battery discharged: Start engine" - }, - { - "criticalness": "nonCritical", - "iconId": 60217, - "id": "50", - "longDescription": "System unable to monitor tire pressure. Check tire pressures manually. Continued driving possible. Consult service center.", - "state": "Low", - "title": "Flat Tire Monitor (FTM) inactive" - } - ], - "checkControlMessagesGeneralState": "Multiple Issues", - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59726, - "state": "Unknown", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59701, - "state": "Unknown", - "title": "Left front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59700, - "state": "Unknown", - "title": "Right front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59703, - "state": "Unknown", - "title": "Left rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59702, - "state": "Unknown", - "title": "Right rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59721, - "state": "Unknown", - "title": "Back window" - } - ], - "doorsGeneralState": "Unknown", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "24", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "mi", - "rangeValue": "- -", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in October 2022", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in October 2022 or 15534 mi", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in October 2024 or 37282 mi", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 3/11/2021 08:02 AM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 158, - "green": 158, - "red": 158 - } - }, - "vin": "some_vin_F11", - "year": 2012 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F31/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F31/vehicles_v2_bmw_0.json deleted file mode 100644 index 7583a8b03a2f0..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F31/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,281 +0,0 @@ -[ - { - "a4aType": "USB_ONLY", - "bodyType": "F31", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Ventilation" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true, - "page": { - "description": "By setting a start time you let the vehicle know when you plan to use it.", - "primaryButtonText": "SEND TO VEHICLE", - "secondaryButtonText": "DEACTIVATE AND SEND TO VEHICLE", - "subtitle": "Set start time", - "title": "Ventilation timer" - }, - "tile": { - "description": "Plan start time", - "iconId": 59774, - "title": "Ventilation timer" - } - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "NOT_CAPABLE" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F020-13-11-502", - "exFactoryPUStep": "1113", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F020-13-11-502", - "isLscSupported": false, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "320d xDrive", - "properties": { - "checkControlMessages": [], - "climateControl": {}, - "doorsAndWindows": { - "doors": {}, - "windows": {} - }, - "fuelLevel": { - "units": "LITERS", - "value": 32 - }, - "inMotion": false, - "isServiceRequired": true, - "lastUpdatedAt": "2021-11-01T16:02:44Z", - "originCountryISO": "DE", - "serviceRequired": [ - { - "dateTime": "2021-11-01T00:00:00.000Z", - "status": "PENDING", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2022-07-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 9000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2022-07-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 9000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2023-02-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_TUV" - } - ] - }, - "puStep": "1113", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59726, - "state": "Unknown", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59701, - "state": "Unknown", - "title": "Left front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59700, - "state": "Unknown", - "title": "Right front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59703, - "state": "Unknown", - "title": "Left rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59702, - "state": "Unknown", - "title": "Right rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59721, - "state": "Unknown", - "title": "Back window" - } - ], - "doorsGeneralState": "Unknown", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "32", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "- -", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "lastUpdatedAt": "2021-11-01T16:02:44Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "semiCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Service due soon. Please make an appointment with your service center.", - "subtitle": "Due in November 2021", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in July 2022 or 9000 km", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in July 2022 or 9000 km", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60111, - "id": "VehicleAdmissionTest", - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in February 2023", - "title": "Vehicle Inspection" - } - ], - "timestampMessage": "Updated from vehicle 11/1/2021 05:02 PM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 51, - "green": 51, - "red": 51 - } - }, - "vin": "some_vin_F31", - "year": 2013 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F44/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F44/vehicles_v2_bmw_0.json deleted file mode 100644 index 12ea886d7f5a6..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F44/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,251 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "F44", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S18A-20-11-538", - "exFactoryPUStep": "1120", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S18A-21-03-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "218i", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 222 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 16 - }, - "fuelPercentage": { - "value": 35 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-10T18:03:43Z", - "originCountryISO": "BE", - "serviceRequired": [ - { - "dateTime": "2022-12-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 24000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2023-12-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2024-12-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 50000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0321", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "3047", - "mileage": 3047, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59682, - "levelUnits": "%", - "levelValue": "35", - "mainBarValue": 35, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "222", - "secondaryBarValue": 0, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-10T18:03:43Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in December 2022 or 24000 km", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in December 2023", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in December 2024 or 50000 km", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 11/10/2021 07:03 PM" - }, - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 84, - "green": 84, - "red": 84 - } - }, - "vin": "some_vin_F44", - "year": 2020 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F45/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F45/vehicles_v2_bmw_0.json deleted file mode 100644 index 0b7102ed083aa..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F45/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,301 +0,0 @@ -[ - { - "a4aType": "USB_ONLY", - "bodyType": "F45", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F056-16-07-502", - "exFactoryPUStep": "0716", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F056-20-07-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "225xe iPerformance", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 40, - "isChargerConnected": false, - "state": "NOT_CHARGING", - "type": "CONDUCTIVE" - }, - "checkControlMessages": [], - "climateControl": {}, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 245 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 245 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 4 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 40, - "distance": { - "units": "KILOMETERS", - "value": 4 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 20 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-10T18:25:38Z", - "originCountryISO": "GB", - "serviceRequired": [], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0720", - "status": { - "chargingProfile": { - "chargingControlType": "twoWeeksTimer", - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 16, - "minute": 0 - }, - "start": { - "hour": 13, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "66720", - "mileage": 66720, - "units": "mi" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "mi", - "rangeValue": "152", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "DEFAULT", - "chargingStatusType": "DEFAULT", - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59694, - "infoLabel": "State of Charge", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59694, - "levelUnits": "%", - "levelValue": "40", - "mainBarValue": 40, - "rangeIconId": 59683, - "rangeUnits": "mi", - "rangeValue": "2", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "20", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "mi", - "rangeValue": "150", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-10T18:25:38Z", - "recallExternalUrl": null, - "recallMessages": [], - "timestampMessage": "Updated from vehicle 11/11/2021 06:25 PM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 66, - "green": 66, - "red": 66 - } - }, - "vin": "some_vin_F45", - "year": 2016 - } - ] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F48/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F48/vehicles_v2_bmw_0.json deleted file mode 100644 index 30d61fb94a4a9..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/F48/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,278 +0,0 @@ -[ - { - "a4aType": "BLUETOOTH", - "bodyType": "F48", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Ventilation" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true, - "page": { - "description": "By setting a start time you let the vehicle know when you plan to use it.", - "primaryButtonText": "SEND TO VEHICLE", - "secondaryButtonText": "DEACTIVATE AND SEND TO VEHICLE", - "subtitle": "Set start time", - "title": "Ventilation timer" - }, - "tile": { - "description": "Plan start time", - "iconId": 59774, - "title": "Ventilation timer" - } - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F056-17-07-503", - "exFactoryPUStep": "0717", - "headUnit": "ID5", - "hmiVersion": "ID5", - "iStep": "F056-18-03-541", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "X1 sDrive18i", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "checkControlMessages": [], - "climateControl": {}, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 308 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 19 - }, - "inMotion": true, - "isServiceRequired": false, - "lastUpdatedAt": "2021-10-30T06:57:45Z", - "originCountryISO": "NL", - "serviceRequired": [ - { - "dateTime": "2021-12-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 8000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2021-12-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 8000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2022-07-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - } - ], - "vehicleLocation": { - "address": { - "formatted": "address, city" - }, - "coordinates": { - "latitude": 0.0, - "longitude": 0.0 - }, - "heading": 123 - } - }, - "puStep": "0318", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "113009", - "mileage": 113009, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "19", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "308", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "issues": {}, - "lastUpdatedAt": "2021-10-30T06:57:45Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in December 2021 or 8000 km", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in December 2021 or 8000 km", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in July 2022", - "title": "Brake fluid" - } - ], - "timestampMessage": "Updated from vehicle 10/30/2021 08:57 AM" - }, - "telematicsUnit": "ATM", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 158, - "green": 158, - "red": 158 - } - }, - "vin": "some_vin_F48", - "year": 2017 - } -] diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G01/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G01/vehicles_v2_bmw_0.json deleted file mode 100644 index e12a0ef3a6e14..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G01/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,429 +0,0 @@ -[ - { - "a4aType": "USB_ONLY", - "bodyType": "G01", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S15A-20-07-549", - "exFactoryPUStep": "0720", - "headUnit": "ID5", - "hmiVersion": "ID5", - "iStep": "S15A-20-07-549", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "X3 xDrive30e", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 12, - "isChargerConnected": true, - "state": "CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": {}, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 439 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 439 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 2 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 12, - "distance": { - "units": "KILOMETERS", - "value": 2 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 30 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-12-13T21:06:27Z", - "originCountryISO": "ES", - "serviceRequired": [ - { - "dateTime": "2024-12-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_TUV" - }, - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 12000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 45000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2023-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0720", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "delayedCharging", - "chargingPreference": "chargingWindow", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "activate", - "id": 1, - "timeStamp": { - "hour": 17, - "minute": 0 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ] - }, - { - "action": "deactivate", - "id": 2, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timeStamp": { - "hour": 17, - "minute": 0 - }, - "timerWeekDays": [ - "tuesday" - ] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 16, - "minute": 59 - }, - "start": { - "hour": 9, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "21068", - "mileage": 21068, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59757, - "state": "Locked", - "title": "Lock status" - }, - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59725, - "state": "Closed", - "title": "All windows" - }, - { - "criticalness": "nonCritical", - "iconId": 59706, - "state": "Closed", - "title": "Hood" - }, - { - "criticalness": "nonCritical", - "iconId": 59704, - "state": "Closed", - "title": "Trunk" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "439", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "PLUGGED_IN", - "chargingStatusType": "PLUGGED_IN", - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "Starts at ~ 09:00 AM", - "isCircleIcon": true, - "isInaccurate": true, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "12", - "mainBarValue": 12, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "2", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "30", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "437", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "issues": {}, - "lastUpdatedAt": "2021-12-13T21:06:27Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60111, - "id": "VehicleAdmissionTest", - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle Inspection" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Brake fluid" - } - ], - "timestampMessage": "Updated from vehicle 12/13/2021 10:06 PM" - }, - "telematicsUnit": "ATM", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 133, - "green": 129, - "red": 127 - } - }, - "vin": "some_vin_G01", - "year": 2020 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G05/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G05/vehicles_v2_bmw_0.json deleted file mode 100644 index bdcf55a6e9a18..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G05/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,401 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "G05", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": true, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remote360": { - "isComingSoonEnabled": false, - "isDataPrivacyEnabled": false, - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S18A-21-03-563", - "exFactoryPUStep": "0321", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S18A-21-07-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "X5 xDrive45e", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 80, - "isChargerConnected": true, - "state": "CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 466 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 466 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "moonroof": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 48 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 80, - "distance": { - "units": "KILOMETERS", - "value": 48 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 47 - }, - "fuelPercentage": { - "value": 74 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-10T22:21:39Z", - "originCountryISO": "BE", - "serviceRequired": [ - { - "dateTime": "2023-06-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 32000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-06-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2025-06-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 60000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0721", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": true, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "activate", - "id": 2, - "timeStamp": { - "hour": 8, - "minute": 10 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday" - ] - }, - { - "action": "deactivate", - "id": 3, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timeStamp": { - "hour": 8, - "minute": 10 - }, - "timerWeekDays": [ - "thursday" - ] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "2667", - "mileage": 2667, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "466", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "CHARGING", - "chargingStatusType": "CHARGING", - "chargingType": "charging", - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "100% at ~03:53 AM", - "isCircleIcon": true, - "isInaccurate": true, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "80", - "mainBarValue": 80, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "48", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59682, - "levelUnits": "%", - "levelValue": "74", - "mainBarValue": 74, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "418", - "secondaryBarValue": 0, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-10T22:21:39Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in June 2023 or 32000 km", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in June 2024", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in June 2025 or 60000 km", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 11/10/2021 11:21 PM" - }, - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 77, - "green": 38, - "red": 31 - } - }, - "vin": "some_vin_G05", - "year": 2021 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G08/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G08/vehicles_v2_bmw_0.json deleted file mode 100644 index e0f89db04a5bf..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G08/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,401 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "G08", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": true, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remote360": { - "isComingSoonEnabled": false, - "isDataPrivacyEnabled": true, - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "ELECTRIC", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S15C-20-11-542", - "exFactoryPUStep": "1120", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S15C-20-11-542", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "iX3", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 50, - "isChargerConnected": true, - "state": "CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 186 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "moonroof": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 179 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 50, - "distance": { - "units": "KILOMETERS", - "value": 179 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 0 - }, - "fuelPercentage": { - "value": 0 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-14T15:08:24Z", - "originCountryISO": "NL", - "serviceRequired": [ - { - "dateTime": "2023-06-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2023-06-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2025-06-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_TUV" - } - ], - "tires": { - "frontLeft": { - "status": { - "currentPressure": 220, - "localizedCurrentPressure": "2.2 bar", - "localizedTargetPressure": "2.3 bar", - "targetPressure": 231 - } - }, - "frontRight": { - "status": { - "currentPressure": 222, - "localizedCurrentPressure": "2.2 bar", - "localizedTargetPressure": "2.3 bar", - "targetPressure": 233 - } - }, - "rearLeft": { - "status": { - "currentPressure": 264, - "localizedCurrentPressure": "2.6 bar", - "localizedTargetPressure": "2.6 bar", - "targetPressure": 265 - } - }, - "rearRight": { - "status": { - "currentPressure": 266, - "localizedCurrentPressure": "2.6 bar", - "localizedTargetPressure": "2.6 bar", - "targetPressure": 267 - } - } - }, - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "1120", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "AUTOMATIC_INTELLIGENT", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": true, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60117, - "state": "OK", - "title": "Tires" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "9527", - "mileage": 9527, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59757, - "state": "Locked", - "title": "Lock status" - }, - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59725, - "state": "Closed", - "title": "All windows" - }, - { - "criticalness": "nonCritical", - "iconId": 59706, - "state": "Closed", - "title": "Hood" - }, - { - "criticalness": "nonCritical", - "iconId": 59704, - "state": "Closed", - "title": "Trunk" - }, - { - "criticalness": "nonCritical", - "iconId": 59705, - "state": "Closed", - "title": "Sunroof" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "barType": null, - "chargingStatusIndicatorType": "CHARGING", - "chargingStatusType": "CHARGING", - "chargingType": "charging", - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "100% at ~04:01 AM", - "isCircleIcon": true, - "isInaccurate": true, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "50", - "mainBarValue": 50, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "179", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-14T15:08:24Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60111, - "id": "VehicleAdmissionTest", - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle Inspection" - } - ], - "timestampMessage": "Updated from vehicle 11/14/2021 04:08 PM" - }, - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 173, - "green": 173, - "red": 173 - } - }, - "vin": "some_vin_G08", - "year": 2021 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/340i.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/340i.json deleted file mode 100644 index d7d11adefc8cc..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/340i.json +++ /dev/null @@ -1,401 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "M340i xDrive", - "year": 2021, - "brand": "BMW", - "headUnit": "MGU", - "isLscSupported": true, - "driveTrain": "HYBRID", - "puStep": "0721", - "iStep": "S18A-21-07-550", - "telematicsUnit": "ATM02", - "hmiVersion": "id7", - "bodyType": "G21", - "a4aType": "NOT_SUPPORTED", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "speechThirdPartyAlexa": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Alexa aktivieren? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern.", - "executionPopup": { - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern.", - "popupType": "DIALOG", - "title": "Belüftung starten", - "primaryButtonText": "Start", - "secondaryButtonText": "Abbrechen", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Jetzt Klimatisierung Ihres Fahrzeugs beenden? Remote-Funktionen können einige Sekunden dauern.", - "title": "Klimatisierung läuft" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "tile": { - "iconId": 59774, - "title": "Belüftungstimer", - "description": "Abfahrtszeit einstellen" - }, - "page": { - "primaryButtonText": "AN FAHRZEUG SENDEN", - "secondaryButtonText": "DEAKTIVIEREN UND AN FAHRZEUG SENDEN", - "title": "Belüftungstimer", - "subtitle": "Abfahrtszeit einstellen", - "description": "Durch das Einstellen einer Abfahrtszeit teilen Sie dem Fahrzeug mit, wann Sie es benutzen wollen." - }, - "isToggleEnabled": true - }, - "isChargingHistorySupported": false, - "isScanAndChargeSupported": false, - "isDCSContractManagementSupported": false, - "isBmwChargingSupported": false, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": false, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false, - "isSustainabilitySupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2022-03-01T07:00:27Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "tires": { - "frontLeft": { - "status": { - "currentPressure": 280, - "localizedCurrentPressure": "2,8 bar", - "targetPressure": 290, - "localizedTargetPressure": "2,9 bar", - "wear": 0 - } - }, - "frontRight": { - "status": { - "currentPressure": 280, - "localizedCurrentPressure": "2,8 bar", - "targetPressure": 290, - "localizedTargetPressure": "2,9 bar", - "wear": 0 - } - }, - "rearLeft": { - "status": { - "currentPressure": 280, - "localizedCurrentPressure": "2,8 bar", - "targetPressure": 290, - "localizedTargetPressure": "2,9 bar", - "wear": 0 - } - }, - "rearRight": { - "status": { - "currentPressure": 280, - "localizedCurrentPressure": "2,8 bar", - "targetPressure": 290, - "localizedTargetPressure": "2,9 bar", - "wear": 0 - } - } - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 36, - "units": "LITERS" - }, - "fuelPercentage": { - "value": 69 - }, - "combustionRange": { - "distance": { - "value": 404, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-06-01T00:00:00.000Z", - "distance": { - "value": 29000, - "units": "KILOMETERS" - } - }, - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2024-06-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2024-08-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2025-06-01T00:00:00.000Z", - "distance": { - "value": 60000, - "units": "KILOMETERS" - } - }, - { - "type": "TIRE_WEAR_FRONT", - "status": "OK" - }, - { - "type": "TIRE_WEAR_REAR", - "status": "OK" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.1, - "longitude": 2.2 - }, - "address": { - "formatted": "anonymous" - }, - "heading": -1 - }, - "climateControl": { - "activity": "INACTIVE" - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 40, - "green": 94, - "blue": 201 - } - }, - "status": { - "lastUpdatedAt": "2022-03-01T07:00:27Z", - "currentMileage": { - "mileage": 4955, - "units": "km", - "formattedMileage": "4.955" - }, - "issues": null, - "doorsGeneralState": "Verriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Verriegelungsstatus", - "state": "Verriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Glasdach", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60117, - "title": "Reifen", - "state": "OK" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "title": "Motoröl", - "state": "OK" - } - ], - "requiredServices": [ - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Service nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im Juni 2023 oder in 29.000 km", - "criticalness": "nonCritical" - }, - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im Juni 2024", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im August 2024", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung zum angegebenen Termin oder nach der ggf. angegebenen Fahrstrecke.", - "subtitle": "Fällig im Juni 2025 oder in 60.000 km", - "criticalness": "nonCritical" - }, - { - "id": "TireWearFront", - "title": "Reifenservice Vorderreifen", - "iconId": 60447, - "subtitle": "OK", - "criticalness": "nonCritical" - }, - { - "id": "TireWearRear", - "title": "Reifenservice Hinterreifen", - "iconId": 60447, - "subtitle": "OK", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "secondaryBarValue": 0, - "infoIconId": 59930, - "infoLabel": "Tankfüllstand", - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "404", - "levelIconId": 59682, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "mainBarValue": 69, - "showsBar": true, - "levelUnits": "%", - "levelValue": "69", - "isInaccurate": false - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 1.3.2022 08:00 AM" - }, - "exFactoryPUStep": "0321", - "exFactoryILevel": "S18A-21-03-555" - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-sessions_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-sessions_0.json deleted file mode 100644 index 8c5769a61e373..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-sessions_0.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "chargingSessions": { - "chargingListState": "HAS_SESSIONS", - "numberOfSessions": "11", - "sessions": [ - { - "energyCharged": "~ 6 kWh", - "id": "2021-11-14T16:42:29Z_39df2d52", - "isPublic": true, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Today 5:42 PM" - }, - { - "energyCharged": "~ 11 kWh", - "id": "2021-11-13T18:15:24Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Yesterday 7:15 PM" - }, - { - "energyCharged": "~ 10 kWh", - "id": "2021-11-11T18:01:58Z_39df2d52", - "isPublic": true, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Thursday 7:01 PM" - }, - { - "energyCharged": "~ 12 kWh", - "id": "2021-11-09T20:33:19Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tuesday 9:33 PM" - }, - { - "energyCharged": "< 2 kWh", - "id": "2021-11-09T12:34:28Z_39df2d52", - "isPublic": true, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tuesday 1:34 PM" - }, - { - "energyCharged": "~ 7 kWh", - "id": "2021-11-08T18:31:20Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Monday 7:31 PM" - }, - { - "energyCharged": "~ 10 kWh", - "id": "2021-11-06T16:52:16Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "11/6/2021 5:52 PM" - }, - { - "energyCharged": "~ 10 kWh", - "id": "2021-11-04T18:24:22Z_39df2d52", - "isPublic": true, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "11/4/2021 7:24 PM" - }, - { - "energyCharged": "~ 6 kWh", - "id": "2021-11-03T14:24:06Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "11/3/2021 3:24 PM" - }, - { - "energyCharged": "~ 13 kWh", - "id": "2021-11-02T19:20:57Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "11/2/2021 8:20 PM" - }, - { - "energyCharged": "< 2 kWh", - "id": "2021-11-01T15:04:09Z_39df2d52", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "11/1/2021 4:04 PM" - } - ], - "total": "~ 87 kWh" - }, - "datePicker": { - "endDate": "2021-11-14T20:20:25Z", - "selectedDate": "2021-11-14T16:42:29Z", - "startDate": "2020-10-21T14:21:31Z" - }, - "paginationInfo": {} -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-statistics_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-statistics_0.json deleted file mode 100644 index a595ee9db4a8c..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/charging-statistics_0.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "November 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "numberOfChargingSessions": 11, - "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics", - "symbol": "~", - "totalEnergyCharged": 87, - "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/json_export.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/json_export.json deleted file mode 100644 index 7c98cb1348d85..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/json_export.json +++ /dev/null @@ -1,831 +0,0 @@ -{ - "attributes": { - "a4aType": "NOT_SUPPORTED", - "bodyType": "G21", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": true, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": true, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remote360": { - "isComingSoonEnabled": false, - "isDataPrivacyEnabled": false, - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "speechThirdPartyAlexa": { - "executionMessage": "Activate Alexa now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S18A-20-07-548", - "exFactoryPUStep": "0720", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S18A-21-07-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "330e xDrive", - "puStep": "0721", - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 201, - "green": 94, - "red": 40 - } - }, - "vin": "some_vin_G21", - "year": 2020 - }, - "status": { - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60117, - "state": "OK", - "title": "Tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "27138", - "mileage": 27138, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59757, - "state": "Locked", - "title": "Lock status" - }, - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59725, - "state": "Closed", - "title": "All windows" - }, - { - "criticalness": "nonCritical", - "iconId": 59706, - "state": "Closed", - "title": "Hood" - }, - { - "criticalness": "nonCritical", - "iconId": 59704, - "state": "Closed", - "title": "Trunk" - }, - { - "criticalness": "nonCritical", - "iconId": 59705, - "state": "Closed", - "title": "Sunroof" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "368", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "CHARGING", - "chargingStatusType": "CHARGING", - "chargingType": "charging", - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "100% at ~12:43 AM", - "isCircleIcon": true, - "isInaccurate": true, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "83", - "mainBarValue": 83, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "23", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59682, - "levelUnits": "%", - "levelValue": "83", - "mainBarValue": 83, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "345", - "secondaryBarValue": 0, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-14T20:20:21Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60447, - "id": "TireWearFront", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tire service, front tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60447, - "id": "TireWearRear", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tire service, rear tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Brake fluid" - } - ], - "timestampMessage": "Updated from vehicle 11/14/2021 09:20 PM" - }, - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 83, - "isChargerConnected": true, - "state": "CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 368 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 368 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "moonroof": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 23 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 83, - "distance": { - "units": "KILOMETERS", - "value": 23 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 29 - }, - "fuelPercentage": { - "value": 83 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-14T20:20:21Z", - "originCountryISO": "DE", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 6000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 39000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "status": "OK", - "type": "TIRE_WEAR_FRONT" - }, - { - "status": "OK", - "type": "TIRE_WEAR_REAR" - }, - { - "dateTime": "2023-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - } - ], - "tires": { - "frontLeft": { - "details": { - "dimension": "225/45 R18 95V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "35 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461777", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 260, - "localizedCurrentPressure": "2.6 bar", - "localizedTargetPressure": "2.7 bar", - "targetPressure": 270, - "wear": 0 - } - }, - "frontRight": { - "details": { - "dimension": "225/45 R18 95V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "27 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461777", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 250, - "localizedCurrentPressure": "2.5 bar", - "localizedTargetPressure": "2.7 bar", - "targetPressure": 270, - "wear": 0 - } - }, - "rearLeft": { - "details": { - "dimension": "255/40 R18 99V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "17 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461778", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 300, - "localizedCurrentPressure": "3.0 bar", - "localizedTargetPressure": "2.9 bar", - "targetPressure": 290, - "wear": 0 - } - }, - "rearRight": { - "details": { - "dimension": "255/40 R18 99V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "26 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461778", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 250, - "localizedCurrentPressure": "2.5 bar", - "localizedTargetPressure": "2.9 bar", - "targetPressure": 290, - "wear": 0 - } - } - }, - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "all_lids_closed": true, - "all_windows_closed": true, - "are_all_cbs_ok": true, - "are_parking_lights_on": null, - "charging_end_time": "2011-11-29 00:43:00+00:00", - "charging_level_hv": 83, - "charging_start_time": null, - "charging_status": "CHARGING", - "charging_time_label": "100% at ~12:43 AM", - "charging_time_remaining": 3.23, - "check_control_messages": [], - "condition_based_services": [ - { - "due_date": "2022-10-01 00:00:00+00:00", - "state": "OK", - "service_type": "OIL", - "due_distance": [ - 6000, - "KILOMETERS" - ], - "description": null - }, - { - "due_date": "2024-10-01 00:00:00+00:00", - "state": "OK", - "service_type": "VEHICLE_CHECK", - "due_distance": [ - 39000, - "KILOMETERS" - ], - "description": null - }, - { - "due_date": null, - "state": "OK", - "service_type": "TIRE_WEAR_FRONT", - "due_distance": null, - "description": null - }, - { - "due_date": null, - "state": "OK", - "service_type": "TIRE_WEAR_REAR", - "due_distance": null, - "description": null - }, - { - "due_date": "2023-10-01 00:00:00+00:00", - "state": "OK", - "service_type": "BRAKE_FLUID", - "due_distance": null, - "description": null - } - ], - "connection_status": "CONNECTED", - "door_lock_state": "LOCKED", - "fuel_indicator_count": 3, - "fuel_percent": 83, - "gps_heading": 123, - "gps_position": [ - 12.3456, - 34.5678 - ], - "has_check_control_messages": false, - "has_parking_light_state": false, - "is_vehicle_active": false, - "last_charging_end_result": null, - "last_update_reason": "Updated from vehicle 11/14/2021 09:20 PM", - "lids": [ - { - "name": "hood", - "state": "CLOSED" - }, - { - "name": "trunk", - "state": "CLOSED" - }, - { - "name": "driverFront", - "state": "CLOSED" - }, - { - "name": "driverRear", - "state": "CLOSED" - }, - { - "name": "passengerFront", - "state": "CLOSED" - }, - { - "name": "passengerRear", - "state": "CLOSED" - } - ], - "max_range_electric": null, - "mileage": [ - 27138, - "km" - ], - "open_lids": [], - "open_windows": [], - "parking_lights": null, - "remaining_fuel": [ - 29, - "LITERS" - ], - "remaining_range_electric": [ - 23, - "km" - ], - "remaining_range_fuel": [ - 345, - "km" - ], - "remaining_range_total": [ - 368, - "km" - ], - "timestamp": "2021-11-14 20:20:21+00:00", - "windows": [ - { - "name": "driverFront", - "state": "CLOSED" - }, - { - "name": "driverRear", - "state": "CLOSED" - }, - { - "name": "passengerFront", - "state": "CLOSED" - }, - { - "name": "passengerRear", - "state": "CLOSED" - }, - { - "name": "moonroof", - "state": "CLOSED" - } - ] - }, - "observer_latitude": null, - "observer_longitude": null, - "available_attributes": [ - "gps_position", - "vin", - "remaining_range_total", - "mileage", - "charging_time_remaining", - "charging_start_time", - "charging_end_time", - "charging_time_label", - "charging_status", - "charging_level_hv", - "connection_status", - "remaining_range_electric", - "last_charging_end_result", - "remaining_fuel", - "remaining_range_fuel", - "fuel_percent", - "condition_based_services", - "check_control_messages", - "door_lock_state", - "timestamp", - "last_update_reason", - "lids", - "windows" - ], - "available_state_services": [ - "status" - ], - "brand": "bmw", - "charging_profile": { - "charging_profile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "charging_mode": "immediateCharging", - "charging_preferences": "noPreSelection", - "is_pre_entry_climatization_enabled": false, - "preferred_charging_window": { - "end_time": "00:00", - "start_time": "00:00" - }, - "timer": { - "1": { - "action": "deactivate", - "start_time": "00:00", - "timer_id": 1, - "weekdays": [] - }, - "2": { - "action": "deactivate", - "start_time": "00:00", - "timer_id": 2, - "weekdays": [] - }, - "3": { - "action": "deactivate", - "start_time": "00:00", - "timer_id": 3, - "weekdays": [] - }, - "4": { - "action": "deactivate", - "start_time": null, - "timer_id": 4, - "weekdays": [] - } - } - }, - "drive_train": "PLUGIN_HYBRID", - "drive_train_attributes": [ - "remaining_range_total", - "mileage", - "charging_time_remaining", - "charging_start_time", - "charging_end_time", - "charging_time_label", - "charging_status", - "charging_level_hv", - "connection_status", - "remaining_range_electric", - "last_charging_end_result", - "remaining_fuel", - "remaining_range_fuel", - "fuel_percent" - ], - "has_hv_battery": true, - "has_internal_combustion_engine": true, - "has_range_extender": false, - "has_weekly_planner_service": true, - "is_vehicle_tracking_enabled": true, - "lsc_type": "ACTIVATED", - "name": "330e xDrive" -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/vehicles_v2_bmw_0.json deleted file mode 100644 index 0d4a372eb6eb0..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G21/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,542 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "G21", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": true, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": true, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remote360": { - "isComingSoonEnabled": false, - "isDataPrivacyEnabled": false, - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "speechThirdPartyAlexa": { - "executionMessage": "Activate Alexa now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S18A-20-07-548", - "exFactoryPUStep": "0720", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S18A-21-07-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "330e xDrive", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 83, - "isChargerConnected": true, - "state": "CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 368 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 368 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "moonroof": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 23 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 83, - "distance": { - "units": "KILOMETERS", - "value": 23 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 29 - }, - "fuelPercentage": { - "value": 83 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-14T20:20:21Z", - "originCountryISO": "DE", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 6000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 39000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "status": "OK", - "type": "TIRE_WEAR_FRONT" - }, - { - "status": "OK", - "type": "TIRE_WEAR_REAR" - }, - { - "dateTime": "2023-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - } - ], - "tires": { - "frontLeft": { - "details": { - "dimension": "225/45 R18 95V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "35 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461777", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 260, - "localizedCurrentPressure": "2.6 bar", - "localizedTargetPressure": "2.7 bar", - "targetPressure": 270, - "wear": 0 - } - }, - "frontRight": { - "details": { - "dimension": "225/45 R18 95V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "27 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461777", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 250, - "localizedCurrentPressure": "2.5 bar", - "localizedTargetPressure": "2.7 bar", - "targetPressure": 270, - "wear": 0 - } - }, - "rearLeft": { - "details": { - "dimension": "255/40 R18 99V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "17 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461778", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 300, - "localizedCurrentPressure": "3.0 bar", - "localizedTargetPressure": "2.9 bar", - "targetPressure": 290, - "wear": 0 - } - }, - "rearRight": { - "details": { - "dimension": "255/40 R18 99V XL", - "manufacturer": "Pirelli", - "manufacturingWeek": "26 / 19", - "maxSpeed": "240 km/h", - "mountingDate": "11/12/2021", - "optimizedForOemBmw": "Yes", - "partNumber": "2461778", - "season": "Winter tires", - "treadDesign": "SOTTOZERO 3" - }, - "status": { - "currentPressure": 250, - "localizedCurrentPressure": "2.5 bar", - "localizedTargetPressure": "2.9 bar", - "targetPressure": 290, - "wear": 0 - } - } - }, - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0721", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timeStamp": { - "hour": 0, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60117, - "state": "OK", - "title": "Tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "27138", - "mileage": 27138, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59757, - "state": "Locked", - "title": "Lock status" - }, - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59725, - "state": "Closed", - "title": "All windows" - }, - { - "criticalness": "nonCritical", - "iconId": 59706, - "state": "Closed", - "title": "Hood" - }, - { - "criticalness": "nonCritical", - "iconId": 59704, - "state": "Closed", - "title": "Trunk" - }, - { - "criticalness": "nonCritical", - "iconId": 59705, - "state": "Closed", - "title": "Sunroof" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "368", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "CHARGING", - "chargingStatusType": "CHARGING", - "chargingType": "charging", - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "100% at ~12:43 AM", - "isCircleIcon": true, - "isInaccurate": true, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "83", - "mainBarValue": 83, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "23", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59682, - "levelUnits": "%", - "levelValue": "83", - "mainBarValue": 83, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "345", - "secondaryBarValue": 0, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-14T20:20:21Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60447, - "id": "TireWearFront", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tire service, front tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60447, - "id": "TireWearRear", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Tire service, rear tires" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "some_road \u2022 duration \u2022 -- EUR", - "title": "Brake fluid" - } - ], - "timestampMessage": "Updated from vehicle 11/14/2021 09:20 PM" - }, - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 201, - "green": 94, - "red": 40 - } - }, - "vin": "some_vin_G21", - "year": 2020 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-sessions_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-sessions_0.json deleted file mode 100644 index e28b736b787fb..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-sessions_0.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "chargingSessions": { - "chargingListState": "HAS_SESSIONS", - "numberOfSessions": "6", - "sessions": [ - { - "energyCharged": "~ 13 kWh", - "id": "2021-11-10T11:24:34Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "Yesterday 11:24 AM" - }, - { - "energyCharged": "~ 11 kWh", - "id": "2021-11-08T16:53:02Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "Monday 4:53 PM" - }, - { - "energyCharged": "~ 12 kWh", - "id": "2021-11-07T13:35:27Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "Sunday 1:35 PM" - }, - { - "energyCharged": "~ 13 kWh", - "id": "2021-11-05T10:53:57Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "Friday 10:53 AM" - }, - { - "energyCharged": "~ 10 kWh", - "id": "2021-11-03T10:00:47Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "11/3/2021 10:00 AM" - }, - { - "energyCharged": "~ 12 kWh", - "id": "2021-11-02T12:31:45Z_e51ab124", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_road 2022 duration 2022 -- EUR", - "title": "11/2/2021 12:31 PM" - } - ], - "total": "~ 71 kWh" - }, - "datePicker": { - "endDate": "2021-11-11T09:09:49Z", - "selectedDate": "2021-11-10T11:24:34Z", - "startDate": "2021-08-16T19:31:45Z" - }, - "paginationInfo": {} -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-statistics_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-statistics_0.json deleted file mode 100644 index 7089ad36280bd..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/charging-statistics_0.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "November 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "numberOfChargingSessions": 6, - "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics", - "symbol": "~", - "totalEnergyCharged": 71, - "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/vehicles_v2_bmw_0.json deleted file mode 100644 index fbcadbc4d967c..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/G30/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,384 +0,0 @@ -[ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "G30", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "remote360": { - "isComingSoonEnabled": false, - "isDataPrivacyEnabled": false, - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true - }, - "remoteSoftwareUpgrade": { - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "speechThirdPartyAlexa": { - "executionMessage": "Activate Alexa now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "S15A-20-07-532", - "exFactoryPUStep": "0720", - "headUnit": "MGU", - "hmiVersion": "id7", - "iStep": "S15A-21-03-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "530e", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 41, - "isChargerConnected": false, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": { - "activity": "INACTIVE" - }, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 116 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 116 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "OPEN", - "driverRear": "CLOSED", - "passengerFront": "OPEN", - "passengerRear": "CLOSED" - }, - "hood": "OPEN", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "OPEN", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 9 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 41, - "distance": { - "units": "KILOMETERS", - "value": 9 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 11 - }, - "fuelPercentage": { - "value": 28 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-11T08:58:53Z", - "originCountryISO": "IE", - "serviceRequired": [ - { - "dateTime": "2022-08-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 25000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2023-08-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2024-08-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 60000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0321", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "noPreSelection", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": true, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "7991", - "mileage": 7991, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "116", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "DEFAULT", - "chargingStatusType": "DEFAULT", - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59694, - "infoLabel": "State of Charge", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59694, - "levelUnits": "%", - "levelValue": "41", - "mainBarValue": 41, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "9", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59682, - "levelUnits": "%", - "levelValue": "28", - "mainBarValue": 28, - "rangeIconId": 59681, - "rangeUnits": "km", - "rangeValue": "107", - "secondaryBarValue": 0, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-11T08:58:53Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in August 2022 or 25000 km", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in August 2023", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in August 2024 or 60000 km", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 11/12/2021 08:58 AM" - }, - "telematicsUnit": "ATM02", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 158, - "green": 158, - "red": 158 - } - }, - "vin": "some_vin_G30", - "year": 2020 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-sessions_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-sessions_0.json deleted file mode 100644 index a092c6366d435..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-sessions_0.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "chargingSessions": { - "chargingListState": "HAS_SESSIONS", - "numberOfSessions": "1", - "sessions": [ - { - "energyCharged": "~ 36 kWh", - "id": "2021-11-09T18:06:18Z_some_id", - "isPublic": false, - "sessionStatus": "FINISHED", - "subtitle": "some_place \u2022 3h 42min \u2022 -- EUR", - "title": "Tuesday 7:06 PM" - } - ], - "total": "~ 36 kWh" - }, - "datePicker": { - "endDate": "2021-11-11T00:44:47Z", - "selectedDate": "2021-11-09T18:06:18Z", - "startDate": "2021-08-08T15:51:27Z" - }, - "paginationInfo": {} -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-statistics_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-statistics_0.json deleted file mode 100644 index 8ca915abdfdbc..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/charging-statistics_0.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "November 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "numberOfChargingSessions": 1, - "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics", - "symbol": "~", - "totalEnergyCharged": 36, - "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/vehicles_v2_bmw_0.json deleted file mode 100644 index 80ba8a41387a7..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_NOREX/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,316 +0,0 @@ -[ - { - "a4aType": "BLUETOOTH", - "bodyType": "I01", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": true, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": true, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "speechThirdPartyAlexa": { - "executionMessage": "Activate Alexa now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [ - "WIFI_HOTSPOT_SERVICE" - ], - "driveTrain": "ELECTRIC", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "I001-20-11-520", - "exFactoryPUStep": "1120", - "headUnit": "ID5", - "hmiVersion": "ID5", - "iStep": "I001-20-11-520", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "i3 120", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 94, - "isChargerConnected": false, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE" - }, - "checkControlMessages": [], - "climateControl": {}, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 0 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 229 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 94, - "distance": { - "units": "KILOMETERS", - "value": 229 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 0 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-10T18:18:05Z", - "originCountryISO": "DE", - "serviceRequired": [ - { - "dateTime": "2023-02-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2023-02-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2024-03-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_TUV" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "1120", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 3, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 0, - "minute": 0 - }, - "start": { - "hour": 0, - "minute": 0 - } - } - }, - "checkControlMessages": [], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "1250", - "mileage": 1250, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "barType": null, - "chargingStatusIndicatorType": "DEFAULT", - "chargingStatusType": "DEFAULT", - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59694, - "infoLabel": "State of Charge", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59694, - "levelUnits": "%", - "levelValue": "94", - "mainBarValue": 94, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "229", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-10T18:18:05Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in February 2023", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in February 2023", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60111, - "id": "VehicleAdmissionTest", - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in March 2024", - "title": "Vehicle Inspection" - } - ], - "timestampMessage": "Updated from vehicle 11/10/2021 07:18 PM" - }, - "telematicsUnit": "ATM", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 152, - "green": 154, - "red": 156 - } - }, - "vin": "some_vin_I01_NOREX", - "year": 2021 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-sessions.json deleted file mode 100644 index 0ea498814ce89..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-sessions.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "paginationInfo": { - - }, - "chargingSessions": { - "total": "~ 218 kWh", - "numberOfSessions": "17", - "chargingListState": "HAS_SESSIONS", - "sessions": [ - { - "id": "2021-12-26T16:57:20Z_128fa4af", - "title": "Gestern 17:57", - "subtitle": "Uferstraße 4B • 7h 45min • -- EUR", - "energyCharged": "~ 31 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-26T16:02:49Z_128fa4af", - "title": "Gestern 17:02", - "subtitle": "Uferstraße 4C • 32 min • -- EUR", - "energyCharged": "~ 2 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-26T09:44:36Z_128fa4af", - "title": "Gestern 10:44", - "subtitle": "Kelzer Weg 24 • 58 min • -- EUR", - "energyCharged": "~ 2 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-25T22:18:46Z_128fa4af", - "title": "Samstag 23:18", - "subtitle": "Kelzer Weg 24 • 3h 42min • -- EUR", - "energyCharged": "~ 8 kWh", - "issues": "2 Probleme", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-24T11:56:09Z_128fa4af", - "title": "Freitag 12:56", - "subtitle": "Kelzer Weg 24 • 8h 46min • -- EUR", - "energyCharged": "~ 19 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-24T09:56:13Z_128fa4af", - "title": "Freitag 10:56", - "subtitle": "Kelzer Weg 15A • 1h 48min • -- EUR", - "energyCharged": "~ 4 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-23T07:55:12Z_128fa4af", - "title": "Donnerstag 08:55", - "subtitle": "Uferstraße 4C • 2h 55min • -- EUR", - "energyCharged": "~ 21 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-20T09:42:28Z_128fa4af", - "title": "20.12.2021 10:42", - "subtitle": "Hermannsteiner Straße 13 • 1h 14min", - "energyCharged": "~ 21 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-20T09:37:51Z_128fa4af", - "title": "20.12.2021 10:37", - "subtitle": "Hermannsteiner Straße 13 • < 1 min", - "energyCharged": "< 2 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-19T12:07:18Z_128fa4af", - "title": "19.12.2021 13:07", - "subtitle": "Uferstraße 4B • 2h 07min • -- EUR", - "energyCharged": "~ 9 kWh", - "issues": "1 Problem", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-18T10:56:31Z_128fa4af", - "title": "18.12.2021 11:56", - "subtitle": "Uferstraße 4C • 41 min • -- EUR", - "energyCharged": "~ 5 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-16T11:08:30Z_128fa4af", - "title": "16.12.2021 12:08", - "subtitle": "Uferstraße 4B • 2h 07min • -- EUR", - "energyCharged": "~ 9 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-15T12:38:23Z_128fa4af", - "title": "15.12.2021 13:38", - "subtitle": "Uferstraße 4C • 1h 55min • -- EUR", - "energyCharged": "~ 8 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-12T10:48:16Z_128fa4af", - "title": "12.12.2021 11:48", - "subtitle": "Uferstraße 4C • 6h 06min • -- EUR", - "energyCharged": "~ 23 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-07T16:09:02Z_128fa4af", - "title": "07.12.2021 17:09", - "subtitle": "Uferstraße 4B • 5h 40min • -- EUR", - "energyCharged": "~ 21 kWh", - "issues": "1 Problem", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-04T09:51:23Z_128fa4af", - "title": "04.12.2021 10:51", - "subtitle": "L3053 • 1h 24min", - "energyCharged": "~ 22 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - }, - { - "id": "2021-12-02T13:42:28Z_128fa4af", - "title": "02.12.2021 14:42", - "subtitle": "Uferstraße 4C • 2h 29min • -- EUR", - "energyCharged": "~ 11 kWh", - "sessionStatus": "FINISHED", - "isPublic": false - } - ] - }, - "datePicker": { - "startDate": "2020-11-07T09:58:20Z", - "selectedDate": "2021-12-26T16:57:20Z", - "endDate": "2021-12-27T16:10:53Z" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-de.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-de.json deleted file mode 100644 index e368f22874a31..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-de.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "Dezember 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "totalEnergyCharged": 173, - "totalEnergyChargedSemantics": "Insgesamt circa 173 Kilowattstunden geladen", - "symbol": "~", - "numberOfChargingSessions": 13, - "numberOfChargingSessionsSemantics": "13 Ladevorgänge" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-en.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-en.json deleted file mode 100644 index a75fb1d1b894d..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charge-statistics-en.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "December 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "totalEnergyCharged": 173, - "totalEnergyChargedSemantics": "Charged a total of approximately 173 kilowatt-hours", - "symbol": "~", - "numberOfChargingSessions": 13, - "numberOfChargingSessionsSemantics": "13 charging sessions" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charging-statistics_1.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charging-statistics_1.json deleted file mode 100644 index 978aeca660800..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/charging-statistics_1.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "description": "November 2021", - "optStateType": "OPT_IN_WITH_SESSIONS", - "statistics": { - "numberOfChargingSessions": 15, - "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics", - "symbol": "~", - "totalEnergyCharged": 144, - "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics" - } -} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-charging.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-charging.json deleted file mode 100644 index 4fb246173f870..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-charging.json +++ /dev/null @@ -1,427 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Flash headlights now? Remote functions may take a few seconds." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Send POI now? Remote functions may take a few seconds." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "popupType": "DIALOG", - "title": "Start Climatization", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2021-12-25T22:29:22Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 9, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 86, - "state": "CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": true - }, - "combustionRange": { - "distance": { - "value": 97, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "distance": { - "value": 97, - "units": "KILOMETERS" - } - }, - "electricRange": { - "distance": { - "value": 121, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 86, - "distance": { - "value": 121, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.234, - "longitude": 9.876 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 73 - }, - "climateControl": { - - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 156, - "green": 154, - "blue": 152 - } - }, - "status": { - "lastUpdatedAt": "2021-12-25T22:29:22Z", - "currentMileage": { - "mileage": 31746, - "units": "km", - "formattedMileage": "31746" - }, - "issues": { - - }, - "doorsGeneralState": "Locked", - "checkControlMessagesGeneralState": "No Issues", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Lock status", - "state": "Locked", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "All doors", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "All windows", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Hood", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Trunk", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Sunroof", - "state": "Closed", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Brake fluid", - "iconId": 60223, - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Vehicle check", - "iconId": 60215, - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Engine oil", - "iconId": 60197, - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Vehicle Inspection", - "iconId": 60111, - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "mainBarValue": 86, - "secondaryBarValue": 0, - "infoIconId": 59689, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "121", - "levelIconId": 59689, - "showsBar": true, - "levelUnits": "%", - "levelValue": "86", - "showBarGoal": false, - "barType": null, - "infoLabel": "100% at ~02:59 AM", - "isInaccurate": true, - "isCircleIcon": true, - "iconOpacity": "high", - "chargingType": "charging", - "chargingStatusType": "CHARGING", - "chargingStatusIndicatorType": "CHARGING" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59691, - "infoLabel": "Combined Range", - "rangeIconId": 59691, - "rangeUnits": "km", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "rangeValue": "218" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59681, - "infoLabel": "Extended Range", - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "97", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null - } - ], - "timestampMessage": "Updated from vehicle 12/25/2021 11:29 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "timeStamp": { - "hour": 16, - "minute": 0 - } - }, - { - "id": 2, - "action": "activate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [ - "saturday" - ], - "timeStamp": { - "hour": 13, - "minute": 3 - } - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500" - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-fully-charged.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-fully-charged.json deleted file mode 100644 index cf524ebb9c644..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicle-fully-charged.json +++ /dev/null @@ -1,423 +0,0 @@ - [ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern.", - "executionPopup": { - "executionMessage": "Jetzt klimatisieren? Remote-Funktionen können einige Sekunden dauern.", - "popupType": "DIALOG", - "title": "Klimatisierung starten", - "primaryButtonText": "Start", - "secondaryButtonText": "Abbrechen", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Jetzt Klimatisierung Ihres Fahrzeugs beenden? Remote-Funktionen können einige Sekunden dauern.", - "title": "Klimatisierung läuft" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2022-01-04T21:04:49Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 7, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 100, - "state": "COMPLETE", - "type": "NOT_AVAILABLE", - "isChargerConnected": true - }, - "combustionRange": { - "distance": { - "value": 90, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "distance": { - "value": 90, - "units": "KILOMETERS" - } - }, - "electricRange": { - "distance": { - "value": 162, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 100, - "distance": { - "value": 162, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.2345, - "longitude": 9.876 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 39 - }, - "climateControl": null - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 156, - "green": 154, - "blue": 152 - } - }, - "status": { - "lastUpdatedAt": "2022-01-04T21:04:49Z", - "currentMileage": { - "mileage": 32219, - "units": "km", - "formattedMileage": "32.219" - }, - "issues": null, - "doorsGeneralState": "Verriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Verriegelungsstatus", - "state": "Verriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Glasdach", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Wechsel nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "mainBarValue": 100, - "secondaryBarValue": 0, - "infoIconId": 59689, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "162", - "levelIconId": 59689, - "showsBar": true, - "levelUnits": "%", - "levelValue": "100", - "showBarGoal": false, - "barType": null, - "infoLabel": "Voll geladen", - "isInaccurate": false, - "isCircleIcon": true, - "iconOpacity": "high", - "chargingType": "charging_complete", - "chargingStatusType": "FULLY_CHARGED", - "chargingStatusIndicatorType": "FULLY_CHARGED" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59691, - "infoLabel": "Kombinierte Reichweite", - "rangeIconId": 59691, - "rangeUnits": "km", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "rangeValue": "252" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59681, - "infoLabel": "Erweiterte Reichweite", - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "90", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 4.1.2022 10:04 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "timeStamp": { - "hour": 16, - "minute": 0 - } - }, - { - "id": 2, - "action": "activate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [ - "saturday" - ], - "timeStamp": { - "hour": 13, - "minute": 3 - } - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500" - } -] diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles-de.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles-de.json deleted file mode 100644 index 3be2f71d756a1..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles-de.json +++ /dev/null @@ -1,427 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern.", - "executionPopup": { - "executionMessage": "Jetzt klimatisieren? Remote-Funktionen können einige Sekunden dauern.", - "popupType": "DIALOG", - "title": "Klimatisierung starten", - "primaryButtonText": "Start", - "secondaryButtonText": "Abbrechen", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Jetzt Klimatisierung Ihres Fahrzeugs beenden? Remote-Funktionen können einige Sekunden dauern.", - "title": "Klimatisierung läuft" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2021-12-26T09:56:05Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 9, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 100, - "state": "CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": true - }, - "combustionRange": { - "distance": { - "value": 98, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "distance": { - "value": 98, - "units": "KILOMETERS" - } - }, - "electricRange": { - "distance": { - "value": 146, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 100, - "distance": { - "value": 146, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.234, - "longitude": 5.678 - }, - "address": { - "formatted": "where-ever" - }, - "heading": 73 - }, - "climateControl": { - - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 156, - "green": 154, - "blue": 152 - } - }, - "status": { - "lastUpdatedAt": "2021-12-26T09:56:05Z", - "currentMileage": { - "mileage": 31746, - "units": "km", - "formattedMileage": "31.746" - }, - "issues": { - - }, - "doorsGeneralState": "Verriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Verriegelungsstatus", - "state": "Verriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Glasdach", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Wechsel nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "mainBarValue": 100, - "secondaryBarValue": 0, - "infoIconId": 59689, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "146", - "levelIconId": 59689, - "showsBar": true, - "levelUnits": "%", - "levelValue": "100", - "showBarGoal": false, - "barType": null, - "infoLabel": "um ~11:21 AM", - "isInaccurate": true, - "isCircleIcon": true, - "iconOpacity": "high", - "chargingType": "charging", - "chargingStatusType": "CHARGING", - "chargingStatusIndicatorType": "CHARGING" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59691, - "infoLabel": "Kombinierte Reichweite", - "rangeIconId": 59691, - "rangeUnits": "km", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "rangeValue": "244" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59681, - "infoLabel": "Erweiterte Reichweite", - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "98", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 26.12.2021 10:56 AM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "timeStamp": { - "hour": 16, - "minute": 0 - } - }, - { - "id": 2, - "action": "activate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [ - "saturday" - ], - "timeStamp": { - "hour": 13, - "minute": 3 - } - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500" - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles.json deleted file mode 100644 index 15afdbad8ca54..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles.json +++ /dev/null @@ -1,427 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Flash headlights now? Remote functions may take a few seconds." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Send POI now? Remote functions may take a few seconds." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "popupType": "DIALOG", - "title": "Start Climatization", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2021-12-21T16:46:02Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 4, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 74, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": false - }, - "combustionRange": { - "distance": { - "value": 31, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "distance": { - "value": 31, - "units": "KILOMETERS" - } - }, - "electricRange": { - "distance": { - "value": 76, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 74, - "distance": { - "value": 76, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 54.321, - "longitude": 9.876 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 222 - }, - "climateControl": { - - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 156, - "green": 154, - "blue": 152 - } - }, - "status": { - "lastUpdatedAt": "2021-12-21T16:46:02Z", - "currentMileage": { - "mileage": 31537, - "units": "km", - "formattedMileage": "31537" - }, - "issues": { - - }, - "doorsGeneralState": "Locked", - "checkControlMessagesGeneralState": "No Issues", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Lock status", - "state": "Locked", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "All doors", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "All windows", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Hood", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Trunk", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Sunroof", - "state": "Closed", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Brake fluid", - "iconId": 60223, - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Vehicle check", - "iconId": 60215, - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Engine oil", - "iconId": 60197, - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Vehicle Inspection", - "iconId": 60111, - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "mainBarValue": 74, - "secondaryBarValue": 0, - "infoIconId": 59694, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "76", - "levelIconId": 59694, - "showsBar": true, - "levelUnits": "%", - "levelValue": "74", - "showBarGoal": false, - "barType": null, - "infoLabel": "State of Charge", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "chargingStatusType": "DEFAULT", - "chargingStatusIndicatorType": "DEFAULT" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59691, - "infoLabel": "Combined Range", - "rangeIconId": 59691, - "rangeUnits": "km", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "rangeValue": "107" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59681, - "infoLabel": "Extended Range", - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "31", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null - } - ], - "timestampMessage": "Updated from vehicle 12/21/2021 05:46 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "timeStamp": { - "hour": 16, - "minute": 0 - } - }, - { - "id": 2, - "action": "activate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [ - "saturday" - ], - "timeStamp": { - "hour": 13, - "minute": 3 - } - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500" - } -] diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles_v2_bmw_0.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles_v2_bmw_0.json deleted file mode 100644 index f8d79739cea58..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/I01_REX/vehicles_v2_bmw_0.json +++ /dev/null @@ -1,387 +0,0 @@ -[ - { - "a4aType": "USB_ONLY", - "bodyType": "I01", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "horn": { - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "ELECTRIC", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "I001-15-03-502", - "exFactoryPUStep": "0315", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "I001-21-03-530", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "i3 (+ REX)", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": false, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 100, - "isChargerConnected": true, - "state": "COMPLETE", - "type": "CONDUCTIVE" - }, - "checkControlMessages": [], - "climateControl": {}, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 64 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 64 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "moonroof": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 164 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 100, - "distance": { - "units": "KILOMETERS", - "value": 164 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 5 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-11T06:49:47Z", - "originCountryISO": "CZ", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2023-05-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_CHECK" - }, - { - "dateTime": "2023-05-01T00:00:00.000Z", - "status": "OK", - "type": "VEHICLE_TUV" - } - ], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0321", - "status": { - "chargingProfile": { - "chargingControlType": "weeklyPlanner", - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": true, - "departureTimes": [ - { - "action": "activate", - "id": 1, - "timeStamp": { - "hour": 7, - "minute": 35 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday" - ] - }, - { - "action": "deactivate", - "id": 2, - "timeStamp": { - "hour": 18, - "minute": 0 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ] - }, - { - "action": "deactivate", - "id": 3, - "timeStamp": { - "hour": 7, - "minute": 0 - }, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 4, - "timeStamp": { - "hour": 7, - "minute": 35 - }, - "timerWeekDays": [ - "friday" - ] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 1, - "minute": 30 - }, - "start": { - "hour": 18, - "minute": 1 - } - } - }, - "checkControlMessages": [], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "124462", - "mileage": 124462, - "units": "km" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Unlocked", - "fuelIndicators": [ - { - "barType": null, - "chargingStatusIndicatorType": "FULLY_CHARGED", - "chargingStatusType": "FULLY_CHARGED", - "chargingType": "charging_complete", - "iconOpacity": "high", - "infoIconId": 59689, - "infoLabel": "Fully Charged", - "isCircleIcon": true, - "isInaccurate": false, - "levelIconId": 59689, - "levelUnits": "%", - "levelValue": "100", - "mainBarValue": 100, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "164", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "km", - "rangeValue": "228", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59681, - "infoLabel": "Extended Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "64", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "issues": { - "doorsAndWindows": { - "iconId": 59737, - "title": "Vehicle unlocked" - } - }, - "lastUpdatedAt": "2021-11-11T06:49:47Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in October 2022", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in May 2023", - "title": "Vehicle check" - }, - { - "criticalness": "nonCritical", - "iconId": 60111, - "id": "VehicleAdmissionTest", - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in May 2023", - "title": "Vehicle Inspection" - } - ], - "timestampMessage": "Updated from vehicle 11/11/2021 07:49 AM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 86, - "green": 88, - "red": 90 - } - }, - "vin": "some_vin_I01_REX", - "year": 2015 - } -] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_sessions.json new file mode 100644 index 0000000000000..47c9cbddb05ae --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_sessions.json @@ -0,0 +1,14 @@ +{ + "chargingSessions": { + "total": "0 kWh", + "numberOfSessions": "0", + "emptyStateDescription": "Charge your BMW and track your charging history.\nSession data collected from 7/27/2020 on, your activation date.", + "chargingListState": "CHARGE_AND_TRACK", + "costsGroupedByCurrency": [] + }, + "datePicker": { + "startDate": "2020-07-27T00:00:00Z", + "selectedDate": "2023-01-21T18:57:42Z", + "endDate": "2023-01-21T18:57:43Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_statistics.json new file mode 100644 index 0000000000000..321efde21e348 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/charging_statistics.json @@ -0,0 +1,8 @@ +{ + "description": "Charge your BMW and track your charging history. Session data collected from 1/1/0001 on, your activation date.", + "optStateType": "OPT_IN_WITHOUT_SESSIONS", + "statistics": { + "totalEnergyCharged": 0, + "numberOfChargingSessions": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_base.json new file mode 100644 index 0000000000000..c77c559a52763 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_base.json @@ -0,0 +1,48 @@ +[ + { + "vin": "anonymousICE", + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2022-12-22T18:58:29.700Z", + "model": "Cooper", + "year": 2022, + "color": 4290295992, + "brand": "MINI", + "driveTrain": "COMBUSTION", + "headUnitType": "ENTRY_EVO", + "headUnitRaw": "ENAVEVO", + "hmiVersion": "ID5", + "softwareVersionCurrent": { + "puStep": { + "month": 3, + "year": 22 + }, + "iStep": 580, + "seriesCluster": "F056" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 3, + "year": 22 + }, + "iStep": 580, + "seriesCluster": "F056" + }, + "telematicsUnit": "ATM1", + "bodyType": "F56", + "countryOfOrigin": "DE", + "driverGuideInfo": { + "androidAppScheme": "com.mini.driversguide.row", + "iosAppScheme": "minidriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.mini.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id834510424?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_state.json new file mode 100644 index 0000000000000..c921334b261e7 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE/vehicles_state.json @@ -0,0 +1,166 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-02-01T21:30:24.651Z", + "lastUpdatedAt": "2023-02-01T21:23:36Z", + "isLscSupported": true, + "range": 123, + "doorsState": { + "combinedSecurityState": "PARTIALLY_LOCKED", + "leftFront": "CLOSED", + "rightFront": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "rightFront": "CLOSED", + "combinedState": "CLOSED" + }, + "location": { + "coordinates": { + "latitude": 1.23, + "longitude": 3.45 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": 173 + }, + "currentMileage": 1358, + "requiredServices": [ + { + "dateTime": "2025-10-01T00:00:00.000Z", + "type": "VEHICLE_TUV", + "status": "OK", + "description": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin." + }, + { + "dateTime": "2024-09-01T00:00:00.000Z", + "mileage": 30000, + "type": "OIL", + "status": "OK", + "description": "Nächster Service nach der angegebenen Fahrstrecke oder zum angegebenen Termin." + }, + { + "dateTime": "2026-09-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Nächste Sichtprüfung zum angegebenen Termin oder nach der ggf. angegebenen Fahrstrecke." + }, + { + "dateTime": "2025-09-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Nächster Wechsel spätestens zum angegebenen Termin." + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW", + "id": 955, + "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.", + "name": "Tire pressure notification" + }, + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "combustionFuelLevel": { + "remainingFuelLiters": 8, + "range": 123 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "BLUETOOTH", + "climateNow": true, + "isClimateTimerSupported": true, + "climateTimerTrigger": "START_TIMER", + "climateFunction": "VENTILATION", + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": true, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": false, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": null, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_sessions.json new file mode 100644 index 0000000000000..47c9cbddb05ae --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_sessions.json @@ -0,0 +1,14 @@ +{ + "chargingSessions": { + "total": "0 kWh", + "numberOfSessions": "0", + "emptyStateDescription": "Charge your BMW and track your charging history.\nSession data collected from 7/27/2020 on, your activation date.", + "chargingListState": "CHARGE_AND_TRACK", + "costsGroupedByCurrency": [] + }, + "datePicker": { + "startDate": "2020-07-27T00:00:00Z", + "selectedDate": "2023-01-21T18:57:42Z", + "endDate": "2023-01-21T18:57:43Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_statistics.json new file mode 100644 index 0000000000000..321efde21e348 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/charging_statistics.json @@ -0,0 +1,8 @@ +{ + "description": "Charge your BMW and track your charging history. Session data collected from 1/1/0001 on, your activation date.", + "optStateType": "OPT_IN_WITHOUT_SESSIONS", + "statistics": { + "totalEnergyCharged": 0, + "numberOfChargingSessions": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_base.json new file mode 100644 index 0000000000000..a474f82599b7b --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_base.json @@ -0,0 +1,49 @@ +[ + { + "vin": "anonymousICE2", + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-21T18:57:39.114Z", + "model": "X3 xDrive20d", + "year": 2018, + "color": 4284900966, + "brand": "BMW", + "driveTrain": "COMBUSTION", + "headUnitType": "NBT_EVO", + "headUnitRaw": "NBTEVO", + "hmiVersion": "ID5", + "softwareVersionCurrent": { + "puStep": { + "month": 3, + "year": 22 + }, + "iStep": 553, + "seriesCluster": "S15A" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 3, + "year": 18 + }, + "iStep": 531, + "seriesCluster": "S15A" + }, + "telematicsUnit": "ATM1", + "bodyType": "G01", + "countryOfOrigin": "AT", + "a4aType": "BLUETOOTH", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_state.json new file mode 100644 index 0000000000000..037a71144c823 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE2/vehicles_state.json @@ -0,0 +1,155 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-21T18:57:40.809Z", + "lastUpdatedAt": "2022-12-21T12:40:32Z", + "isLscSupported": true, + "range": 497, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED" + }, + "location": { + "coordinates": { + "latitude": 1.1, + "longitude": 2.2 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": -1 + }, + "currentMileage": 122912, + "requiredServices": [ + { + "dateTime": "2024-06-01T00:00:00.000Z", + "mileage": 29000, + "type": "OIL", + "status": "OK", + "description": "Next service due after the specified distance or date." + }, + { + "dateTime": "2026-06-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next visual inspection due by specified date or, if shown, when stated distance has been reached." + }, + { + "dateTime": "2023-08-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + } + ], + "checkControlMessages": [ + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "combustionFuelLevel": { + "remainingFuelLiters": 35, + "range": 497 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "BLUETOOTH", + "climateNow": true, + "isClimateTimerSupported": true, + "climateTimerTrigger": "DEPARTURE_TIMER", + "climateFunction": "PARK_HEATING", + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "sendPoi": true, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": true, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": {}, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_sessions.json new file mode 100644 index 0000000000000..6c43b37be62fc --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_sessions.json @@ -0,0 +1,14 @@ +{ + "chargingSessions": { + "total": "0 kWh", + "numberOfSessions": "0", + "emptyStateDescription": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie.\nLadevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 27.07.2020.", + "chargingListState": "CHARGE_AND_TRACK", + "costsGroupedByCurrency": [] + }, + "datePicker": { + "startDate": "2020-07-27T00:00:00Z", + "selectedDate": "2023-01-20T10:38:08Z", + "endDate": "2023-01-20T10:38:09Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_statistics.json new file mode 100644 index 0000000000000..c3a23ad76a894 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/charging_statistics.json @@ -0,0 +1,8 @@ +{ + "description": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie. Ladevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 01.01.0001.", + "optStateType": "OPT_IN_WITHOUT_SESSIONS", + "statistics": { + "totalEnergyCharged": 0, + "numberOfChargingSessions": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_base.json new file mode 100644 index 0000000000000..2981a866abedd --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_base.json @@ -0,0 +1,47 @@ +[ + { + "vin": "anonymousICE3", + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-20T10:38:06.689Z", + "model": "530d xDrive", + "year": 2015, + "color": 4283055410, + "brand": "BMW", + "driveTrain": "COMBUSTION", + "headUnitType": "NBT", + "headUnitRaw": "NBT", + "hmiVersion": "ID4", + "softwareVersionCurrent": { + "puStep": { + "month": 7, + "year": 15 + }, + "iStep": 504, + "seriesCluster": "F010" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 7, + "year": 15 + }, + "iStep": 504, + "seriesCluster": "F010" + }, + "bodyType": "F11", + "countryOfOrigin": "DE", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_state.json new file mode 100644 index 0000000000000..da0d972911051 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE3/vehicles_state.json @@ -0,0 +1,103 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-20T10:38:07.492Z", + "lastUpdatedAt": "2022-08-29T16:04:30Z", + "isLscSupported": false, + "requiredServices": [], + "checkControlMessages": [ + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "combustionFuelLevel": { + "remainingFuelLiters": 43 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "USB_ONLY", + "climateNow": true, + "isClimateTimerSupported": true, + "climateTimerTrigger": "START_TIMER", + "climateFunction": "VENTILATION", + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lights": true, + "lock": true, + "sendPoi": true, + "unlock": true, + "vehicleFinder": false, + "vehicleStateSource": "A4A", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": false, + "isNonLscFeatureEnabled": true, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": {}, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_sessions.json new file mode 100644 index 0000000000000..6c43b37be62fc --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_sessions.json @@ -0,0 +1,14 @@ +{ + "chargingSessions": { + "total": "0 kWh", + "numberOfSessions": "0", + "emptyStateDescription": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie.\nLadevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 27.07.2020.", + "chargingListState": "CHARGE_AND_TRACK", + "costsGroupedByCurrency": [] + }, + "datePicker": { + "startDate": "2020-07-27T00:00:00Z", + "selectedDate": "2023-01-20T10:38:08Z", + "endDate": "2023-01-20T10:38:09Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_statistics.json new file mode 100644 index 0000000000000..c3a23ad76a894 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/charging_statistics.json @@ -0,0 +1,8 @@ +{ + "description": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie. Ladevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 01.01.0001.", + "optStateType": "OPT_IN_WITHOUT_SESSIONS", + "statistics": { + "totalEnergyCharged": 0, + "numberOfChargingSessions": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_base.json new file mode 100644 index 0000000000000..b13ec7a28db9a --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_base.json @@ -0,0 +1,48 @@ +[ + { + "vin": "anonymousICE4", + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-16T15:33:53.940Z", + "model": "435i", + "year": 2014, + "color": 4284572001, + "brand": "BMW", + "driveTrain": "COMBUSTION", + "headUnitType": "NBT", + "headUnitRaw": "NBT", + "hmiVersion": "ID4", + "softwareVersionCurrent": { + "puStep": { + "month": 7, + "year": 19 + }, + "iStep": 539, + "seriesCluster": "F020" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 3, + "year": 14 + }, + "iStep": 502, + "seriesCluster": "F020" + }, + "bodyType": "F33", + "countryOfOrigin": "DE", + "a4aType": "USB_ONLY", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_state.json new file mode 100644 index 0000000000000..93ebca1d305f9 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/ICE4/vehicles_state.json @@ -0,0 +1,103 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-16T15:33:57.194Z", + "lastUpdatedAt": "2022-06-14T08:21:50Z", + "isLscSupported": false, + "requiredServices": [], + "checkControlMessages": [ + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "combustionFuelLevel": { + "remainingFuelLiters": 20 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "USB_ONLY", + "climateNow": true, + "isClimateTimerSupported": true, + "climateTimerTrigger": "START_TIMER", + "climateFunction": "VENTILATION", + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lights": true, + "lock": true, + "sendPoi": true, + "unlock": true, + "vehicleFinder": false, + "vehicleStateSource": "A4A", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": false, + "isNonLscFeatureEnabled": true, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": null, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_sessions.json new file mode 100644 index 0000000000000..7c9790e9a21f5 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_sessions.json @@ -0,0 +1,14 @@ +{ + "chargingSessions": { + "total": "0 kWh", + "numberOfSessions": "0", + "emptyStateDescription": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie.\nLadevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 27.07.2020.", + "chargingListState": "CHARGE_AND_TRACK", + "costsGroupedByCurrency": [] + }, + "datePicker": { + "startDate": "2020-07-27T00:00:00Z", + "selectedDate": "2023-01-19T20:53:52Z", + "endDate": "2023-01-19T20:53:53Z" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_statistics.json new file mode 100644 index 0000000000000..67a6aaf0bedd1 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/charging_statistics.json @@ -0,0 +1,8 @@ +{ + "description": "Laden Sie Ihren BMW und verfolgen Sie Ihre Ladehistorie. Ladevorgänge werden ab dem Aktivierungszeitpunkt gespeichert, d. h. ab dem 27.07.2020.", + "optStateType": "OPT_IN_WITHOUT_SESSIONS", + "statistics": { + "totalEnergyCharged": 0, + "numberOfChargingSessions": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_call.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_call.json new file mode 100644 index 0000000000000..e14ab172d08c3 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_call.json @@ -0,0 +1,4 @@ +{ + "eventId": "4131c5b8-4b55-42a3-8682-0dcef479c2dc", + "creationTime": "2022-12-31T21:49:29.423034Z" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_error.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_error.json new file mode 100644 index 0000000000000..90f21c4d2c0e8 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_error.json @@ -0,0 +1,11 @@ +{ + "eventStatus": "ERROR", + "errorDetails": { + "title": null, + "description": null, + "presentationType": "DIALOG", + "iconId": null, + "isRetriable": false, + "errorDetails": "" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_status.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_status.json new file mode 100644 index 0000000000000..07dddb5f70f81 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/remote_service_status.json @@ -0,0 +1,3 @@ +{ + "eventStatus": "EXECUTED" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_base.json new file mode 100644 index 0000000000000..18d211927e69c --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_base.json @@ -0,0 +1,48 @@ +[ + { + "vin": "anonymousMILD_HYBRID", + "mappingInfo": { + "isAssociated": true, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2022-12-21T17:30:40.363Z", + "model": "M340i xDrive", + "year": 2022, + "color": 4284572001, + "brand": "BMW", + "driveTrain": "MILD_HYBRID", + "headUnitType": "MGU", + "headUnitRaw": "HU_MGU", + "hmiVersion": "ID7", + "softwareVersionCurrent": { + "puStep": { + "month": 7, + "year": 22 + }, + "iStep": 558, + "seriesCluster": "S18A" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 3, + "year": 22 + }, + "iStep": 560, + "seriesCluster": "S18A" + }, + "telematicsUnit": "ATM2", + "bodyType": "G21", + "countryOfOrigin": "DE", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_state.json new file mode 100644 index 0000000000000..5c255221aaa9e --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/MILD_HYBRID/vehicles_state.json @@ -0,0 +1,264 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2022-12-21T17:31:26.560Z", + "lastUpdatedAt": "2022-12-21T15:41:23Z", + "isLscSupported": true, + "range": 435, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "rear": "CLOSED", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "tireState": { + "frontLeft": { + "details": { + "dimension": "225/45 R18 95V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471558", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 290 + } + }, + "frontRight": { + "details": { + "dimension": "225/45 R18 95V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471558", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 290 + } + }, + "rearLeft": { + "details": { + "dimension": "255/40 R18 99V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471559", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 290 + } + }, + "rearRight": { + "details": { + "dimension": "255/40 R18 99V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471559", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 290 + } + } + }, + "location": { + "coordinates": { + "latitude": 1.234, + "longitude": 5.678 + }, + "address": { + "formatted": "Leopoldstraße 25, 80333 München" + }, + "heading": 180 + }, + "currentMileage": 4376, + "climateControlState": { + "activity": "INACTIVE" + }, + "requiredServices": [ + { + "dateTime": "2024-06-01T00:00:00.000Z", + "mileage": 29000, + "type": "OIL", + "status": "OK", + "description": "Next service due after the specified distance or date." + }, + { + "dateTime": "2025-06-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + }, + { + "dateTime": "2025-07-01T00:00:00.000Z", + "type": "VEHICLE_TUV", + "status": "OK", + "description": "Next state inspection due by the specified date." + }, + { + "dateTime": "2026-06-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next visual inspection due by specified date or, if shown, when stated distance has been reached." + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW" + }, + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "combustionFuelLevel": { + "remainingFuelPercent": 65, + "remainingFuelLiters": 34, + "range": 435 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "NOT_SUPPORTED", + "climateNow": true, + "isClimateTimerSupported": true, + "climateTimerTrigger": "DEPARTURE_TIMER", + "climateFunction": "VENTILATION", + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": true, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": {}, + "isClimateTimerWeeklyActive": true, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_base.json new file mode 100644 index 0000000000000..9db1bec197d6c --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_base.json @@ -0,0 +1,49 @@ +[ + { + "vin": "anonymousPHEV", + "mappingInfo": { + "isAssociated": false, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-01-16T15:33:53.940Z", + "model": "530e iPerformance", + "year": 2019, + "color": 4282532418, + "brand": "BMW", + "driveTrain": "PLUGIN_HYBRID", + "headUnitType": "NBT_EVO", + "headUnitRaw": "NBTEVO", + "hmiVersion": "ID5", + "softwareVersionCurrent": { + "puStep": { + "month": 3, + "year": 19 + }, + "iStep": 537, + "seriesCluster": "S15A" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 3, + "year": 19 + }, + "iStep": 537, + "seriesCluster": "S15A" + }, + "telematicsUnit": "ATM1", + "bodyType": "G30", + "countryOfOrigin": "DE", + "a4aType": "BLUETOOTH", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" + } + } + } +] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_state.json new file mode 100644 index 0000000000000..483ddbc5c2bbc --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV/vehicles_state.json @@ -0,0 +1,223 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-16T15:33:52.449Z", + "lastUpdatedAt": "2023-01-16T14:52:04Z", + "isLscSupported": true, + "range": 544, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "location": { + "coordinates": { + "latitude": 1.1, + "longitude": 2.2 + }, + "address": { + "formatted": "anonymousAddress" + }, + "heading": -1 + }, + "currentMileage": 45343, + "requiredServices": [ + { + "dateTime": "2024-11-01T00:00:00.000Z", + "mileage": 31000, + "type": "OIL", + "status": "OK", + "description": "Nächster Service nach der angegebenen Fahrstrecke oder zum angegebenen Termin." + }, + { + "dateTime": "2026-11-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Nächste Sichtprüfung zum angegebenen Termin oder nach der ggf. angegebenen Fahrstrecke." + }, + { + "dateTime": "2024-04-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Nächster Wechsel spätestens zum angegebenen Termin." + } + ], + "checkControlMessages": [ + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "reductionOfChargeCurrent": { + "start": { + "hour": 0, + "minute": 0 + }, + "end": { + "hour": 0, + "minute": 0 + } + }, + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "CHARGING_WINDOW", + "departureTimes": [ + { + "id": 1, + "timeStamp": { + "hour": 22, + "minute": 10 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 2, + "timeStamp": { + "hour": 8, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 3, + "timeStamp": { + "hour": 8, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 4, + "action": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "climatisationOn": true, + "chargingSettings": { + "targetSoc": 100, + "idcc": "NO_ACTION", + "hospitality": "NO_ACTION" + } + }, + "electricChargingState": { + "chargingLevelPercent": 79, + "range": 15, + "isChargerConnected": false, + "chargingConnectionType": "UNKNOWN", + "chargingStatus": "INVALID", + "chargingTarget": 100 + }, + "combustionFuelLevel": { + "remainingFuelLiters": 43, + "range": 544 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "BLUETOOTH", + "climateNow": true, + "climateFunction": "AIR_CONDITIONING", + "horn": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": true, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": true, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": true, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remote360": true, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": true, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": null, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_sessions.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_sessions.json new file mode 100644 index 0000000000000..568da14301c4b --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_sessions.json @@ -0,0 +1,50 @@ +{ + "paginationInfo": {}, + "chargingSessions": { + "total": "~ 16 kWh", + "numberOfSessions": "4", + "chargingListState": "HAS_SESSIONS", + "sessions": [ + { + "id": "2023-02-01T15:51:30Z_213a0c9f", + "title": "Yesterday 17:51", + "subtitle": "anonymousAddress • 2h 16min • 0 EUR", + "energyCharged": "~ 5 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-02-01T13:00:12Z_213a0c9f", + "title": "Yesterday 15:00", + "subtitle": "anonymousAddress • 1h 02min • ~ 0,45 EUR", + "energyCharged": "~ 3 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-02-01T03:27:56Z_213a0c9f", + "title": "Yesterday 5:27", + "subtitle": "anonymousAddress • 2h 42min • 0 EUR", + "energyCharged": "~ 6 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + }, + { + "id": "2023-02-01T03:10:29Z_213a0c9f", + "title": "Yesterday 5:10", + "subtitle": "anonymousAddress • 17 min • 0 EUR", + "energyCharged": "\u003c 2 kWh", + "sessionStatus": "FINISHED", + "isPublic": false + } + ], + "costsGroupedByCurrency": [ + "~ 0.45 EUR" + ] + }, + "datePicker": { + "startDate": "2021-01-05T09:03:35Z", + "selectedDate": "2023-02-01T15:51:30Z", + "endDate": "2023-02-02T03:32:19Z" + } + } \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_statistics.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_statistics.json new file mode 100644 index 0000000000000..c4e2e691913d7 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/charging_statistics.json @@ -0,0 +1,11 @@ +{ + "description": "February 2023", + "optStateType": "OPT_IN_WITH_SESSIONS", + "statistics": { + "totalEnergyCharged": 16, + "totalEnergyChargedSemantics": "Charged a total of approximately 16 kilowatt-hours", + "symbol": "~", + "numberOfChargingSessions": 4, + "numberOfChargingSessionsSemantics": "4 charging sessions" + } + } \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_base.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_base.json new file mode 100644 index 0000000000000..fc75bbaab49f0 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_base.json @@ -0,0 +1,49 @@ +[ + { + "vin": "anonymousPHEV2", + "mappingInfo": { + "isAssociated": true, + "isLmmEnabled": false, + "mappingStatus": "CONFIRMED", + "isPrimaryUser": true + }, + "appVehicleType": "CONNECTED", + "attributes": { + "lastFetched": "2023-02-02T03:32:13.686Z", + "model": "330e xDrive", + "year": 2020, + "color": 4284572518, + "brand": "BMW", + "driveTrain": "PLUGIN_HYBRID", + "headUnitType": "MGU", + "headUnitRaw": "HU_MGU", + "hmiVersion": "ID7", + "softwareVersionCurrent": { + "puStep": { + "month": 7, + "year": 21 + }, + "iStep": 550, + "seriesCluster": "S18A" + }, + "softwareVersionExFactory": { + "puStep": { + "month": 7, + "year": 20 + }, + "iStep": 554, + "seriesCluster": "S18A" + }, + "telematicsUnit": "ATM2", + "bodyType": "G21", + "countryOfOrigin": "FI", + "a4aType": "NOT_SUPPORTED", + "driverGuideInfo": { + "androidAppScheme": "com.bmwgroup.driversguide.row", + "iosAppScheme": "bmwdriversguide:///open", + "androidStoreUrl": "https://play.google.com/store/apps/details?id\u003dcom.bmwgroup.driversguide.row", + "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt\u003d8" + } + } + } + ] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_state.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_state.json new file mode 100644 index 0000000000000..059a406b89a9c --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/PHEV2/vehicles_state.json @@ -0,0 +1,256 @@ +{ + "state": { + "isLeftSteering": true, + "lastFetched": "2023-02-02T03:32:14.049Z", + "lastUpdatedAt": "2023-02-02T03:31:31Z", + "isLscSupported": true, + "range": 290, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "rear": "CLOSED", + "combinedState": "CLOSED" + }, + "tireState": { + "frontLeft": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 290, + "targetPressure": 230 + } + }, + "frontRight": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 290, + "targetPressure": 230 + } + }, + "rearLeft": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 280 + } + }, + "rearRight": { + "details": { + "identificationInProgress": false + }, + "status": { + "currentPressure": 280, + "targetPressure": 280 + } + } + }, + "currentMileage": 28878, + "climateControlState": { + "activity": "INACTIVE" + }, + "requiredServices": [ + { + "dateTime": "2023-10-01T00:00:00.000Z", + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + }, + { + "dateTime": "2024-10-01T00:00:00.000Z", + "mileage": 32000, + "type": "OIL", + "status": "OK", + "description": "Next service due after the specified distance or date." + }, + { + "dateTime": "2024-10-01T00:00:00.000Z", + "mileage": 32000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next visual inspection due by specified date or, if shown, when stated distance has been reached." + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW" + }, + { + "type": "ENGINE_OIL", + "severity": "LOW" + } + ], + "chargingProfile": { + "chargingControlType": "WEEKLY_PLANNER", + "reductionOfChargeCurrent": { + "start": { + "hour": 0, + "minute": 0 + }, + "end": { + "hour": 0, + "minute": 0 + } + }, + "chargingMode": "IMMEDIATE_CHARGING", + "chargingPreference": "NO_PRESELECTION", + "departureTimes": [ + { + "id": 1, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 2, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 3, + "timeStamp": { + "hour": 0, + "minute": 0 + }, + "action": "DEACTIVATE", + "timerWeekDays": [] + }, + { + "id": 4, + "action": "DEACTIVATE", + "timerWeekDays": [] + } + ], + "climatisationOn": false, + "chargingSettings": { + "targetSoc": 100, + "idcc": "NO_ACTION", + "hospitality": "NO_ACTION" + } + }, + "electricChargingState": { + "chargingLevelPercent": 57, + "remainingChargingMinutes": 177, + "range": 19, + "isChargerConnected": true, + "chargingConnectionType": "UNKNOWN", + "chargingStatus": "CHARGING", + "chargingTarget": 100 + }, + "combustionFuelLevel": { + "remainingFuelPercent": 56, + "remainingFuelLiters": 20, + "range": 290 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ] + }, + "capabilities": { + "a4aType": "NOT_SUPPORTED", + "climateNow": true, + "climateFunction": "AIR_CONDITIONING", + "horn": true, + "isBmwChargingSupported": true, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": true, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": true, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": true, + "isEasyChargeEnabled": false, + "isMiniChargingSupported": false, + "isEvGoChargingSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteEngineStartSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "lastStateCallState": "ACTIVATED", + "lights": true, + "lock": true, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "unlock": true, + "vehicleFinder": true, + "vehicleStateSource": "LAST_STATE_CALL", + "isRemoteHistorySupported": true, + "isWifiHotspotServiceSupported": false, + "isNonLscFeatureEnabled": false, + "isSustainabilitySupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "checkSustainabilityDPP": false, + "specialThemeSupport": [], + "isRemoteParkingSupported": false, + "remoteChargingCommands": {}, + "isClimateTimerWeeklyActive": false, + "digitalKey": { + "bookedServicePackage": "NONE", + "state": "NOT_AVAILABLE" + } + } + } \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/anonymous-raw.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/anonymous-raw.json deleted file mode 100644 index 5f092beee9bdf..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/anonymous-raw.json +++ /dev/null @@ -1,386 +0,0 @@ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern." - } - }, - "properties": { - "lastUpdatedAt": "2022-01-03T18:54:57Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 7, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 47, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": false - }, - "combustionRange": { - "chargePercentage": 0, - "distance": { - "value": 96, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "chargePercentage": 0, - "distance": { - "value": 96, - "units": "KILOMETERS" - } - }, - "electricRange": { - "chargePercentage": 0, - "distance": { - "value": 78, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 47, - "distance": { - "value": 78, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.234, - "longitude": 9.876 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 39 - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "status": { - "lastUpdatedAt": "2022-01-03T18:54:57Z", - "currentMileage": { - "mileage": 32179, - "units": "km", - "formattedMileage": "32.179" - }, - "issues": null, - "doorsGeneralState": "Verriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Verriegelungsstatus", - "state": "Verriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Glasdach", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Wechsel nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - } - ], - "fuelIndicators": [ - { - "mainBarValue": 47, - "rangeUnits": "km", - "rangeValue": "78", - "levelUnits": "%", - "levelValue": "47", - "secondaryBarValue": 0, - "infoIconId": 59694, - "rangeIconId": 59683, - "levelIconId": 59694, - "showsBar": true, - "showBarGoal": false, - "infoLabel": "Ladezustand", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingStatusType": "DEFAULT", - "chargingStatusIndicatorType": "DEFAULT" - }, - { - "mainBarValue": 0, - "rangeUnits": "km", - "rangeValue": "174", - "secondaryBarValue": 0, - "infoIconId": 59691, - "rangeIconId": 59691, - "levelIconId": 0, - "showsBar": false, - "showBarGoal": false, - "infoLabel": "Kombinierte Reichweite", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high" - }, - { - "mainBarValue": 0, - "rangeUnits": "km", - "rangeValue": "96", - "secondaryBarValue": 0, - "infoIconId": 59681, - "rangeIconId": 0, - "levelIconId": 0, - "showsBar": false, - "showBarGoal": false, - "infoLabel": "Erweiterte Reichweite", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high" - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 3.1.2022 07:54 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timeStamp": { - "hour": 16, - "minute": 0 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ] - }, - { - "id": 2, - "action": "activate", - "timeStamp": { - "hour": 12, - "minute": 2 - }, - "timerWeekDays": [ - "sunday" - ] - }, - { - "id": 3, - "action": "deactivate", - "timeStamp": { - "hour": 13, - "minute": 3 - }, - "timerWeekDays": [ - "saturday" - ] - }, - { - "id": 4, - "action": "deactivate", - "timeStamp": { - "hour": 12, - "minute": 2 - }, - "timerWeekDays": [ - "sunday" - ] - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "valid": false -} - diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/f11-raw.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/f11-raw.json deleted file mode 100644 index 9fb517b38c67d..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/f11-raw.json +++ /dev/null @@ -1,283 +0,0 @@ - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "F11", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Ventilation" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true, - "page": { - "description": "By setting a start time you let the vehicle know when you plan to use it.", - "primaryButtonText": "SEND TO VEHICLE", - "secondaryButtonText": "DEACTIVATE AND SEND TO VEHICLE", - "subtitle": "Set start time", - "title": "Ventilation timer" - }, - "tile": { - "description": "Plan start time", - "iconId": 59774, - "title": "Ventilation timer" - } - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "NOT_CAPABLE" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": false, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F010-12-11-503", - "exFactoryPUStep": "1112", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F010-12-11-503", - "isLscSupported": false, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "530d", - "properties": { - "checkControlMessages": [], - "climateControl": { - - }, - "doorsAndWindows": { - "doors": { - - }, - "windows": { - - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 24 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "originCountryISO": "GB", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 25000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 60000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ] - }, - "puStep": "1112", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - }, - { - "criticalness": "semiCritical", - "iconId": 60217, - "id": "229", - "longDescription": "Charge by driving for longer periods or use external charger. Functions requiring battery will be switched off.", - "state": "Medium", - "title": "Battery discharged: Start engine" - }, - { - "criticalness": "nonCritical", - "iconId": 60217, - "id": "50", - "longDescription": "System unable to monitor tire pressure. Check tire pressures manually. Continued driving possible. Consult service center.", - "state": "Low", - "title": "Flat Tire Monitor (FTM) inactive" - } - ], - "checkControlMessagesGeneralState": "Multiple Issues", - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59726, - "state": "Unknown", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59701, - "state": "Unknown", - "title": "Left front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59700, - "state": "Unknown", - "title": "Right front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59703, - "state": "Unknown", - "title": "Left rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59702, - "state": "Unknown", - "title": "Right rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59721, - "state": "Unknown", - "title": "Back window" - } - ], - "doorsGeneralState": "Unknown", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "24", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "mi", - "rangeValue": "- -", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in October 2022", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in October 2022 or 15534 mi", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in October 2024 or 37282 mi", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 3/11/2021 08:02 AM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 158, - "green": 158, - "red": 158 - } - }, - "vin": "some_vin_F11", - "year": 2012 -} diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/two-vehicles.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/two-vehicles.json deleted file mode 100644 index 59cf181bbc589..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/TwoVehicles/two-vehicles.json +++ /dev/null @@ -1,665 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug verriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Jetzt Ihr Fahrzeug entriegeln? Remote-Funktionen können einige Sekunden dauern." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Scheinwerfer aufleuchten lassen? Remote-Funktionen können einige Sekunden dauern." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Hupen ist in vielen Ländern nur in bestimmten Situationen erlaubt. Die Verantwortung für den Einsatz und die Einhaltung der jeweils geltenden Bestimmungen liegt allein bei Ihnen als Nutzer. \n\nJetzt hupen? Remote-Funktionen können einige Sekunden dauern." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt Ihr Fahrzeug finden? Remote-Funktionen können einige Sekunden dauern." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt POI senden? Remote-Funktionen können einige Sekunden dauern." - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Jetzt belüften? Remote-Funktionen können einige Sekunden dauern." - } - }, - "properties": { - "lastUpdatedAt": "2022-01-03T18:54:57Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 7, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 47, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": false - }, - "combustionRange": { - "chargePercentage": 0, - "distance": { - "value": 96, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "chargePercentage": 0, - "distance": { - "value": 96, - "units": "KILOMETERS" - } - }, - "electricRange": { - "chargePercentage": 0, - "distance": { - "value": 78, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 47, - "distance": { - "value": 78, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.234, - "longitude": 9.876 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 39 - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "status": { - "lastUpdatedAt": "2022-01-03T18:54:57Z", - "currentMileage": { - "mileage": 32179, - "units": "km", - "formattedMileage": "32.179" - }, - "issues": null, - "doorsGeneralState": "Verriegelt", - "checkControlMessagesGeneralState": "Keine Probleme", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Verriegelungsstatus", - "state": "Verriegelt", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "Alle Türen", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "Alle Fenster", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Frontklappe", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Gepäckraum", - "state": "Geschlossen", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Glasdach", - "state": "Geschlossen", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Bremsflüssigkeit", - "iconId": 60223, - "longDescription": "Nächster Wechsel spätestens zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Fahrzeug-Check", - "iconId": 60215, - "longDescription": "Nächste Sichtprüfung nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Motoröl", - "iconId": 60197, - "longDescription": "Nächster Wechsel nach der angegebenen Fahrstrecke oder zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Fahrzeuginspektion (HU)", - "iconId": 60111, - "longDescription": "Nächste gesetzliche Fahrzeuguntersuchung zum angegebenen Termin.", - "subtitle": "Fällig im November 2023", - "criticalness": "nonCritical" - } - ], - "fuelIndicators": [ - { - "mainBarValue": 47, - "rangeUnits": "km", - "rangeValue": "78", - "levelUnits": "%", - "levelValue": "47", - "secondaryBarValue": 0, - "infoIconId": 59694, - "rangeIconId": 59683, - "levelIconId": 59694, - "showsBar": true, - "showBarGoal": false, - "infoLabel": "Ladezustand", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingStatusType": "DEFAULT", - "chargingStatusIndicatorType": "DEFAULT" - }, - { - "mainBarValue": 0, - "rangeUnits": "km", - "rangeValue": "174", - "secondaryBarValue": 0, - "infoIconId": 59691, - "rangeIconId": 59691, - "levelIconId": 0, - "showsBar": false, - "showBarGoal": false, - "infoLabel": "Kombinierte Reichweite", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high" - }, - { - "mainBarValue": 0, - "rangeUnits": "km", - "rangeValue": "96", - "secondaryBarValue": 0, - "infoIconId": 59681, - "rangeIconId": 0, - "levelIconId": 0, - "showsBar": false, - "showBarGoal": false, - "infoLabel": "Erweiterte Reichweite", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high" - } - ], - "timestampMessage": "Aktualisiert vom Fahrzeug 3.1.2022 07:54 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timeStamp": { - "hour": 16, - "minute": 0 - }, - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ] - }, - { - "id": 2, - "action": "activate", - "timeStamp": { - "hour": 12, - "minute": 2 - }, - "timerWeekDays": [ - "sunday" - ] - }, - { - "id": 3, - "action": "deactivate", - "timeStamp": { - "hour": 13, - "minute": 3 - }, - "timerWeekDays": [ - "saturday" - ] - }, - { - "id": 4, - "action": "deactivate", - "timeStamp": { - "hour": 12, - "minute": 2 - }, - "timerWeekDays": [ - "sunday" - ] - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "valid": false - } -, - { - "a4aType": "NOT_SUPPORTED", - "bodyType": "F11", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Ventilation" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "climateTimer": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "isToggleEnabled": true, - "page": { - "description": "By setting a start time you let the vehicle know when you plan to use it.", - "primaryButtonText": "SEND TO VEHICLE", - "secondaryButtonText": "DEACTIVATE AND SEND TO VEHICLE", - "subtitle": "Set start time", - "title": "Ventilation timer" - }, - "tile": { - "description": "Plan start time", - "iconId": 59774, - "title": "Ventilation timer" - } - }, - "isBmwChargingSupported": false, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": false, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": false, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": false, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": false, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "NOT_CAPABLE" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": false, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "COMBUSTION", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F010-12-11-503", - "exFactoryPUStep": "1112", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F010-12-11-503", - "isLscSupported": false, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "530d", - "properties": { - "checkControlMessages": [], - "climateControl": {}, - "doorsAndWindows": { - "doors": {}, - "windows": {} - }, - "fuelLevel": { - "units": "LITERS", - "value": 24 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "originCountryISO": "GB", - "serviceRequired": [ - { - "dateTime": "2022-10-01T00:00:00.000Z", - "status": "OK", - "type": "BRAKE_FLUID" - }, - { - "dateTime": "2022-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 25000 - }, - "status": "OK", - "type": "OIL" - }, - { - "dateTime": "2024-10-01T00:00:00.000Z", - "distance": { - "units": "KILOMETERS", - "value": 60000 - }, - "status": "OK", - "type": "VEHICLE_CHECK" - } - ] - }, - "puStep": "1112", - "status": { - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - }, - { - "criticalness": "semiCritical", - "iconId": 60217, - "id": "229", - "longDescription": "Charge by driving for longer periods or use external charger. Functions requiring battery will be switched off.", - "state": "Medium", - "title": "Battery discharged: Start engine" - }, - { - "criticalness": "nonCritical", - "iconId": 60217, - "id": "50", - "longDescription": "System unable to monitor tire pressure. Check tire pressures manually. Continued driving possible. Consult service center.", - "state": "Low", - "title": "Flat Tire Monitor (FTM) inactive" - } - ], - "checkControlMessagesGeneralState": "Multiple Issues", - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59726, - "state": "Unknown", - "title": "All doors" - }, - { - "criticalness": "nonCritical", - "iconId": 59701, - "state": "Unknown", - "title": "Left front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59700, - "state": "Unknown", - "title": "Right front window" - }, - { - "criticalness": "nonCritical", - "iconId": 59703, - "state": "Unknown", - "title": "Left rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59702, - "state": "Unknown", - "title": "Right rear window" - }, - { - "criticalness": "nonCritical", - "iconId": 59721, - "state": "Unknown", - "title": "Back window" - } - ], - "doorsGeneralState": "Unknown", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "24", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "mi", - "rangeValue": "- -", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "lastUpdatedAt": "2021-03-10T08:02:08Z", - "recallExternalUrl": null, - "recallMessages": [], - "requiredServices": [ - { - "criticalness": "nonCritical", - "iconId": 60223, - "id": "BrakeFluid", - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in October 2022", - "title": "Brake fluid" - }, - { - "criticalness": "nonCritical", - "iconId": 60197, - "id": "Oil", - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in October 2022 or 15534 mi", - "title": "Engine oil" - }, - { - "criticalness": "nonCritical", - "iconId": 60215, - "id": "VehicleCheck", - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in October 2024 or 37282 mi", - "title": "Vehicle check" - } - ], - "timestampMessage": "Updated from vehicle 3/11/2021 08:02 AM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 158, - "green": 158, - "red": 158 - } - }, - "vin": "some_vin_F11", - "year": 2012 - } -] diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/two-weeks-timer.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/two-weeks-timer.json deleted file mode 100644 index 7b49d4fdd2699..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/two-weeks-timer.json +++ /dev/null @@ -1,301 +0,0 @@ -[ - { - "a4aType": "USB_ONLY", - "bodyType": "F45", - "brand": "BMW", - "capabilities": { - "canRemoteHistoryBeDeleted": false, - "climateNow": { - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "iconId": 59733, - "popupType": "DIALOG", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "title": "Start Climatization" - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - }, - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "isBmwChargingSupported": true, - "isCarSharingSupported": false, - "isChargeNowForBusinessSupported": false, - "isChargingHistorySupported": true, - "isChargingHospitalityEnabled": false, - "isChargingLoudnessEnable": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingSettingsEnabled": false, - "isChargingTargetSocEnable": false, - "isCustomerEsimSupported": false, - "isDCSContractManagementSupported": true, - "isDataPrivacyEnabled": false, - "isEasyChargeSupported": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isMiniChargingSupported": false, - "isRemoteHistorySupported": true, - "isRemoteServicesActivationRequired": false, - "isRemoteServicesBookingRequired": false, - "isScanAndChargeSupported": false, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "lights": { - "executionMessage": "Flash headlights now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "lock": { - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "sendPoi": { - "executionMessage": "Send POI now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - }, - "unlock": { - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": true - }, - "vehicleFinder": { - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds.", - "isEnabled": true, - "isPinAuthenticationRequired": false - } - }, - "connectedDriveServices": [], - "driveTrain": "PLUGIN_HYBRID", - "driverGuideInfo": { - "androidAppScheme": "com.bmwgroup.driversguide.row", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8", - "title": "BMW\nDriver's Guide" - }, - "exFactoryILevel": "F056-16-07-502", - "exFactoryPUStep": "0716", - "headUnit": "ID5", - "hmiVersion": "ID4", - "iStep": "F056-20-07-550", - "isLscSupported": true, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "model": "225xe iPerformance", - "properties": { - "areDoorsClosed": true, - "areDoorsLocked": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "chargingState": { - "chargePercentage": 40, - "isChargerConnected": false, - "state": "NOT_CHARGING", - "type": "CONDUCTIVE" - }, - "checkControlMessages": [], - "climateControl": {}, - "combinedRange": { - "distance": { - "units": "KILOMETERS", - "value": 245 - } - }, - "combustionRange": { - "distance": { - "units": "KILOMETERS", - "value": 245 - } - }, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "hood": "CLOSED", - "trunk": "CLOSED", - "windows": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - } - }, - "electricRange": { - "distance": { - "units": "KILOMETERS", - "value": 4 - } - }, - "electricRangeAndStatus": { - "chargePercentage": 40, - "distance": { - "units": "KILOMETERS", - "value": 4 - } - }, - "fuelLevel": { - "units": "LITERS", - "value": 20 - }, - "inMotion": false, - "isServiceRequired": false, - "lastUpdatedAt": "2021-11-10T18:25:38Z", - "originCountryISO": "GB", - "serviceRequired": [], - "vehicleLocation": { - "address": { - "formatted": "some_formatted_address" - }, - "coordinates": { - "latitude": 12.3456, - "longitude": 34.5678 - }, - "heading": 123 - } - }, - "puStep": "0720", - "status": { - "chargingProfile": { - "chargingControlType": "twoWeeksTimer", - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingSettings": { - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION", - "isAcCurrentLimitActive": false, - "targetSoc": 100 - }, - "climatisationOn": false, - "departureTimes": [ - { - "action": "deactivate", - "id": 1, - "timerWeekDays": [] - }, - { - "action": "deactivate", - "id": 2, - "timerWeekDays": [] - } - ], - "reductionOfChargeCurrent": { - "end": { - "hour": 16, - "minute": 0 - }, - "start": { - "hour": 13, - "minute": 0 - } - } - }, - "checkControlMessages": [ - { - "criticalness": "nonCritical", - "iconId": 60197, - "state": "OK", - "title": "Engine Oil" - } - ], - "checkControlMessagesGeneralState": "No Issues", - "currentMileage": { - "formattedMileage": "66720", - "mileage": 66720, - "units": "mi" - }, - "doorsAndWindows": [ - { - "criticalness": "nonCritical", - "iconId": 59722, - "state": "Closed", - "title": "All doors and windows" - } - ], - "doorsGeneralState": "Locked", - "fuelIndicators": [ - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59691, - "infoLabel": "Combined Range", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": null, - "levelUnits": null, - "levelValue": null, - "mainBarValue": 0, - "rangeIconId": 59691, - "rangeUnits": "mi", - "rangeValue": "152", - "secondaryBarValue": 0, - "showsBar": false - }, - { - "barType": null, - "chargingStatusIndicatorType": "DEFAULT", - "chargingStatusType": "DEFAULT", - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59694, - "infoLabel": "State of Charge", - "isCircleIcon": false, - "isInaccurate": false, - "levelIconId": 59694, - "levelUnits": "%", - "levelValue": "40", - "mainBarValue": 40, - "rangeIconId": 59683, - "rangeUnits": "mi", - "rangeValue": "2", - "secondaryBarValue": 0, - "showBarGoal": false, - "showsBar": true - }, - { - "chargingType": null, - "iconOpacity": "high", - "infoIconId": 59930, - "infoLabel": "Fuel Level", - "isCircleIcon": false, - "isInaccurate": true, - "levelIconId": 59682, - "levelUnits": "l", - "levelValue": "20", - "mainBarValue": 0, - "rangeIconId": 59681, - "rangeUnits": "mi", - "rangeValue": "150", - "secondaryBarValue": 0, - "showsBar": false - } - ], - "issues": {}, - "lastUpdatedAt": "2021-11-10T18:25:38Z", - "recallExternalUrl": null, - "recallMessages": [], - "timestampMessage": "Updated from vehicle 11/11/2021 06:25 PM" - }, - "telematicsUnit": "TCB1", - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "blue": 66, - "green": 66, - "red": 66 - } - }, - "vin": "anonymous", - "year": 2016 - } - ] \ No newline at end of file diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/weekly-planner-t2-active.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/weekly-planner-t2-active.json deleted file mode 100644 index a822abefd9ae5..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/chargingprofile/weekly-planner-t2-active.json +++ /dev/null @@ -1,427 +0,0 @@ -[ - { - "vin": "anonymous", - "model": "i3 94 (+ REX)", - "year": 2017, - "brand": "BMW", - "headUnit": "ID5", - "isLscSupported": true, - "driveTrain": "ELECTRIC", - "puStep": "0321", - "iStep": "I001-21-03-530", - "telematicsUnit": "TCB1", - "hmiVersion": "ID4", - "bodyType": "I01", - "a4aType": "USB_ONLY", - "capabilities": { - "isRemoteServicesBookingRequired": false, - "isRemoteServicesActivationRequired": false, - "lock": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Lock your vehicle now? Remote functions may take a few seconds." - }, - "unlock": { - "isEnabled": true, - "isPinAuthenticationRequired": true, - "executionMessage": "Unlock your vehicle now? Remote functions may take a few seconds." - }, - "lights": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Flash headlights now? Remote functions may take a few seconds." - }, - "horn": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Using your horn is only allowed in certain situations in many countries. Responsibility for the use and adherence to the respective regulations lies solely with you as the user. \n\nDo you want to use the horn now? Remote functions may take a few seconds." - }, - "vehicleFinder": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Find your vehicle now? Remote functions may take a few seconds." - }, - "sendPoi": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Send POI now? Remote functions may take a few seconds." - }, - "lastStateCall": { - "isNonLscFeatureEnabled": false, - "lscState": "ACTIVATED" - }, - "climateNow": { - "isEnabled": true, - "isPinAuthenticationRequired": false, - "executionMessage": "Do you want to ventilate now? Remote functions may take a few seconds.", - "executionPopup": { - "executionMessage": "Turn pre-conditioning on now? Remote functions may take a few seconds.", - "popupType": "DIALOG", - "title": "Start Climatization", - "primaryButtonText": "Start", - "secondaryButtonText": "Cancel", - "iconId": 59733 - }, - "executionStopPopup": { - "executionMessage": "Stop climate control in your vehicle now? Remote functions may take a few seconds.", - "title": "Climate control is running" - } - }, - "isRemoteHistorySupported": true, - "canRemoteHistoryBeDeleted": false, - "isChargingHistorySupported": true, - "isScanAndChargeSupported": true, - "isDCSContractManagementSupported": true, - "isBmwChargingSupported": true, - "isMiniChargingSupported": false, - "isChargeNowForBusinessSupported": true, - "isDataPrivacyEnabled": false, - "isChargingPlanSupported": true, - "isChargingPowerLimitEnable": false, - "isChargingTargetSocEnable": false, - "isChargingLoudnessEnable": false, - "isChargingSettingsEnabled": false, - "isChargingHospitalityEnabled": false, - "isEvGoChargingSupported": false, - "isFindChargingEnabled": true, - "isCustomerEsimSupported": false, - "isCarSharingSupported": false, - "isEasyChargeSupported": false - }, - "connectedDriveServices": [], - "properties": { - "lastUpdatedAt": "2021-12-21T16:46:02Z", - "inMotion": false, - "areDoorsLocked": true, - "originCountryISO": "DE", - "areDoorsClosed": true, - "areDoorsOpen": false, - "areWindowsClosed": true, - "doorsAndWindows": { - "doors": { - "driverFront": "CLOSED", - "driverRear": "CLOSED", - "passengerFront": "CLOSED", - "passengerRear": "CLOSED" - }, - "windows": { - "driverFront": "CLOSED", - "passengerFront": "CLOSED" - }, - "trunk": "CLOSED", - "hood": "CLOSED", - "moonroof": "CLOSED" - }, - "isServiceRequired": false, - "fuelLevel": { - "value": 4, - "units": "LITERS" - }, - "chargingState": { - "chargePercentage": 74, - "state": "NOT_CHARGING", - "type": "NOT_AVAILABLE", - "isChargerConnected": false - }, - "combustionRange": { - "distance": { - "value": 31, - "units": "KILOMETERS" - } - }, - "combinedRange": { - "distance": { - "value": 31, - "units": "KILOMETERS" - } - }, - "electricRange": { - "distance": { - "value": 76, - "units": "KILOMETERS" - } - }, - "electricRangeAndStatus": { - "chargePercentage": 74, - "distance": { - "value": 76, - "units": "KILOMETERS" - } - }, - "checkControlMessages": [], - "serviceRequired": [ - { - "type": "BRAKE_FLUID", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_CHECK", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "OIL", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - }, - { - "type": "VEHICLE_TUV", - "status": "OK", - "dateTime": "2023-11-01T00:00:00.000Z" - } - ], - "vehicleLocation": { - "coordinates": { - "latitude": 1.2345, - "longitude": 6.789 - }, - "address": { - "formatted": "anonymous" - }, - "heading": 222 - }, - "climateControl": { - - } - }, - "isMappingPending": false, - "isMappingUnconfirmed": false, - "driverGuideInfo": { - "title": "BMW\nDriver's Guide", - "androidAppScheme": "com.bmwgroup.driversguide.row", - "iosAppScheme": "bmwdriversguide:///open", - "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row", - "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8" - }, - "themeSpecs": { - "vehicleStatusBackgroundColor": { - "red": 156, - "green": 154, - "blue": 152 - } - }, - "status": { - "lastUpdatedAt": "2021-12-21T16:46:02Z", - "currentMileage": { - "mileage": 31537, - "units": "km", - "formattedMileage": "31537" - }, - "issues": { - - }, - "doorsGeneralState": "Locked", - "checkControlMessagesGeneralState": "No Issues", - "doorsAndWindows": [ - { - "iconId": 59757, - "title": "Lock status", - "state": "Locked", - "criticalness": "nonCritical" - }, - { - "iconId": 59722, - "title": "All doors", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59725, - "title": "All windows", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59706, - "title": "Hood", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59704, - "title": "Trunk", - "state": "Closed", - "criticalness": "nonCritical" - }, - { - "iconId": 59705, - "title": "Sunroof", - "state": "Closed", - "criticalness": "nonCritical" - } - ], - "checkControlMessages": [], - "requiredServices": [ - { - "id": "BrakeFluid", - "title": "Brake fluid", - "iconId": 60223, - "longDescription": "Next service due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleCheck", - "title": "Vehicle check", - "iconId": 60215, - "longDescription": "Next vehicle check due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "Oil", - "title": "Engine oil", - "iconId": 60197, - "longDescription": "Next service due after the specified distance or date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - }, - { - "id": "VehicleAdmissionTest", - "title": "Vehicle Inspection", - "iconId": 60111, - "longDescription": "Next state inspection due by the specified date.", - "subtitle": "Due in November 2023", - "criticalness": "nonCritical" - } - ], - "recallMessages": [], - "recallExternalUrl": null, - "fuelIndicators": [ - { - "mainBarValue": 74, - "secondaryBarValue": 0, - "infoIconId": 59694, - "rangeIconId": 59683, - "rangeUnits": "km", - "rangeValue": "76", - "levelIconId": 59694, - "showsBar": true, - "levelUnits": "%", - "levelValue": "74", - "showBarGoal": false, - "barType": null, - "infoLabel": "State of Charge", - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "chargingStatusType": "DEFAULT", - "chargingStatusIndicatorType": "DEFAULT" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59691, - "infoLabel": "Combined Range", - "rangeIconId": 59691, - "rangeUnits": "km", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null, - "rangeValue": "107" - }, - { - "mainBarValue": 0, - "secondaryBarValue": 0, - "infoIconId": 59681, - "infoLabel": "Extended Range", - "rangeIconId": null, - "rangeUnits": "km", - "rangeValue": "31", - "levelIconId": null, - "showsBar": false, - "levelUnits": null, - "levelValue": null, - "isInaccurate": false, - "isCircleIcon": false, - "iconOpacity": "high", - "chargingType": null - } - ], - "timestampMessage": "Updated from vehicle 12/21/2021 05:46 PM", - "chargingProfile": { - "reductionOfChargeCurrent": { - "start": { - "hour": 11, - "minute": 0 - }, - "end": { - "hour": 14, - "minute": 30 - } - }, - "chargingMode": "immediateCharging", - "chargingPreference": "chargingWindow", - "chargingControlType": "weeklyPlanner", - "departureTimes": [ - { - "id": 1, - "action": "deactivate", - "timerWeekDays": [ - "monday", - "tuesday", - "wednesday", - "thursday", - "friday", - "saturday", - "sunday" - ], - "timeStamp": { - "hour": 16, - "minute": 0 - } - }, - { - "id": 2, - "action": "activate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - }, - { - "id": 3, - "action": "deactivate", - "timerWeekDays": [ - "saturday" - ], - "timeStamp": { - "hour": 13, - "minute": 3 - } - }, - { - "id": 4, - "action": "deactivate", - "timerWeekDays": [ - "sunday" - ], - "timeStamp": { - "hour": 12, - "minute": 2 - } - } - ], - "climatisationOn": false, - "chargingSettings": { - "targetSoc": 100, - "isAcCurrentLimitActive": false, - "hospitality": "NO_ACTION", - "idcc": "NO_ACTION" - } - } - }, - "exFactoryPUStep": "0717", - "exFactoryILevel": "I001-17-07-500" - } -] diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/remote_services/service-error.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/remote_services/service-error.json index 3e645c5930b00..c338dada95ec5 100644 --- a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/remote_services/service-error.json +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/remote_services/service-error.json @@ -2,7 +2,7 @@ "eventStatus": "ERROR", "errorDetails": { "title": "Etwas ist schiefgelaufen", - "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 ausgeführt werden, wenn die Fahrertür geschlossen und der Türstatus bekannt ist.", + "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 ausgeführt werden, wenn die Fahrertür geschlossen und der Türstatus bekannt ist.", "presentationType": "PAGE", "iconId": 60217, "isRetriable": true, diff --git a/bundles/org.openhab.binding.mybmw/src/test/resources/responses/vehicles.json b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/vehicles.json new file mode 100644 index 0000000000000..b8fcb3a06c035 --- /dev/null +++ b/bundles/org.openhab.binding.mybmw/src/test/resources/responses/vehicles.json @@ -0,0 +1,603 @@ +[ + { + "vehicleBase": { + "vin": "VIN1234567", + "attributes": { + "lastFetched": "2023-01-02T19:52:55.678Z", + "model": "M340i xDrive", + "year": 2022, + "color": 4284572001, + "brand": "BMW", + "driveTrain": "MILD_HYBRID", + "headUnitType": "MGU", + "headUnitRaw": "HU_MGU", + "hmiVersion": "ID7", + "telematicsUnit": "ATM2", + "bodyType": "G21", + "countryOfOrigin": "DE" + } + }, + "vehicleState": { + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-02T19:52:56.420Z", + "lastUpdatedAt": "2023-01-01T22:53:03Z", + "isLscSupported": true, + "range": 224, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "CLOSED", + "rightFront": "CLOSED", + "rightRear": "CLOSED", + "rear": "CLOSED", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "CLOSED", + "roofStateType": "SUN_ROOF" + }, + "tireState": { + "frontLeft": { + "details": { + "dimension": "225/45 R18 95V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471558", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 250, + "targetPressure": 270 + } + }, + "frontRight": { + "details": { + "dimension": "225/45 R18 95V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471558", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 250, + "targetPressure": 270 + } + }, + "rearLeft": { + "details": { + "dimension": "255/40 R18 99V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471559", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 250, + "targetPressure": 270 + } + }, + "rearRight": { + "details": { + "dimension": "255/40 R18 99V XL", + "treadDesign": "Winter Contact TS 860 S SSR", + "manufacturer": "Continental", + "manufacturingWeek": 5299, + "isOptimizedForOemBmw": true, + "partNumber": "2471559", + "speedClassification": { + "speedRating": 240, + "atLeast": false + }, + "mountingDate": "2022-10-06T00:00:00.000Z", + "season": 4, + "identificationInProgress": false + }, + "status": { + "currentPressure": 260, + "targetPressure": 270 + } + } + }, + "location": { + "coordinates": { + "latitude": 2.34567, + "longitude": 3.45678 + }, + "address": { + "formatted": "Teststraße 123, 11111 Testort" + }, + "heading": 184 + }, + "currentMileage": 4573, + "climateControlState": { + "activity": "INACTIVE" + }, + "requiredServices": [ + { + "dateTime": "2024-06-01T00:00:00.000Z", + "mileage": 29000, + "type": "OIL", + "status": "OK", + "description": "Next service due after the specified distance or date." + }, + { + "dateTime": "2025-06-01T00:00:00.000Z", + "mileage": -1, + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + }, + { + "dateTime": "2025-07-01T00:00:00.000Z", + "mileage": -1, + "type": "VEHICLE_TUV", + "status": "OK", + "description": "Next state inspection due by the specified date." + }, + { + "dateTime": "2026-06-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next visual inspection due by specified date or, if shown, when stated distance has been reached." + } + ], + "checkControlMessages": [ + { + "type": "TIRE_PRESSURE", + "severity": "LOW", + "id": -1, + "description": "", + "name": "" + }, + { + "type": "ENGINE_OIL", + "severity": "LOW", + "id": -1, + "description": "", + "name": "" + } + ], + "combustionFuelLevel": { + "remainingFuelPercent": 33, + "remainingFuelLiters": 17, + "range": 224 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "electricChargingState": { + "chargingConnectionType": "", + "chargingStatus": "", + "isChargerConnected": false, + "chargingTarget": -1, + "chargingLevelPercent": -1, + "range": -1 + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ], + "chargingProfile": { + "INVALID_TIMER": { + "id": -1 + }, + "climatisationOn": false + } + }, + "capabilities": { + "checkSustainabilityDPP": false, + "climateNow": true, + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": false, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isClimateTimerSupported": true, + "isClimateTimerWeeklyActive": true, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isEvGoChargingSupported": false, + "isMiniChargingSupported": false, + "isNonLscFeatureEnabled": false, + "isRemoteEngineStartSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteHistorySupported": true, + "isRemoteParkingSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "isSustainabilitySupported": false, + "isWifiHotspotServiceSupported": true, + "lights": true, + "lock": true, + "remote360": false, + "remoteChargingCommands": { + "chargingControl": [], + "flapControl": [], + "plugControl": [] + }, + "remoteSoftwareUpgrade": true, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "digitalKey": { + "bookedServicePackage": "NONE", + "readerGraphics": "", + "state": "NOT_AVAILABLE" + }, + "a4aType": "NOT_SUPPORTED", + "climateFunction": "VENTILATION", + "climateTimerTrigger": "DEPARTURE_TIMER", + "lastStateCallState": "ACTIVATED", + "vehicleStateSource": "LAST_STATE_CALL" + }, + "rawStateJson": "{\"state\":{\"isLeftSteering\":true,\"lastFetched\":\"2023-01-02T19:52:56.420Z\",\"lastUpdatedAt\":\"2023-01-01T22:53:03Z\",\"isLscSupported\":true,\"range\":224,\"doorsState\":{\"combinedSecurityState\":\"SECURED\",\"leftFront\":\"CLOSED\",\"leftRear\":\"CLOSED\",\"rightFront\":\"CLOSED\",\"rightRear\":\"CLOSED\",\"combinedState\":\"CLOSED\",\"hood\":\"CLOSED\",\"trunk\":\"CLOSED\"},\"windowsState\":{\"leftFront\":\"CLOSED\",\"leftRear\":\"CLOSED\",\"rightFront\":\"CLOSED\",\"rightRear\":\"CLOSED\",\"rear\":\"CLOSED\",\"combinedState\":\"CLOSED\"},\"roofState\":{\"roofState\":\"CLOSED\",\"roofStateType\":\"SUN_ROOF\"},\"tireState\":{\"frontLeft\":{\"details\":{\"dimension\":\"225/45 R18 95V XL\",\"treadDesign\":\"Winter Contact TS 860 S SSR\",\"manufacturer\":\"Continental\",\"manufacturingWeek\":5299,\"isOptimizedForOemBmw\":true,\"partNumber\":\"2471558\",\"speedClassification\":{\"speedRating\":240,\"atLeast\":false},\"mountingDate\":\"2022-10-06T00:00:00.000Z\",\"season\":4,\"identificationInProgress\":false},\"status\":{\"currentPressure\":250,\"targetPressure\":270}},\"frontRight\":{\"details\":{\"dimension\":\"225/45 R18 95V XL\",\"treadDesign\":\"Winter Contact TS 860 S SSR\",\"manufacturer\":\"Continental\",\"manufacturingWeek\":5299,\"isOptimizedForOemBmw\":true,\"partNumber\":\"2471558\",\"speedClassification\":{\"speedRating\":240,\"atLeast\":false},\"mountingDate\":\"2022-10-06T00:00:00.000Z\",\"season\":4,\"identificationInProgress\":false},\"status\":{\"currentPressure\":250,\"targetPressure\":270}},\"rearLeft\":{\"details\":{\"dimension\":\"255/40 R18 99V XL\",\"treadDesign\":\"Winter Contact TS 860 S SSR\",\"manufacturer\":\"Continental\",\"manufacturingWeek\":5299,\"isOptimizedForOemBmw\":true,\"partNumber\":\"2471559\",\"speedClassification\":{\"speedRating\":240,\"atLeast\":false},\"mountingDate\":\"2022-10-06T00:00:00.000Z\",\"season\":4,\"identificationInProgress\":false},\"status\":{\"currentPressure\":250,\"targetPressure\":270}},\"rearRight\":{\"details\":{\"dimension\":\"255/40 R18 99V XL\",\"treadDesign\":\"Winter Contact TS 860 S SSR\",\"manufacturer\":\"Continental\",\"manufacturingWeek\":5299,\"isOptimizedForOemBmw\":true,\"partNumber\":\"2471559\",\"speedClassification\":{\"speedRating\":240,\"atLeast\":false},\"mountingDate\":\"2022-10-06T00:00:00.000Z\",\"season\":4,\"identificationInProgress\":false},\"status\":{\"currentPressure\":260,\"targetPressure\":270}}},\"location\":{\"coordinates\":{\"latitude\":2.34567,\"longitude\":3.45678},\"address\":{\"formatted\":\"Teststraße 123, 11111 Testort\"},\"heading\":184},\"currentMileage\":4573,\"climateControlState\":{\"activity\":\"INACTIVE\"},\"requiredServices\":[{\"dateTime\":\"2024-06-01T00:00:00.000Z\",\"mileage\":29000,\"type\":\"OIL\",\"status\":\"OK\",\"description\":\"Next service due after the specified distance or date.\"},{\"dateTime\":\"2025-06-01T00:00:00.000Z\",\"type\":\"BRAKE_FLUID\",\"status\":\"OK\",\"description\":\"Next service due by the specified date.\"},{\"dateTime\":\"2025-07-01T00:00:00.000Z\",\"type\":\"VEHICLE_TUV\",\"status\":\"OK\",\"description\":\"Next state inspection due by the specified date.\"},{\"dateTime\":\"2026-06-01T00:00:00.000Z\",\"mileage\":60000,\"type\":\"VEHICLE_CHECK\",\"status\":\"OK\",\"description\":\"Next visual inspection due by specified date or, if shown, when stated distance has been reached.\"}],\"checkControlMessages\":[{\"type\":\"TIRE_PRESSURE\",\"severity\":\"LOW\"},{\"type\":\"ENGINE_OIL\",\"severity\":\"LOW\"}],\"combustionFuelLevel\":{\"remainingFuelPercent\":33,\"remainingFuelLiters\":17,\"range\":224},\"driverPreferences\":{\"lscPrivacyMode\":\"OFF\"},\"isDeepSleepModeActive\":false,\"climateTimers\":[{\"isWeeklyTimer\":false,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[],\"departureTime\":{\"hour\":7,\"minute\":0}},{\"isWeeklyTimer\":true,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[\"MONDAY\"],\"departureTime\":{\"hour\":7,\"minute\":0}},{\"isWeeklyTimer\":true,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[\"MONDAY\"],\"departureTime\":{\"hour\":7,\"minute\":0}}]},\"capabilities\":{\"a4aType\":\"NOT_SUPPORTED\",\"climateNow\":true,\"isClimateTimerSupported\":true,\"climateTimerTrigger\":\"DEPARTURE_TIMER\",\"climateFunction\":\"VENTILATION\",\"horn\":true,\"isBmwChargingSupported\":false,\"isCarSharingSupported\":false,\"isChargeNowForBusinessSupported\":false,\"isChargingHistorySupported\":false,\"isChargingHospitalityEnabled\":false,\"isChargingLoudnessEnabled\":false,\"isChargingPlanSupported\":false,\"isChargingPowerLimitEnabled\":false,\"isChargingSettingsEnabled\":false,\"isChargingTargetSocEnabled\":false,\"isCustomerEsimSupported\":false,\"isDataPrivacyEnabled\":false,\"isDCSContractManagementSupported\":false,\"isEasyChargeEnabled\":false,\"isMiniChargingSupported\":false,\"isEvGoChargingSupported\":false,\"isRemoteHistoryDeletionSupported\":false,\"isRemoteEngineStartSupported\":false,\"isRemoteServicesActivationRequired\":false,\"isRemoteServicesBookingRequired\":false,\"isScanAndChargeSupported\":false,\"lastStateCallState\":\"ACTIVATED\",\"lights\":true,\"lock\":true,\"remoteSoftwareUpgrade\":true,\"sendPoi\":true,\"speechThirdPartyAlexa\":true,\"speechThirdPartyAlexaSDK\":false,\"unlock\":true,\"vehicleFinder\":true,\"vehicleStateSource\":\"LAST_STATE_CALL\",\"isRemoteHistorySupported\":true,\"isWifiHotspotServiceSupported\":true,\"isNonLscFeatureEnabled\":false,\"isSustainabilitySupported\":false,\"isSustainabilityAccumulatedViewEnabled\":false,\"checkSustainabilityDPP\":false,\"specialThemeSupport\":[],\"isRemoteParkingSupported\":false,\"remoteChargingCommands\":{},\"isClimateTimerWeeklyActive\":true,\"digitalKey\":{\"bookedServicePackage\":\"NONE\",\"state\":\"NOT_AVAILABLE\"}}}" + }, + "valid": false + }, + { + "vehicleBase": { + "vin": "VIN1234568", + "attributes": { + "lastFetched": "2023-01-02T19:52:56.255Z", + "model": "Cooper", + "year": 2022, + "color": 4290295992, + "brand": "MINI", + "driveTrain": "COMBUSTION", + "headUnitType": "ENTRY_EVO", + "headUnitRaw": "ENAVEVO", + "hmiVersion": "ID5", + "telematicsUnit": "ATM1", + "bodyType": "F56", + "countryOfOrigin": "DE" + } + }, + "vehicleState": { + "state": { + "isLeftSteering": true, + "lastFetched": "2023-01-02T19:52:57.116Z", + "lastUpdatedAt": "2023-01-02T19:03:43Z", + "isLscSupported": true, + "range": 194, + "doorsState": { + "combinedSecurityState": "SECURED", + "leftFront": "CLOSED", + "leftRear": "", + "rightFront": "CLOSED", + "rightRear": "", + "combinedState": "CLOSED", + "hood": "CLOSED", + "trunk": "CLOSED" + }, + "windowsState": { + "leftFront": "CLOSED", + "leftRear": "", + "rightFront": "CLOSED", + "rightRear": "", + "rear": "", + "combinedState": "CLOSED" + }, + "roofState": { + "roofState": "", + "roofStateType": "" + }, + "tireState": { + "frontLeft": { + "details": { + "dimension": "", + "treadDesign": "", + "manufacturer": "", + "manufacturingWeek": -1, + "isOptimizedForOemBmw": false, + "partNumber": "", + "mountingDate": "", + "season": -1, + "identificationInProgress": false + }, + "status": { + "currentPressure": -1, + "targetPressure": -1 + } + }, + "frontRight": { + "details": { + "dimension": "", + "treadDesign": "", + "manufacturer": "", + "manufacturingWeek": -1, + "isOptimizedForOemBmw": false, + "partNumber": "", + "mountingDate": "", + "season": -1, + "identificationInProgress": false + }, + "status": { + "currentPressure": -1, + "targetPressure": -1 + } + }, + "rearLeft": { + "details": { + "dimension": "", + "treadDesign": "", + "manufacturer": "", + "manufacturingWeek": -1, + "isOptimizedForOemBmw": false, + "partNumber": "", + "mountingDate": "", + "season": -1, + "identificationInProgress": false + }, + "status": { + "currentPressure": -1, + "targetPressure": -1 + } + }, + "rearRight": { + "details": { + "dimension": "", + "treadDesign": "", + "manufacturer": "", + "manufacturingWeek": -1, + "isOptimizedForOemBmw": false, + "partNumber": "", + "mountingDate": "", + "season": -1, + "identificationInProgress": false + }, + "status": { + "currentPressure": -1, + "targetPressure": -1 + } + } + }, + "location": { + "coordinates": { + "latitude": 2.34567, + "longitude": 3.45678 + }, + "address": { + "formatted": "Teststraße 123, 11111 Testort" + }, + "heading": 181 + }, + "currentMileage": 897, + "climateControlState": { + "activity": "" + }, + "requiredServices": [ + { + "dateTime": "2025-10-01T00:00:00.000Z", + "mileage": -1, + "type": "VEHICLE_TUV", + "status": "OK", + "description": "Next state inspection due by the specified date." + }, + { + "dateTime": "2024-09-01T00:00:00.000Z", + "mileage": 30000, + "type": "OIL", + "status": "OK", + "description": "Next service due after the specified distance or date." + }, + { + "dateTime": "2026-09-01T00:00:00.000Z", + "mileage": 60000, + "type": "VEHICLE_CHECK", + "status": "OK", + "description": "Next vehicle check due on the specified date or, if shown, after the specified distance." + }, + { + "dateTime": "2025-09-01T00:00:00.000Z", + "mileage": -1, + "type": "BRAKE_FLUID", + "status": "OK", + "description": "Next service due by the specified date." + } + ], + "checkControlMessages": [ + { + "type": "ENGINE_OIL", + "severity": "LOW", + "id": -1, + "description": "", + "name": "" + } + ], + "combustionFuelLevel": { + "remainingFuelPercent": -1, + "remainingFuelLiters": 13, + "range": 194 + }, + "driverPreferences": { + "lscPrivacyMode": "OFF" + }, + "electricChargingState": { + "chargingConnectionType": "", + "chargingStatus": "", + "isChargerConnected": false, + "chargingTarget": -1, + "chargingLevelPercent": -1, + "range": -1 + }, + "isDeepSleepModeActive": false, + "climateTimers": [ + { + "isWeeklyTimer": false, + "timerAction": "DEACTIVATE", + "timerWeekDays": [], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + }, + { + "isWeeklyTimer": true, + "timerAction": "DEACTIVATE", + "timerWeekDays": [ + "MONDAY" + ], + "departureTime": { + "hour": 7, + "minute": 0 + } + } + ], + "chargingProfile": { + "INVALID_TIMER": { + "id": -1 + }, + "climatisationOn": false + } + }, + "capabilities": { + "checkSustainabilityDPP": false, + "climateNow": true, + "horn": true, + "isBmwChargingSupported": false, + "isCarSharingSupported": true, + "isChargeNowForBusinessSupported": false, + "isChargingHistorySupported": false, + "isChargingHospitalityEnabled": false, + "isChargingLoudnessEnabled": false, + "isChargingPlanSupported": false, + "isChargingPowerLimitEnabled": false, + "isChargingSettingsEnabled": false, + "isChargingTargetSocEnabled": false, + "isClimateTimerSupported": true, + "isClimateTimerWeeklyActive": false, + "isCustomerEsimSupported": false, + "isDataPrivacyEnabled": false, + "isDCSContractManagementSupported": false, + "isEasyChargeEnabled": false, + "isEvGoChargingSupported": false, + "isMiniChargingSupported": false, + "isNonLscFeatureEnabled": false, + "isRemoteEngineStartSupported": false, + "isRemoteHistoryDeletionSupported": false, + "isRemoteHistorySupported": true, + "isRemoteParkingSupported": false, + "isRemoteServicesActivationRequired": false, + "isRemoteServicesBookingRequired": false, + "isScanAndChargeSupported": false, + "isSustainabilityAccumulatedViewEnabled": false, + "isSustainabilitySupported": false, + "isWifiHotspotServiceSupported": false, + "lights": true, + "lock": true, + "remote360": false, + "remoteChargingCommands": { + "chargingControl": [], + "flapControl": [], + "plugControl": [] + }, + "remoteSoftwareUpgrade": false, + "sendPoi": true, + "speechThirdPartyAlexa": true, + "speechThirdPartyAlexaSDK": false, + "unlock": true, + "vehicleFinder": true, + "digitalKey": { + "bookedServicePackage": "NONE", + "readerGraphics": "", + "state": "NOT_AVAILABLE" + }, + "a4aType": "BLUETOOTH", + "climateFunction": "VENTILATION", + "climateTimerTrigger": "START_TIMER", + "lastStateCallState": "ACTIVATED", + "vehicleStateSource": "LAST_STATE_CALL" + }, + "rawStateJson": "{\"state\":{\"isLeftSteering\":true,\"lastFetched\":\"2023-01-02T19:52:57.116Z\",\"lastUpdatedAt\":\"2023-01-02T19:03:43Z\",\"isLscSupported\":true,\"range\":194,\"doorsState\":{\"combinedSecurityState\":\"SECURED\",\"leftFront\":\"CLOSED\",\"rightFront\":\"CLOSED\",\"combinedState\":\"CLOSED\",\"hood\":\"CLOSED\",\"trunk\":\"CLOSED\"},\"windowsState\":{\"leftFront\":\"CLOSED\",\"rightFront\":\"CLOSED\",\"combinedState\":\"CLOSED\"},\"location\":{\"coordinates\":{\"latitude\":2.34567,\"longitude\":3.45678},\"address\":{\"formatted\":\"Teststraße 123, 11111 Testort\"},\"heading\":181},\"currentMileage\":897,\"requiredServices\":[{\"dateTime\":\"2025-10-01T00:00:00.000Z\",\"type\":\"VEHICLE_TUV\",\"status\":\"OK\",\"description\":\"Next state inspection due by the specified date.\"},{\"dateTime\":\"2024-09-01T00:00:00.000Z\",\"mileage\":30000,\"type\":\"OIL\",\"status\":\"OK\",\"description\":\"Next service due after the specified distance or date.\"},{\"dateTime\":\"2026-09-01T00:00:00.000Z\",\"mileage\":60000,\"type\":\"VEHICLE_CHECK\",\"status\":\"OK\",\"description\":\"Next vehicle check due on the specified date or, if shown, after the specified distance.\"},{\"dateTime\":\"2025-09-01T00:00:00.000Z\",\"type\":\"BRAKE_FLUID\",\"status\":\"OK\",\"description\":\"Next service due by the specified date.\"}],\"checkControlMessages\":[{\"type\":\"ENGINE_OIL\",\"severity\":\"LOW\"}],\"combustionFuelLevel\":{\"remainingFuelLiters\":13,\"range\":194},\"driverPreferences\":{\"lscPrivacyMode\":\"OFF\"},\"climateTimers\":[{\"isWeeklyTimer\":false,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[],\"departureTime\":{\"hour\":7,\"minute\":0}},{\"isWeeklyTimer\":true,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[\"MONDAY\"],\"departureTime\":{\"hour\":7,\"minute\":0}},{\"isWeeklyTimer\":true,\"timerAction\":\"DEACTIVATE\",\"timerWeekDays\":[\"MONDAY\"],\"departureTime\":{\"hour\":7,\"minute\":0}}]},\"capabilities\":{\"a4aType\":\"BLUETOOTH\",\"climateNow\":true,\"isClimateTimerSupported\":true,\"climateTimerTrigger\":\"START_TIMER\",\"climateFunction\":\"VENTILATION\",\"horn\":true,\"isBmwChargingSupported\":false,\"isCarSharingSupported\":true,\"isChargeNowForBusinessSupported\":false,\"isChargingHistorySupported\":false,\"isChargingHospitalityEnabled\":false,\"isChargingLoudnessEnabled\":false,\"isChargingPlanSupported\":false,\"isChargingPowerLimitEnabled\":false,\"isChargingSettingsEnabled\":false,\"isChargingTargetSocEnabled\":false,\"isCustomerEsimSupported\":false,\"isDataPrivacyEnabled\":false,\"isDCSContractManagementSupported\":false,\"isEasyChargeEnabled\":false,\"isMiniChargingSupported\":false,\"isEvGoChargingSupported\":false,\"isRemoteHistoryDeletionSupported\":false,\"isRemoteEngineStartSupported\":false,\"isRemoteServicesActivationRequired\":false,\"isRemoteServicesBookingRequired\":false,\"isScanAndChargeSupported\":false,\"lastStateCallState\":\"ACTIVATED\",\"lights\":true,\"lock\":true,\"sendPoi\":true,\"speechThirdPartyAlexa\":true,\"speechThirdPartyAlexaSDK\":false,\"unlock\":true,\"vehicleFinder\":true,\"vehicleStateSource\":\"LAST_STATE_CALL\",\"isRemoteHistorySupported\":true,\"isWifiHotspotServiceSupported\":false,\"isNonLscFeatureEnabled\":false,\"isSustainabilitySupported\":false,\"isSustainabilityAccumulatedViewEnabled\":false,\"checkSustainabilityDPP\":false,\"specialThemeSupport\":[],\"isRemoteParkingSupported\":false,\"remoteChargingCommands\":{},\"isClimateTimerWeeklyActive\":false,\"digitalKey\":{\"bookedServicePackage\":\"NONE\",\"state\":\"NOT_AVAILABLE\"}}}" + }, + "valid": false + } +] \ No newline at end of file