/bundles/org.openhab.binding.mqtt.homie/ @ccutrer
/bundles/org.openhab.binding.mqtt.ruuvigateway/ @ssalonen
/bundles/org.openhab.binding.mycroft/ @dalgwen
-/bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess
+/bundles/org.openhab.binding.mybmw/ @ntruchsess @mherwege @martingrassl
/bundles/org.openhab.binding.mynice/ @clinique
/bundles/org.openhab.binding.mystrom/ @pail23
/bundles/org.openhab.binding.nanoleaf/ @stefan-hoehn
| 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
- 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
- _horn-blow_
- _climate-now-start_
- _climate-now-stop_
+- _charge-now_
The channel _state_ shows the progress of the command execution in the following order
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
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
#### Generate Debug Fingerprint
-<img align="right" src="./doc/DiscoveryScan.png" width="400" height="350"/>
+Login to the openHAB console and use the `mybmw fingerprint` command.
-First [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) for the binding.
+Fingerprint information on your account and vehicle(s) will show in the console and can be copiedfrom there.
+A zip file with fingerprint information for your vehicle(s) will also be generated and put into the `mybmw` folder in the userdata folder.
+This fingerprint information is valuable for the developers to better support your vehicle.
-```shell
-log:set DEBUG org.openhab.binding.mybmw
-```
+You can restrict the accounts and vehicles for the fingerprint generation.
+Full syntax is available through the `mybmw help` console command.
-The debug fingerprint is generated every time the discovery is executed.
-To force a new fingerprint perform a _Scan_ for MyBMW things.
-Personal data is eliminated from the log entries so it should be possible to share them in public.
+Personal data is eliminated from fingerprints so it should be possible to share them in public.
Data like
- Vehicle Identification Number (VIN)
- Location data
-are anonymized.
-You'll find the fingerprint in the logs with the command
+are anonymized in the JSON response and URL's.
-```shell
-grep "Discovery Fingerprint Data" openhab.log
-```
+After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint!
-After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint data!
Your feedback is highly appreciated!
+#### Debug Logging
+
+You can [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) to get more information on the behaviour of the binding.
+The package.subpackage in this case would be "org.openhab.binding.mybmw".
+
+As with fingerprint data, personal data is eliminated from logs.
+
### Range vs Range Radius
<img align="right" src="./doc/range-radius.png" width="400" height="350"/>
<name>openHAB Add-ons :: Bundles :: MyBMW Binding</name>
+ <profiles>
+ <profile>
+ <id>test-coverage</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.8</version>
+ <executions>
+ <execution>
+ <id>default-instrument</id>
+ <goals>
+ <goal>instrument</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-restore-instrumented-classes</id>
+ <phase>test</phase>
+ <goals>
+ <goal>restore-instrumented-classes</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-report</id>
+ <phase>test</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-check</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <rule>
+ <element>BUNDLE</element>
+ <limits>
+ <limit>
+ <counter>INSTRUCTION</counter>
+ <value>COVEREDRATIO</value>
+ <minimum>0.20</minimum>
+ </limit>
+ <limit>
+ <counter>BRANCH</counter>
+ <value>COVEREDRATIO</value>
+ <minimum>0.20</minimum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <!-- must be on the classpath -->
+ <groupId>org.jacoco</groupId>
+ <artifactId>org.jacoco.agent</artifactId>
+ <classifier>runtime</classifier>
+ <version>0.8.8</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <!--
+ If you activate this profile, the MyBmwProxyIT is executed which means real
+ backend requests. The test is only successful if you provide CONNECTED_USER and
+ CONNECTED_PASSWORD as environment variable of the Maven command.
+ -->
+ <id>integration-tests</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>3.0.0-M7</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ <profile>
+ <!--
+ This profile generates a jar file <regular-jar-file-name>-testenv.jar in the target folder. This
+ testenv jar contains the regular classes and in addition all responses from
+ src/test/resources. If you copy this jar file to your addons folder, you can simulate all
+ accounts which are available as fingerprints in the responses folder. This can be done like
+ this:
+ 1. start openhab with the environment variable "ENVIRONMENT=test"
+
+ 2. configure the connected account with username "testuser"
+
+ 3. configure as connected password the folder which you want to test, e.g. "BEV", "BEV2", "PHEV", "ICE", "ICE2",
+ "MILD_HYBRID"
+
+ after that you should get the vehicles loaded properly so you can check if the channels are populated with data properly.
+ -->
+ <id>test-jar</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.3.0</version>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <!-- here the phase you need -->
+ <phase>package</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/classes</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/test/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.3.0</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <classifier>testenv</classifier>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
--- /dev/null
+/**
+ * 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 MyBMWBridgeConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - renamed
+ */
+@NonNullByDefault
+public class MyBMWBridgeConfiguration {
+
+ /**
+ * Depending on the location the correct server needs to be called
+ */
+ public String region = Constants.EMPTY;
+
+ /**
+ * MyBMW App Username
+ */
+ public String userName = Constants.EMPTY;
+
+ /**
+ * MyBMW App Password
+ */
+ public String password = Constants.EMPTY;
+
+ /**
+ * Preferred Locale language
+ */
+ public String language = Constants.LANGUAGE_AUTODETECT;
+}
+++ /dev/null
-/**
- * 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 MyBMWConfiguration} class contains fields mapping thing configuration parameters.
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public class MyBMWConfiguration {
-
- /**
- * Depending on the location the correct server needs to be called
- */
- public String region = Constants.EMPTY;
-
- /**
- * MyBMW App Username
- */
- public String userName = Constants.EMPTY;
-
- /**
- * MyBMW App Password
- */
- public String password = Constants.EMPTY;
-
- /**
- * Preferred Locale language
- */
- public String language = Constants.LANGUAGE_AUTODETECT;
-}
*
* @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"),
}
}
- public enum ChargingMode {
- immediateCharging,
- delayedCharging
+ enum ChargingMode {
+ IMMEDIATE_CHARGING,
+ DELAYED_CHARGING
}
- public enum ChargingPreference {
- noPreSelection,
- chargingWindow
+ enum ChargingPreference {
+ NO_PRESELECTION,
+ CHARGING_WINDOW
}
- public static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
+ static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
- public static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
+ static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
// List of all Thing Type UIDs
- public static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
- public static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID,
- VehicleType.CONVENTIONAL.toString());
- public static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID,
- VehicleType.PLUGIN_HYBRID.toString());
- public static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID,
- VehicleType.ELECTRIC_REX.toString());
- public static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
- public static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT,
- THING_TYPE_CONV, THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
+ static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
+ static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID, VehicleType.CONVENTIONAL.toString());
+ static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID, VehicleType.PLUGIN_HYBRID.toString());
+ static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC_REX.toString());
+ static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
+ static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT, THING_TYPE_CONV,
+ THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
// Thing Group definitions
- public static final String CHANNEL_GROUP_STATUS = "status";
- public static final String CHANNEL_GROUP_SERVICE = "service";
- public static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
- public static final String CHANNEL_GROUP_DOORS = "doors";
- public static final String CHANNEL_GROUP_RANGE = "range";
- public static final String CHANNEL_GROUP_LOCATION = "location";
- public static final String CHANNEL_GROUP_REMOTE = "remote";
- public static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
- public static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
- public static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
- public static final String CHANNEL_GROUP_TIRES = "tires";
- public static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
+ static final String CHANNEL_GROUP_STATUS = "status";
+ static final String CHANNEL_GROUP_SERVICE = "service";
+ static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
+ static final String CHANNEL_GROUP_DOORS = "doors";
+ static final String CHANNEL_GROUP_RANGE = "range";
+ static final String CHANNEL_GROUP_LOCATION = "location";
+ static final String CHANNEL_GROUP_REMOTE = "remote";
+ static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
+ static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
+ static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
+ static final String CHANNEL_GROUP_TIRES = "tires";
+ static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
// Charge Statistics & Sessions
- public static final String SESSIONS = "sessions";
- public static final String ENERGY = "energy";
- public static final String TITLE = "title";
- public static final String SUBTITLE = "subtitle";
- public static final String ISSUE = "issue";
- public static final String STATUS = "status";
+ static final String SESSIONS = "sessions";
+ static final String ENERGY = "energy";
+ static final String TITLE = "title";
+ static final String SUBTITLE = "subtitle";
+ static final String ISSUE = "issue";
+ static final String STATUS = "status";
// Generic Constants for several groups
- public static final String NAME = "name";
- public static final String DETAILS = "details";
- public static final String SEVERITY = "severity";
- public static final String DATE = "date";
- public static final String MILEAGE = "mileage";
- public static final String GPS = "gps";
- public static final String HEADING = "heading";
- public static final String ADDRESS = "address";
- public static final String HOME_DISTANCE = "home-distance";
+ static final String NAME = "name";
+ static final String DETAILS = "details";
+ static final String SEVERITY = "severity";
+ static final String DATE = "date";
+ static final String MILEAGE = "mileage";
+ static final String GPS = "gps";
+ static final String HEADING = "heading";
+ static final String ADDRESS = "address";
+ static final String HOME_DISTANCE = "home-distance";
// Status
- public static final String DOORS = "doors";
- public static final String WINDOWS = "windows";
- public static final String LOCK = "lock";
- public static final String SERVICE_DATE = "service-date";
- public static final String SERVICE_MILEAGE = "service-mileage";
- public static final String CHECK_CONTROL = "check-control";
- public static final String PLUG_CONNECTION = "plug-connection";
- public static final String CHARGE_STATUS = "charge";
- public static final String CHARGE_INFO = "charge-info";
- public static final String MOTION = "motion";
- public static final String LAST_UPDATE = "last-update";
- public static final String RAW = "raw";
+ static final String DOORS = "doors";
+ static final String WINDOWS = "windows";
+ static final String LOCK = "lock";
+ static final String SERVICE_DATE = "service-date";
+ static final String SERVICE_MILEAGE = "service-mileage";
+ static final String CHECK_CONTROL = "check-control";
+ static final String PLUG_CONNECTION = "plug-connection";
+ static final String CHARGE_STATUS = "charge";
+ static final String CHARGE_REMAINING = "charge-remaining";
+ static final String LAST_UPDATE = "last-update";
+ static final String LAST_FETCHED = "last-fetched";
+ static final String RAW = "raw";
// Door Details
- public static final String DOOR_DRIVER_FRONT = "driver-front";
- public static final String DOOR_DRIVER_REAR = "driver-rear";
- public static final String DOOR_PASSENGER_FRONT = "passenger-front";
- public static final String DOOR_PASSENGER_REAR = "passenger-rear";
- public static final String HOOD = "hood";
- public static final String TRUNK = "trunk";
- public static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
- public static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
- public static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
- public static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
- public static final String WINDOW_REAR = "win-rear";
- public static final String SUNROOF = "sunroof";
+ static final String DOOR_DRIVER_FRONT = "driver-front";
+ static final String DOOR_DRIVER_REAR = "driver-rear";
+ static final String DOOR_PASSENGER_FRONT = "passenger-front";
+ static final String DOOR_PASSENGER_REAR = "passenger-rear";
+ static final String HOOD = "hood";
+ static final String TRUNK = "trunk";
+ static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
+ static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
+ static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
+ static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
+ static final String WINDOW_REAR = "win-rear";
+ static final String SUNROOF = "sunroof";
// Charge Profile
- public static final String CHARGE_PROFILE_CLIMATE = "climate";
- public static final String CHARGE_PROFILE_MODE = "mode";
- public static final String CHARGE_PROFILE_PREFERENCE = "prefs";
- public static final String CHARGE_PROFILE_CONTROL = "control";
- public static final String CHARGE_PROFILE_TARGET = "target";
- public static final String CHARGE_PROFILE_LIMIT = "limit";
- public static final String CHARGE_WINDOW_START = "window-start";
- public static final String CHARGE_WINDOW_END = "window-end";
- public static final String CHARGE_TIMER1 = "timer1";
- public static final String CHARGE_TIMER2 = "timer2";
- public static final String CHARGE_TIMER3 = "timer3";
- public static final String CHARGE_TIMER4 = "timer4";
- public static final String CHARGE_DEPARTURE = "-departure";
- public static final String CHARGE_ENABLED = "-enabled";
- public static final String CHARGE_DAY_MON = "-day-mon";
- public static final String CHARGE_DAY_TUE = "-day-tue";
- public static final String CHARGE_DAY_WED = "-day-wed";
- public static final String CHARGE_DAY_THU = "-day-thu";
- public static final String CHARGE_DAY_FRI = "-day-fri";
- public static final String CHARGE_DAY_SAT = "-day-sat";
- public static final String CHARGE_DAY_SUN = "-day-sun";
+ static final String CHARGE_PROFILE_CLIMATE = "climate";
+ static final String CHARGE_PROFILE_MODE = "mode";
+ static final String CHARGE_PROFILE_PREFERENCE = "prefs";
+ static final String CHARGE_PROFILE_CONTROL = "control";
+ static final String CHARGE_PROFILE_TARGET = "target";
+ static final String CHARGE_PROFILE_LIMIT = "limit";
+ static final String CHARGE_WINDOW_START = "window-start";
+ static final String CHARGE_WINDOW_END = "window-end";
+ static final String CHARGE_TIMER1 = "timer1";
+ static final String CHARGE_TIMER2 = "timer2";
+ static final String CHARGE_TIMER3 = "timer3";
+ static final String CHARGE_TIMER4 = "timer4";
+ static final String CHARGE_DEPARTURE = "-departure";
+ static final String CHARGE_ENABLED = "-enabled";
+ static final String CHARGE_DAY_MON = "-day-mon";
+ static final String CHARGE_DAY_TUE = "-day-tue";
+ static final String CHARGE_DAY_WED = "-day-wed";
+ static final String CHARGE_DAY_THU = "-day-thu";
+ static final String CHARGE_DAY_FRI = "-day-fri";
+ static final String CHARGE_DAY_SAT = "-day-sat";
+ static final String CHARGE_DAY_SUN = "-day-sun";
// Range
- public static final String RANGE_ELECTRIC = "electric";
- public static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
- public static final String RANGE_FUEL = "fuel";
- public static final String RANGE_RADIUS_FUEL = "radius-fuel";
- public static final String RANGE_HYBRID = "hybrid";
- public static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
- public static final String REMAINING_FUEL = "remaining-fuel";
- public static final String SOC = "soc";
+ static final String RANGE_ELECTRIC = "electric";
+ static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
+ static final String RANGE_FUEL = "fuel";
+ static final String RANGE_RADIUS_FUEL = "radius-fuel";
+ static final String RANGE_HYBRID = "hybrid";
+ static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
+ static final String REMAINING_FUEL = "remaining-fuel";
+ static final String ESTIMATED_FUEL_L_100KM = "estimated-fuel-l-100km";
+ static final String ESTIMATED_FUEL_MPG = "estimated-fuel-mpg";
+ static final String SOC = "soc";
// Image
- public static final String IMAGE_FORMAT = "png";
- public static final String IMAGE_VIEWPORT = "view";
+ static final String IMAGE_FORMAT = "png";
+ static final String IMAGE_VIEWPORT = "view";
// Remote Services
- public static final String REMOTE_SERVICE_LIGHT_FLASH = "light-flash";
- public static final String REMOTE_SERVICE_VEHICLE_FINDER = "vehicle-finder";
- public static final String REMOTE_SERVICE_DOOR_LOCK = "door-lock";
- public static final String REMOTE_SERVICE_DOOR_UNLOCK = "door-unlock";
- public static final String REMOTE_SERVICE_HORN = "horn-blow";
- public static final String REMOTE_SERVICE_AIR_CONDITIONING_START = "climate-now-start";
- public static final String REMOTE_SERVICE_AIR_CONDITIONING_STOP = "climate-now-stop";
-
- 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";
}
*/
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;
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;
* handlers.
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - changed localeProvider handling
*/
@NonNullByDefault
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
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
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;
}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
+++ /dev/null
-/**
- * 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;
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.console;
+
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.BINDING_ID;
+import static org.openhab.binding.mybmw.internal.MyBMWConstants.THING_TYPE_CONNECTED_DRIVE_ACCOUNT;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
+import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
+import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
+import org.openhab.binding.mybmw.internal.handler.backend.ResponseContentAnonymizer;
+import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
+import org.openhab.core.io.console.Console;
+import org.openhab.core.io.console.ConsoleCommandCompleter;
+import org.openhab.core.io.console.StringsCompleter;
+import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
+import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
+import org.openhab.core.thing.ThingRegistry;
+import org.openhab.core.thing.ThingStatus;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link MyBMWCommandExtension} is responsible for handling console commands
+ *
+ * @author Mark Herwege - Initial contribution
+ * @author Martin Grassl - improved exception handling
+ */
+
+@NonNullByDefault
+@Component(service = ConsoleCommandExtension.class)
+public class MyBMWCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
+
+ private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+ private static final String FINGERPRINT_ROOT_PATH = System.getProperty("user.home") + File.separator + BINDING_ID;
+
+ private static final String FINGERPRINT = "fingerprint";
+ private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(FINGERPRINT), false);
+
+ private final ThingRegistry thingRegistry;
+
+ @Activate
+ public MyBMWCommandExtension(final @Reference ThingRegistry thingRegistry) {
+ super("mybmw", "Interact with the MyBMW binding");
+ this.thingRegistry = thingRegistry;
+ }
+
+ @Override
+ public void execute(String[] args, Console console) {
+ if ((args.length < 1) || (args.length > 3)) {
+ console.println("Invalid number of arguments");
+ printUsage(console);
+ return;
+ }
+
+ List<MyBMWBridgeHandler> bridgeHandlers = thingRegistry.stream()
+ .filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
+ .map(b -> ((MyBMWBridgeHandler) b.getHandler())).filter(Objects::nonNull).collect(Collectors.toList());
+ if (bridgeHandlers.isEmpty()) {
+ console.println("No account bridges configured");
+ return;
+ }
+
+ if (!FINGERPRINT.equalsIgnoreCase(args[0])) {
+ console.println("Unsupported command '" + args[0] + "'");
+ printUsage(console);
+ return;
+ }
+
+ List<MyBMWBridgeHandler> handlers;
+ if (args.length > 1) {
+ handlers = bridgeHandlers.stream()
+ .filter(b -> args[1].equalsIgnoreCase(b.getThing().getConfiguration().get("userName").toString()))
+ .filter(Objects::nonNull).collect(Collectors.toList());
+ if (handlers.isEmpty()) {
+ console.println("No myBMW account bridge for user '" + args[1] + "'");
+ printUsage(console);
+ return;
+ }
+ } else {
+ handlers = bridgeHandlers;
+ }
+
+ String basePath = FINGERPRINT_ROOT_PATH + File.separator
+ + LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE);
+ String path = nextPath(basePath, null);
+
+ console.println("# Start fingerprint");
+ int accountNdx = 0;
+ for (MyBMWBridgeHandler handler : handlers) {
+ accountNdx++;
+ console.println("### Account " + String.valueOf(accountNdx));
+ if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
+ console.println("MyBMW bridge for account not online, cannot create fingerprint");
+ } else {
+ String accountPath = path + File.separator + "Account-" + String.valueOf(accountNdx);
+ handler.getMyBmwProxy().ifPresentOrElse(prox -> {
+ // get list of vehicles
+ List<@NonNull VehicleBase> vehicles = null;
+ try {
+ vehicles = prox.requestVehiclesBase();
+
+ for (String brand : BimmerConstants.REQUESTED_BRANDS) {
+ console.println("###### Vehicles base for brand " + brand);
+ printAndSave(console, accountPath, "VehicleBase_" + brand,
+ prox.requestVehiclesBaseJson(brand));
+ }
+
+ if (args.length == 3) {
+ Optional<VehicleBase> vehicleOptional = vehicles.stream()
+ .filter(v -> v.getVin().equalsIgnoreCase(args[2])).findAny();
+ if (vehicleOptional.isEmpty()) {
+ console.println("'" + args[2] + "' is not a valid vin on the account bridge with id '"
+ + handler.getThing().getUID().getId() + "'");
+ printUsage(console);
+ return;
+ }
+ vehicles = List.of(vehicleOptional.get());
+ }
+
+ int vinNdx = 0;
+ for (VehicleBase vehicleBase : vehicles) {
+ vinNdx++;
+ String vinPath = accountPath + File.separator + "Vin-" + String.valueOf(vinNdx);
+ console.println("###### Vehicle " + String.valueOf(vinNdx));
+
+ // get state
+ console.println("######## Vehicle state");
+ printAndSave(console, vinPath, "VehicleState", prox.requestVehicleStateJson(
+ vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
+
+ // get charge statistics -> only successful for electric vehicles
+ console.println("######### Vehicle charging statistics");
+ printAndSave(console, vinPath, "VehicleChargingStatistics",
+ prox.requestChargeStatisticsJson(vehicleBase.getVin(),
+ vehicleBase.getAttributes().getBrand()));
+
+ // get charge sessions -> only successful for electric vehicles
+ console.println("######### Vehicle charging sessions");
+ printAndSave(console, vinPath, "VehicleChargingSessions", prox.requestChargeSessionsJson(
+ vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
+
+ console.println("###### End vehicle " + String.valueOf(vinNdx));
+ }
+ } catch (NetworkException e) {
+ console.println("Fingerprint failed, network exception: " + e.getReason());
+ }
+ }, () -> {
+ console.println("MyBMW bridge with id '" + handler.getThing().getUID().getId()
+ + "', communication not started, cannot retrieve fingerprint");
+ });
+ }
+ console.println("### End account " + String.valueOf(accountNdx));
+ }
+
+ try {
+ String zipfile = nextPath(basePath, "zip");
+ zipDirectory(Paths.get(path), Paths.get(zipfile));
+ deleteDirectory(path);
+ console.println("### Fingerprint has been written to zipfile: " + zipfile);
+ } catch (IOException e) {
+ console.println("Exception zipping fingerprint: " + e.getMessage());
+ console.println("### Fingerprint has been written to files in directory: " + path);
+ }
+
+ console.println("# End fingerprint");
+ }
+
+ private void printAndSave(Console console, String path, String filename, String content) throws NetworkException {
+ String json = prettyJson(ResponseContentAnonymizer.anonymizeResponseContent(content));
+ console.println(json);
+ try {
+ writeJsonToFile(path, filename, json);
+ } catch (IOException e) {
+ console.println("Exception writing to file: " + e.getMessage());
+ }
+ }
+
+ private String nextPath(String pathString, @Nullable String extension) {
+ String path = pathString + ((extension != null) ? ("." + extension) : "");
+ int pathNdx = 1;
+ while (Files.exists(Paths.get(path))) {
+ path = pathString + "_" + String.valueOf(pathNdx) + ((extension != null) ? ("." + extension) : "");
+ pathNdx++;
+ }
+ return path;
+ }
+
+ private String prettyJson(String json) {
+ try {
+ return GSON.toJson(JsonParser.parseString(json));
+ } catch (JsonSyntaxException e) {
+ // Keep the unformatted json if there is a syntax exception
+ return json;
+ }
+ }
+
+ private void writeJsonToFile(String pathString, String filename, String json) throws IOException {
+ try {
+ JsonElement element = JsonParser.parseString(json);
+ if (element.isJsonNull() || (element.isJsonArray() && ((JsonArray) element).size() == 0)) {
+ // Don't write a file if empty
+ return;
+ }
+ } catch (JsonSyntaxException e) {
+ // Just continue and write the file with non-valid json anyway
+ }
+
+ String path = nextPath(pathString + File.separator + filename, "json");
+
+ // ensure full path exists
+ File file = new File(path);
+ file.getParentFile().mkdirs();
+
+ final byte[] contents = json.getBytes(StandardCharsets.UTF_8);
+ Files.write(file.toPath(), contents);
+ }
+
+ // Stackoverflow:
+ // https://stackoverflow.com/questions/57997257/how-can-i-zip-a-complete-directory-with-all-subfolders-in-java
+ private void zipDirectory(Path sourceDirectoryPath, Path zipPath) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
+ ZipOutputStream zos = new ZipOutputStream(fos)) {
+ Files.walkFileTree(sourceDirectoryPath, new SimpleFileVisitor<@Nullable Path>() {
+ @Override
+ public FileVisitResult visitFile(@Nullable Path file, @Nullable BasicFileAttributes attrs)
+ throws IOException {
+ zos.putNextEntry(new ZipEntry(sourceDirectoryPath.relativize(file).toString()));
+ Files.copy(file, zos);
+ zos.closeEntry();
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw e;
+ }
+ }
+
+ private void deleteDirectory(String path) throws IOException {
+ Files.walk(Paths.get(path)).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
+ }
+
+ @Override
+ public List<String> getUsages() {
+ return Arrays.asList(
+ new String[] { buildCommandUsage(FINGERPRINT, "generate fingerprint for all vehicles on all accounts"),
+ buildCommandUsage(FINGERPRINT + " <userName>", "generate fingerprint for vehicles on account"),
+ buildCommandUsage(FINGERPRINT + " <userName> <vin>",
+ "generate fingerprint for vehicle with vin on account") });
+ }
+
+ @Override
+ public @Nullable ConsoleCommandCompleter getCompleter() {
+ return this;
+ }
+
+ @Override
+ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
+ try {
+ if (cursorArgumentIndex <= 0) {
+ return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
+ } else if (cursorArgumentIndex == 1) {
+ return new StringsCompleter(
+ thingRegistry.stream()
+ .filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
+ .map(t -> t.getConfiguration().get("userName").toString()).collect(Collectors.toList()),
+ false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
+ } else if (cursorArgumentIndex == 2) {
+ MyBMWBridgeHandler handler = (MyBMWBridgeHandler) thingRegistry.stream()
+ .filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID())
+ && args[1].equals(t.getConfiguration().get("userName")))
+ .map(t -> t.getHandler()).findAny().get();
+ List<VehicleBase> vehicles = handler.getMyBmwProxy().get().requestVehiclesBase();
+ return new StringsCompleter(
+ vehicles.stream().map(v -> v.getVin()).filter(Objects::nonNull).collect(Collectors.toList()),
+ false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
+ }
+ } catch (NoSuchElementException | NetworkException e) {
+ return false;
+ }
+ return false;
+ }
+}
*/
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;
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<MyBMWBridgeHandler> 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<Vehicle> vehicleList) {
- bridgeHandler.ifPresent(bridge -> {
- final ThingUID bridgeUID = bridge.getThing().getUID();
- vehicleList.forEach(vehicle -> {
- // the DriveTrain field in the delivered json is defining the Vehicle Type
- String vehicleType = VehicleStatusUtils.vehicleType(vehicle.driveTrain, vehicle.model).toString();
- SUPPORTED_THING_SET.forEach(entry -> {
- if (entry.getId().equals(vehicleType)) {
- ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
- Map<String, String> properties = new HashMap<>();
- // Vehicle Properties
- properties.put("vehicleModel", vehicle.model);
- properties.put("vehicleDriveTrain", vehicle.driveTrain);
- properties.put("vehicleConstructionYear", Integer.toString(vehicle.year));
- properties.put("vehicleBodytype", vehicle.bodyType);
-
- properties.put("servicesSupported", getServices(vehicle, SUPPORTED_SUFFIX, true));
- properties.put("servicesUnsupported", getServices(vehicle, SUPPORTED_SUFFIX, false));
- String servicesEnabled = getServices(vehicle, ENABLED_SUFFIX, true) + Constants.SEMICOLON
- + getServices(vehicle, ENABLE_SUFFIX, true);
- properties.put("servicesEnabled", servicesEnabled.trim());
- String servicesDisabled = getServices(vehicle, ENABLED_SUFFIX, false) + Constants.SEMICOLON
- + getServices(vehicle, ENABLE_SUFFIX, false);
- properties.put("servicesDisabled", servicesDisabled.trim());
-
- // For RemoteServices we need to do it step-by-step
- StringBuffer remoteServicesEnabled = new StringBuffer();
- StringBuffer remoteServicesDisabled = new StringBuffer();
- if (vehicle.capabilities.lock.isEnabled) {
- remoteServicesEnabled.append(
- RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled.append(
- RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
- }
- if (vehicle.capabilities.unlock.isEnabled) {
- remoteServicesEnabled.append(
- RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled.append(
- RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
- }
- if (vehicle.capabilities.lights.isEnabled) {
- remoteServicesEnabled.append(
- RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled.append(
- RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
- }
- if (vehicle.capabilities.horn.isEnabled) {
- remoteServicesEnabled.append(
- RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled.append(
- RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
- }
- if (vehicle.capabilities.vehicleFinder.isEnabled) {
- remoteServicesEnabled.append(
- RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled.append(
- RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
- }
- if (vehicle.capabilities.climateNow.isEnabled) {
- remoteServicesEnabled.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
- + Constants.SEMICOLON);
- } else {
- remoteServicesDisabled
- .append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
- + Constants.SEMICOLON);
- }
- properties.put("remoteServicesEnabled", remoteServicesEnabled.toString().trim());
- properties.put("remoteServicesDisabled", remoteServicesDisabled.toString().trim());
-
- // Update Properties for already created Things
- bridge.getThing().getThings().forEach(vehicleThing -> {
- Configuration c = vehicleThing.getConfiguration();
- if (c.containsKey(MyBMWConstants.VIN)) {
- String thingVIN = c.get(MyBMWConstants.VIN).toString();
- if (vehicle.vin.equals(thingVIN)) {
- vehicleThing.setProperties(properties);
- }
- }
- });
+ 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<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
+ private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
+ private Optional<ThingUID> bridgeUid = Optional.empty();
- String vehicleLabel = vehicle.brand + " " + vehicle.model;
- Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
- thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
- .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
- .withProperties(convertedProperties).build());
- }
- });
- });
- });
+ 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());
}
}
@Override
protected void startScan() {
- bridgeHandler.ifPresent(MyBMWBridgeHandler::requestVehicles);
+ logger.trace("VehicleDiscovery.startScan");
+ discoverVehicles();
}
@Override
public void deactivate() {
+ logger.trace("VehicleDiscovery.deactivate");
+
super.deactivate();
}
- public static String getServices(Vehicle vehicle, String suffix, boolean enabled) {
- StringBuffer sb = new StringBuffer();
- List<String> l = getObject(vehicle.capabilities, enabled);
- for (String capEntry : l) {
- // remove "is" prefix
- String cut = capEntry.substring(2);
- if (cut.endsWith(suffix)) {
- if (sb.length() > 0) {
- sb.append(Constants.SEMICOLON);
+ public void discoverVehicles() {
+ logger.trace("VehicleDiscovery.discoverVehicles");
+
+ myBMWProxy = bridgeHandler.get().getMyBmwProxy();
+
+ try {
+ Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
+ try {
+ return prox.requestVehicles();
+ } catch (NetworkException e) {
+ throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
}
- sb.append(cut.substring(0, cut.length() - suffix.length()));
- }
+ });
+ vehicleList.ifPresentOrElse(vehicles -> {
+ bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoverySuccess());
+ processVehicles(vehicles);
+ }, () -> bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError()));
+ } catch (IllegalStateException ex) {
+ bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError());
}
- return sb.toString();
}
/**
- * Get all field names from a DTO with a specific value
- * Used to get e.g. all services which are "ACTIVATED"
- *
- * @param dto Object
- * @param compare String which needs to map with the value
- * @return String with all field names matching this value separated with Spaces
+ * this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
+ *
+ * it iterates through the list of existing things and checks if the vehicles found via the API
+ * call are already known to OH. If not, it creates a new thing and puts it into the inbox
+ *
+ * @param vehicleList
*/
- public static List<String> getObject(Object dto, Object compare) {
- List<String> l = new ArrayList<String>();
- for (Field field : dto.getClass().getDeclaredFields()) {
- try {
- Object value = field.get(dto);
- if (compare.equals(value)) {
- l.add(field.getName());
+ private void processVehicles(List<Vehicle> vehicleList) {
+ logger.trace("VehicleDiscovery.processVehicles");
+
+ vehicleList.forEach(vehicle -> {
+ // the DriveTrain field in the delivered json is defining the Vehicle Type
+ String vehicleType = VehicleStatusUtils
+ .vehicleType(vehicle.getVehicleBase().getAttributes().getDriveTrain(),
+ vehicle.getVehicleBase().getAttributes().getModel())
+ .toString();
+ MyBMWConstants.SUPPORTED_THING_SET.forEach(entry -> {
+ if (entry.getId().equals(vehicleType)) {
+ ThingUID uid = new ThingUID(entry, vehicle.getVehicleBase().getVin(), bridgeUid.get().getId());
+
+ Map<String, String> properties = generateProperties(vehicle);
+
+ boolean thingFound = false;
+ // Update Properties for already created Things
+ List<Thing> vehicleThings = bridgeHandler.get().getThing().getThings();
+ for (Thing vehicleThing : vehicleThings) {
+ Configuration configuration = vehicleThing.getConfiguration();
+
+ if (configuration.containsKey(MyBMWConstants.VIN)) {
+ String thingVIN = configuration.get(MyBMWConstants.VIN).toString();
+ if (vehicle.getVehicleBase().getVin().equals(thingVIN)) {
+ vehicleThing.setProperties(properties);
+ thingFound = true;
+ }
+ }
+ }
+
+ // the vehicle found is not yet known to OH, so put it into the inbox
+ if (!thingFound) {
+ // Properties needed for functional Thing
+ VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
+ Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
+ convertedProperties.put(MyBMWConstants.VIN, vehicle.getVehicleBase().getVin());
+ convertedProperties.put(MyBMWConstants.VEHICLE_BRAND, vehicleAttributes.getBrand());
+ convertedProperties.put(MyBMWConstants.REFRESH_INTERVAL,
+ Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
+
+ String vehicleLabel = vehicleAttributes.getBrand() + " " + vehicleAttributes.getModel();
+ thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUid.get())
+ .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
+ .withProperties(convertedProperties).build());
+ }
}
- } catch (IllegalArgumentException | IllegalAccessException e) {
- LOGGER.debug("Field {} not found {}", compare, e.getMessage());
- }
+ });
+ });
+ }
+
+ private Map<String, String> generateProperties(Vehicle vehicle) {
+ Map<String, String> properties = new HashMap<>();
+
+ // Vehicle Properties
+ VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
+ properties.put(MyBMWConstants.VEHICLE_MODEL, vehicleAttributes.getModel());
+ properties.put(MyBMWConstants.VEHICLE_DRIVE_TRAIN, vehicleAttributes.getDriveTrain());
+ properties.put(MyBMWConstants.VEHICLE_CONSTRUCTION_YEAR, Integer.toString(vehicleAttributes.getYear()));
+ properties.put(MyBMWConstants.VEHICLE_BODYTYPE, vehicleAttributes.getBodyType());
+
+ VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
+
+ properties.put(MyBMWConstants.SERVICES_SUPPORTED,
+ vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, true));
+ properties.put(MyBMWConstants.SERVICES_UNSUPPORTED,
+ vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, false));
+ properties.put(MyBMWConstants.SERVICES_ENABLED,
+ vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, true));
+ properties.put(MyBMWConstants.SERVICES_DISABLED,
+ vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, false));
+
+ // For RemoteServices we need to do it step-by-step
+ StringBuffer remoteServicesEnabled = new StringBuffer();
+ StringBuffer remoteServicesDisabled = new StringBuffer();
+ if (vehicleCapabilities.isLock()) {
+ remoteServicesEnabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
}
- return l;
+ if (vehicleCapabilities.isUnlock()) {
+ remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
+ }
+ if (vehicleCapabilities.isLights()) {
+ remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
+ }
+ if (vehicleCapabilities.isHorn()) {
+ remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
+ }
+ if (vehicleCapabilities.isVehicleFinder()) {
+ remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
+ }
+ if (vehicleCapabilities.isVehicleFinder()) {
+ remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
+ } else {
+ remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
+ }
+ properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
+ properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());
+
+ return properties;
}
}
* 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",
// "authenticate_user"
// ],
public List<String> promptValues; // ": ["login"]
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return "AuthQueryResponse [clientName=" + clientName + ", clientSecret=" + clientSecret + ", clientId="
+ + clientId + ", gcdmBaseUrl=" + gcdmBaseUrl + ", returnUrl=" + returnUrl + ", brand=" + brand
+ + ", language=" + language + ", country=" + country + ", authorizationEndpoint=" + authorizationEndpoint
+ + ", tokenEndpoint=" + tokenEndpoint + ", scopes=" + scopes + ", promptValues=" + promptValues + "]";
+ }
}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.dto.charge;
-
-import java.util.List;
-
-/**
- * The {@link ChargeProfile} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- * @author Norbert Truchsess - edit and send of charge profile
- */
-public class ChargeProfile {
- public static final Timer INVALID_TIMER = new Timer();
-
- public ChargingWindow reductionOfChargeCurrent;
- public String chargingMode;// ": "immediateCharging",
- public String chargingPreference;// ": "chargingWindow",
- public String chargingControlType;// ": "weeklyPlanner",
- public List<Timer> departureTimes;
- public boolean climatisationOn;// ": false,
- public ChargingSettings chargingSettings;
-
- public Timer getTimerId(int id) {
- if (departureTimes != null) {
- for (Timer t : departureTimes) {
- if (t.id == id) {
- return t;
- }
- }
- }
- return INVALID_TIMER;
- }
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.dto.charge;
-
-import java.util.List;
-
-/**
- * The {@link ChargeSessions} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- */
-public class ChargeSessions {
- public String total;// ": "~ 218 kWh",
- public String numberOfSessions;// ": "17",
- public String chargingListState;// ": "HAS_SESSIONS",
- public List<ChargeSession> sessions;
-}
+++ /dev/null
-/**
- * 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 ChargeSessionsContainer} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- */
-public class ChargeSessionsContainer {
- public Object paginationInfo;
- public ChargeSessions chargingSessions;
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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;// ": {
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.charge;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link ChargingProfile} Data Transfer Object
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Norbert Truchsess - edit and send of charge profile
+ * @author Martin Grassl - refactored to Java Bean
+ */
+public class ChargingProfile {
+ private ChargingWindow reductionOfChargeCurrent = new ChargingWindow();
+ private String chargingMode = "";// ": "immediateCharging",
+ private String chargingPreference = "";// ": "chargingWindow",
+ private String chargingControlType = "";// ": "weeklyPlanner",
+ private List<Timer> departureTimes = new ArrayList<>();
+ private boolean climatisationOn = false;// ": false,
+ private ChargingSettings chargingSettings = new ChargingSettings();
+
+ public Timer getTimerId(int id) {
+ if (departureTimes != null) {
+ for (Timer t : departureTimes) {
+ if (t.id == id) {
+ return t;
+ }
+ }
+ }
+ return new Timer();
+ }
+
+ public ChargingWindow getReductionOfChargeCurrent() {
+ return reductionOfChargeCurrent;
+ }
+
+ public String getChargingMode() {
+ return chargingMode;
+ }
+
+ public String getChargingPreference() {
+ return chargingPreference;
+ }
+
+ public String getChargingControlType() {
+ return chargingControlType;
+ }
+
+ public List<Timer> getDepartureTimes() {
+ return departureTimes;
+ }
+
+ public boolean isClimatisationOn() {
+ return climatisationOn;
+ }
+
+ public ChargingSettings getChargingSettings() {
+ return chargingSettings;
+ }
+
+ @Override
+ public String toString() {
+ return "ChargingProfile [reductionOfChargeCurrent=" + reductionOfChargeCurrent + ", chargingMode="
+ + chargingMode + ", chargingPreference=" + chargingPreference + ", chargingControlType="
+ + chargingControlType + ", departureTimes=" + departureTimes + ", climatisationOn=" + climatisationOn
+ + ", chargingSettings=" + chargingSettings + "]";
+ }
+}
--- /dev/null
+/**
+ * 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
+ + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.charge;
+
+import java.util.List;
+
+/**
+ * The {@link ChargingSessions} Data Transfer Object
+ *
+ * @author Bernd Weymann - Initial contribution
+ */
+public class ChargingSessions {
+ private String total;// ": "~ 218 kWh",
+ private String numberOfSessions;// ": "17",
+ private String chargingListState;// ": "HAS_SESSIONS",
+ private List<ChargingSession> sessions;
+
+ /**
+ * @return the total
+ */
+ public String getTotal() {
+ return total;
+ }
+
+ /**
+ * @return the numberOfSessions
+ */
+ public String getNumberOfSessions() {
+ return numberOfSessions;
+ }
+
+ /**
+ * @return the chargingListState
+ */
+ public String getChargingListState() {
+ return chargingListState;
+ }
+
+ /**
+ * @return the sessions
+ */
+ public List<ChargingSession> getSessions() {
+ return sessions;
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return "ChargingSessions [total=" + total + ", numberOfSessions=" + numberOfSessions + ", chargingListState="
+ + chargingListState + ", sessions=" + sessions + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 ChargingSessionsContainer} Data Transfer Object
+ *
+ * @author Bernd Weymann - Initial contribution
+ */
+public class ChargingSessionsContainer {
+ public Object paginationInfo;
+ public ChargingSessions chargingSessions;
+}
* 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 + "]";
+ }
}
--- /dev/null
+/**
+ * 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
+ + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
* 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 + "]";
+ }
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.charge;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * derived from the API responses
+ *
+ * @author Martin Grassl - initial contribution
+ */
+public class RemoteChargingCommands {
+ private List<String> chargingControl = new ArrayList<>();
+ private List<String> flapControl = new ArrayList<>();
+ private List<String> plugControl = new ArrayList<>();
+
+ /**
+ * @return the chargingControl
+ */
+ public List<String> getChargingControl() {
+ return chargingControl;
+ }
+
+ /**
+ * @param chargingControl the chargingControl to set
+ */
+ public void setChargingControl(List<String> chargingControl) {
+ this.chargingControl = chargingControl;
+ }
+
+ /**
+ * @return the flapControl
+ */
+ public List<String> getFlapControl() {
+ return flapControl;
+ }
+
+ /**
+ * @param flapControl the flapControl to set
+ */
+ public void setFlapControl(List<String> flapControl) {
+ this.flapControl = flapControl;
+ }
+
+ /**
+ * @return the plugControl
+ */
+ public List<String> getPlugControl() {
+ return plugControl;
+ }
+
+ /**
+ * @param plugControl the plugControl to set
+ */
+ public void setPlugControl(List<String> plugControl) {
+ this.plugControl = plugControl;
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ @Override
+ public String toString() {
+ return "RemoteChargingCommands [chargingControl=" + chargingControl + ", flapControl=" + flapControl
+ + ", plugControl=" + plugControl + "]";
+ }
+}
*/
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 + "]";
}
}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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 Address} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- */
-public class Address {
- public String formatted;
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.dto.properties;
-
-import java.util.List;
-
-/**
- * The {@link Properties} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- */
-public class Properties {
- public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
- public boolean inMotion;// ": false,
- public boolean areDoorsLocked;// ": true,
- public String originCountryISO;// ": "DE",
- public boolean areDoorsClosed;// ": true,
- public boolean areDoorsOpen;// ": false,
- public boolean areWindowsClosed;// ": true,
- public DoorsWindows doorsAndWindows;// ":
- public boolean isServiceRequired;// ":false
- public FuelLevel fuelLevel;
- public ChargingState chargingState;// ":
- public Range combustionRange;
- public Range combinedRange;
- public Range electricRange;
- public Range electricRangeAndStatus;
- public List<CCM> checkControlMessages;
- public List<CBS> serviceRequired;
- public Location vehicleLocation;
- public Tires tires;
- // "climateControl":{} [todo] definition currently unknown
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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"
-}
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,
* 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 + "]";
+ }
}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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.
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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"
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.dto.status;
-
-import java.util.List;
-
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
-
-/**
- * The {@link Status} Data Transfer Object
- *
- * @author Bernd Weymann - Initial contribution
- */
-public class Status {
- public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
- public Mileage currentMileage;
- public Issues issues;
- public String doorsGeneralState;// ":"Locked",
- public String checkControlMessagesGeneralState;// ":"No Issues",
- public List<DoorWindow> doorsAndWindows;// ":[
- public List<CCMMessage> checkControlMessages;//
- public List<CBSMessage> requiredServices;//
- // "recallMessages":[],
- // "recallExternalUrl":null,
- public List<FuelIndicator> fuelIndicators;
- public String timestampMessage;// ":"Updated from vehicle 12/21/2021 05:46 PM",
- public ChargeProfile chargingProfile;
-}
--- /dev/null
+/**
+ * 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 Address} Data Transfer Object
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - refactored to Java Bean
+ */
+public class Address {
+ private String formatted = "";
+
+ public String getFormatted() {
+ return formatted;
+ }
+
+ @Override
+ public String toString() {
+ return "Address [formatted=" + formatted + "]";
+ }
+}
+++ /dev/null
-/**
- * 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;
-}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.vehicle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * derived from the API responses
+ *
+ * @author Martin Grassl - initial contribution
+ */
+public class ClimateTimer {
+ private boolean isWeeklyTimer = false; // true,
+ private String timerAction = ""; // DEACTIVATE,
+ private List<String> timerWeekDays = new ArrayList<>(); // [ MONDAY ]
+ private DepartureTime departureTime = new DepartureTime();
+
+ public boolean isWeeklyTimer() {
+ return isWeeklyTimer;
+ }
+
+ public void setWeeklyTimer(boolean isWeeklyTimer) {
+ this.isWeeklyTimer = isWeeklyTimer;
+ }
+
+ public String getTimerAction() {
+ return timerAction;
+ }
+
+ public void setTimerAction(String timerAction) {
+ this.timerAction = timerAction;
+ }
+
+ public List<String> getTimerWeekDays() {
+ return timerWeekDays;
+ }
+
+ public void setTimerWeekDays(List<String> timerWeekDays) {
+ this.timerWeekDays = timerWeekDays;
+ }
+
+ public DepartureTime getDepartureTime() {
+ return departureTime;
+ }
+
+ public void setDepartureTime(DepartureTime departureTime) {
+ this.departureTime = departureTime;
+ }
+
+ @Override
+ public String toString() {
+ return "ClimateTimer [isWeeklyTimer=" + isWeeklyTimer + ", timerAction=" + timerAction + ", timerWeekDays="
+ + timerWeekDays + ", departureTime=" + departureTime + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
+++ /dev/null
-/**
- * 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 RemoteService} Data Transfer Object
- *
- * @author Bernd Weymann - 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."
-}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
*/
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 + "]";
+ }
}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.vehicle;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.openhab.binding.mybmw.internal.dto.charge.RemoteChargingCommands;
+import org.openhab.binding.mybmw.internal.utils.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link VehicleCapabilities} Data Transfer Object
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - refactored to Java Bean
+ */
+
+public class VehicleCapabilities {
+ private final Logger logger = LoggerFactory.getLogger(VehicleCapabilities.class);
+
+ private static final String PREFIX_IS = "is";
+ public static final String SUPPORTED_SUFFIX = "Supported";
+ public static final String ENABLED_SUFFIX = "Enabled";
+
+ private boolean checkSustainabilityDPP = false;
+ private boolean climateNow = false;
+ private boolean horn = false;
+ private boolean isBmwChargingSupported = false;
+ private boolean isCarSharingSupported = false;
+ private boolean isChargeNowForBusinessSupported = false;
+ private boolean isChargingHistorySupported = false;
+ private boolean isChargingHospitalityEnabled = false;
+ private boolean isChargingLoudnessEnabled = false;
+ private boolean isChargingPlanSupported = false;
+ private boolean isChargingPowerLimitEnabled = false;
+ private boolean isChargingSettingsEnabled = false;
+ private boolean isChargingTargetSocEnabled = false;
+ private boolean isClimateTimerSupported = false;
+ private boolean isClimateTimerWeeklyActive = false;
+ private boolean isCustomerEsimSupported = false;
+ private boolean isDataPrivacyEnabled = false;
+ private boolean isDCSContractManagementSupported = false;
+ private boolean isEasyChargeEnabled = false;
+ private boolean isEvGoChargingSupported = false;
+ private boolean isMiniChargingSupported = false;
+ private boolean isNonLscFeatureEnabled = false;
+ private boolean isRemoteEngineStartSupported = false;
+ private boolean isRemoteHistoryDeletionSupported = false;
+ private boolean isRemoteHistorySupported = false;
+ private boolean isRemoteParkingSupported = false;
+ private boolean isRemoteServicesActivationRequired = false;
+ private boolean isRemoteServicesBookingRequired = false;
+ private boolean isScanAndChargeSupported = false;
+ private boolean isSustainabilityAccumulatedViewEnabled = false;
+ private boolean isSustainabilitySupported = false;
+ private boolean isWifiHotspotServiceSupported = false;
+ private boolean lights = false;
+ private boolean lock = false;
+ private boolean remote360 = false;
+ private RemoteChargingCommands remoteChargingCommands = new RemoteChargingCommands();
+ private boolean remoteSoftwareUpgrade = false;
+ private boolean sendPoi = false;
+ private boolean speechThirdPartyAlexa = false;
+ private boolean speechThirdPartyAlexaSDK = false;
+ private boolean unlock = false;
+ private boolean vehicleFinder = false;
+ private DigitalKey digitalKey = new DigitalKey();
+ private String a4aType = ""; // NOT_SUPPORTED,
+ private String climateFunction = ""; // VENTILATION,
+ private String climateTimerTrigger = ""; // DEPARTURE_TIMER,
+ private String lastStateCallState = ""; // ACTIVATED,
+ private String vehicleStateSource = ""; // LAST_STATE_CALL,
+
+ /**
+ * @return the climateNow
+ */
+ public boolean isClimateNow() {
+ return climateNow;
+ }
+
+ /**
+ * @return the horn
+ */
+ public boolean isHorn() {
+ return horn;
+ }
+
+ /**
+ * @return the lights
+ */
+ public boolean isLights() {
+ return lights;
+ }
+
+ /**
+ * @return the lock
+ */
+ public boolean isLock() {
+ return lock;
+ }
+
+ /**
+ * @return the remote360
+ */
+ public boolean isRemote360() {
+ return remote360;
+ }
+
+ /**
+ * @return the sendPoi
+ */
+ public boolean isSendPoi() {
+ return sendPoi;
+ }
+
+ /**
+ * @return the unlock
+ */
+ public boolean isUnlock() {
+ return unlock;
+ }
+
+ /**
+ * @return the vehicleFinder
+ */
+ public boolean isVehicleFinder() {
+ return vehicleFinder;
+ }
+
+ /**
+ * @return the digitalKey
+ */
+ public DigitalKey getDigitalKey() {
+ return digitalKey;
+ }
+
+ /**
+ * returns a list of capabilities filtered by the provided suffix and the enabled requirement
+ *
+ * @param suffix the suffix of the capability
+ * @param enabled if it should return only enabled or disabled capabilities
+ * @return the list of capabilities as single string
+ */
+ public String getCapabilitiesAsString(String suffix, boolean enabled) {
+ StringBuffer capabilitiesAsString = new StringBuffer();
+ List<String> capabilitiesAsStringList = getCapabilitiesAsStringList(suffix, enabled);
+
+ for (String capEntry : capabilitiesAsStringList) {
+ // remove "is" prefix and provided suffix
+ String cut = capEntry.substring(2);
+ if (cut.endsWith(suffix)) {
+ if (capabilitiesAsString.length() > 0) {
+ capabilitiesAsString.append(Constants.SEMICOLON);
+ }
+ capabilitiesAsString.append(cut.substring(0, cut.length() - suffix.length()));
+ }
+ }
+ return capabilitiesAsString.toString();
+ }
+
+ private List<String> getCapabilitiesAsStringList(String suffix, boolean compare) {
+ List<String> l = new ArrayList<>();
+
+ Arrays.asList(VehicleCapabilities.class.getDeclaredFields()).stream()
+ .filter(field -> field.getName().startsWith(PREFIX_IS) && field.getName().endsWith(suffix))
+ .forEach(field -> {
+ try {
+ boolean value = field.getBoolean(this);
+ if (compare == value) {
+ l.add(field.getName());
+ }
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ logger.trace("field {} not usable: ", field.getName());
+ }
+ });
+
+ return l;
+ }
+
+ @Override
+ public String toString() {
+ return "VehicleCapabilities [checkSustainabilityDPP=" + checkSustainabilityDPP + ", climateNow=" + climateNow
+ + ", horn=" + horn + ", isBmwChargingSupported=" + isBmwChargingSupported + ", isCarSharingSupported="
+ + isCarSharingSupported + ", isChargeNowForBusinessSupported=" + isChargeNowForBusinessSupported
+ + ", isChargingHistorySupported=" + isChargingHistorySupported + ", isChargingHospitalityEnabled="
+ + isChargingHospitalityEnabled + ", isChargingLoudnessEnabled=" + isChargingLoudnessEnabled
+ + ", isChargingPlanSupported=" + isChargingPlanSupported + ", isChargingPowerLimitEnabled="
+ + isChargingPowerLimitEnabled + ", isChargingSettingsEnabled=" + isChargingSettingsEnabled
+ + ", isChargingTargetSocEnabled=" + isChargingTargetSocEnabled + ", isClimateTimerSupported="
+ + isClimateTimerSupported + ", isClimateTimerWeeklyActive=" + isClimateTimerWeeklyActive
+ + ", isCustomerEsimSupported=" + isCustomerEsimSupported + ", isDataPrivacyEnabled="
+ + isDataPrivacyEnabled + ", isDCSContractManagementSupported=" + isDCSContractManagementSupported
+ + ", isEasyChargeEnabled=" + isEasyChargeEnabled + ", isEvGoChargingSupported="
+ + isEvGoChargingSupported + ", isMiniChargingSupported=" + isMiniChargingSupported
+ + ", isNonLscFeatureEnabled=" + isNonLscFeatureEnabled + ", isRemoteEngineStartSupported="
+ + isRemoteEngineStartSupported + ", isRemoteHistoryDeletionSupported="
+ + isRemoteHistoryDeletionSupported + ", isRemoteHistorySupported=" + isRemoteHistorySupported
+ + ", isRemoteParkingSupported=" + isRemoteParkingSupported + ", isRemoteServicesActivationRequired="
+ + isRemoteServicesActivationRequired + ", isRemoteServicesBookingRequired="
+ + isRemoteServicesBookingRequired + ", isScanAndChargeSupported=" + isScanAndChargeSupported
+ + ", isSustainabilityAccumulatedViewEnabled=" + isSustainabilityAccumulatedViewEnabled
+ + ", isSustainabilitySupported=" + isSustainabilitySupported + ", isWifiHotspotServiceSupported="
+ + isWifiHotspotServiceSupported + ", lights=" + lights + ", lock=" + lock + ", remote360=" + remote360
+ + ", remoteChargingCommands=" + remoteChargingCommands + ", remoteSoftwareUpgrade="
+ + remoteSoftwareUpgrade + ", sendPoi=" + sendPoi + ", speechThirdPartyAlexa=" + speechThirdPartyAlexa
+ + ", speechThirdPartyAlexaSDK=" + speechThirdPartyAlexaSDK + ", unlock=" + unlock + ", vehicleFinder="
+ + vehicleFinder + ", digitalKey=" + digitalKey + ", a4aType=" + a4aType + ", climateFunction="
+ + climateFunction + ", climateTimerTrigger=" + climateTimerTrigger + ", lastStateCallState="
+ + lastStateCallState + ", vehicleStateSource=" + vehicleStateSource + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.dto.vehicle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
+
+/**
+ *
+ * derived from the API responses
+ *
+ * @author Martin Grassl - initial contribution
+ */
+public class VehicleState {
+
+ public static final String CHECK_CONTROL_OVERALL_MESSAGE_OK = "No Issues";
+
+ private boolean isLeftSteering = false;
+ private String lastFetched = ""; // 2022-12-21T17:31:26.560Z,
+ private String lastUpdatedAt = ""; // 2022-12-21T15:41:23Z,
+ private boolean isLscSupported = false; // true,
+ private int range = -1; // 435,
+ private VehicleDoorsState doorsState = new VehicleDoorsState();
+ private VehicleWindowsState windowsState = new VehicleWindowsState();
+ private VehicleRoofState roofState = new VehicleRoofState();
+ private VehicleTireStates tireState = new VehicleTireStates();
+
+ private VehicleLocation location = new VehicleLocation();
+ private int currentMileage = -1;
+ private ClimateControlState climateControlState = new ClimateControlState();
+ private List<RequiredService> requiredServices = new ArrayList<>();
+ private List<CheckControlMessage> checkControlMessages = new ArrayList<>();
+ private CombustionFuelLevel combustionFuelLevel = new CombustionFuelLevel();
+ private DriverPreferences driverPreferences = new DriverPreferences();
+ private ElectricChargingState electricChargingState = new ElectricChargingState();
+ private boolean isDeepSleepModeActive = false; // false
+ private List<ClimateTimer> climateTimers = new ArrayList<>();
+ private ChargingProfile chargingProfile = new ChargingProfile();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+
+ /**
+ * @return the isLeftSteering
+ */
+ public boolean isLeftSteering() {
+ return isLeftSteering;
+ }
+
+ /**
+ * @return the lastFetched
+ */
+ public String getLastFetched() {
+ return lastFetched;
+ }
+
+ /**
+ * @return the lastUpdatedAt
+ */
+ public String getLastUpdatedAt() {
+ return lastUpdatedAt;
+ }
+
+ /**
+ * @return the isLscSupported
+ */
+ public boolean isLscSupported() {
+ return isLscSupported;
+ }
+
+ /**
+ * @return the range
+ */
+ public int getRange() {
+ return range;
+ }
+
+ /**
+ * @return the doorsState
+ */
+ public VehicleDoorsState getDoorsState() {
+ return doorsState;
+ }
+
+ /**
+ * @return the windowsState
+ */
+ public VehicleWindowsState getWindowsState() {
+ return windowsState;
+ }
+
+ /**
+ * @return the roofState
+ */
+ public VehicleRoofState getRoofState() {
+ return roofState;
+ }
+
+ /**
+ * @return the tireState
+ */
+ public VehicleTireStates getTireState() {
+ return tireState;
+ }
+
+ /**
+ * @return the location
+ */
+ public VehicleLocation getLocation() {
+ return location;
+ }
+
+ /**
+ * @return the currentMileage
+ */
+ public int getCurrentMileage() {
+ return currentMileage;
+ }
+
+ /**
+ * @return the climateControlState
+ */
+ public ClimateControlState getClimateControlState() {
+ return climateControlState;
+ }
+
+ /**
+ * @return the requiredServices
+ */
+ public List<RequiredService> getRequiredServices() {
+ return requiredServices;
+ }
+
+ /**
+ * @return the checkControlMessages
+ */
+ public List<CheckControlMessage> getCheckControlMessages() {
+ return checkControlMessages;
+ }
+
+ /**
+ * @return the combustionFuelLevel
+ */
+ public CombustionFuelLevel getCombustionFuelLevel() {
+ return combustionFuelLevel;
+ }
+
+ /**
+ * @return the driverPreferences
+ */
+ public DriverPreferences getDriverPreferences() {
+ return driverPreferences;
+ }
+
+ /**
+ * @return the electricChargingState
+ */
+ public ElectricChargingState getElectricChargingState() {
+ return electricChargingState;
+ }
+
+ /**
+ * @return the isDeepSleepModeActive
+ */
+ public boolean isDeepSleepModeActive() {
+ return isDeepSleepModeActive;
+ }
+
+ /**
+ * @return the climateTimers
+ */
+ public List<ClimateTimer> getClimateTimers() {
+ return climateTimers;
+ }
+
+ /**
+ * @return the chargingProfile
+ */
+ public ChargingProfile getChargingProfile() {
+ return chargingProfile;
+ }
+
+ @Override
+ public String toString() {
+ return "VehicleState [isLeftSteering=" + isLeftSteering + ", lastFetched=" + lastFetched + ", lastUpdatedAt="
+ + lastUpdatedAt + ", isLscSupported=" + isLscSupported + ", range=" + range + ", doorsState="
+ + doorsState + ", windowsState=" + windowsState + ", roofState=" + roofState + ", tireState="
+ + tireState + ", location=" + location + ", currentMileage=" + currentMileage + ", climateControlState="
+ + climateControlState + ", requiredServices=" + requiredServices + ", checkControlMessages="
+ + checkControlMessages + ", combustionFuelLevel=" + combustionFuelLevel + ", driverPreferences="
+ + driverPreferences + ", electricChargingState=" + electricChargingState + ", isDeepSleepModeActive="
+ + isDeepSleepModeActive + ", climateTimers=" + climateTimers + ", chargingProfile=" + chargingProfile
+ + "]";
+ }
+
+ /**
+ * helper methods
+ */
+ public String getOverallCheckControlStatus() {
+ StringBuilder overallMessage = new StringBuilder();
+
+ for (CheckControlMessage checkControlMessage : checkControlMessages) {
+ if (checkControlMessage.getId() > 0) {
+ overallMessage.append(checkControlMessage.getName() + "; ");
+ }
+ }
+
+ String overallMessageString = overallMessage.toString();
+
+ if (overallMessageString.isEmpty()) {
+ overallMessageString = CHECK_CONTROL_OVERALL_MESSAGE_OK;
+ }
+
+ return overallMessageString;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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
+ + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
+++ /dev/null
-/**
- * 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;
-
-/**
- * The {@link ByteResponseCallback} Interface for all raw byte results from ASYNC REST API
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public interface ByteResponseCallback extends ResponseCallback {
-
- void onResponse(byte[] result);
-}
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;
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<VehicleDiscovery> discoveryService = Optional.empty();
- private Optional<MyBMWProxy> proxy = Optional.empty();
+ private Optional<MyBMWProxy> myBmwProxy = Optional.empty();
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
- private Optional<String> troubleshootFingerprint = Optional.empty();
- private String localeLanguage;
+ private Optional<VehicleDiscovery> 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<Vehicle> vehicleList = Converter.getVehicleList(response);
- discoveryService.get().onResponse(vehicleList);
- troubleshootFingerprint = Optional.of(Converter.anonymousFingerprint(response));
- logFingerPrint();
- }
- }
+ private void discoverVehicles() {
+ logger.trace("MyBMWBridgeHandler.requestVehicles");
- @Override
- public void onError(NetworkError error) {
- troubleshootFingerprint = Optional.of(error.toJson());
- logFingerPrint();
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
+ MyBMWBridgeConfiguration config = getConfigAs(MyBMWBridgeConfiguration.class);
+
+ myBmwProxy.ifPresent(proxy -> proxy.setBridgeConfiguration(config));
+
+ vehicleDiscovery.ifPresent(discovery -> discovery.discoverVehicles());
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
- return Set.of(VehicleDiscovery.class);
- }
-
- public Optional<MyBMWProxy> getProxy() {
- return proxy;
+ logger.trace("MyBMWBridgeHandler.getServices");
+ return List.of(VehicleDiscovery.class);
}
- public void setDiscoveryService(VehicleDiscovery discoveryService) {
- this.discoveryService = Optional.of(discoveryService);
+ public Optional<MyBMWProxy> getMyBmwProxy() {
+ logger.trace("MyBMWBridgeHandler.getProxy");
+ createMyBmwProxy(getConfigAs(MyBMWBridgeConfiguration.class), ENVIRONMENT);
+ return myBmwProxy;
}
}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.handler;
-
-import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
-
-import java.nio.charset.StandardCharsets;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.PublicKey;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.Base64;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-
-import javax.crypto.Cipher;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.HttpResponseException;
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.client.util.BufferingResponseListener;
-import org.eclipse.jetty.client.util.StringContentProvider;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.UrlEncoded;
-import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
-import org.openhab.binding.mybmw.internal.VehicleConfiguration;
-import org.openhab.binding.mybmw.internal.dto.auth.AuthQueryResponse;
-import org.openhab.binding.mybmw.internal.dto.auth.AuthResponse;
-import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse;
-import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration;
-import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse;
-import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
-import org.openhab.binding.mybmw.internal.handler.simulation.Injector;
-import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
-import org.openhab.binding.mybmw.internal.utils.ImageProperties;
-import org.openhab.core.io.net.http.HttpClientFactory;
-import org.openhab.core.util.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link MyBMWProxy} This class holds the important constants for the BMW Connected Drive Authorization.
- * They are taken from the Bimmercode from github <a href="https://github.com/bimmerconnected/bimmer_connected">
- * https://github.com/bimmerconnected/bimmer_connected</a>.
- * File defining these constants
- * <a href="https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py">
- * https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py</a>
- * <a href="https://customer.bmwgroup.com/one/app/oauth.js">https://customer.bmwgroup.com/one/app/oauth.js</a>
- *
- * @author Bernd Weymann - Initial contribution
- * @author Norbert Truchsess - edit and send of charge profile
- */
-@NonNullByDefault
-public class MyBMWProxy {
- private final Logger logger = LoggerFactory.getLogger(MyBMWProxy.class);
- private Optional<RemoteServiceHandler> remoteServiceHandler = Optional.empty();
- private final Token token = new Token();
- private final HttpClient httpClient;
- private final MyBMWConfiguration configuration;
-
- /**
- * URLs taken from https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
- */
- final String vehicleUrl;
- final String remoteCommandUrl;
- final String remoteStatusUrl;
- final String serviceExecutionAPI = "/executeService";
- final String serviceExecutionStateAPI = "/serviceExecutionStatus";
- final String remoteServiceEADRXstatusUrl = BimmerConstants.API_REMOTE_SERVICE_BASE_URL
- + "eventStatus?eventId={event_id}";
-
- public MyBMWProxy(HttpClientFactory httpClientFactory, MyBMWConfiguration config) {
- httpClient = httpClientFactory.getCommonHttpClient();
- configuration = config;
-
- vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + BimmerConstants.API_VEHICLES;
-
- remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
- remoteStatusUrl = remoteCommandUrl + "eventStatus";
- }
-
- public synchronized void call(final String url, final boolean post, final @Nullable String encoding,
- final @Nullable String params, final String brand, final ResponseCallback callback) {
- // only executed in "simulation mode"
- // SimulationTest.testSimulationOff() assures Injector is off when releasing
- if (Injector.isActive()) {
- if (url.equals(vehicleUrl)) {
- ((StringResponseCallback) callback).onResponse(Injector.getDiscovery());
- } else if (url.endsWith(vehicleUrl)) {
- ((StringResponseCallback) callback).onResponse(Injector.getStatus());
- } else {
- logger.debug("Simulation of {} not supported", url);
- }
- return;
- }
-
- // return in case of unknown brand
- if (!BimmerConstants.ALL_BRANDS.contains(brand.toLowerCase())) {
- logger.warn("Unknown Brand {}", brand);
- return;
- }
-
- final Request req;
- final String completeUrl;
-
- if (post) {
- completeUrl = url;
- req = httpClient.POST(url);
- if (encoding != null) {
- req.header(HttpHeader.CONTENT_TYPE, encoding);
- if (CONTENT_TYPE_URL_ENCODED.equals(encoding)) {
- req.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
- } else if (CONTENT_TYPE_JSON_ENCODED.equals(encoding)) {
- req.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8));
- }
- }
- } else {
- completeUrl = params == null ? url : url + Constants.QUESTION + params;
- req = httpClient.newRequest(completeUrl);
- }
- req.header(HttpHeader.AUTHORIZATION, getToken().getBearerToken());
- req.header(HTTPConstants.X_USER_AGENT,
- String.format(BimmerConstants.X_USER_AGENT, brand, configuration.region));
- req.header(HttpHeader.ACCEPT_LANGUAGE, configuration.language);
- if (callback instanceof ByteResponseCallback) {
- req.header(HttpHeader.ACCEPT, "image/png");
- } else {
- req.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON_ENCODED);
- }
-
- req.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send(new BufferingResponseListener() {
- @NonNullByDefault({})
- @Override
- public void onComplete(Result result) {
- if (result.getResponse().getStatus() != 200) {
- NetworkError error = new NetworkError();
- error.url = completeUrl;
- error.status = result.getResponse().getStatus();
- if (result.getResponse().getReason() != null) {
- error.reason = result.getResponse().getReason();
- } else {
- error.reason = result.getFailure().getMessage();
- }
- error.params = result.getRequest().getParams().toString();
- logger.debug("HTTP Error {}", error.toString());
- callback.onError(error);
- } else {
- if (callback instanceof StringResponseCallback responseCallback) {
- responseCallback.onResponse(getContentAsString());
- } else if (callback instanceof ByteResponseCallback responseCallback) {
- responseCallback.onResponse(getContent());
- } else {
- logger.error("unexpected reponse type {}", callback.getClass().getName());
- }
- }
- }
- });
- }
-
- public void get(String url, @Nullable String coding, @Nullable String params, final String brand,
- ResponseCallback callback) {
- call(url, false, coding, params, brand, callback);
- }
-
- public void post(String url, @Nullable String coding, @Nullable String params, final String brand,
- ResponseCallback callback) {
- call(url, true, coding, params, brand, callback);
- }
-
- /**
- * request all vehicles for one specific brand
- *
- * @param brand
- * @param callback
- */
- public void requestVehicles(String brand, StringResponseCallback callback) {
- // calculate necessary parameters for query
- MultiMap<String> vehicleParams = new MultiMap<String>();
- vehicleParams.put(BimmerConstants.TIRE_GUARD_MODE, Constants.ENABLED);
- vehicleParams.put(BimmerConstants.APP_DATE_TIME, Long.toString(System.currentTimeMillis()));
- vehicleParams.put(BimmerConstants.APP_TIMEZONE, Integer.toString(Converter.getOffsetMinutes()));
- String params = UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false);
- get(vehicleUrl + "?" + params, null, null, brand, callback);
- }
-
- /**
- * request vehicles for all possible brands
- *
- * @param callback
- */
- public void requestVehicles(StringResponseCallback callback) {
- BimmerConstants.ALL_BRANDS.forEach(brand -> {
- requestVehicles(brand, callback);
- });
- }
-
- public void requestImage(VehicleConfiguration config, ImageProperties props, ByteResponseCallback callback) {
- final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + "/eadrax-ics/v3/presentation/vehicles/" + config.vin + "/images?carView=" + props.viewport;
- get(localImageUrl, null, null, config.vehicleBrand, callback);
- }
-
- /**
- * request charge statistics for electric vehicles
- *
- * @param callback
- */
- public void requestChargeStatistics(VehicleConfiguration config, StringResponseCallback callback) {
- MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
- chargeStatisticsParams.put("vin", config.vin);
- chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
- String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
- String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + "/eadrax-chs/v1/charging-statistics?" + params;
- get(chargeStatisticsUrl, null, null, config.vehicleBrand, callback);
- }
-
- /**
- * request charge statistics for electric vehicles
- *
- * @param callback
- */
- public void requestChargeSessions(VehicleConfiguration config, StringResponseCallback callback) {
- MultiMap<String> chargeSessionsParams = new MultiMap<String>();
- chargeSessionsParams.put("vin", "WBY1Z81040V905639");
- chargeSessionsParams.put("maxResults", "40");
- chargeSessionsParams.put("include_date_picker", "true");
- String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
- String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + "/eadrax-chs/v1/charging-sessions?" + params;
-
- get(chargeSessionsUrl, null, null, config.vehicleBrand, callback);
- }
-
- RemoteServiceHandler getRemoteServiceHandler(VehicleHandler vehicleHandler) {
- remoteServiceHandler = Optional.of(new RemoteServiceHandler(vehicleHandler, this));
- return remoteServiceHandler.get();
- }
-
- // Token handling
-
- /**
- * Gets new token if old one is expired or invalid. In case of error the token remains.
- * So if token refresh fails the corresponding requests will also fail and update the
- * Thing status accordingly.
- *
- * @return token
- */
- public Token getToken() {
- if (!token.isValid()) {
- boolean tokenUpdateSuccess = false;
- switch (configuration.region) {
- case BimmerConstants.REGION_CHINA:
- tokenUpdateSuccess = updateTokenChina();
- break;
- case BimmerConstants.REGION_NORTH_AMERICA:
- tokenUpdateSuccess = updateToken();
- break;
- case BimmerConstants.REGION_ROW:
- tokenUpdateSuccess = updateToken();
- break;
- default:
- logger.warn("Region {} not supported", configuration.region);
- break;
- }
- if (!tokenUpdateSuccess) {
- logger.debug("Authorization failed!");
- }
- }
- return token;
- }
-
- /**
- * Everything is catched by surroundig try catch
- * - HTTP Exceptions
- * - JSONSyntax Exceptions
- * - potential NullPointer Exceptions
- *
- * @return
- */
- @SuppressWarnings("null")
- public synchronized boolean updateToken() {
- try {
- /*
- * Step 1) Get basic values for further queries
- */
- String authValuesUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
- + BimmerConstants.API_OAUTH_CONFIG;
- Request authValuesRequest = httpClient.newRequest(authValuesUrl).timeout(HTTP_TIMEOUT_SEC,
- TimeUnit.SECONDS);
- authValuesRequest.header(ACP_SUBSCRIPTION_KEY, BimmerConstants.OCP_APIM_KEYS.get(configuration.region));
- authValuesRequest.header(X_USER_AGENT,
- String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
-
- ContentResponse authValuesResponse = authValuesRequest.send();
- if (authValuesResponse.getStatus() != 200) {
- throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
- + authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
- authValuesResponse);
- }
- AuthQueryResponse aqr = Converter.getGson().fromJson(authValuesResponse.getContentAsString(),
- AuthQueryResponse.class);
-
- /*
- * Step 2) Calculate values for base parameters
- */
- String verfifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
- String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verfifierBytes.getBytes());
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
- String codeChallange = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
- String stateBytes = StringUtils.getRandomAlphabetic(16).toLowerCase();
- String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
-
- MultiMap<String> baseParams = new MultiMap<String>();
- baseParams.put(CLIENT_ID, aqr.clientId);
- baseParams.put(RESPONSE_TYPE, CODE);
- baseParams.put(REDIRECT_URI, aqr.returnUrl);
- baseParams.put(STATE, state);
- baseParams.put(NONCE, BimmerConstants.LOGIN_NONCE);
- baseParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
- baseParams.put(CODE_CHALLENGE, codeChallange);
- baseParams.put(CODE_CHALLENGE_METHOD, "S256");
-
- /**
- * Step 3) Authorization with username and password
- */
- String loginUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
- Request loginRequest = httpClient.POST(loginUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
- loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
-
- MultiMap<String> loginParams = new MultiMap<String>(baseParams);
- loginParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
- loginParams.put(USERNAME, configuration.userName);
- loginParams.put(PASSWORD, configuration.password);
- loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
- ContentResponse loginResponse = loginRequest.send();
- if (loginResponse.getStatus() != 200) {
- throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
- + loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
- loginResponse);
- }
- String authCode = getAuthCode(loginResponse.getContentAsString());
-
- /**
- * Step 4) Authorize with code
- */
- Request authRequest = httpClient.POST(loginUrl).followRedirects(false).timeout(HTTP_TIMEOUT_SEC,
- TimeUnit.SECONDS);
- MultiMap<String> authParams = new MultiMap<String>(baseParams);
- authParams.put(AUTHORIZATION, authCode);
- authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
- authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
- ContentResponse authResponse = authRequest.send();
- if (authResponse.getStatus() != 302) {
- throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
- + ", Message: " + authResponse.getContentAsString(), authResponse);
- }
- String code = MyBMWProxy.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
-
- /**
- * Step 5) Request token
- */
- Request codeRequest = httpClient.POST(aqr.tokenEndpoint).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
- String basicAuth = "Basic "
- + Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
- codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
- codeRequest.header(AUTHORIZATION, basicAuth);
-
- MultiMap<String> codeParams = new MultiMap<String>();
- codeParams.put(CODE, code);
- codeParams.put(CODE_VERIFIER, codeVerifier);
- codeParams.put(REDIRECT_URI, aqr.returnUrl);
- codeParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
- codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
- ContentResponse codeResponse = codeRequest.send();
- if (codeResponse.getStatus() != 200) {
- throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
- + ", Message: " + codeResponse.getContentAsString(), codeResponse);
- }
- AuthResponse ar = Converter.getGson().fromJson(codeResponse.getContentAsString(), AuthResponse.class);
- token.setType(ar.tokenType);
- token.setToken(ar.accessToken);
- token.setExpiration(ar.expiresIn);
- return true;
- } catch (Exception e) {
- logger.warn("Authorization Exception: {}", e.getMessage());
- }
- return false;
- }
-
- private String getAuthCode(String response) {
- String[] keys = response.split("&");
- for (int i = 0; i < keys.length; i++) {
- if (keys[i].startsWith(AUTHORIZATION)) {
- String authCode = keys[i].split("=")[1];
- authCode = authCode.split("\"")[0];
- return authCode;
- }
- }
- return Constants.EMPTY;
- }
-
- public static String codeFromUrl(String encodedUrl) {
- final MultiMap<String> tokenMap = new MultiMap<String>();
- UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
- final StringBuilder codeFound = new StringBuilder();
- tokenMap.forEach((key, value) -> {
- if (!value.isEmpty()) {
- String val = value.get(0);
- if (key.endsWith(CODE)) {
- codeFound.append(val);
- }
- }
- });
- return codeFound.toString();
- }
-
- @SuppressWarnings("null")
- public synchronized boolean updateTokenChina() {
- try {
- /**
- * Step 1) get public key
- */
- String publicKeyUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
- + BimmerConstants.CHINA_PUBLIC_KEY;
- Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
- oauthQueryRequest.header(HttpHeader.USER_AGENT, BimmerConstants.USER_AGENT);
- oauthQueryRequest.header(X_USER_AGENT,
- String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
- ContentResponse publicKeyResponse = oauthQueryRequest.send();
- if (publicKeyResponse.getStatus() != 200) {
- throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
- + publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(),
- publicKeyResponse);
- }
- ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponse.getContentAsString(),
- ChinaPublicKeyResponse.class);
-
- /**
- * Step 2) Encode password with public key
- */
- // https://www.baeldung.com/java-read-pem-file-keys
- String publicKeyStr = pkr.data.value;
- String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
- .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "")
- .replace("\\n", "").trim();
- byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
- X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
- KeyFactory kf = KeyFactory.getInstance("RSA");
- PublicKey publicKey = kf.generatePublic(spec);
- // https://www.thexcoders.net/java-ciphers-rsa/
- Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
- cipher.init(Cipher.ENCRYPT_MODE, publicKey);
- byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
- String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
-
- /**
- * Step 3) Send Auth with encoded password
- */
- String tokenUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
- + BimmerConstants.CHINA_LOGIN;
- Request loginRequest = httpClient.POST(tokenUrl).timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS);
- loginRequest.header(X_USER_AGENT,
- String.format(BimmerConstants.X_USER_AGENT, BimmerConstants.BRAND_BMW, configuration.region));
- String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
- + "\"}";
- loginRequest.content(new StringContentProvider(jsonContent));
- ContentResponse tokenResponse = loginRequest.send();
- if (tokenResponse.getStatus() != 200) {
- throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
- + tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(),
- tokenResponse);
- }
- String authCode = getAuthCode(tokenResponse.getContentAsString());
-
- /**
- * Step 4) Decode access token
- */
- ChinaTokenResponse cat = Converter.getGson().fromJson(authCode, ChinaTokenResponse.class);
- String token = cat.data.accessToken;
- // https://www.baeldung.com/java-jwt-token-decode
- String[] chunks = token.split("\\.");
- String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1]));
- ChinaTokenExpiration cte = Converter.getGson().fromJson(tokenJwtDecodeStr, ChinaTokenExpiration.class);
- Token t = new Token();
- t.setToken(token);
- t.setType(cat.data.tokenType);
- t.setExpirationTotal(cte.exp);
- return true;
- } catch (Exception e) {
- logger.warn("Authorization Exception: {}", e.getMessage());
- }
- return false;
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler;
+
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
+import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
+import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
+import org.openhab.binding.mybmw.internal.handler.enums.ExecutionState;
+import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
+import org.openhab.binding.mybmw.internal.utils.Constants;
+import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RemoteServiceExecutor} handles executions of remote services
+ * towards your Vehicle
+ *
+ * @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Norbert Truchsess - edit and send of charge profile
+ * @author Martin Grassl - rename and refactor for v2
+ */
+@NonNullByDefault
+public class RemoteServiceExecutor {
+ private final Logger logger = LoggerFactory.getLogger(RemoteServiceExecutor.class);
+
+ private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
+ private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
+
+ private final MyBMWProxy proxy;
+ private final VehicleHandler handler;
+
+ private int counter = 0;
+ private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
+ private Optional<String> serviceExecuting = Optional.empty();
+ private Optional<String> executingEventId = Optional.empty();
+
+ public RemoteServiceExecutor(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
+ handler = vehicleHandler;
+ proxy = myBmwProxy;
+ }
+
+ public boolean execute(RemoteService service) {
+ synchronized (this) {
+ if (serviceExecuting.isPresent()) {
+ logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
+ // only one service executing
+ return false;
+ }
+ serviceExecuting = Optional.of(service.getId());
+ }
+ try {
+ ExecutionStatusContainer executionStatus = proxy.executeRemoteServiceCall(
+ handler.getVehicleConfiguration().get().getVin(),
+ handler.getVehicleConfiguration().get().getVehicleBrand(), service);
+ handleRemoteExecution(executionStatus);
+ } catch (NetworkException e) {
+ handleRemoteServiceException(e);
+ }
+
+ return true;
+ }
+
+ private void getState() {
+ synchronized (this) {
+ serviceExecuting.ifPresentOrElse(service -> {
+ if (counter >= GIVEUP_COUNTER) {
+ logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
+ handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
+ ExecutionState.TIMEOUT.name().toLowerCase());
+ reset();
+ // immediately refresh data
+ handler.getData();
+ } else {
+ counter++;
+ try {
+ ExecutionStatusContainer executionStatusContainer = proxy.executeRemoteServiceStatusCall(
+ handler.getVehicleConfiguration().get().getVehicleBrand(), executingEventId.get());
+ handleRemoteExecution(executionStatusContainer);
+ } catch (NetworkException e) {
+ handleRemoteServiceException(e);
+ }
+ }
+ }, () -> {
+ logger.warn("No Service executed to get state");
+ });
+ stateJob = Optional.empty();
+ }
+ }
+
+ private void handleRemoteServiceException(NetworkException e) {
+ synchronized (this) {
+ handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
+ ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(e.getStatus()));
+ reset();
+ }
+ }
+
+ private void handleRemoteExecution(ExecutionStatusContainer executionStatusContainer) {
+ if (!executionStatusContainer.getEventId().isEmpty()) {
+ // service initiated - store event id for further MyBMW updates
+ executingEventId = Optional.of(executionStatusContainer.getEventId());
+ handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
+ ExecutionState.INITIATED.name().toLowerCase());
+ } else if (!executionStatusContainer.getEventStatus().isEmpty()) {
+ // service status updated
+ synchronized (this) {
+ handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
+ executionStatusContainer.getEventStatus().toLowerCase());
+ if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
+ || ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
+ // refresh loop ends - update of status handled in the normal refreshInterval.
+ // Earlier update doesn't show better results!
+ reset();
+ return;
+ }
+ }
+ }
+
+ // schedule even if no result is present until retries exceeded
+ synchronized (this) {
+ stateJob.ifPresent(job -> {
+ if (!job.isDone()) {
+ job.cancel(true);
+ }
+ });
+ stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
+ }
+ }
+
+ private void reset() {
+ serviceExecuting = Optional.empty();
+ executingEventId = Optional.empty();
+ counter = 0;
+ }
+
+ public void cancel() {
+ synchronized (this) {
+ stateJob.ifPresent(action -> {
+ if (!action.isDone()) {
+ action.cancel(true);
+ }
+ stateJob = Optional.empty();
+ });
+ }
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.handler;
-
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
-import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_JSON_ENCODED;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Optional;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.util.MultiMap;
-import org.eclipse.jetty.util.UrlEncoded;
-import org.openhab.binding.mybmw.internal.VehicleConfiguration;
-import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
-import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.JsonSyntaxException;
-
-/**
- * The {@link RemoteServiceHandler} handles executions of remote services towards your Vehicle
- *
- * @see <a href="https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py">
- * https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py</a>
- *
- * @author Bernd Weymann - Initial contribution
- * @author Norbert Truchsess - edit and send of charge profile
- */
-@NonNullByDefault
-public class RemoteServiceHandler implements StringResponseCallback {
- private final Logger logger = LoggerFactory.getLogger(RemoteServiceHandler.class);
-
- private static final String EVENT_ID = "eventId";
- private static final String DATA = "data";
- private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
- private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
-
- private final MyBMWProxy proxy;
- private final VehicleHandler handler;
- private final String serviceExecutionAPI;
- private final String serviceExecutionStateAPI;
-
- private int counter = 0;
- private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
- private Optional<String> serviceExecuting = Optional.empty();
- private Optional<String> executingEventId = Optional.empty();
-
- public enum ExecutionState {
- READY,
- INITIATED,
- PENDING,
- DELIVERED,
- EXECUTED,
- ERROR,
- TIMEOUT
- }
-
- public enum RemoteService {
- LIGHT_FLASH("Flash Lights", REMOTE_SERVICE_LIGHT_FLASH, REMOTE_SERVICE_LIGHT_FLASH),
- VEHICLE_FINDER("Vehicle Finder", REMOTE_SERVICE_VEHICLE_FINDER, REMOTE_SERVICE_VEHICLE_FINDER),
- DOOR_LOCK("Door Lock", REMOTE_SERVICE_DOOR_LOCK, REMOTE_SERVICE_DOOR_LOCK),
- DOOR_UNLOCK("Door Unlock", REMOTE_SERVICE_DOOR_UNLOCK, REMOTE_SERVICE_DOOR_UNLOCK),
- HORN_BLOW("Horn Blow", REMOTE_SERVICE_HORN, REMOTE_SERVICE_HORN),
- CLIMATE_NOW_START("Start Climate", REMOTE_SERVICE_AIR_CONDITIONING_START, "climate-now?action=START"),
- CLIMATE_NOW_STOP("Stop Climate", REMOTE_SERVICE_AIR_CONDITIONING_STOP, "climate-now?action=STOP");
-
- private final String label;
- private final String id;
- private final String command;
-
- RemoteService(final String label, final String id, String command) {
- this.label = label;
- this.id = id;
- this.command = command;
- }
-
- public String getLabel() {
- return label;
- }
-
- public String getId() {
- return id;
- }
-
- public String getCommand() {
- return command;
- }
- }
-
- public RemoteServiceHandler(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
- handler = vehicleHandler;
- proxy = myBmwProxy;
- final VehicleConfiguration config = handler.getConfiguration().get();
- serviceExecutionAPI = proxy.remoteCommandUrl + config.vin + "/";
- serviceExecutionStateAPI = proxy.remoteStatusUrl;
- }
-
- boolean execute(RemoteService service, String... data) {
- synchronized (this) {
- if (serviceExecuting.isPresent()) {
- logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
- // only one service executing
- return false;
- }
- serviceExecuting = Optional.of(service.getId());
- }
- final MultiMap<String> dataMap = new MultiMap<String>();
- if (data.length > 0) {
- dataMap.add(DATA, data[0]);
- proxy.post(serviceExecutionAPI + service.getCommand(), CONTENT_TYPE_JSON_ENCODED, data[0],
- handler.getConfiguration().get().vehicleBrand, this);
- } else {
- proxy.post(serviceExecutionAPI + service.getCommand(), null, null,
- handler.getConfiguration().get().vehicleBrand, this);
- }
- return true;
- }
-
- public void getState() {
- synchronized (this) {
- serviceExecuting.ifPresentOrElse(service -> {
- if (counter >= GIVEUP_COUNTER) {
- logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
- handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
- ExecutionState.TIMEOUT.name().toLowerCase());
- reset();
- // immediately refresh data
- handler.getData();
- } else {
- counter++;
- final MultiMap<String> dataMap = new MultiMap<String>();
- dataMap.add(EVENT_ID, executingEventId.get());
- final String encoded = UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false);
- proxy.post(serviceExecutionStateAPI + Constants.QUESTION + encoded, null, null,
- handler.getConfiguration().get().vehicleBrand, this);
- }
- }, () -> {
- logger.warn("No Service executed to get state");
- });
- stateJob = Optional.empty();
- }
- }
-
- @Override
- public void onResponse(@Nullable String result) {
- if (result != null) {
- try {
- ExecutionStatusContainer esc = Converter.getGson().fromJson(result, ExecutionStatusContainer.class);
- if (esc != null) {
- if (esc.eventId != null) {
- // service initiated - store event id for further MyBMW updates
- executingEventId = Optional.of(esc.eventId);
- handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
- ExecutionState.INITIATED.name().toLowerCase());
- } else if (esc.eventStatus != null) {
- // service status updated
- synchronized (this) {
- handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
- esc.eventStatus.toLowerCase());
- if (ExecutionState.EXECUTED.name().equalsIgnoreCase(esc.eventStatus)
- || ExecutionState.ERROR.name().equalsIgnoreCase(esc.eventStatus)) {
- // refresh loop ends - update of status handled in the normal refreshInterval.
- // Earlier update doesn't show better results!
- reset();
- return;
- }
- }
- }
- }
- } catch (JsonSyntaxException jse) {
- logger.debug("RemoteService response is unparseable: {} {}", result, jse.getMessage());
- }
- }
- // schedule even if no result is present until retries exceeded
- synchronized (this) {
- stateJob.ifPresent(job -> {
- if (!job.isDone()) {
- job.cancel(true);
- }
- });
- stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
- }
- }
-
- @Override
- public void onError(NetworkError error) {
- synchronized (this) {
- handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
- ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(error.status));
- reset();
- }
- }
-
- private void reset() {
- serviceExecuting = Optional.empty();
- executingEventId = Optional.empty();
- counter = 0;
- }
-
- public void cancel() {
- synchronized (this) {
- stateJob.ifPresent(action -> {
- if (!action.isDone()) {
- action.cancel(true);
- }
- stateJob = Optional.empty();
- });
- }
- }
-}
+++ /dev/null
-/**
- * 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);
-}
+++ /dev/null
-/**
- * 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);
-}
+++ /dev/null
-/**
- * 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.utils.Constants;
-
-/**
- * The {@link Token} MyBMW Token storage
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public class Token {
- private String token = Constants.EMPTY;
- private String tokenType = Constants.EMPTY;
- private long expiration = 0;
-
- public String getBearerToken() {
- return new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
- }
-
- public void setToken(String token) {
- this.token = token;
- }
-
- public void setExpiration(int expiration) {
- this.expiration = System.currentTimeMillis() / 1000 + expiration;
- }
-
- public void setExpirationTotal(long expiration) {
- this.expiration = expiration;
- }
-
- public void setType(String type) {
- tokenType = type;
- }
-
- public boolean isValid() {
- return (!token.equals(Constants.EMPTY) && !tokenType.equals(Constants.EMPTY)
- && (this.expiration - System.currentTimeMillis() / 1000) > 1);
- }
-
- @Override
- public String toString() {
- return tokenType + Constants.COLON + token + Constants.COLON + isValid();
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.handler;
-
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
-
-import java.time.DayOfWeek;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import javax.measure.Unit;
-import javax.measure.quantity.Length;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeSession;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
-import org.openhab.binding.mybmw.internal.dto.properties.CBS;
-import org.openhab.binding.mybmw.internal.dto.properties.DoorsWindows;
-import org.openhab.binding.mybmw.internal.dto.properties.Location;
-import org.openhab.binding.mybmw.internal.dto.properties.Tires;
-import org.openhab.binding.mybmw.internal.dto.status.CCMMessage;
-import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils.TimedChannel;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-import org.openhab.binding.mybmw.internal.utils.Converter;
-import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
-import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
-import org.openhab.core.i18n.LocationProvider;
-import org.openhab.core.library.types.DateTimeType;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.OnOffType;
-import org.openhab.core.library.types.PointType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.ImperialUnits;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.binding.BaseThingHandler;
-import org.openhab.core.types.CommandOption;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link VehicleChannelHandler} handles Channel updates
- *
- * @author Bernd Weymann - Initial contribution
- * @author Norbert Truchsess - edit and send of charge profile
- */
-@NonNullByDefault
-public abstract class VehicleChannelHandler extends BaseThingHandler {
- protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
- protected boolean hasFuel = false;
- protected boolean isElectric = false;
- protected boolean isHybrid = false;
-
- // List Interfaces
- protected List<CBS> serviceList = new ArrayList<CBS>();
- protected String selectedService = Constants.UNDEF;
- protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
- protected String selectedCC = Constants.UNDEF;
- protected List<ChargeSession> sessionList = new ArrayList<ChargeSession>();
- protected String selectedSession = Constants.UNDEF;
-
- protected MyBMWCommandOptionProvider commandOptionProvider;
- private LocationProvider locationProvider;
-
- // Data Caches
- protected Optional<String> vehicleStatusCache = Optional.empty();
- protected Optional<byte[]> imageCache = Optional.empty();
-
- public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
- super(thing);
- commandOptionProvider = cop;
- locationProvider = lp;
- if (lp.getLocation() == null) {
- logger.debug("Home location not available");
- }
-
- hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
- || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
- isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
- || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
- isHybrid = hasFuel && isElectric;
-
- setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
- }
-
- private void setOptions(final String group, final String id, List<CommandOption> options) {
- commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
- }
-
- protected void updateChannel(final String group, final String id, final State state) {
- updateState(new ChannelUID(thing.getUID(), group, id), state);
- }
-
- protected void updateChargeStatistics(ChargeStatisticsContainer csc) {
- updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, StringType.valueOf(csc.description));
- updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY,
- QuantityType.valueOf(csc.statistics.totalEnergyCharged, Units.KILOWATT_HOUR));
- updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
- DecimalType.valueOf(Integer.toString(csc.statistics.numberOfChargingSessions)));
- }
-
- protected void updateVehicle(Vehicle v) {
- updateVehicleStatus(v);
- updateRange(v);
- updateDoors(v.properties.doorsAndWindows);
- updateWindows(v.properties.doorsAndWindows);
- updatePosition(v.properties.vehicleLocation);
- updateServices(v.properties.serviceRequired);
- updateCheckControls(v.status.checkControlMessages);
- updateTires(v.properties.tires);
- }
-
- private void updateTires(@Nullable Tires tires) {
- if (tires == null) {
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF);
- } else {
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
- QuantityType.valueOf(tires.frontLeft.status.currentPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
- QuantityType.valueOf(tires.frontLeft.status.targetPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
- QuantityType.valueOf(tires.frontRight.status.currentPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
- QuantityType.valueOf(tires.frontRight.status.targetPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
- QuantityType.valueOf(tires.rearLeft.status.currentPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
- QuantityType.valueOf(tires.rearLeft.status.targetPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
- QuantityType.valueOf(tires.rearRight.status.currentPressure / 100, Units.BAR));
- updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
- QuantityType.valueOf(tires.rearRight.status.targetPressure / 100, Units.BAR));
- }
- }
-
- protected void updateVehicleStatus(Vehicle v) {
- updateChannel(CHANNEL_GROUP_STATUS, LOCK, Converter.getLockState(v.properties.areDoorsLocked));
- updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
- VehicleStatusUtils.getNextServiceDate(v.properties.serviceRequired));
- updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
- VehicleStatusUtils.getNextServiceMileage(v.properties.serviceRequired));
- updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
- StringType.valueOf(v.status.checkControlMessagesGeneralState));
- updateChannel(CHANNEL_GROUP_STATUS, MOTION, OnOffType.from(v.properties.inMotion));
- updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
- DateTimeType.valueOf(Converter.zonedToLocalDateTime(v.properties.lastUpdatedAt)));
- updateChannel(CHANNEL_GROUP_STATUS, DOORS, Converter.getClosedState(v.properties.areDoorsClosed));
- updateChannel(CHANNEL_GROUP_STATUS, WINDOWS, Converter.getClosedState(v.properties.areWindowsClosed));
-
- if (isElectric) {
- updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
- Converter.getConnectionState(v.properties.chargingState.isChargerConnected));
- updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
- StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(v))));
- updateChannel(CHANNEL_GROUP_STATUS, CHARGE_INFO,
- StringType.valueOf(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(v))));
- }
- }
-
- protected void updateRange(Vehicle v) {
- // get the right unit
- Unit<Length> lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
- if (lengthUnit == null) {
- return;
- }
- if (isElectric) {
- int rangeElectric = VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, v);
- QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
- QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
- lengthUnit);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius);
- }
- if (hasFuel) {
- int rangeFuel = VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, v);
- QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
- QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius);
- }
- if (isHybrid) {
- int rangeCombined = VehicleStatusUtils.getRange(Constants.PHEV, v);
- QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
- QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
- lengthUnit);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange);
- updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius);
- }
- if (v.status.currentMileage.mileage == Constants.INT_UNDEF) {
- updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF);
- } else {
- updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
- QuantityType.valueOf(v.status.currentMileage.mileage, lengthUnit));
- }
- if (isElectric) {
- updateChannel(CHANNEL_GROUP_RANGE, SOC,
- QuantityType.valueOf(v.properties.chargingState.chargePercentage, Units.PERCENT));
- }
- if (hasFuel) {
- updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
- QuantityType.valueOf(v.properties.fuelLevel.value, Units.LITRE));
- }
- }
-
- protected void updateCheckControls(List<CCMMessage> ccl) {
- if (ccl.isEmpty()) {
- // No Check Control available - show not active
- CCMMessage ccm = new CCMMessage();
- ccm.title = Constants.NO_ENTRIES;
- ccm.longDescription = Constants.NO_ENTRIES;
- ccm.state = Constants.NO_ENTRIES;
- ccl.add(ccm);
- }
-
- // add all elements to options
- checkControlList = ccl;
- List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
- boolean isSelectedElementIn = false;
- int index = 0;
- for (CCMMessage ccEntry : checkControlList) {
- ccmDescriptionOptions.add(new CommandOption(Integer.toString(index), ccEntry.title));
- if (selectedCC.equals(ccEntry.title)) {
- isSelectedElementIn = true;
- }
- index++;
- }
- setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
-
- // if current selected item isn't anymore in the list select first entry
- if (!isSelectedElementIn) {
- selectCheckControl(0);
- }
- }
-
- protected void selectCheckControl(int index) {
- if (index >= 0 && index < checkControlList.size()) {
- CCMMessage ccEntry = checkControlList.get(index);
- selectedCC = ccEntry.title;
- updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.title));
- updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.longDescription));
- updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY, StringType.valueOf(ccEntry.state));
- }
- }
-
- protected void updateServices(List<CBS> sl) {
- // if list is empty add "undefined" element
- if (sl.isEmpty()) {
- CBS cbsm = new CBS();
- cbsm.type = Constants.NO_ENTRIES;
- sl.add(cbsm);
- }
-
- // add all elements to options
- serviceList = sl;
- List<CommandOption> serviceNameOptions = new ArrayList<>();
- boolean isSelectedElementIn = false;
- int index = 0;
- for (CBS serviceEntry : serviceList) {
- // create StateOption with "value = list index" and "label = human readable string"
- serviceNameOptions.add(new CommandOption(Integer.toString(index), serviceEntry.type));
- if (selectedService.equals(serviceEntry.type)) {
- isSelectedElementIn = true;
- }
- index++;
- }
- setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
-
- // if current selected item isn't anymore in the list select first entry
- if (!isSelectedElementIn) {
- selectService(0);
- }
- }
-
- protected void selectService(int index) {
- if (index >= 0 && index < serviceList.size()) {
- CBS serviceEntry = serviceList.get(index);
- selectedService = serviceEntry.type;
- updateChannel(CHANNEL_GROUP_SERVICE, NAME, StringType.valueOf(Converter.toTitleCase(serviceEntry.type)));
- if (serviceEntry.dateTime != null) {
- updateChannel(CHANNEL_GROUP_SERVICE, DATE,
- DateTimeType.valueOf(Converter.zonedToLocalDateTime(serviceEntry.dateTime)));
- } else {
- updateChannel(CHANNEL_GROUP_SERVICE, DATE, UnDefType.UNDEF);
- }
- if (serviceEntry.distance != null) {
- if (Constants.KILOMETERS_JSON.equals(serviceEntry.distance.units)) {
- updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
- QuantityType.valueOf(serviceEntry.distance.value, Constants.KILOMETRE_UNIT));
- } else {
- updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
- QuantityType.valueOf(serviceEntry.distance.value, ImperialUnits.MILE));
- }
- } else {
- updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
- QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT));
- }
- }
- }
-
- protected void updateSessions(List<ChargeSession> sl) {
- // if list is empty add "undefined" element
- if (sl.isEmpty()) {
- ChargeSession cs = new ChargeSession();
- cs.title = Constants.NO_ENTRIES;
- sl.add(cs);
- }
-
- // add all elements to options
- sessionList = sl;
- List<CommandOption> sessionNameOptions = new ArrayList<>();
- boolean isSelectedElementIn = false;
- int index = 0;
- for (ChargeSession session : sessionList) {
- // create StateOption with "value = list index" and "label = human readable string"
- sessionNameOptions.add(new CommandOption(Integer.toString(index), session.title));
- if (selectedService.equals(session.title)) {
- isSelectedElementIn = true;
- }
- index++;
- }
- setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
-
- // if current selected item isn't anymore in the list select first entry
- if (!isSelectedElementIn) {
- selectSession(0);
- }
- }
-
- protected void selectSession(int index) {
- if (index >= 0 && index < sessionList.size()) {
- ChargeSession sessionEntry = sessionList.get(index);
- selectedService = sessionEntry.title;
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.title));
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.subtitle));
- if (sessionEntry.energyCharged != null) {
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.energyCharged));
- } else {
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF));
- }
- if (sessionEntry.issues != null) {
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.issues));
- } else {
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN));
- }
- updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.sessionStatus));
- }
- }
-
- protected void updateChargeProfile(ChargeProfile cp) {
- ChargeProfileWrapper cpw = new ChargeProfileWrapper(cp);
-
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()));
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()));
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()));
- ChargingSettings cs = cpw.getChargeSettings();
- if (cs != null) {
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
- DecimalType.valueOf(Integer.toString(cs.targetSoc)));
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
- OnOffType.from(cs.isAcCurrentLimitActive));
- }
- final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
- climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
- updateTimedState(cpw, ProfileKey.WINDOWSTART);
- updateTimedState(cpw, ProfileKey.WINDOWEND);
- updateTimedState(cpw, ProfileKey.TIMER1);
- updateTimedState(cpw, ProfileKey.TIMER2);
- updateTimedState(cpw, ProfileKey.TIMER3);
- updateTimedState(cpw, ProfileKey.TIMER4);
- }
-
- protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
- final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
- if (timed != null) {
- final LocalTime time = profile.getTime(key);
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
- time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
- : new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
- if (timed.timer != null) {
- final Boolean enabled = profile.isEnabled(key);
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
- enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
- if (timed.hasDays) {
- final Set<DayOfWeek> days = profile.getDays(key);
- EnumSet.allOf(DayOfWeek.class).forEach(day -> {
- updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
- timed.timer + ChargeProfileUtils.getDaysChannel(day),
- days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
- });
- }
- }
- }
- }
-
- protected void updateDoors(DoorsWindows dw) {
- updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
- StringType.valueOf(Converter.toTitleCase(dw.doors.driverFront)));
- updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
- StringType.valueOf(Converter.toTitleCase(dw.doors.driverRear)));
- updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
- StringType.valueOf(Converter.toTitleCase(dw.doors.passengerFront)));
- updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
- StringType.valueOf(Converter.toTitleCase(dw.doors.passengerRear)));
- updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(dw.trunk)));
- updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(dw.hood)));
- }
-
- protected void updateWindows(DoorsWindows dw) {
- updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
- StringType.valueOf(Converter.toTitleCase(dw.windows.driverFront)));
- updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
- StringType.valueOf(Converter.toTitleCase(dw.windows.driverRear)));
- updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
- StringType.valueOf(Converter.toTitleCase(dw.windows.passengerFront)));
- updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
- StringType.valueOf(Converter.toTitleCase(dw.windows.passengerRear)));
- updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(dw.moonroof)));
- }
-
- protected void updatePosition(Location pos) {
- if (pos.coordinates.latitude < 0) {
- updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF);
- updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
- } else {
- PointType vehicleLocation = PointType.valueOf(
- Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude));
- updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation);
- updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
- updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
- PointType homeLocation = locationProvider.getLocation();
- if (homeLocation != null) {
- updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
- QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE));
- } else {
- updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
- }
- }
- }
-}
*/
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<RequiredService> serviceList = List.of();
+ private volatile String selectedService = Constants.UNDEF;
+ private volatile List<CheckControlMessage> checkControlList = List.of();
+ private volatile String selectedCC = Constants.UNDEF;
+ private volatile List<ChargingSession> sessionList = List.of();
+ private volatile String selectedSession = Constants.UNDEF;
+
+ private MyBMWCommandOptionProvider commandOptionProvider;
+ private LocationProvider locationProvider;
+ private TimeZoneProvider timeZoneProvider;
+
+ // Data Caches
+ private Optional<VehicleStateContainer> vehicleStatusCache = Optional.empty();
+ private Optional<byte[]> imageCache = Optional.empty();
+
private Optional<MyBMWProxy> proxy = Optional.empty();
- private Optional<RemoteServiceHandler> remote = Optional.empty();
- public Optional<VehicleConfiguration> configuration = Optional.empty();
+ private Optional<RemoteServiceExecutor> remote = Optional.empty();
+ private Optional<MyBMWVehicleConfiguration> vehicleConfiguration = Optional.empty();
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
private Optional<ScheduledFuture<?>> 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<CommandOption> 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");
}
}
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
@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");
});
}
+ 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<VehicleConfiguration> getConfiguration() {
- return configuration;
+ public Optional<MyBMWVehicleConfiguration> 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<Length> lengthUnit = Constants.KILOMETRE_UNIT;
+
+ if (isElectric) {
+ int rangeElectric = vehicleState.getElectricChargingState().getRange();
+ QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
+ QuantityType<Length> 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<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
+ QuantityType<Length> 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<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
+ QuantityType<Length> 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<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
+ QuantityType<Length> 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<CheckControlMessage> 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<CommandOption> 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<RequiredService> 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<CommandOption> 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<ChargingSession> 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<CommandOption> 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<DayOfWeek> 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<byte[]> 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();
+ }
}
}
}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.utils.Constants;
+
+/**
+ * The {@link Token} MyBMW Token storage
+ *
+ * @author Bernd Weymann - Initial contribution
+ */
+@NonNullByDefault
+public class Token {
+ private String token = Constants.EMPTY;
+ private String tokenType = Constants.EMPTY;
+ private long expiration = 0;
+
+ public String getBearerToken() {
+ return new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public void setExpiration(int expiration) {
+ this.expiration = System.currentTimeMillis() / 1000 + expiration;
+ }
+
+ public void setExpirationTotal(long expiration) {
+ this.expiration = expiration;
+ }
+
+ public void setType(String type) {
+ tokenType = type;
+ }
+
+ public boolean isValid() {
+ return (!token.equals(Constants.EMPTY) && !tokenType.equals(Constants.EMPTY)
+ && (this.expiration - System.currentTimeMillis() / 1000) > 1);
+ }
+
+ @Override
+ public String toString() {
+ return tokenType + Constants.COLON + token + Constants.COLON + isValid();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler.backend;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ *
+ * deserialization of a JSON string to a Java Object
+ *
+ * @author Martin Grassl - initial contribution
+ */
+@NonNullByDefault
+public interface JsonStringDeserializer {
+
+ static final Logger LOGGER = LoggerFactory.getLogger(JsonStringDeserializer.class);
+
+ static final Gson GSON = new Gson();
+
+ public static List<VehicleBase> getVehicleBaseList(String vehicleBaseJson) {
+ try {
+ VehicleBase[] vehicleBaseArray = deserializeString(vehicleBaseJson, VehicleBase[].class);
+ return Arrays.asList(vehicleBaseArray);
+ } catch (JsonSyntaxException e) {
+ LOGGER.debug("JsonSyntaxException {}", e.getMessage());
+ return new ArrayList<VehicleBase>();
+ }
+ }
+
+ public static VehicleStateContainer getVehicleState(String vehicleStateJson) {
+ try {
+ VehicleStateContainer vehicleState = deserializeString(vehicleStateJson, VehicleStateContainer.class);
+ vehicleState.setRawStateJson(vehicleStateJson);
+ return vehicleState;
+ } catch (JsonSyntaxException e) {
+ LOGGER.debug("JsonSyntaxException {}", e.getMessage());
+ return new VehicleStateContainer();
+ }
+ }
+
+ public static ChargingStatisticsContainer getChargingStatistics(String chargeStatisticsJson) {
+ try {
+ ChargingStatisticsContainer chargeStatistics = deserializeString(chargeStatisticsJson,
+ ChargingStatisticsContainer.class);
+ return chargeStatistics;
+ } catch (JsonSyntaxException e) {
+ LOGGER.debug("JsonSyntaxException {}", e.getMessage());
+ return new ChargingStatisticsContainer();
+ }
+ }
+
+ public static ChargingSessionsContainer getChargingSessions(String chargeSessionsJson) {
+ try {
+ return deserializeString(chargeSessionsJson, ChargingSessionsContainer.class);
+ } catch (JsonSyntaxException e) {
+ LOGGER.debug("JsonSyntaxException {}", e.getMessage());
+ return new ChargingSessionsContainer();
+ }
+ }
+
+ public static ExecutionStatusContainer getExecutionStatus(String executionStatusJson) {
+ try {
+ return deserializeString(executionStatusJson, ExecutionStatusContainer.class);
+ } catch (JsonSyntaxException e) {
+ LOGGER.debug("JsonSyntaxException {}", e.getMessage());
+ return new ExecutionStatusContainer();
+ }
+ }
+
+ public static <T> T deserializeString(String toBeDeserialized, Class<T> deserializedClass) {
+ return GSON.fromJson(toBeDeserialized, deserializedClass);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler.backend;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
+import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
+import org.openhab.binding.mybmw.internal.utils.ImageProperties;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is for local testing. You have to configure a connected account with username = "testuser" and password =
+ * vehicle to be tested (e.g. BEV, ICE, BEV2, MILD_HYBRID,...)
+ * The respective files are loaded from the resources folder
+ *
+ * You have to set the environment variable "ENVIRONMENT" to the value "test"
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - refactoring
+ */
+@NonNullByDefault
+public class MyBMWFileProxy implements MyBMWProxy {
+ private final Logger logger = LoggerFactory.getLogger(MyBMWFileProxy.class);
+ private String vehicleToBeTested;
+
+ private static final String RESPONSES = "responses" + File.separator;
+ private static final String VEHICLES_BASE = File.separator + "vehicles_base.json";
+ private static final String VEHICLES_STATE = File.separator + "vehicles_state.json";
+ private static final String CHARGING_SESSIONS = File.separator + "charging_sessions.json";
+ private static final String CHARGING_STATISTICS = File.separator + "charging_statistics.json";
+ private static final String REMOTE_SERVICES_CALL = File.separator + "remote_service_call.json";
+ private static final String REMOTE_SERVICES_STATE = File.separator + "remote_service_status.json";
+
+ public MyBMWFileProxy(HttpClientFactory httpClientFactory, MyBMWBridgeConfiguration bridgeConfiguration) {
+ logger.trace("MyBMWFileProxy - initialize");
+ vehicleToBeTested = bridgeConfiguration.password;
+ }
+
+ public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
+ logger.trace("MyBMWFileProxy - update bridge");
+ vehicleToBeTested = bridgeConfiguration.password;
+ }
+
+ public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
+ List<@NonNull Vehicle> vehicles = new ArrayList<>();
+ List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
+
+ for (VehicleBase vehicleBase : vehiclesBase) {
+ VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
+ vehicleBase.getAttributes().getBrand());
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setVehicleBase(vehicleBase);
+ vehicle.setVehicleState(vehicleState);
+ vehicles.add(vehicle);
+ }
+
+ return vehicles;
+ }
+
+ /**
+ * request all vehicles for one specific brand and their state
+ *
+ * @param brand
+ */
+ public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
+ String vehicleResponseString = requestVehiclesBaseJson(brand);
+ return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
+ }
+
+ public String requestVehiclesBaseJson(String brand) throws NetworkException {
+ String vehicleResponseString = fileToString(VEHICLES_BASE);
+ return vehicleResponseString;
+ }
+
+ /**
+ * request vehicles for all possible brands
+ *
+ * @param callback
+ */
+ public List<VehicleBase> requestVehiclesBase() throws NetworkException {
+ List<VehicleBase> vehicles = new ArrayList<>();
+
+ for (String brand : BimmerConstants.REQUESTED_BRANDS) {
+ vehicles.addAll(requestVehiclesBase(brand));
+ }
+
+ return vehicles;
+ }
+
+ /**
+ * request the vehicle image
+ *
+ * @param config
+ * @param props
+ * @return
+ */
+ public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
+ return "".getBytes();
+ }
+
+ /**
+ * request the state for one specific vehicle
+ *
+ * @param baseVehicle
+ * @return
+ */
+ public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
+ String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
+ return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
+ }
+
+ public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
+ String vehicleStateResponseString = fileToString(VEHICLES_STATE);
+ return vehicleStateResponseString;
+ }
+
+ /**
+ * request charge statistics for electric vehicles
+ *
+ */
+ public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
+ String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
+ return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
+ }
+
+ public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
+ String chargeStatisticsResponseString = fileToString(CHARGING_STATISTICS);
+ return chargeStatisticsResponseString;
+ }
+
+ /**
+ * request charge sessions for electric vehicles
+ *
+ */
+ public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
+ String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
+ return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
+ }
+
+ public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
+ String chargeSessionsResponseString = fileToString(CHARGING_SESSIONS);
+ return chargeSessionsResponseString;
+ }
+
+ public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
+ throws NetworkException {
+ return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_CALL));
+ }
+
+ public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
+ throws NetworkException {
+ return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_STATE));
+ }
+
+ private String fileToString(String filename) {
+ logger.trace("reading file {}", RESPONSES + vehicleToBeTested + filename);
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(
+ MyBMWFileProxy.class.getClassLoader().getResourceAsStream(RESPONSES + vehicleToBeTested + filename),
+ "UTF-8"))) {
+ StringBuilder buf = new StringBuilder();
+ String sCurrentLine;
+
+ while ((sCurrentLine = br.readLine()) != null) {
+ buf.append(sCurrentLine);
+ }
+ logger.trace("successful");
+ return buf.toString();
+ } catch (IOException e) {
+ logger.error("file {} could not be loaded: {}", filename, e.getMessage());
+ return "";
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler.backend;
+
+import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.APP_VERSIONS;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.openhab.binding.mybmw.internal.handler.auth.MyBMWTokenController;
+import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
+import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
+import org.openhab.binding.mybmw.internal.utils.Constants;
+import org.openhab.binding.mybmw.internal.utils.Converter;
+import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
+import org.openhab.binding.mybmw.internal.utils.ImageProperties;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link MyBMWHttpProxy} This class holds the important constants for the BMW Connected Drive Authorization.
+ * They are taken from the Bimmercode from github
+ * {@link https://github.com/bimmerconnected/bimmer_connected}
+ * File defining these constants
+ * {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
+ * https://customer.bmwgroup.com/one/app/oauth.js
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Norbert Truchsess - edit and send of charge profile
+ * @author Martin Grassl - refactoring
+ * @author Mark Herwege - extended log anonymization
+ */
+@NonNullByDefault
+public class MyBMWHttpProxy implements MyBMWProxy {
+ private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxy.class);
+ private final HttpClient httpClient;
+ private MyBMWBridgeConfiguration bridgeConfiguration;
+ private final MyBMWTokenController myBMWTokenHandler;
+
+ /**
+ * URLs taken from
+ * https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
+ */
+ private final String vehicleUrl;
+ private final String vehicleStateUrl;
+ private final String remoteCommandUrl;
+ private final String remoteStatusUrl;
+
+ public MyBMWHttpProxy(HttpClientFactory httpClientFactory, MyBMWBridgeConfiguration bridgeConfiguration) {
+ logger.trace("MyBMWHttpProxy - initialize");
+ httpClient = httpClientFactory.getCommonHttpClient();
+
+ myBMWTokenHandler = new MyBMWTokenController(bridgeConfiguration, httpClient);
+
+ this.bridgeConfiguration = bridgeConfiguration;
+
+ vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
+ + BimmerConstants.API_VEHICLES;
+
+ vehicleStateUrl = vehicleUrl + "/state";
+
+ remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
+ + BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
+ remoteStatusUrl = remoteCommandUrl + "eventStatus";
+ logger.trace("MyBMWHttpProxy - ready");
+ }
+
+ @Override
+ public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
+ this.bridgeConfiguration = bridgeConfiguration;
+ }
+
+ /**
+ * requests all vehicles
+ *
+ * @return list of vehicles
+ */
+ public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
+ List<@NonNull Vehicle> vehicles = new ArrayList<>();
+ List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
+
+ for (VehicleBase vehicleBase : vehiclesBase) {
+ VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
+ vehicleBase.getAttributes().getBrand());
+
+ Vehicle vehicle = new Vehicle();
+ vehicle.setVehicleBase(vehicleBase);
+ vehicle.setVehicleState(vehicleState);
+ vehicles.add(vehicle);
+ }
+
+ return vehicles;
+ }
+
+ /**
+ * request all vehicles for one specific brand and their state
+ *
+ * @param brand
+ * @return the vehicles of one brand
+ */
+ public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
+ String vehicleResponseString = requestVehiclesBaseJson(brand);
+ return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
+ }
+
+ /**
+ * request the raw JSON for the vehicle
+ *
+ * @param brand
+ * @return the base vehicle information as JSON string
+ */
+ public String requestVehiclesBaseJson(String brand) throws NetworkException {
+ byte[] vehicleResponse = get(vehicleUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON);
+ String vehicleResponseString = new String(vehicleResponse, Charset.defaultCharset());
+ return vehicleResponseString;
+ }
+
+ /**
+ * request vehicles for all possible brands
+ *
+ * @return the list of vehicles
+ */
+ public List<VehicleBase> requestVehiclesBase() throws NetworkException {
+ List<VehicleBase> vehicles = new ArrayList<>();
+
+ for (String brand : BimmerConstants.REQUESTED_BRANDS) {
+ try {
+ vehicles.addAll(requestVehiclesBase(brand));
+
+ Thread.sleep(10000);
+ } catch (Exception e) {
+ logger.warn("error retrieving the base vehicles for brand {}: {}", brand, e.getMessage());
+ }
+ }
+
+ return vehicles;
+ }
+
+ /**
+ * request the vehicle image
+ *
+ * @param vin the vin of the vehicle
+ * @param brand the brand of the vehicle
+ * @param props the image properties
+ * @return the image as a byte array
+ */
+ public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
+ final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
+ + "/eadrax-ics/v3/presentation/vehicles/" + vin + "/images?carView=" + props.viewport;
+ return get(localImageUrl, brand, vin, HTTPConstants.CONTENT_TYPE_IMAGE);
+ }
+
+ /**
+ * request the state for one specific vehicle
+ *
+ * @param vin
+ * @param brand
+ * @return the vehicle state
+ */
+ public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
+ String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
+ return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
+ }
+
+ /**
+ * request the raw state as JSON for one specific vehicle
+ *
+ * @param vin
+ * @param brand
+ * @return the vehicle state as string
+ */
+ public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
+ byte[] vehicleStateResponse = get(vehicleStateUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
+ String vehicleStateResponseString = new String(vehicleStateResponse, Charset.defaultCharset());
+ return vehicleStateResponseString;
+ }
+
+ /**
+ * request charge statistics for electric vehicles
+ *
+ * @param vin
+ * @param brand
+ * @return the charge statistics
+ */
+ public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
+ String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
+ return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
+ }
+
+ /**
+ * request charge statistics for electric vehicles as JSON
+ *
+ * @param vin
+ * @param brand
+ * @return the charge statistics as JSON string
+ */
+ public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
+ MultiMap<@Nullable String> chargeStatisticsParams = new MultiMap<>();
+ chargeStatisticsParams.put("vin", vin);
+ chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
+ String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
+ String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
+ + "/eadrax-chs/v1/charging-statistics?" + params;
+ byte[] chargeStatisticsResponse = get(chargeStatisticsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
+ String chargeStatisticsResponseString = new String(chargeStatisticsResponse);
+ return chargeStatisticsResponseString;
+ }
+
+ /**
+ * request charge sessions for electric vehicles
+ *
+ * @param vin
+ * @param brand
+ * @return the charge sessions
+ */
+ public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
+ String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
+ return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
+ }
+
+ /**
+ * request charge sessions for electric vehicles as JSON string
+ *
+ * @param vin
+ * @param brand
+ * @return the charge sessions as JSON string
+ */
+ public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
+ MultiMap<@Nullable String> chargeSessionsParams = new MultiMap<>();
+ chargeSessionsParams.put("vin", vin);
+ chargeSessionsParams.put("maxResults", "40");
+ chargeSessionsParams.put("include_date_picker", "true");
+ String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
+ String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
+ + "/eadrax-chs/v1/charging-sessions?" + params;
+ byte[] chargeSessionsResponse = get(chargeSessionsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
+ String chargeSessionsResponseString = new String(chargeSessionsResponse);
+ return chargeSessionsResponseString;
+ }
+
+ /**
+ * execute a remote service call
+ *
+ * @param vin
+ * @param brand
+ * @param service the service which should be executed
+ * @return the running service execution for status checks
+ */
+ public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
+ throws NetworkException {
+ String executionUrl = remoteCommandUrl + vin + "/" + service.getCommand();
+
+ byte[] response = post(executionUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON, service.getBody());
+
+ return JsonStringDeserializer.getExecutionStatus(new String(response));
+ }
+
+ /**
+ * check the status of a service call
+ *
+ * @param brand
+ * @param eventid the ID of the currently running service execution
+ * @return the running service execution for status checks
+ */
+ public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
+ throws NetworkException {
+ String executionUrl = remoteStatusUrl + Constants.QUESTION + "eventId=" + eventId;
+
+ byte[] response = post(executionUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON, null);
+
+ return JsonStringDeserializer.getExecutionStatus(new String(response));
+ }
+
+ /**
+ * prepares a GET request to the backend
+ *
+ * @param url
+ * @param brand
+ * @param vin
+ * @param contentType
+ * @return byte array of the response body
+ */
+ private byte[] get(String url, final String brand, @Nullable String vin, String contentType)
+ throws NetworkException {
+ return call(url, false, brand, vin, contentType, null);
+ }
+
+ /**
+ * prepares a POST request to the backend
+ *
+ * @param url
+ * @param brand
+ * @param vin
+ * @param contentType
+ * @param body
+ * @return byte array of the response body
+ */
+ private byte[] post(String url, final String brand, @Nullable String vin, String contentType, @Nullable String body)
+ throws NetworkException {
+ return call(url, true, brand, vin, contentType, body);
+ }
+
+ /**
+ * executes the real call to the backend
+ *
+ * @param url
+ * @param post boolean value indicating if it is a post request
+ * @param brand
+ * @param vin
+ * @param contentType
+ * @param body
+ * @return byte array of the response body
+ */
+ private synchronized byte[] call(final String url, final boolean post, final String brand,
+ final @Nullable String vin, final String contentType, final @Nullable String body) throws NetworkException {
+ byte[] responseByteArray = "".getBytes();
+
+ // return in case of unknown brand
+ if (!BimmerConstants.REQUESTED_BRANDS.contains(brand.toLowerCase())) {
+ logger.warn("Unknown Brand {}", brand);
+ throw new NetworkException("Unknown Brand " + brand);
+ }
+
+ final Request req;
+
+ if (post) {
+ req = httpClient.POST(url);
+ } else {
+ req = httpClient.newRequest(url);
+ }
+
+ req.header(HttpHeader.AUTHORIZATION, myBMWTokenHandler.getToken().getBearerToken());
+ req.header(HTTPConstants.HEADER_X_USER_AGENT, String.format(BimmerConstants.X_USER_AGENT, brand.toLowerCase(),
+ APP_VERSIONS.get(bridgeConfiguration.region), bridgeConfiguration.region));
+ req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.language);
+ req.header(HttpHeader.ACCEPT, contentType);
+ req.header(HTTPConstants.HEADER_BMW_VIN, vin);
+
+ try {
+ ContentResponse response = req.timeout(HTTPConstants.HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
+ if (response.getStatus() >= 300) {
+ responseByteArray = "".getBytes();
+ NetworkException exception = new NetworkException(url, response.getStatus(),
+ ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()), body);
+ logResponse(ResponseContentAnonymizer.replaceVin(exception.getUrl(), vin), exception.getReason(),
+ ResponseContentAnonymizer.anonymizeResponseContent(body));
+ throw exception;
+ } else {
+ responseByteArray = response.getContent();
+
+ // don't print images
+ if (!HTTPConstants.CONTENT_TYPE_IMAGE.equals(contentType)) {
+ logResponse(ResponseContentAnonymizer.replaceVin(url, vin),
+ ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()),
+ ResponseContentAnonymizer.anonymizeResponseContent(body));
+ }
+ }
+ } catch (TimeoutException | ExecutionException e) {
+ logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
+ ResponseContentAnonymizer.anonymizeResponseContent(vin));
+ throw new NetworkException(url, -1, null, body, e);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
+ ResponseContentAnonymizer.anonymizeResponseContent(vin));
+ throw new NetworkException(url, -1, null, body, e);
+ }
+
+ return responseByteArray;
+ }
+
+ private void logResponse(@Nullable String url, @Nullable String fingerprint, @Nullable String body) {
+ logger.debug("###### Request URL - BEGIN ######");
+ logger.debug("{}", url);
+ logger.debug("###### Request Body - BEGIN ######");
+ logger.debug("{}", body);
+ logger.debug("###### Response Data - BEGIN ######");
+ logger.debug("{}", fingerprint);
+ logger.debug("###### Response Data - END ######");
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler.backend;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
+import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
+import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
+import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
+import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
+import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
+import org.openhab.binding.mybmw.internal.utils.ImageProperties;
+
+/**
+ * this is the interface for requesting the myBMW responses
+ *
+ * @author Martin Grassl - Initial Contribution
+ */
+@NonNullByDefault
+public interface MyBMWProxy {
+
+ void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration);
+
+ List<@NonNull Vehicle> requestVehicles() throws NetworkException;
+
+ /**
+ * request all vehicles for one specific brand and their state
+ *
+ * @param brand
+ */
+ List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException;
+
+ String requestVehiclesBaseJson(String brand) throws NetworkException;
+
+ /**
+ * request vehicles for all possible brands
+ *
+ * @param callback
+ */
+ List<VehicleBase> requestVehiclesBase() throws NetworkException;
+
+ /**
+ * request the vehicle image
+ *
+ * @param config
+ * @param props
+ * @return
+ */
+ byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException;
+
+ /**
+ * request the state for one specific vehicle
+ *
+ * @param baseVehicle
+ * @return
+ */
+ VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException;
+
+ String requestVehicleStateJson(String vin, String brand) throws NetworkException;
+
+ /**
+ * request charge statistics for electric vehicles
+ *
+ */
+ ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException;
+
+ String requestChargeStatisticsJson(String vin, String brand) throws NetworkException;
+
+ /**
+ * request charge sessions for electric vehicles
+ *
+ */
+ ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException;
+
+ String requestChargeSessionsJson(String vin, String brand) throws NetworkException;
+
+ ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
+ throws NetworkException;
+
+ ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId) throws NetworkException;
+}
--- /dev/null
+/**
+ * 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 + "]";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mybmw.internal.handler.backend;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ * anonymizes all occurrencies of locations and vins
+ *
+ * @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - refactoring & extension for any occurrence
+ * @author Mark Herwege - extended log anonymization
+ */
+@NonNullByDefault
+public interface ResponseContentAnonymizer {
+
+ static final String ANONYMOUS_VIN = "anonymousVin";
+ static final String VIN_PATTERN = "\"vin\":";
+ static final String VEHICLE_CHARGING_LOCATION_PATTERN = "\"subtitle\":";
+ static final String VEHICLE_LOCATION_PATTERN = "\"location\":";
+ static final String VEHICLE_LOCATION_LATITUDE_PATTERN = "latitude";
+ static final String VEHICLE_LOCATION_LONGITUDE_PATTERN = "longitude";
+ static final String VEHICLE_LOCATION_FORMATTED_PATTERN = "formatted";
+ static final String VEHICLE_LOCATION_HEADING_PATTERN = "heading";
+ static final String VEHICLE_LOCATION_LATITUDE = "1.1";
+ static final String VEHICLE_LOCATION_LONGITUDE = "2.2";
+ static final String ANONYMOUS_ADDRESS = "anonymousAddress";
+ static final String VEHICLE_LOCATION_HEADING = "-1";
+ static final String RAW_VEHICLE_LOCATION_PATTERN_START = "\\\"location\\\"";
+ static final String RAW_VEHICLE_LOCATION_PATTERN_END = "\\\"heading\\\"";
+ static final String RAW_VEHICLE_LOCATION_PATTERN_REPLACER = "\"location\":{\"coordinates\":{\"latitude\":"
+ + VEHICLE_LOCATION_LATITUDE + ",\"longitude\":" + VEHICLE_LOCATION_LONGITUDE
+ + "},\"address\":{\"formatted\":\"" + ANONYMOUS_ADDRESS + "\"},";
+
+ static final String CLOSING_BRACKET = "}";
+ static final String QUOTE = "\"";
+ static final String CLOSE_VALUE = "\":";
+ static final String COMMA = ",";
+
+ /**
+ * anonymizes the responseContent
+ * <p>
+ * - vin
+ * </p>
+ * <p>
+ * - location
+ * </p>
+ *
+ * @param responseContent
+ * @return
+ */
+ public static String anonymizeResponseContent(@Nullable String responseContent) {
+ if (responseContent == null) {
+ return "";
+ }
+
+ String anonymizedVinString = replaceVins(responseContent);
+
+ String anonymizedLocationString = replaceLocations(anonymizedVinString);
+
+ String anonymizedRawLocationString = replaceRawLocations(anonymizedLocationString);
+
+ String anonymizedChargingLocationString = replaceChargingLocations(anonymizedRawLocationString);
+
+ return anonymizedChargingLocationString;
+ }
+
+ static String replaceChargingLocations(String stringToBeReplaced) {
+ String[] locationStrings = stringToBeReplaced.split(VEHICLE_CHARGING_LOCATION_PATTERN);
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(locationStrings[0]);
+ for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
+ replacedString.append(VEHICLE_CHARGING_LOCATION_PATTERN);
+ replacedString.append(replaceChargingLocation(locationStrings[i]));
+ }
+
+ return replacedString.toString();
+ }
+
+ static String replaceChargingLocation(String responseContent) {
+ String[] subtitleStrings = responseContent.split(" • ", 2);
+
+ StringBuffer replacedString = new StringBuffer();
+
+ replacedString.append("\"");
+ replacedString.append(ANONYMOUS_ADDRESS);
+ if (subtitleStrings.length > 1) {
+ replacedString.append(" • ");
+ replacedString.append(subtitleStrings[1]);
+ }
+
+ return replacedString.toString();
+ }
+
+ static String replaceRawLocations(String stringToBeReplaced) {
+ String[] locationStrings = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_START));
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(locationStrings[0]);
+ for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
+ replacedString.append(replaceRawLocation(locationStrings[i]));
+ }
+
+ return replacedString.toString();
+ }
+
+ /**
+ * this just replaces a string
+ *
+ * @param string
+ * @return
+ */
+ static String replaceRawLocation(String stringToBeReplaced) {
+ String[] stringParts = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_END));
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_REPLACER);
+ replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_END);
+ replacedString.append(stringParts[1]);
+ return replacedString.toString();
+ }
+
+ static String replaceLocations(String stringToBeReplaced) {
+ String[] locationStrings = stringToBeReplaced.split(VEHICLE_LOCATION_PATTERN);
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(locationStrings[0]);
+ for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
+ replacedString.append(VEHICLE_LOCATION_PATTERN);
+ replacedString.append(replaceLocation(locationStrings[i]));
+ }
+
+ return replacedString.toString();
+ }
+
+ static String replaceLocation(String responseContent) {
+ String stringToBeReplaced = responseContent;
+
+ StringBuffer replacedString = new StringBuffer();
+ // latitude
+ stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LATITUDE_PATTERN,
+ VEHICLE_LOCATION_LATITUDE);
+
+ // longitude
+ stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LONGITUDE_PATTERN,
+ VEHICLE_LOCATION_LONGITUDE);
+
+ // formatted address
+ stringToBeReplaced = replaceStringValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_FORMATTED_PATTERN,
+ ANONYMOUS_ADDRESS);
+
+ // heading
+ stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_HEADING_PATTERN,
+ VEHICLE_LOCATION_HEADING);
+
+ replacedString.append(stringToBeReplaced);
+
+ return replacedString.toString();
+ }
+
+ static String replaceNumberValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
+ String replacerValue) {
+ int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
+ + (replacerPattern.length() + CLOSE_VALUE.length());
+ int endIndex = -1;
+
+ // in an object, the comma comes after the value or a closing bracket
+ if (stringToBeReplaced.indexOf(COMMA, startIndex) < stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex)) {
+ endIndex = stringToBeReplaced.indexOf(COMMA, startIndex);
+ } else {
+ endIndex = stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex);
+ }
+
+ replacedString.append(stringToBeReplaced.substring(0, startIndex));
+ replacedString.append(replacerValue);
+
+ return stringToBeReplaced.substring(endIndex);
+ }
+
+ static String replaceStringValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
+ String replacerValue) {
+ // the startIndex is the String after the first quote of the value after the key
+ // detect end of key
+ int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
+ + (replacerPattern.length() + CLOSE_VALUE.length());
+ // detect start of value
+ startIndex = stringToBeReplaced.indexOf(QUOTE, startIndex) + 1;
+
+ // detect end of value
+ int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
+
+ replacedString.append(stringToBeReplaced.substring(0, startIndex));
+ replacedString.append(replacerValue);
+
+ return stringToBeReplaced.substring(endIndex);
+ }
+
+ static String replaceVins(String stringToBeReplaced) {
+ String[] vinStrings = stringToBeReplaced.split(VIN_PATTERN);
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(vinStrings[0]);
+ for (int i = 1; vinStrings.length > 0 && i < vinStrings.length; i++) {
+ replacedString.append(VIN_PATTERN);
+ replacedString.append(replaceVin(vinStrings[i]));
+ }
+
+ return replacedString.toString();
+ }
+
+ static String replaceVin(String stringToBeReplaced) {
+ // the vin is between two quotes
+ int startIndex = stringToBeReplaced.indexOf(QUOTE) + 1;
+ int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
+
+ StringBuffer replacedString = new StringBuffer();
+ replacedString.append(stringToBeReplaced.substring(0, startIndex));
+ replacedString.append(ANONYMOUS_VIN);
+ replacedString.append(stringToBeReplaced.substring(endIndex));
+
+ return replacedString.toString();
+ }
+
+ static @Nullable String replaceVin(@Nullable String stringToBeReplaced, @Nullable String vin) {
+ if (stringToBeReplaced == null) {
+ return null;
+ }
+ return vin != null ? stringToBeReplaced.replace(vin, ANONYMOUS_VIN) : stringToBeReplaced;
+ }
+}
--- /dev/null
+/**
+ * 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 org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ *
+ * execution state of a remote command
+ *
+ * @author Martin Grassl - initial contribution
+ */
+@NonNullByDefault
+public enum ExecutionState {
+ READY,
+ INITIATED,
+ PENDING,
+ DELIVERED,
+ EXECUTED,
+ ERROR,
+ TIMEOUT
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
+++ /dev/null
-/**
- * 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;
- }
-}
* <a href="https://customer.bmwgroup.com/one/app/oauth.js">https://customer.bmwgroup.com/one/app/oauth.js</a>
*
* @author Bernd Weymann - Initial contribution
+ * @author Martin Grassl - update to v2 API
*/
@NonNullByDefault
-public class BimmerConstants {
+public interface BimmerConstants {
- public static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
- public static final String REGION_CHINA = "CHINA";
- public static final String REGION_ROW = "ROW";
+ static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
+ static final String REGION_CHINA = "CHINA";
+ static final String REGION_ROW = "ROW";
- public static final String BRAND_BMW = "bmw";
- public static final String BRAND_MINI = "mini";
- public static final List<String> ALL_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
+ static final String BRAND_BMW = "bmw";
+ static final String BRAND_BMWI = "bmw_i";
+ static final String BRAND_MINI = "mini";
+ static final List<String> REQUESTED_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
- public static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
+ static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
+ static final String AUTH_PROVIDER = "gcdm";
- public static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
- public static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
- public static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn";
- public static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA,
- EADRAX_SERVER_NORTH_AMERICA, REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
+ static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
+ static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
+ static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn";
+ static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, EADRAX_SERVER_NORTH_AMERICA,
+ REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
- public static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
- public static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
- public static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
+ static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
+ static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
+ static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
REGION_ROW, OCP_APIM_KEY_ROW);
- public static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey";
- public static final String CHINA_LOGIN = "/eadrax-coas/v1/login/pwd";
+ static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey";
+ static final String CHINA_LOGIN = "/eadrax-coas/v2/login/pwd";
// Http variables
- public static final String USER_AGENT = "Dart/2.14 (dart:io)";
- public static final String X_USER_AGENT = "android(SP1A.210812.016.C1);%s;2.5.2(14945);%s";
+ static final String APP_VERSION_NORTH_AMERICA = "2.12.0(19883)";
+ static final String APP_VERSION_ROW = "2.12.0(19883)";
+ static final String APP_VERSION_CHINA = "2.3.0(13603)";
+ static final Map<String, String> APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW,
+ APP_VERSION_ROW, REGION_CHINA, APP_VERSION_CHINA);
+ static final String USER_AGENT = "Dart/2.16 (dart:io)";
+ // see const.py of bimmer_constants: user-agent; brand; app_version; region
+ static final String X_USER_AGENT = "android(SP1A.210812.016.C1);%s;%s;%s";
- public static final String LOGIN_NONCE = "login_nonce";
- public static final String AUTHORIZATION_CODE = "authorization_code";
+ static final String LOGIN_NONCE = "login_nonce";
+ static final String AUTHORIZATION_CODE = "authorization_code";
// Parameters for API Requests
- public static final String TIRE_GUARD_MODE = "tireGuardMode";
- public static final String APP_DATE_TIME = "appDateTime";
- public static final String APP_TIMEZONE = "apptimezone";
+ static final String TIRE_GUARD_MODE = "tireGuardMode";
+ static final String APP_DATE_TIME = "appDateTime";
+ static final String APP_TIMEZONE = "apptimezone";
// API endpoints
- public static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
- public static final String API_VEHICLES = "/eadrax-vcs/v1/vehicles";
- public static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v2/presentation/remote-commands/"; // '/{vin}/{service_type}'
- public static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
+ static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
+ static final String API_VEHICLES = "/eadrax-vcs/v4/vehicles";
+ static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v3/presentation/remote-commands/"; // '/{vin}/{service_type}'
+ static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
}
+++ /dev/null
-/**
- * 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.MyBMWConstants.*;
-
-import java.time.DayOfWeek;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
-
-/**
- * The {@link ChargeProfileUtils} utility functions for charging profiles
- *
- * @author Norbert Truchsess - initial contribution
- */
-@NonNullByDefault
-public class ChargeProfileUtils {
-
- // Charging
- public static class TimedChannel {
- public final String time;
- public final @Nullable String timer;
- public final boolean hasDays;
-
- TimedChannel(final String time, @Nullable final String timer, final boolean hasDays) {
- this.time = time;
- this.timer = timer;
- this.hasDays = hasDays;
- }
- }
-
- @SuppressWarnings("serial")
- private static final Map<ProfileKey, TimedChannel> TIMED_CHANNELS = new HashMap<>() {
- {
- put(ProfileKey.WINDOWSTART, new TimedChannel(CHARGE_WINDOW_START, null, false));
- put(ProfileKey.WINDOWEND, new TimedChannel(CHARGE_WINDOW_END, null, false));
- put(ProfileKey.TIMER1, new TimedChannel(CHARGE_TIMER1 + CHARGE_DEPARTURE, CHARGE_TIMER1, true));
- put(ProfileKey.TIMER2, new TimedChannel(CHARGE_TIMER2 + CHARGE_DEPARTURE, CHARGE_TIMER2, true));
- put(ProfileKey.TIMER3, new TimedChannel(CHARGE_TIMER3 + CHARGE_DEPARTURE, CHARGE_TIMER3, true));
- put(ProfileKey.TIMER4, new TimedChannel(CHARGE_TIMER4 + CHARGE_DEPARTURE, CHARGE_TIMER4, true));
- }
- };
-
- @SuppressWarnings("serial")
- private static final Map<DayOfWeek, String> DAY_CHANNELS = new HashMap<>() {
- {
- put(DayOfWeek.MONDAY, CHARGE_DAY_MON);
- put(DayOfWeek.TUESDAY, CHARGE_DAY_TUE);
- put(DayOfWeek.WEDNESDAY, CHARGE_DAY_WED);
- put(DayOfWeek.THURSDAY, CHARGE_DAY_THU);
- put(DayOfWeek.FRIDAY, CHARGE_DAY_FRI);
- put(DayOfWeek.SATURDAY, CHARGE_DAY_SAT);
- put(DayOfWeek.SUNDAY, CHARGE_DAY_SUN);
- }
- };
-
- public static class ChargeKeyDay {
- public final ProfileKey key;
- public final DayOfWeek day;
-
- ChargeKeyDay(final ProfileKey key, final DayOfWeek day) {
- this.key = key;
- this.day = day;
- }
- }
-
- @SuppressWarnings("serial")
- private static final Map<String, ProfileKey> CHARGE_ENABLED_CHANNEL_KEYS = new HashMap<>() {
- {
- TIMED_CHANNELS.forEach((key, channel) -> {
- put(channel.timer + CHARGE_ENABLED, key);
- });
- put(CHARGE_PROFILE_CLIMATE, ProfileKey.CLIMATE);
- }
- };
-
- @SuppressWarnings("serial")
- private static final Map<String, ProfileKey> CHARGE_TIME_CHANNEL_KEYS = new HashMap<>() {
- {
- TIMED_CHANNELS.forEach((key, channel) -> {
- put(channel.time, key);
- });
- }
- };
-
- @SuppressWarnings("serial")
- private static final Map<String, ChargeKeyDay> CHARGE_DAYS_CHANNEL_KEYS = new HashMap<>() {
- {
- DAY_CHANNELS.forEach((dayOfWeek, dayChannel) -> {
- put(CHARGE_TIMER1 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER1, dayOfWeek));
- put(CHARGE_TIMER2 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER2, dayOfWeek));
- put(CHARGE_TIMER3 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
- put(CHARGE_TIMER4 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
- });
- }
- };
-
- public static @Nullable TimedChannel getTimedChannel(ProfileKey key) {
- return TIMED_CHANNELS.get(key);
- }
-
- public static @Nullable String getDaysChannel(DayOfWeek day) {
- return DAY_CHANNELS.get(day);
- }
-
- public static @Nullable ProfileKey getEnableKey(final String id) {
- return CHARGE_ENABLED_CHANNEL_KEYS.get(id);
- }
-
- public static @Nullable ChargeKeyDay getKeyDay(final String id) {
- return CHARGE_DAYS_CHANNEL_KEYS.get(id);
- }
-
- public static @Nullable ProfileKey getTimeKey(final String id) {
- return CHARGE_TIME_CHANNEL_KEYS.get(id);
- }
-
- public static String formatDays(final Set<DayOfWeek> weekdays) {
- return weekdays.stream().map(day -> Constants.DAYS.get(day)).collect(Collectors.joining(Constants.COMMA));
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2023 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mybmw.internal.utils;
-
-import static org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey.*;
-import static org.openhab.binding.mybmw.internal.utils.Constants.*;
-
-import java.time.DayOfWeek;
-import java.time.LocalTime;
-import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingMode;
-import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingPreference;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
-import org.openhab.binding.mybmw.internal.dto.charge.ChargingWindow;
-import org.openhab.binding.mybmw.internal.dto.charge.Time;
-import org.openhab.binding.mybmw.internal.dto.charge.Timer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link ChargeProfileWrapper} Wrapper for ChargeProfiles
- *
- * @author Bernd Weymann - Initial contribution
- * @author Norbert Truchsess - add ChargeProfileActions
- */
-@NonNullByDefault
-public class ChargeProfileWrapper {
- private static final Logger LOGGER = LoggerFactory.getLogger(ChargeProfileWrapper.class);
-
- private static final String CHARGING_WINDOW = "chargingWindow";
- private static final String WEEKLY_PLANNER = "weeklyPlanner";
- private static final String ACTIVATE = "activate";
- private static final String DEACTIVATE = "deactivate";
-
- public enum ProfileKey {
- CLIMATE,
- TIMER1,
- TIMER2,
- TIMER3,
- TIMER4,
- WINDOWSTART,
- WINDOWEND
- }
-
- private Optional<ChargingMode> mode = Optional.empty();
- private Optional<ChargingPreference> preference = Optional.empty();
- private Optional<String> controlType = Optional.empty();
- private Optional<ChargingSettings> chargeSettings = Optional.empty();
-
- private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
- private final Map<ProfileKey, LocalTime> times = new HashMap<>();
- private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
-
- public ChargeProfileWrapper(final ChargeProfile profile) {
- setPreference(profile.chargingPreference);
- setMode(profile.chargingMode);
- controlType = Optional.of(profile.chargingControlType);
- chargeSettings = Optional.of(profile.chargingSettings);
- setEnabled(CLIMATE, profile.climatisationOn);
-
- addTimer(TIMER1, profile.getTimerId(1));
- addTimer(TIMER2, profile.getTimerId(2));
- if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
- addTimer(TIMER3, profile.getTimerId(3));
- addTimer(TIMER4, profile.getTimerId(4));
- }
-
- if (CHARGING_WINDOW.equals(profile.chargingPreference)) {
- addTime(WINDOWSTART, profile.reductionOfChargeCurrent.start);
- addTime(WINDOWEND, profile.reductionOfChargeCurrent.end);
- } else {
- preference.ifPresent(pref -> {
- if (ChargingPreference.chargingWindow.equals(pref)) {
- addTime(WINDOWSTART, null);
- addTime(WINDOWEND, null);
- }
- });
- }
- }
-
- public @Nullable Boolean isEnabled(final ProfileKey key) {
- return enabled.get(key);
- }
-
- public void setEnabled(final ProfileKey key, @Nullable final Boolean enabled) {
- if (enabled == null) {
- this.enabled.remove(key);
- } else {
- this.enabled.put(key, enabled);
- }
- }
-
- public @Nullable String getMode() {
- return mode.map(m -> m.name()).orElse(null);
- }
-
- public @Nullable String getControlType() {
- return controlType.get();
- }
-
- public @Nullable ChargingSettings getChargeSettings() {
- return chargeSettings.get();
- }
-
- public void setMode(final @Nullable String mode) {
- if (mode != null) {
- try {
- this.mode = Optional.of(ChargingMode.valueOf(mode));
- return;
- } catch (IllegalArgumentException iae) {
- LOGGER.warn("unexpected value for chargingMode: {}", mode);
- }
- }
- this.mode = Optional.empty();
- }
-
- public @Nullable String getPreference() {
- return preference.map(pref -> pref.name()).orElse(null);
- }
-
- public void setPreference(final @Nullable String preference) {
- if (preference != null) {
- try {
- this.preference = Optional.of(ChargingPreference.valueOf(preference));
- return;
- } catch (IllegalArgumentException iae) {
- LOGGER.warn("unexpected value for chargingPreference: {}", preference);
- }
- }
- this.preference = Optional.empty();
- }
-
- public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
- return daysOfWeek.get(key);
- }
-
- public void setDays(final ProfileKey key, final @Nullable Set<DayOfWeek> days) {
- if (days == null) {
- daysOfWeek.remove(key);
- } else {
- daysOfWeek.put(key, days);
- }
- }
-
- public void setDayEnabled(final ProfileKey key, final DayOfWeek day, final boolean enabled) {
- final Set<DayOfWeek> days = daysOfWeek.get(key);
- if (days == null) {
- daysOfWeek.put(key, enabled ? EnumSet.of(day) : EnumSet.noneOf(DayOfWeek.class));
- } else {
- if (enabled) {
- days.add(day);
- } else {
- days.remove(day);
- }
- }
- }
-
- public LocalTime getTime(final ProfileKey key) {
- LocalTime t = times.get(key);
- if (t != null) {
- return t;
- } else {
- LOGGER.debug("Profile not valid - Key {} doesn't contain boolean value", key);
- return Constants.NULL_LOCAL_TIME;
- }
- }
-
- public void setTime(final ProfileKey key, @Nullable LocalTime time) {
- if (time == null) {
- times.remove(key);
- } else {
- times.put(key, time);
- }
- }
-
- public String getJson() {
- final ChargeProfile profile = new ChargeProfile();
-
- preference.ifPresent(pref -> profile.chargingPreference = pref.name());
- profile.chargingControlType = controlType.get();
- Boolean enabledBool = isEnabled(CLIMATE);
- profile.climatisationOn = enabledBool == null ? false : enabledBool;
- preference.ifPresent(pref -> {
- if (ChargingPreference.chargingWindow.equals(pref)) {
- profile.chargingMode = getMode();
- final LocalTime start = getTime(WINDOWSTART);
- final LocalTime end = getTime(WINDOWEND);
- if (!start.equals(Constants.NULL_LOCAL_TIME) && !end.equals(Constants.NULL_LOCAL_TIME)) {
- ChargingWindow cw = new ChargingWindow();
- profile.reductionOfChargeCurrent = cw;
- cw.start = new Time();
- cw.start.hour = start.getHour();
- cw.start.minute = start.getMinute();
- cw.end = new Time();
- cw.end.hour = end.getHour();
- cw.end.minute = end.getMinute();
- }
- }
- });
- profile.departureTimes = new ArrayList<Timer>();
- profile.departureTimes.add(getTimer(TIMER1));
- profile.departureTimes.add(getTimer(TIMER2));
- if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
- profile.departureTimes.add(getTimer(TIMER3));
- profile.departureTimes.add(getTimer(TIMER4));
- }
-
- profile.chargingSettings = chargeSettings.get();
- return Converter.getGson().toJson(profile);
- }
-
- private void addTime(final ProfileKey key, @Nullable final Time time) {
- try {
- times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(Converter.getTime(time), TIME_FORMATER));
- } catch (DateTimeParseException dtpe) {
- LOGGER.warn("unexpected value for {} time: {}", key.name(), time);
- }
- }
-
- private void addTimer(final ProfileKey key, @Nullable final Timer timer) {
- if (timer == null) {
- enabled.put(key, false);
- addTime(key, null);
- daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
- } else {
- enabled.put(key, ACTIVATE.equals(timer.action));
- addTime(key, timer.timeStamp);
- final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
- if (timer.timerWeekDays != null) {
- daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
- for (String day : timer.timerWeekDays) {
- try {
- daySet.add(DayOfWeek.valueOf(day.toUpperCase()));
- } catch (IllegalArgumentException iae) {
- LOGGER.warn("unexpected value for {} day: {}", key.name(), day);
- }
- daysOfWeek.put(key, daySet);
- }
- }
- }
- }
-
- private Timer getTimer(final ProfileKey key) {
- final Timer timer = new Timer();
- switch (key) {
- case TIMER1:
- timer.id = 1;
- break;
- case TIMER2:
- timer.id = 2;
- break;
- case TIMER3:
- timer.id = 3;
- break;
- case TIMER4:
- timer.id = 4;
- break;
- default:
- // timer id stays -1
- break;
- }
- Boolean enabledBool = isEnabled(key);
- if (enabledBool != null) {
- timer.action = enabledBool ? ACTIVATE : DEACTIVATE;
- } else {
- timer.action = DEACTIVATE;
- }
- final LocalTime time = getTime(key);
- if (!time.equals(Constants.NULL_LOCAL_TIME)) {
- timer.timeStamp = new Time();
- timer.timeStamp.hour = time.getHour();
- timer.timeStamp.minute = time.getMinute();
- }
- final Set<DayOfWeek> days = daysOfWeek.get(key);
- if (days != null) {
- timer.timerWeekDays = new ArrayList<>();
- for (DayOfWeek day : days) {
- timer.timerWeekDays.add(day.name().toLowerCase());
- }
- }
- return timer;
- }
-}
--- /dev/null
+/**
+ * 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.MyBMWConstants.*;
+
+import java.time.DayOfWeek;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
+
+/**
+ * The {@link ChargingProfileUtils} utility functions for charging profiles
+ *
+ * @author Norbert Truchsess - initial contribution
+ */
+@NonNullByDefault
+public class ChargingProfileUtils {
+
+ // Charging
+ public static class TimedChannel {
+ public final String time;
+ public final @Nullable String timer;
+ public final boolean hasDays;
+
+ TimedChannel(final String time, @Nullable final String timer, final boolean hasDays) {
+ this.time = time;
+ this.timer = timer;
+ this.hasDays = hasDays;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private static final Map<ProfileKey, TimedChannel> TIMED_CHANNELS = new HashMap<>() {
+ {
+ put(ProfileKey.WINDOWSTART, new TimedChannel(CHARGE_WINDOW_START, null, false));
+ put(ProfileKey.WINDOWEND, new TimedChannel(CHARGE_WINDOW_END, null, false));
+ put(ProfileKey.TIMER1, new TimedChannel(CHARGE_TIMER1 + CHARGE_DEPARTURE, CHARGE_TIMER1, true));
+ put(ProfileKey.TIMER2, new TimedChannel(CHARGE_TIMER2 + CHARGE_DEPARTURE, CHARGE_TIMER2, true));
+ put(ProfileKey.TIMER3, new TimedChannel(CHARGE_TIMER3 + CHARGE_DEPARTURE, CHARGE_TIMER3, true));
+ put(ProfileKey.TIMER4, new TimedChannel(CHARGE_TIMER4 + CHARGE_DEPARTURE, CHARGE_TIMER4, true));
+ }
+ };
+
+ @SuppressWarnings("serial")
+ private static final Map<DayOfWeek, String> DAY_CHANNELS = new HashMap<>() {
+ {
+ put(DayOfWeek.MONDAY, CHARGE_DAY_MON);
+ put(DayOfWeek.TUESDAY, CHARGE_DAY_TUE);
+ put(DayOfWeek.WEDNESDAY, CHARGE_DAY_WED);
+ put(DayOfWeek.THURSDAY, CHARGE_DAY_THU);
+ put(DayOfWeek.FRIDAY, CHARGE_DAY_FRI);
+ put(DayOfWeek.SATURDAY, CHARGE_DAY_SAT);
+ put(DayOfWeek.SUNDAY, CHARGE_DAY_SUN);
+ }
+ };
+
+ public static class ChargeKeyDay {
+ public final ProfileKey key;
+ public final DayOfWeek day;
+
+ ChargeKeyDay(final ProfileKey key, final DayOfWeek day) {
+ this.key = key;
+ this.day = day;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private static final Map<String, ProfileKey> CHARGE_ENABLED_CHANNEL_KEYS = new HashMap<>() {
+ {
+ TIMED_CHANNELS.forEach((key, channel) -> {
+ put(channel.timer + CHARGE_ENABLED, key);
+ });
+ put(CHARGE_PROFILE_CLIMATE, ProfileKey.CLIMATE);
+ }
+ };
+
+ @SuppressWarnings("serial")
+ private static final Map<String, ProfileKey> CHARGE_TIME_CHANNEL_KEYS = new HashMap<>() {
+ {
+ TIMED_CHANNELS.forEach((key, channel) -> {
+ put(channel.time, key);
+ });
+ }
+ };
+
+ @SuppressWarnings("serial")
+ private static final Map<String, ChargeKeyDay> CHARGE_DAYS_CHANNEL_KEYS = new HashMap<>() {
+ {
+ DAY_CHANNELS.forEach((dayOfWeek, dayChannel) -> {
+ put(CHARGE_TIMER1 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER1, dayOfWeek));
+ put(CHARGE_TIMER2 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER2, dayOfWeek));
+ put(CHARGE_TIMER3 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
+ put(CHARGE_TIMER4 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
+ });
+ }
+ };
+
+ public static @Nullable TimedChannel getTimedChannel(ProfileKey key) {
+ return TIMED_CHANNELS.get(key);
+ }
+
+ public static @Nullable String getDaysChannel(DayOfWeek day) {
+ return DAY_CHANNELS.get(day);
+ }
+
+ public static @Nullable ProfileKey getEnableKey(final String id) {
+ return CHARGE_ENABLED_CHANNEL_KEYS.get(id);
+ }
+
+ public static @Nullable ChargeKeyDay getKeyDay(final String id) {
+ return CHARGE_DAYS_CHANNEL_KEYS.get(id);
+ }
+
+ public static @Nullable ProfileKey getTimeKey(final String id) {
+ return CHARGE_TIME_CHANNEL_KEYS.get(id);
+ }
+
+ public static String formatDays(final Set<DayOfWeek> weekdays) {
+ return weekdays.stream().map(day -> Constants.DAYS.get(day)).collect(Collectors.joining(Constants.COMMA));
+ }
+}
--- /dev/null
+/**
+ * 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<ChargingMode> mode = Optional.empty();
+ private Optional<ChargingPreference> preference = Optional.empty();
+ private Optional<String> controlType = Optional.empty();
+ private Optional<ChargingSettings> chargeSettings = Optional.empty();
+
+ private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
+ private final Map<ProfileKey, LocalTime> times = new HashMap<>();
+ private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
+
+ public 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<DayOfWeek> 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<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
+ if (timer.timerWeekDays != null) {
+ daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
+ for (String day : timer.timerWeekDays) {
+ try {
+ daySet.add(DayOfWeek.valueOf(day.toUpperCase()));
+ } catch (IllegalArgumentException iae) {
+ logger.warn("unexpected value for {} day: {}", key.name(), day);
+ }
+ daysOfWeek.put(key, daySet);
+ }
+ }
+ }
+ }
+}
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - contributor
+ * @author Martin Grassl - rename drivetrain options
*/
@NonNullByDefault
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";
*/
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<ArrayList<Vehicle>>() {
- }.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);
}
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) {
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
*
* @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;
/**
* 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);
return index;
}
- /**
- * Returns list of found vehicles
- * In case of errors return empty list
- *
- * @param json
- * @return
- */
- public static List<Vehicle> getVehicleList(String json) {
- try {
- List<Vehicle> l = GSON.fromJson(json, VEHICLE_LIST_TYPE);
- if (l != null) {
- return l;
- } else {
- return new ArrayList<Vehicle>();
- }
- } catch (JsonSyntaxException e) {
- LOGGER.warn("JsonSyntaxException {}", e.getMessage());
- return new ArrayList<Vehicle>();
- }
- }
-
- public static Vehicle getVehicle(String vin, String json) {
- List<Vehicle> 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 {
}
}
- 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;
- }
}
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";
}
* 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;
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}
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 {
private static final Map<String, RemoteService> COMMAND_SERVICES = Stream.of(RemoteService.values())
.collect(Collectors.toUnmodifiableMap(RemoteService::getId, service -> service));
- public static Optional<RemoteService> getRemoteService(final String command) {
+ public static Optional<RemoteService> getRemoteServiceFromCommand(final String command) {
return Optional.ofNullable(COMMAND_SERVICES.get(command));
}
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;
* 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<CBS> cbsMessageList) {
+ /**
+ * the date can be empty
+ *
+ * @param requiredServices
+ * @return
+ */
+ public static State getNextServiceDate(List<RequiredService> 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
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<CBS> cbsMessageList) {
- boolean imperial = false;
+ /**
+ * the mileage can be empty
+ *
+ * @param requiredServices
+ * @return
+ */
+ public static State getNextServiceMileage(List<RequiredService> 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;
}
* @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<Length> getLengthUnit(List<FuelIndicator> indicators) {
- Unit<Length> 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();
- }
}
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
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
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
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
<channel id="mileage" typeId="mileage-channel"/>
<channel id="fuel" typeId="range-fuel-channel"/>
<channel id="remaining-fuel" typeId="remaining-fuel-channel"/>
+ <channel id="estimated-fuel-l-100km" typeId="estimated-fuel-l-100km-channel"/>
+ <channel id="estimated-fuel-mpg" typeId="estimated-fuel-mpg-channel"/>
<channel id="radius-fuel" typeId="range-radius-fuel-channel"/>
</channels>
</channel-group-type>
<channel id="check-control" typeId="check-control-channel"/>
<channel id="plug-connection" typeId="plug-connection-channel"/>
<channel id="charge" typeId="charging-status-channel"/>
- <channel id="charge-info" typeId="charging-info-channel"/>
- <channel id="motion" typeId="motion-channel"/>
+ <channel id="charge-remaining" typeId="charging-remaining-channel"/>
<channel id="last-update" typeId="last-update-channel"/>
+ <channel id="last-fetched" typeId="last-fetched-channel"/>
<channel id="raw" typeId="raw-channel"/>
</channels>
</channel-group-type>
<channel id="radius-hybrid" typeId="range-radius-hybrid-channel"/>
<channel id="soc" typeId="soc-channel"/>
<channel id="remaining-fuel" typeId="remaining-fuel-channel"/>
+ <channel id="estimated-fuel-l-100km" typeId="estimated-fuel-l-100km-channel"/>
+ <channel id="estimated-fuel-mpg" typeId="estimated-fuel-mpg-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>
<command>
<options>
<option value="VehicleStatus">Front Side View</option>
- <option value="VehicleInfo">Front View</option>
- <option value="ChargingHistory">Side View</option>
- <option value="Default">Default View</option>
+ <option value="FrontView">Front View</option>
+ <option value="FrontLeft">Left Side View</option>
+ <option value="FrontRight">Right Side View</option>
+ <option value="RearView">Rear View</option>
</options>
</command>
</channel-type>
</command>
</channel-type>
<channel-type id="profile-target-channel">
- <item-type>Number</item-type>
+ <item-type>Number:Dimensionless</item-type>
<label>SOC Target</label>
<description>SOC charging target </description>
+ <state pattern="%d %%" readOnly="true"/>
</channel-type>
<channel-type id="profile-limit-channel">
<item-type>Switch</item-type>
<channel-type id="soc-channel">
<item-type>Number:Dimensionless</item-type>
<label>Battery Charge Level</label>
- <state pattern="%d %unit%" readOnly="true"/>
+ <state pattern="%d %%" readOnly="true"/>
</channel-type>
<channel-type id="remaining-fuel-channel">
<item-type>Number:Volume</item-type>
<label>Remaining Fuel</label>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
+ <channel-type id="estimated-fuel-l-100km-channel">
+ <item-type>Number</item-type>
+ <label>Estimated Fuel Consumption l/100km</label>
+ <state pattern="%.2f l/100km" readOnly="true"/>
+ </channel-type>
+ <channel-type id="estimated-fuel-mpg-channel">
+ <item-type>Number</item-type>
+ <label>Estimated Fuel Consumption mpg</label>
+ <state pattern="%.2f mpg" readOnly="true"/>
+ </channel-type>
<channel-type id="range-radius-electric-channel">
<item-type>Number:Length</item-type>
<label>Electric Range Radius</label>
- <state pattern="%.0f %unit%" readOnly="true"/>
+ <state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="range-radius-fuel-channel">
<item-type>Number:Length</item-type>
<label>Fuel Range Radius</label>
- <state pattern="%.0f %unit%" readOnly="true"/>
+ <state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="range-radius-hybrid-channel">
<item-type>Number:Length</item-type>
<label>Hybrid Range Radius</label>
- <state pattern="%.0f %unit%" readOnly="true"/>
+ <state pattern="%d %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>
<channel-group id="image" typeId="image-values"/>
</channel-groups>
+ <properties>
+ <property name="thingTypeVersion">1</property>
+ </properties>
+
<representation-property>vin</representation-property>
<config-description-ref uri="thing-type:mybmw:vehicle"/>
<channel-group id="image" typeId="image-values"/>
</channel-groups>
+ <properties>
+ <property name="thingTypeVersion">1</property>
+ </properties>
+
<representation-property>vin</representation-property>
<config-description-ref uri="thing-type:mybmw:vehicle"/>
<channel-group id="image" typeId="image-values"/>
</channel-groups>
+ <properties>
+ <property name="thingTypeVersion">1</property>
+ </properties>
+
<representation-property>vin</representation-property>
<config-description-ref uri="thing-type:mybmw:vehicle"/>
<channel-group id="image" typeId="image-values"/>
</channel-groups>
+ <properties>
+ <property name="thingTypeVersion">1</property>
+ </properties>
+
<representation-property>vin</representation-property>
<config-description-ref uri="thing-type:mybmw:vehicle"/>
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="tire-pressures">
<label>Tire Pressure</label>
- <description>Current and wanted pressure for all tires</description>
+ <description>Current and target pressure for all tires</description>
<channels>
<channel id="fl-current" typeId="front-left-current-channel"/>
<channel id="fl-target" typeId="front-left-target-channel"/>
<label>Charging Status</label>
<state readOnly="true"/>
</channel-type>
- <channel-type id="charging-info-channel">
- <item-type>String</item-type>
- <label>Charging Information</label>
- <state readOnly="true"/>
+ <channel-type id="charging-remaining-channel">
+ <item-type>Number:Time</item-type>
+ <label>Remaining Charging Time</label>
+ <state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="plug-connection-channel">
<item-type>String</item-type>
<label>Plug Connection Status</label>
<state readOnly="true"/>
</channel-type>
- <channel-type id="motion-channel">
- <item-type>Switch</item-type>
- <label>Motion Status</label>
- <state readOnly="true"/>
- </channel-type>
<channel-type id="last-update-channel">
<item-type>DateTime</item-type>
- <label>Last Status Timestamp</label>
+ <label>Last Car Status Timestamp</label>
+ <state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
+ </channel-type>
+ <channel-type id="last-fetched-channel">
+ <item-type>DateTime</item-type>
+ <label>Last Openhab Update Timestamp</label>
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
</channel-type>
<channel-type id="raw-channel" advanced="true">
<channel id="service-date" typeId="next-service-date-channel"/>
<channel id="service-mileage" typeId="next-service-mileage-channel"/>
<channel id="check-control" typeId="check-control-channel"/>
- <channel id="motion" typeId="motion-channel"/>
<channel id="last-update" typeId="last-update-channel"/>
+ <channel id="last-fetched" typeId="last-fetched-channel"/>
<channel id="raw" typeId="raw-channel"/>
</channels>
</channel-group-type>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
+
+ <thing-type uid="mybmw:bev_rex">
+ <instruction-set targetVersion="1">
+ <!-- channels to be removed -->
+ <remove-channel id="charge-info" groupIds="status"/>
+ <remove-channel id="motion" groupIds="status"/>
+ <!-- channels to be added -->
+ <add-channel id="charge-remaining" groupIds="status">
+ <type>mybmw:charging-remaining-channel</type>
+ <label>Remaining Charging Time</label>
+ </add-channel>
+ <add-channel id="last-fetched" groupIds="status">
+ <type>mybmw:last-fetched-channel</type>
+ <label>Last Openhab Update Timestamp</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-l-100km" groupIds="range">
+ <type>mybmw:estimated-fuel-l-100km-channel</type>
+ <label>Estimated Fuel Consumption l/100km</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-mpg" groupIds="range">
+ <type>mybmw:estimated-fuel-mpg-channel</type>
+ <label>Estimated Fuel Consumption mpg</label>
+ </add-channel>
+ <!-- channels to be updated -->
+ </instruction-set>
+ </thing-type>
+ <thing-type uid="mybmw:bev">
+ <instruction-set targetVersion="1">
+ <!-- channels to be removed -->
+ <remove-channel id="charge-info" groupIds="status"/>
+ <remove-channel id="motion" groupIds="status"/>
+ <!-- channels to be added -->
+ <add-channel id="charge-remaining" groupIds="status">
+ <type>mybmw:charging-remaining-channel</type>
+ <label>Remaining Charging Time</label>
+ </add-channel>
+ <add-channel id="last-fetched" groupIds="status">
+ <type>mybmw:last-fetched-channel</type>
+ <label>Last Openhab Update Timestamp</label>
+ </add-channel>
+ <!-- channels to be updated -->
+ </instruction-set>
+ </thing-type>
+ <thing-type uid="mybmw:conv">
+ <instruction-set targetVersion="1">
+ <!-- channels to be removed -->
+ <!-- channels to be added -->
+ <add-channel id="last-fetched" groupIds="status">
+ <type>mybmw:last-fetched-channel</type>
+ <label>Last Openhab Update Timestamp</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-l-100km" groupIds="range">
+ <type>mybmw:estimated-fuel-l-100km-channel</type>
+ <label>Estimated Fuel Consumption l/100km</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-mpg" groupIds="range">
+ <type>mybmw:estimated-fuel-mpg-channel</type>
+ <label>Estimated Fuel Consumption mpg</label>
+ </add-channel>
+ <!-- channels to be updated -->
+ </instruction-set>
+ </thing-type>
+ <thing-type uid="mybmw:phev">
+ <instruction-set targetVersion="1">
+ <!-- channels to be removed -->
+ <remove-channel id="charge-info" groupIds="status"/>
+ <remove-channel id="motion" groupIds="status"/>
+ <!-- channels to be added -->
+ <add-channel id="charge-remaining" groupIds="status">
+ <type>mybmw:charging-remaining-channel</type>
+ <label>Remaining Charging Time</label>
+ </add-channel>
+ <add-channel id="last-fetched" groupIds="status">
+ <type>mybmw:last-fetched-channel</type>
+ <label>Last Openhab Update Timestamp</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-l-100km" groupIds="range">
+ <type>mybmw:estimated-fuel-l-100km-channel</type>
+ <label>Estimated Fuel Consumption l/100km</label>
+ </add-channel>
+ <add-channel id="estimated-fuel-mpg" groupIds="range">
+ <type>mybmw:estimated-fuel-mpg-channel</type>
+ <label>Estimated Fuel Consumption mpg</label>
+ </add-channel>
+ <!-- channels to be updated -->
+ </instruction-set>
+ </thing-type>
+
+</update:update-descriptions>
+++ /dev/null
-/**
- * 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<Vehicle> vl = Converter.getVehicleList(content);
- assertEquals(1, vl.size(), "Vehicles found");
- ArgumentCaptor<DiscoveryResult> discoveries = ArgumentCaptor.forClass(DiscoveryResult.class);
- ArgumentCaptor<DiscoveryService> services = ArgumentCaptor.forClass(DiscoveryService.class);
- bh.onResponse(content);
- verify(listener, times(1)).thingDiscovered(services.capture(), discoveries.capture());
- List<DiscoveryResult> 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");
- }
-}
--- /dev/null
+/**
+ * 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<Vehicle> vehicleList = Arrays.asList(new Gson().fromJson(content, Vehicle[].class));
+
+ VehicleDiscovery vehicleDiscovery = new VehicleDiscovery();
+
+ MyBMWBridgeHandler bridgeHandler = mock(MyBMWBridgeHandler.class);
+
+ List<Thing> 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<DiscoveryResult> discoveries = ArgumentCaptor.forClass(DiscoveryResult.class);
+ ArgumentCaptor<DiscoveryService> services = ArgumentCaptor.forClass(DiscoveryService.class);
+
+ // call the discovery
+ vehicleDiscovery.startScan();
+
+ Mockito.verify(listener, Mockito.times(2)).thingDiscovered(services.capture(), discoveries.capture());
+ List<DiscoveryResult> 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<DiscoveryResult> discoveries2 = ArgumentCaptor.forClass(DiscoveryResult.class);
+ ArgumentCaptor<DiscoveryService> 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;
+ }
+}
+++ /dev/null
-/**
- * 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");
- }
-}
+++ /dev/null
-/**
- * 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.*;
-import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
-
-import java.util.List;
-
-import javax.measure.quantity.Energy;
-
-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.core.library.types.DecimalType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.ChannelUID;
-import org.openhab.core.types.State;
-
-/**
- * The {@link ChargeStatisticWrapper} tests stored fingerprint responses from BMW API
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-@SuppressWarnings("null")
-public class ChargeStatisticWrapper {
- private ChargeStatisticsContainer chargeStatisticContainer;
-
- public ChargeStatisticWrapper(String content) {
- ChargeStatisticsContainer fromJson = Converter.getGson().fromJson(content, ChargeStatisticsContainer.class);
- if (fromJson != null) {
- chargeStatisticContainer = fromJson;
- } else {
- chargeStatisticContainer = new ChargeStatisticsContainer();
- }
- }
-
- /**
- * Test results auctomatically against json values
- *
- * @param channels
- * @param states
- * @return
- */
- public boolean checkResults(@Nullable List<ChannelUID> channels, @Nullable List<State> states) {
- assertNotNull(channels);
- assertNotNull(states);
- assertTrue(channels.size() == states.size(), "Same list sizes");
- for (int i = 0; i < channels.size(); i++) {
- checkResult(channels.get(i), states.get(i));
- }
- return true;
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- private void checkResult(ChannelUID channelUID, State state) {
- String cUid = channelUID.getIdWithoutGroup();
- String gUid = channelUID.getGroupId();
- StringType st;
- DecimalType dt;
- QuantityType<Energy> qte;
- switch (cUid) {
- case TITLE:
- assertTrue(state instanceof StringType);
- st = (StringType) state;
- switch (gUid) {
- case CHANNEL_GROUP_CHARGE_STATISTICS:
- assertEquals(chargeStatisticContainer.description, st.toString(), "Statistics name");
- break;
- default:
- assertFalse(true, "Channel " + channelUID + " " + state + " not found");
- break;
- }
- break;
- case SESSIONS:
- assertTrue(state instanceof DecimalType);
- dt = ((DecimalType) state);
- assertEquals(chargeStatisticContainer.statistics.numberOfChargingSessions, 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");
- break;
- default:
- // fail in case of unknown update
- assertFalse(true, "Channel " + channelUID + " " + state + " not found");
- break;
- }
- }
-}
--- /dev/null
+/**
+ * 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 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;
+
+import javax.measure.quantity.Energy;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * 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 ChargingStatisticsWrapper {
+ private ChargingStatisticsContainer chargeStatisticContainer;
+
+ public ChargingStatisticsWrapper(String content) {
+ chargeStatisticContainer = JsonStringDeserializer.getChargingStatistics(content);
+ }
+
+ /**
+ * Test results auctomatically against json values
+ *
+ * @param channels
+ * @param states
+ * @return
+ */
+ public boolean checkResults(@Nullable List<ChannelUID> channels, @Nullable List<State> states) {
+ assertNotNull(channels);
+ assertNotNull(states);
+ assertTrue(channels.size() == states.size(), "Same list sizes");
+ for (int i = 0; i < channels.size(); i++) {
+ checkResult(channels.get(i), states.get(i));
+ }
+ return true;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private void checkResult(ChannelUID channelUID, State state) {
+ String cUid = channelUID.getIdWithoutGroup();
+ String gUid = channelUID.getGroupId();
+ StringType st;
+ DecimalType dt;
+ QuantityType<Energy> qte;
+ switch (cUid) {
+ case TITLE:
+ assertTrue(state instanceof StringType);
+ st = (StringType) state;
+ switch (gUid) {
+ case CHANNEL_GROUP_CHARGE_STATISTICS:
+ assertEquals(chargeStatisticContainer.getDescription(), st.toString(), "Statistics name");
+ break;
+ default:
+ assertFalse(true, "Channel " + channelUID + " " + state + " not found");
+ break;
+ }
+ break;
+ case SESSIONS:
+ assertTrue(state instanceof DecimalType);
+ dt = ((DecimalType) state);
+ 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.getStatistics().getTotalEnergyCharged(), qte.intValue(),
+ "Energy");
+ break;
+ default:
+ // fail in case of unknown update
+ assertFalse(true, "Channel " + channelUID + " " + state + " not found");
+ break;
+ }
+ }
+}
*/
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;
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;
* 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<Length> KILOMETRE = Constants.KILOMETRE_UNIT;
- private Vehicle vehicle;
+ private VehicleState vehicleState;
private boolean isElectric;
private boolean hasFuel;
private boolean isHybrid;
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
isHybrid = hasFuel && isElectric;
- List<Vehicle> 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
StringType wanted;
DateTimeType dtt;
PointType pt;
- OnOffType oot;
- Unit<Length> wantedUnit;
+ DecimalType dt;
+ Unit<Length> wantedUnit = KILOMETRE;
switch (cUid) {
case MILEAGE:
switch (gUid) {
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");
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<lctric");
+ assertTrue(isElectric, "Is Electric");
assertTrue(state instanceof QuantityType);
qt = ((QuantityType) state);
assertEquals(Units.PERCENT, qt.getUnit(), "Percent");
- assertEquals(vehicle.properties.chargingState.chargePercentage, qt.intValue(), "Charge Level");
+ assertEquals(vehicleState.getElectricChargingState().getChargingLevelPercent(), qt.intValue(),
+ "Charge Level");
break;
case LOCK:
assertTrue(state instanceof StringType);
st = (StringType) state;
- assertEquals(Converter.getLockState(vehicle.properties.areDoorsLocked), st, "Vehicle locked");
+ wanted = StringType
+ .valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getCombinedSecurityState()));
+ assertEquals(wanted.toString(), st.toString(), "Vehicle locked");
break;
case DOORS:
assertTrue(state instanceof StringType);
st = (StringType) state;
- assertEquals(Converter.getClosedState(vehicle.properties.areDoorsClosed), st, "Doors Closed");
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getCombinedState()));
+ assertEquals(wanted.toString(), st.toString(), "Doors closed");
break;
case WINDOWS:
assertTrue(state instanceof StringType);
if (specialHandlingMap.containsKey(WINDOWS)) {
assertEquals(specialHandlingMap.get(WINDOWS).toString(), st.toString(), "Windows");
} else {
- assertEquals(Converter.getClosedState(vehicle.properties.areWindowsClosed), st, "Windows");
+ wanted = StringType
+ .valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getCombinedState()));
+ assertEquals(wanted.toString(), st.toString(), "Windows");
}
break;
if (specialHandlingMap.containsKey(CHECK_CONTROL)) {
assertEquals(specialHandlingMap.get(CHECK_CONTROL).toString(), st.toString(), "Check Control");
} else {
- assertEquals(vehicle.status.checkControlMessagesGeneralState, st.toString(), "Check Control");
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getOverallCheckControlStatus()));
+ assertEquals(wanted.toString(), st.toString(), "Check Control");
}
break;
- case CHARGE_INFO:
+ case CHARGE_REMAINING:
+ // charge-remaining can be either a number in minutes, or UNDEF
assertTrue(isElectric, "Is Electric");
- assertTrue(state instanceof StringType);
- st = (StringType) state;
- assertEquals(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(vehicle)), st.toString(),
- "Charge Info");
+ if (state instanceof QuantityType) {
+ assertTrue(state instanceof QuantityType);
+ qt = ((QuantityType) state);
+ assertEquals(Units.MINUTE, qt.getUnit(), "Minute Unit");
+ assertEquals(vehicleState.getElectricChargingState().getRemainingChargingMinutes(), qt.intValue(),
+ "Charge Time Remaining");
+ } else {
+ assertTrue(state instanceof UnDefType);
+ }
break;
case CHARGE_STATUS:
assertTrue(isElectric, "Is Electric");
assertTrue(state instanceof StringType);
st = (StringType) state;
- assertEquals(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(vehicle)), st.toString(),
- "Charge Status");
+ assertEquals(Converter.toTitleCase(vehicleState.getElectricChargingState().getChargingStatus()),
+ st.toString(), "Charge Status");
break;
case PLUG_CONNECTION:
assertTrue(state instanceof StringType);
st = (StringType) state;
- assertEquals(Converter.getConnectionState(vehicle.properties.chargingState.isChargerConnected), st,
- "Plug Connection State");
+ assertEquals(Converter.getConnectionState(vehicleState.getElectricChargingState().isChargerConnected()),
+ st, "Plug Connection State");
break;
case LAST_UPDATE:
assertTrue(state instanceof DateTimeType);
dtt = (DateTimeType) state;
- DateTimeType expected = DateTimeType
- .valueOf(Converter.zonedToLocalDateTime(vehicle.properties.lastUpdatedAt));
- assertEquals(expected.toString(), dtt.toString(), "Last Update");
+ State expectedUpdateDate = Converter.zonedToLocalDateTime(vehicleState.getLastUpdatedAt(),
+ ZoneId.systemDefault());
+ assertEquals(expectedUpdateDate.toString(), dtt.toString(), "Last Update");
+ break;
+ case LAST_FETCHED:
+ assertTrue(state instanceof DateTimeType);
+ dtt = (DateTimeType) state;
+ State expectedFetchedDate = Converter.zonedToLocalDateTime(vehicleState.getLastFetched(),
+ ZoneId.systemDefault());
+ assertEquals(expectedFetchedDate.toString(), dtt.toString(), "Last Fetched");
break;
case GPS:
- if (state instanceof PointType point) {
- pt = point;
- assertNotNull(vehicle.properties.vehicleLocation);
+ if (state instanceof PointType) {
+ pt = (PointType) state;
+ assertNotNull(vehicleState.getLocation());
assertEquals(
- PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude)
- + "," + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
+ PointType.valueOf(Double.toString(vehicleState.getLocation().getCoordinates().getLatitude())
+ + ","
+ + Double.toString(vehicleState.getLocation().getCoordinates().getLongitude())),
pt, "Coordinates");
} // else no check needed
break;
if (state instanceof QuantityType quantityCommand) {
qt = quantityCommand;
assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
- assertNotNull(vehicle.properties.vehicleLocation);
- assertEquals(vehicle.properties.vehicleLocation.heading, qt.intValue(), 0.01, "Heading");
+ assertNotNull(vehicleState.getLocation());
+ assertEquals(vehicleState.getLocation().getHeading(), qt.intValue(), 0.01, "Heading");
} // else no check needed
break;
case RANGE_RADIUS_ELECTRIC:
assertTrue(state instanceof QuantityType);
assertTrue(isElectric);
qt = ((QuantityType) state);
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
- assertEquals(wantedUnit, qt.getUnit());
- assertEquals(
- Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle)),
+ assertEquals(Converter.guessRangeRadius(vehicleState.getElectricChargingState().getRange()),
qt.intValue(), "Range Radius Electric");
break;
case RANGE_RADIUS_FUEL:
assertTrue(state instanceof QuantityType);
assertTrue(hasFuel);
qt = (QuantityType) state;
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
- assertEquals(wantedUnit, qt.getUnit());
- assertEquals(
- Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle)),
- qt.intValue(), "Range Radius Fuel");
+ if (!isHybrid) {
+ assertEquals(Converter.guessRangeRadius(vehicleState.getCombustionFuelLevel().getRange()),
+ qt.intValue(), "Range Radius Fuel");
+ } else {
+ assertEquals(
+ Converter.guessRangeRadius(vehicleState.getCombustionFuelLevel().getRange()
+ - vehicleState.getElectricChargingState().getRange()),
+ qt.intValue(), "Range Radius Fuel");
+ }
break;
case RANGE_RADIUS_HYBRID:
assertTrue(state instanceof QuantityType);
assertTrue(isHybrid);
qt = (QuantityType) state;
- wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
- assertEquals(wantedUnit, qt.getUnit());
- assertEquals(Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.PHEV, vehicle)),
- qt.intValue(), "Range Radius Combined");
+ assertEquals(Converter.guessRangeRadius(vehicleState.getRange()), qt.intValue(), "Range Radius Hybrid");
break;
case DOOR_DRIVER_FRONT:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.driverFront));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getLeftFront()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case DOOR_DRIVER_REAR:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.driverRear));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getLeftRear()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case DOOR_PASSENGER_FRONT:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.passengerFront));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getRightFront()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case DOOR_PASSENGER_REAR:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.passengerRear));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getRightRear()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case TRUNK:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.trunk));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getTrunk()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case HOOD:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.hood));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getHood()));
assertEquals(wanted.toString(), st.toString(), "Door");
break;
case WINDOW_DOOR_DRIVER_FRONT:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.driverFront));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getLeftFront()));
assertEquals(wanted.toString(), st.toString(), "Window");
break;
case WINDOW_DOOR_DRIVER_REAR:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.driverRear));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getLeftRear()));
assertEquals(wanted.toString(), st.toString(), "Window");
break;
case WINDOW_DOOR_PASSENGER_FRONT:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.passengerFront));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getRightFront()));
assertEquals(wanted.toString(), st.toString(), "Window");
break;
case WINDOW_DOOR_PASSENGER_REAR:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType
- .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.passengerRear));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getRightRear()));
assertEquals(wanted.toString(), st.toString(), "Window");
break;
case SUNROOF:
assertTrue(state instanceof StringType);
st = (StringType) state;
- wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.moonroof));
+ wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getRoofState().getRoofState()));
assertEquals(wanted.toString(), st.toString(), "Window");
break;
case SERVICE_DATE:
"Next Service");
} else {
String dueDateString = VehicleStatusUtils
- .getNextServiceDate(vehicle.properties.serviceRequired).toString();
+ .getNextServiceDate(vehicleState.getRequiredServices()).toString();
DateTimeType expectedDTT = DateTimeType.valueOf(dueDateString);
assertEquals(expectedDTT.toString(), dtt.toString(), "Next Service");
}
} else if (gUid.equals(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(), "First Service Date");
}
}
qt = ((QuantityType) state);
if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
QuantityType<Length> 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");
}
}
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;
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;
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:
}
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;
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
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;
* 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<Vehicle> 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");
@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<CommandOption> l = RemoteServiceUtils.getOptions(true);
assertEquals(commandReference, l.toString(), "Commad Options");
}
+++ /dev/null
-/**
- * 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<Vehicle> 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<Vehicle> 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");
- }
-}
--- /dev/null
+/**
+ * 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 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;
+
+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.binding.mybmw.internal.MyBMWConstants.VehicleType;
+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;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.openhab.core.types.State;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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 ChargingStatisticsTest {
+ private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
+
+ private static final int EXPECTED_UPDATE_COUNT = 3;
+
+ @Nullable
+ ArgumentCaptor<ChannelUID> channelCaptor;
+ @Nullable
+ ArgumentCaptor<State> stateCaptor;
+ @Nullable
+ ThingHandlerCallback thingHandlerCallback;
+ @Nullable
+ VehicleHandler vehicleHandler;
+ @Nullable
+ List<ChannelUID> allChannels;
+ @Nullable
+ List<State> allStates;
+ String driveTrain = Constants.EMPTY;
+ 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 myBmwCommandOptionProvider = mock(MyBMWCommandOptionProvider.class);
+ LocationProvider locationProvider = mock(LocationProvider.class);
+ 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);
+ }
+
+ private boolean testVehicle(String statusContent, int callbacksExpected,
+ Optional<Map<String, State>> concreteChecks) {
+ assertNotNull(statusContent);
+
+ 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);
+ ChargingStatisticsWrapper checker = new ChargingStatisticsWrapper(statusContent);
+ trace();
+ return checker.checkResults(allChannels, allStates);
+ }
+
+ private void trace() {
+ for (int i = 0; i < allChannels.size(); i++) {
+ logger.info("Channel {} {}", allChannels.get(i), allStates.get(i));
+ }
+ }
+
+ @Test
+ public void testBevIx() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ setup(VehicleType.ELECTRIC_REX.toString(), false);
+ String content = FileReader.fileToString("responses/BEV/charging_statistics.json");
+ assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
+ }
+
+ @Test
+ public void testBevIX3() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testIceX320d() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ setup(VehicleType.PLUGIN_HYBRID.toString(), false);
+ String content = FileReader.fileToString("responses/ICE2/charging_statistics.json");
+ assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
+ }
+
+ @Test
+ public void testPhev330e() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ setup(VehicleType.PLUGIN_HYBRID.toString(), false);
+ String content = FileReader.fileToString("responses/PHEV2/charging_statistics.json");
+ assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
+ }
+}
--- /dev/null
+/**
+ * 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<VehicleBase> 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");
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
+++ /dev/null
-/**
- * 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 static org.mockito.Mockito.*;
-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 javax.crypto.Cipher;
-
-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.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.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.jupiter.api.Test;
-import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
-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.util.FileReader;
-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.core.io.net.http.HttpClientFactory;
-import org.openhab.core.util.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link AuthTest} test authorization flow
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-class AuthTest {
- private final Logger logger = LoggerFactory.getLogger(AuthTest.class);
-
- void testAuth() {
- String user = "usr";
- String pwd = "pwd";
-
- SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
- HttpClient authHttpClient = new HttpClient(sslContextFactory);
- try {
- authHttpClient.start();
- Request firstRequest = authHttpClient
- .newRequest("https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
- + "/eadrax-ucs/v1/presentation/oauth/config");
- firstRequest.header("ocp-apim-subscription-key",
- BimmerConstants.OCP_APIM_KEYS.get(BimmerConstants.REGION_ROW));
- firstRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)");
-
- ContentResponse firstResponse = firstRequest.send();
- logger.info(firstResponse.getContentAsString());
- AuthQueryResponse aqr = Converter.getGson().fromJson(firstResponse.getContentAsString(),
- AuthQueryResponse.class);
-
- String verifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
- String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verifierBytes.getBytes());
-
- MessageDigest digest = MessageDigest.getInstance("SHA-256");
- byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
- String codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
-
- String stateBytes = StringUtils.getRandomAlphabetic(16).toLowerCase();
- String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
-
- String authUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
- logger.info(authUrl);
- Request loginRequest = authHttpClient.POST(authUrl);
- loginRequest.header("Content-Type", "application/x-www-form-urlencoded");
-
- MultiMap<String> baseParams = new MultiMap<String>();
- baseParams.put("client_id", aqr.clientId);
- baseParams.put("response_type", "code");
- baseParams.put("redirect_uri", aqr.returnUrl);
- baseParams.put("state", state);
- baseParams.put("nonce", "login_nonce");
- baseParams.put("scope", String.join(" ", aqr.scopes));
- baseParams.put("code_challenge", codeChallenge);
- baseParams.put("code_challenge_method", "S256");
-
- MultiMap<String> loginParams = new MultiMap<String>(baseParams);
- loginParams.put("grant_type", "authorization_code");
- loginParams.put("username", user);
- loginParams.put("password", pwd);
- loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
- ContentResponse secondResonse = loginRequest.send();
- logger.info(secondResonse.getContentAsString());
- String authCode = getAuthCode(secondResonse.getContentAsString());
- logger.info(authCode);
-
- MultiMap<String> authParams = new MultiMap<String>(baseParams);
- authParams.put("authorization", authCode);
- Request authRequest = authHttpClient.POST(authUrl).followRedirects(false);
- authRequest.header("Content-Type", "application/x-www-form-urlencoded");
- authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
- ContentResponse authResponse = authRequest.send();
- logger.info("{}", authResponse.getHeaders());
- logger.info("Response " + authResponse.getHeaders().get(HttpHeader.LOCATION));
- String code = AuthTest.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
- logger.info("Code " + code);
- 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());
- logger.info(basicAuth);
- codeRequest.header("Content-Type", "application/x-www-form-urlencoded");
- codeRequest.header(AUTHORIZATION, basicAuth);
-
- MultiMap<String> codeParams = new MultiMap<String>();
- codeParams.put("code", code);
- codeParams.put("code_verifier", codeVerifier);
- codeParams.put("redirect_uri", aqr.returnUrl);
- codeParams.put("grant_type", "authorization_code");
- codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
- 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);
- Token t = new Token();
- t.setType(ar.tokenType);
- t.setToken(ar.accessToken);
- t.setExpiration(ar.expiresIn);
- logger.info(t.getBearerToken());
-
- /**
- * REQUEST CONTENT
- */
- HttpClient apiHttpClient = new HttpClient(sslContextFactory);
- apiHttpClient.start();
-
- MultiMap<String> vehicleParams = new MultiMap<String>();
- 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");
- 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());
-
- /**
- * CHARGE STATISTICS
- */
- MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
- chargeStatisticsParams.put("vin", "WBY1Z81040V905639");
- chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
- params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
-
- String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
- + "/eadrax-chs/v1/charging-statistics";
- 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<String> chargeStatisticsParams = new MultiMap<String>();
- // 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 chargeStatisticsResponse = chargeStatisticsRequest.send();
- logger.info("{}", chargeStatisticsResponse.getStatus());
- logger.info("{}", chargeStatisticsResponse.getReason());
- logger.info("{}", chargeStatisticsResponse.getContentAsString());
-
- /**
- * CHARGE SESSIONS
- */
- MultiMap<String> chargeSessionsParams = new MultiMap<String>();
- chargeSessionsParams.put("vin", "WBY1Z81040V905639");
- chargeSessionsParams.put("maxResults", "40");
- chargeSessionsParams.put("include_date_picker", "true");
-
- params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
-
- String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
- + "/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<String> chargeStatisticsParams = new MultiMap<String>();
- // 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());
- logger.info("{}", chargeSessionsResponse.getReason());
- logger.info("{}", chargeSessionsResponse.getContentAsString());
-
- String chargingControlUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
- + "/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());
-
- } catch (Exception e) {
- logger.error("{}", e.getMessage());
- }
- }
-
- private String getAuthCode(String response) {
- String[] keys = response.split("&");
- for (int i = 0; i < keys.length; i++) {
- if (keys[i].startsWith(AUTHORIZATION)) {
- String authCode = keys[i].split("=")[1];
- authCode = authCode.split("\"")[0];
- return authCode;
- }
- }
- return Constants.EMPTY;
- }
-
- public static String codeFromUrl(String encodedUrl) {
- final MultiMap<String> tokenMap = new MultiMap<String>();
- UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
- final StringBuilder codeFound = new StringBuilder();
- tokenMap.forEach((key, value) -> {
- if (!value.isEmpty()) {
- String val = value.get(0);
- if (key.endsWith(CODE)) {
- codeFound.append(val);
- }
- }
- });
- return codeFound.toString();
- }
-
- @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);
-
- // 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);
- Token t = new Token();
- t.setToken(token);
- t.setType(cat.data.tokenType);
- t.setExpirationTotal(cte.exp);
- assertEquals(
- "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJEVU1NWSQxJEEkMTYzNzcwNzkxNjc4MiIsIm5iZiI6MTYzNzcwNzkxNiwiZXhwIjoxNjM3NzExMjE2LCJpYXQiOjE2Mzc3MDc5MTZ9.hpi-P97W68g7avGwu9dcBRapIsaG4F8MwOdPHe6PuTA",
- t.getBearerToken(), "Token");
- }
-
- public void testChina() {
- SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
- HttpClient authHttpClient = new HttpClient(sslContextFactory);
- try {
- authHttpClient.start();
- HttpClientFactory mockHCF = mock(HttpClientFactory.class);
- when(mockHCF.getCommonHttpClient()).thenReturn(authHttpClient);
- MyBMWConfiguration config = new MyBMWConfiguration();
- config.region = BimmerConstants.REGION_CHINA;
- config.userName = "Hello User";
- config.password = "Hello Password";
- MyBMWProxy bmwProxy = new MyBMWProxy(mockHCF, config);
- bmwProxy.updateTokenChina();
- } catch (Exception e) {
- logger.warn("Exception: " + e.getMessage());
- }
- }
-
- @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 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;
- try {
- 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("Hello World".getBytes());
- String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
- logger.info(encodedPassword);
- } catch (Exception e) {
- assertTrue(false, "Excpetion: " + e.getMessage());
- }
- }
-
- public void testChinaToken() {
- SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
- HttpClient authHttpClient = new HttpClient(sslContextFactory);
- try {
- authHttpClient.start();
- 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,
- String.format(BimmerConstants.BRAND_BMW, BimmerConstants.BRAND_BMW, BimmerConstants.REGION_ROW));
-
- ContentResponse publicKeyResponse = oauthQueryRequest.send();
- ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(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);
- 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("Hello World".getBytes());
- String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
- logger.info(encodedPassword);
- } catch (Exception e) {
- assertTrue(false, "Excpetion: " + e.getMessage());
- }
- }
-}
+++ /dev/null
-/**
- * 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 static org.mockito.Mockito.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-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.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.util.FileReader;
-import org.openhab.binding.mybmw.internal.utils.Constants;
-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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link ChargeStatisticsTest} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-@SuppressWarnings("null")
-public class ChargeStatisticsTest {
- private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
-
- private static final int EXPECTED_UPDATE_COUNT = 3;
-
- @Nullable
- ArgumentCaptor<ChannelUID> channelCaptor;
- @Nullable
- ArgumentCaptor<State> stateCaptor;
- @Nullable
- ThingHandlerCallback tc;
- @Nullable
- VehicleHandler cch;
- @Nullable
- List<ChannelUID> allChannels;
- @Nullable
- List<State> allStates;
- String driveTrain = Constants.EMPTY;
- 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);
- VehicleConfiguration vc = new VehicleConfiguration();
- vc.vin = Constants.ANONYMOUS;
- Optional<VehicleConfiguration> ovc = Optional.of(vc);
- cch.configuration = ovc;
- tc = mock(ThingHandlerCallback.class);
- cch.setCallback(tc);
- channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
- stateCaptor = ArgumentCaptor.forClass(State.class);
- }
-
- private boolean testVehicle(String statusContent, int callbacksExpected,
- Optional<Map<String, State>> concreteChecks) {
- assertNotNull(statusContent);
- cch.chargeStatisticsCallback.onResponse(statusContent);
- verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture());
- allChannels = channelCaptor.getAllValues();
- allStates = stateCaptor.getAllValues();
-
- assertNotNull(driveTrain);
- ChargeStatisticWrapper checker = new ChargeStatisticWrapper(statusContent);
- trace();
- return checker.checkResults(allChannels, allStates);
- }
-
- private void trace() {
- for (int i = 0; i < allChannels.size(); i++) {
- logger.info("Channel {} {}", allChannels.get(i), allStates.get(i));
- }
- }
-
- @Test
- public void testI01REX() {
- 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");
- assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
- }
-
- @Test
- public void testG21() {
- 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");
- assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
- }
-
- @Test
- public void testG30() {
- 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");
- assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
- }
-
- @Test
- public void testI01NOREX() {
- 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");
- assertTrue(testVehicle(content, EXPECTED_UPDATE_COUNT, Optional.empty()));
- }
-}
+++ /dev/null
-/**
- * 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));
- }
-}
+++ /dev/null
-/**
- * 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<ChannelUID> channelCaptor;
- @Nullable
- ArgumentCaptor<State> stateCaptor;
- @Nullable
- ThingHandlerCallback tc;
- @Nullable
- VehicleHandler cch;
- @Nullable
- List<ChannelUID> allChannels;
- @Nullable
- List<State> 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);
- }
-}
+++ /dev/null
-/**
- * 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");
- }
-}
--- /dev/null
+/**
+ * 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.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;
+
+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.binding.mybmw.internal.MyBMWConstants.VehicleType;
+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 VehicleHandlerTest} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Bernd Weymann - Initial contribution
+ */
+@NonNullByDefault
+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 = 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 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");
+
+ // I couldn't resolve all NonNull compile errors, hence I'm initializing the values here...
+ ArgumentCaptor<ChannelUID> channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
+ ArgumentCaptor<State> stateCaptor = ArgumentCaptor.forClass(State.class);
+ ThingHandlerCallback thingHandlerCallback = mock(ThingHandlerCallback.class);
+ VehicleHandler vehicleHandler = mock(VehicleHandler.class);
+ List<ChannelUID> allChannels = new ArrayList<>();
+ List<State> allStates = new ArrayList<>();
+
+ String driveTrain = Constants.EMPTY;
+
+ /**
+ * Prepare environment for Vehicle Status Updates
+ */
+ 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);
+ 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<Map<String, State>> concreteChecks) {
+ assertNotNull(statusContent);
+
+ 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();
+
+ assertNotNull(driveTrain);
+ StatusWrapper checker = new StatusWrapper(driveTrain, statusContent);
+ trace();
+ if (concreteChecks.isPresent()) {
+ return checker.append(concreteChecks.get()).checkResults(allChannels, allStates);
+ } else {
+ return checker.checkResults(allChannels, allStates);
+ }
+ }
+
+ private void trace() {
+ for (int i = 0; i < allChannels.size(); i++) {
+ // change to info for debugging channel updates
+ logger.debug("Channel {} {}", allChannels.get(i), allStates.get(i));
+ }
+ }
+
+ @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".
+ * Checks are done in an automated way cross checking the data from JSON and data delivered via Channel.
+ * Also important the updates are counted in order to check if code changes are affecting Channel Updates.
+ *
+ * With the given output the updated Channels are visible.
+ * Example:
+ *
+ * testi3Rex
+ * Channel testbinding::test:status#lock Locked
+ * Channel testbinding::test:status#service-date 2023-11-01T00:00:00.000+0100
+ * Channel testbinding::test:status#check-control No Issues
+ * Channel testbinding::test:status#last-update 2021-12-21T16:46:02.000+0100
+ * Channel testbinding::test:status#doors Closed
+ * Channel testbinding::test:status#windows Closed
+ * Channel testbinding::test:status#plug-connection Not connected
+ * Channel testbinding::test:status#charge Not Charging
+ * Channel testbinding::test:status#charge-type Not Available
+ * Channel testbinding::test:range#electric 76 km
+ * Channel testbinding::test:range#radius-electric 60.800000000000004 km
+ * Channel testbinding::test:range#fuel 31 km
+ * Channel testbinding::test:range#radius-fuel 24.8 km
+ * Channel testbinding::test:range#hybrid 31 km
+ * Channel testbinding::test:range#radius-hybrid 24.8 km
+ * Channel testbinding::test:range#mileage 31537 km
+ * Channel testbinding::test:range#soc 74 %
+ * Channel testbinding::test:range#remaining-fuel 4 l
+ * Channel testbinding::test:doors#driver-front Closed
+ * Channel testbinding::test:doors#driver-rear Closed
+ * Channel testbinding::test:doors#passenger-front Closed
+ * Channel testbinding::test:doors#passenger-rear Closed
+ * Channel testbinding::test:doors#trunk Closed
+ * Channel testbinding::test:doors#hood Closed
+ * Channel testbinding::test:doors#win-driver-front Closed
+ * Channel testbinding::test:doors#win-driver-rear Undef
+ * Channel testbinding::test:doors#win-passenger-front Closed
+ * Channel testbinding::test:doors#win-passenger-rear Undef
+ * Channel testbinding::test:doors#sunroof Closed
+ * Channel testbinding::test:location#gps 1.2345,6.789
+ * Channel testbinding::test:location#heading 222 °
+ * Channel testbinding::test:service#name Brake Fluid
+ * Channel testbinding::test:service#date 2023-11-01T00:00:00.000+0100
+ * Channel testbinding::test:profile#prefs Chargingwindow
+ * Channel testbinding::test:profile#mode Immediatecharging
+ * Channel testbinding::test:profile#control Weeklyplanner
+ * Channel testbinding::test:profile#target 100
+ * Channel testbinding::test:profile#limit OFF
+ * Channel testbinding::test:profile#climate OFF
+ * Channel testbinding::test:profile#window-start 1970-01-01T11:00:00.000+0100
+ * Channel testbinding::test:profile#window-end 1970-01-01T14:30:00.000+0100
+ * Channel testbinding::test:profile#timer1-departure 1970-01-01T16:00:00.000+0100
+ * Channel testbinding::test:profile#timer1-enabled OFF
+ * Channel testbinding::test:profile#timer1-day-mon ON
+ * Channel testbinding::test:profile#timer1-day-tue ON
+ * Channel testbinding::test:profile#timer1-day-wed ON
+ * Channel testbinding::test:profile#timer1-day-thu ON
+ * Channel testbinding::test:profile#timer1-day-fri ON
+ * Channel testbinding::test:profile#timer1-day-sat ON
+ * Channel testbinding::test:profile#timer1-day-sun ON
+ * Channel testbinding::test:profile#timer2-departure 1970-01-01T12:02:00.000+0100
+ * Channel testbinding::test:profile#timer2-enabled ON
+ * Channel testbinding::test:profile#timer2-day-mon OFF
+ * Channel testbinding::test:profile#timer2-day-tue OFF
+ * Channel testbinding::test:profile#timer2-day-wed OFF
+ * Channel testbinding::test:profile#timer2-day-thu OFF
+ * Channel testbinding::test:profile#timer2-day-fri OFF
+ * Channel testbinding::test:profile#timer2-day-sat OFF
+ * Channel testbinding::test:profile#timer2-day-sun ON
+ * Channel testbinding::test:profile#timer3-departure 1970-01-01T13:03:00.000+0100
+ * Channel testbinding::test:profile#timer3-enabled OFF
+ * Channel testbinding::test:profile#timer3-day-mon OFF
+ * Channel testbinding::test:profile#timer3-day-tue OFF
+ * Channel testbinding::test:profile#timer3-day-wed OFF
+ * Channel testbinding::test:profile#timer3-day-thu OFF
+ * Channel testbinding::test:profile#timer3-day-fri OFF
+ * Channel testbinding::test:profile#timer3-day-sat ON
+ * Channel testbinding::test:profile#timer3-day-sun OFF
+ * Channel testbinding::test:profile#timer4-departure 1970-01-01T12:02:00.000+0100
+ * Channel testbinding::test:profile#timer4-enabled OFF
+ * Channel testbinding::test:profile#timer4-day-mon OFF
+ * Channel testbinding::test:profile#timer4-day-tue OFF
+ * Channel testbinding::test:profile#timer4-day-wed OFF
+ * Channel testbinding::test:profile#timer4-day-thu OFF
+ * Channel testbinding::test:profile#timer4-day-fri OFF
+ * Channel testbinding::test:profile#timer4-day-sat OFF
+ * Channel testbinding::test:profile#timer4-day-sun ON
+ */
+
+ @Test
+ public void testBevIx() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testBevI3() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testBevIX3() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testBevI4() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testBevI7() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testIceMiniCooper() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ setup(VehicleType.CONVENTIONAL.toString(), "anonymous");
+ String content = FileReader.fileToString("responses/ICE/vehicles_state.json");
+ assertTrue(testVehicle(content,
+ STATUS_CONV + DOORS + RANGE_CONV + LOCATION + SERVICE_EMPTY + CHECK_EMPTY + TIRES, Optional.empty()));
+ }
+
+ @Test
+ public void testIceX320d() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testIce530d() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testIce435i() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testMildHybrid340i() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testPhev530e() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ 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 testPhev330e() {
+ logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
+ setup(VehicleType.PLUGIN_HYBRID.toString(), "anonymous");
+ 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()));
+ }
+}
+++ /dev/null
-/**
- * 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 static org.mockito.Mockito.*;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-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.binding.mybmw.internal.MyBMWConstants.VehicleType;
-import org.openhab.binding.mybmw.internal.VehicleConfiguration;
-import org.openhab.binding.mybmw.internal.dto.StatusWrapper;
-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.library.types.PointType;
-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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The {@link VehicleTests} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-@SuppressWarnings("null")
-public class VehicleTests {
- private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
-
- 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_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 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<ChannelUID> channelCaptor;
- @Nullable
- ArgumentCaptor<State> stateCaptor;
- @Nullable
- ThingHandlerCallback tc;
- @Nullable
- VehicleHandler cch;
- @Nullable
- List<ChannelUID> allChannels;
- @Nullable
- List<State> allStates;
- String driveTrain = Constants.EMPTY;
-
- /**
- * Prepare environment for Vehicle Status Updates
- */
- public 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<VehicleConfiguration> ovc = Optional.of(vc);
- cch.configuration = ovc;
- tc = mock(ThingHandlerCallback.class);
- cch.setCallback(tc);
- channelCaptor = ArgumentCaptor.forClass(ChannelUID.class);
- stateCaptor = ArgumentCaptor.forClass(State.class);
- }
-
- private boolean testVehicle(String statusContent, int callbacksExpected,
- Optional<Map<String, State>> concreteChecks) {
- assertNotNull(statusContent);
- cch.vehicleStatusCallback.onResponse(statusContent);
- verify(tc, times(callbacksExpected)).stateUpdated(channelCaptor.capture(), stateCaptor.capture());
- allChannels = channelCaptor.getAllValues();
- allStates = stateCaptor.getAllValues();
-
- assertNotNull(driveTrain);
- StatusWrapper checker = new StatusWrapper(driveTrain, statusContent);
- trace();
- if (concreteChecks.isPresent()) {
- return checker.append(concreteChecks.get()).checkResults(allChannels, allStates);
- } else {
- return checker.checkResults(allChannels, allStates);
- }
- }
-
- private void trace() {
- for (int i = 0; i < allChannels.size(); i++) {
- // change to info for debugging channel updates
- logger.debug("Channel {} {}", allChannels.get(i), allStates.get(i));
- }
- }
-
- /**
- * Test various Vehicles from users which delivered their fingerprint.
- * The tests are checking the chain from "JSON to Channel update".
- * Checks are done in an automated way cross checking the data from JSON and data delivered via Channel.
- * Also important the updates are counted in order to check if code changes are affecting Channel Updates.
- *
- * With the given output the updated Channels are visible.
- * Example:
- *
- * testi3Rex
- * Channel testbinding::test:status#lock Locked
- * Channel testbinding::test:status#service-date 2023-11-01T00:00:00.000+0100
- * Channel testbinding::test:status#check-control No Issues
- * Channel testbinding::test:status#last-update 2021-12-21T16:46:02.000+0100
- * Channel testbinding::test:status#doors Closed
- * Channel testbinding::test:status#windows Closed
- * Channel testbinding::test:status#plug-connection Not connected
- * Channel testbinding::test:status#charge Not Charging
- * Channel testbinding::test:status#charge-type Not Available
- * Channel testbinding::test:range#electric 76 km
- * Channel testbinding::test:range#radius-electric 60.800000000000004 km
- * Channel testbinding::test:range#fuel 31 km
- * Channel testbinding::test:range#radius-fuel 24.8 km
- * Channel testbinding::test:range#hybrid 31 km
- * Channel testbinding::test:range#radius-hybrid 24.8 km
- * Channel testbinding::test:range#mileage 31537 km
- * Channel testbinding::test:range#soc 74 %
- * Channel testbinding::test:range#remaining-fuel 4 l
- * Channel testbinding::test:doors#driver-front Closed
- * Channel testbinding::test:doors#driver-rear Closed
- * Channel testbinding::test:doors#passenger-front Closed
- * Channel testbinding::test:doors#passenger-rear Closed
- * Channel testbinding::test:doors#trunk Closed
- * Channel testbinding::test:doors#hood Closed
- * Channel testbinding::test:doors#win-driver-front Closed
- * Channel testbinding::test:doors#win-driver-rear Undef
- * Channel testbinding::test:doors#win-passenger-front Closed
- * Channel testbinding::test:doors#win-passenger-rear Undef
- * Channel testbinding::test:doors#sunroof Closed
- * Channel testbinding::test:location#gps 1.2345,6.789
- * Channel testbinding::test:location#heading 222 °
- * Channel testbinding::test:service#name Brake Fluid
- * Channel testbinding::test:service#date 2023-11-01T00:00:00.000+0100
- * Channel testbinding::test:profile#prefs Chargingwindow
- * Channel testbinding::test:profile#mode Immediatecharging
- * Channel testbinding::test:profile#control Weeklyplanner
- * Channel testbinding::test:profile#target 100
- * Channel testbinding::test:profile#limit OFF
- * Channel testbinding::test:profile#climate OFF
- * Channel testbinding::test:profile#window-start 1970-01-01T11:00:00.000+0100
- * Channel testbinding::test:profile#window-end 1970-01-01T14:30:00.000+0100
- * Channel testbinding::test:profile#timer1-departure 1970-01-01T16:00:00.000+0100
- * Channel testbinding::test:profile#timer1-enabled OFF
- * Channel testbinding::test:profile#timer1-day-mon ON
- * Channel testbinding::test:profile#timer1-day-tue ON
- * Channel testbinding::test:profile#timer1-day-wed ON
- * Channel testbinding::test:profile#timer1-day-thu ON
- * Channel testbinding::test:profile#timer1-day-fri ON
- * Channel testbinding::test:profile#timer1-day-sat ON
- * Channel testbinding::test:profile#timer1-day-sun ON
- * Channel testbinding::test:profile#timer2-departure 1970-01-01T12:02:00.000+0100
- * Channel testbinding::test:profile#timer2-enabled ON
- * Channel testbinding::test:profile#timer2-day-mon OFF
- * Channel testbinding::test:profile#timer2-day-tue OFF
- * Channel testbinding::test:profile#timer2-day-wed OFF
- * Channel testbinding::test:profile#timer2-day-thu OFF
- * Channel testbinding::test:profile#timer2-day-fri OFF
- * Channel testbinding::test:profile#timer2-day-sat OFF
- * Channel testbinding::test:profile#timer2-day-sun ON
- * Channel testbinding::test:profile#timer3-departure 1970-01-01T13:03:00.000+0100
- * Channel testbinding::test:profile#timer3-enabled OFF
- * Channel testbinding::test:profile#timer3-day-mon OFF
- * Channel testbinding::test:profile#timer3-day-tue OFF
- * Channel testbinding::test:profile#timer3-day-wed OFF
- * Channel testbinding::test:profile#timer3-day-thu OFF
- * Channel testbinding::test:profile#timer3-day-fri OFF
- * Channel testbinding::test:profile#timer3-day-sat ON
- * Channel testbinding::test:profile#timer3-day-sun OFF
- * Channel testbinding::test:profile#timer4-departure 1970-01-01T12:02:00.000+0100
- * Channel testbinding::test:profile#timer4-enabled OFF
- * Channel testbinding::test:profile#timer4-day-mon OFF
- * Channel testbinding::test:profile#timer4-day-tue OFF
- * Channel testbinding::test:profile#timer4-day-wed OFF
- * Channel testbinding::test:profile#timer4-day-thu OFF
- * Channel testbinding::test:profile#timer4-day-fri OFF
- * Channel testbinding::test:profile#timer4-day-sat OFF
- * Channel testbinding::test:profile#timer4-day-sun ON
- */
- @Test
- public void testI01Rex() {
- 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()));
- }
-
- @Test
- public void testF11() {
- 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()));
- }
-
- @Test
- public void testF31() {
- 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()));
- }
-
- @Test
- public void testF44() {
- 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()));
- }
-
- @Test
- public void testF45() {
- 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
- + LOCATION + CHARGE_PROFILE + TIRES, Optional.empty()));
- }
-
- @Test
- public void testF48() {
- 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");
- assertTrue(testVehicle(content,
- STATUS_CONV + DOORS + RANGE_CONV + SERVICE_AVAILABLE + CHECK_AVAILABLE + LOCATION + TIRES,
- Optional.empty()));
- }
-
- @Test
- public void testG01() {
- 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()));
- }
-
- @Test
- public void testG05() {
- 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()));
- }
-
- @Test
- public void testG08() {
- 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()));
- }
-
- @Test
- public void testG21() {
- 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()));
- }
-
- @Test
- public void testG30() {
- 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");
- 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() {
- logger.info("{}", Thread.currentThread().getStackTrace()[1].getMethodName());
- setup(VehicleType.PLUGIN_HYBRID.toString(), "anonymous");
- String content = FileReader.readFileInString("src/test/resources/responses/530e/vehicles.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()));
- }
-}
--- /dev/null
+/**
+ * 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.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;
+import java.security.MessageDigest;
+import java.security.PublicKey;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+import javax.crypto.Cipher;
+
+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.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.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.jupiter.api.Test;
+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;
+import org.openhab.binding.mybmw.internal.utils.Converter;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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);
+
+ @Test
+ public void testAuth() {
+ String user = "usr";
+ String pwd = "pwd";
+
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HttpClient authHttpClient = new HttpClient(sslContextFactory);
+ try {
+ authHttpClient.start();
+ Request firstRequest = authHttpClient
+ .newRequest("https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
+ + "/eadrax-ucs/v1/presentation/oauth/config");
+ firstRequest.header("ocp-apim-subscription-key",
+ BimmerConstants.OCP_APIM_KEYS.get(BimmerConstants.REGION_ROW));
+ firstRequest.header("x-user-agent", "android(v1.07_20200330);bmw;1.7.0(11152)");
+
+ ContentResponse firstResponse = firstRequest.send();
+ logger.info(firstResponse.getContentAsString());
+ AuthQueryResponse aqr = JsonStringDeserializer.deserializeString(firstResponse.getContentAsString(),
+ AuthQueryResponse.class);
+
+ String verifierBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
+ String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verifierBytes.getBytes());
+
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
+ String codeChallenge = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
+
+ String stateBytes = StringUtils.getRandomAlphabetic(16).toLowerCase();
+ String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
+
+ String authUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
+ logger.info(authUrl);
+ Request loginRequest = authHttpClient.POST(authUrl);
+ loginRequest.header("Content-Type", "application/x-www-form-urlencoded");
+
+ MultiMap<String> baseParams = new MultiMap<String>();
+ baseParams.put("client_id", aqr.clientId);
+ baseParams.put("response_type", "code");
+ baseParams.put("redirect_uri", aqr.returnUrl);
+ baseParams.put("state", state);
+ baseParams.put("nonce", "login_nonce");
+ baseParams.put("scope", String.join(" ", aqr.scopes));
+ baseParams.put("code_challenge", codeChallenge);
+ baseParams.put("code_challenge_method", "S256");
+
+ MultiMap<String> loginParams = new MultiMap<String>(baseParams);
+ loginParams.put("grant_type", "authorization_code");
+ loginParams.put("username", user);
+ loginParams.put("password", pwd);
+ loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
+ UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
+ ContentResponse secondResonse = loginRequest.send();
+ logger.info(secondResonse.getContentAsString());
+ String authCode = getAuthCode(secondResonse.getContentAsString());
+ logger.info(authCode);
+
+ MultiMap<String> authParams = new MultiMap<String>(baseParams);
+ authParams.put("authorization", authCode);
+ Request authRequest = authHttpClient.POST(authUrl).followRedirects(false);
+ authRequest.header("Content-Type", "application/x-www-form-urlencoded");
+ authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
+ UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
+ ContentResponse authResponse = authRequest.send();
+ logger.info("{}", authResponse.getHeaders());
+ logger.info("Response " + authResponse.getHeaders().get(HttpHeader.LOCATION));
+ String code = AuthTest.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
+ logger.info("Code " + code);
+ logger.info("Auth");
+
+ logger.info(aqr.tokenEndpoint);
+
+ Request codeRequest = authHttpClient.POST(aqr.tokenEndpoint);
+ String basicAuth = "Basic "
+ + Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
+ logger.info(basicAuth);
+ codeRequest.header("Content-Type", "application/x-www-form-urlencoded");
+ codeRequest.header(AUTHORIZATION, basicAuth);
+
+ MultiMap<String> codeParams = new MultiMap<String>();
+ codeParams.put("code", code);
+ codeParams.put("code_verifier", codeVerifier);
+ codeParams.put("redirect_uri", aqr.returnUrl);
+ codeParams.put("grant_type", "authorization_code");
+ codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
+ UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
+ ContentResponse codeResponse = codeRequest.send();
+ logger.info(codeResponse.getContentAsString());
+ AuthResponse ar = JsonStringDeserializer.deserializeString(codeResponse.getContentAsString(),
+ AuthResponse.class);
+ Token t = new Token();
+ t.setType(ar.tokenType);
+ t.setToken(ar.accessToken);
+ t.setExpiration(ar.expiresIn);
+ logger.info(t.getBearerToken());
+
+ /**
+ * REQUEST CONTENT
+ */
+ HttpClient apiHttpClient = new HttpClient(sslContextFactory);
+ apiHttpClient.start();
+
+ MultiMap<String> vehicleParams = new MultiMap<String>();
+ vehicleParams.put("tireGuardMode", "ENABLED");
+ vehicleParams.put("appDateTime", Long.toString(System.currentTimeMillis()));
+ vehicleParams.put("apptimezone", "60");
+
+ 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);
+
+ 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)");
+
+ ContentResponse vehicleResponse = vehicleRequest.send();
+ logger.info("Vehicle Status {} {}", vehicleResponse.getStatus(), vehicleResponse.getContentAsString());
+
+ /**
+ * CHARGE STATISTICS
+ */
+ MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
+ chargeStatisticsParams.put("vin", "WBY1Z81040V905639");
+ chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
+ params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
+
+ String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
+ + "/eadrax-chs/v1/charging-statistics";
+ Request chargeStatisticsRequest = apiHttpClient.newRequest(chargeStatisticsUrl)
+ .param("vin", "WBY1Z81040V905639").param("currentDate", Converter.getCurrentISOTime());
+ logger.info("{}", chargeStatisticsUrl);
+
+ 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");
+
+ logger.info("{}", params);
+ chargeStatisticsRequest
+ .content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
+
+ ContentResponse chargeStatisticsResponse = chargeStatisticsRequest.send();
+ logger.info("{}", chargeStatisticsResponse.getStatus());
+ logger.info("{}", chargeStatisticsResponse.getReason());
+ logger.info("{}", chargeStatisticsResponse.getContentAsString());
+
+ /**
+ * CHARGE SESSIONS
+ */
+ MultiMap<String> chargeSessionsParams = new MultiMap<String>();
+ chargeSessionsParams.put("vin", "WBY1Z81040V905639");
+ chargeSessionsParams.put("maxResults", "40");
+ chargeSessionsParams.put("include_date_picker", "true");
+
+ params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
+
+ String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
+ + "/eadrax-chs/v1/charging-sessions";
+ Request chargeSessionsRequest = apiHttpClient.newRequest(chargeSessionsUrl + "?" + params);
+ logger.info("{}", chargeSessionsUrl);
+
+ 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");
+
+ logger.info("{}", params);
+
+ ContentResponse chargeSessionsResponse = chargeSessionsRequest.send();
+ logger.info("{}", chargeSessionsResponse.getStatus());
+ logger.info("{}", chargeSessionsResponse.getReason());
+ logger.info("{}", chargeSessionsResponse.getContentAsString());
+
+ String chargingControlUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_ROW)
+ + "/eadrax-vrccs/v2/presentation/remote-commands/WBY1Z81040V905639/charging-control";
+ Request chargingControlRequest = apiHttpClient.POST(chargingControlUrl);
+ logger.info("{}", chargingControlUrl);
+
+ 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);
+
+ } catch (Exception e) {
+ logger.error("{}", e.getMessage());
+ }
+ }
+
+ private String getAuthCode(String response) {
+ String[] keys = response.split("&");
+ for (int i = 0; i < keys.length; i++) {
+ if (keys[i].startsWith(AUTHORIZATION)) {
+ String authCode = keys[i].split("=")[1];
+ authCode = authCode.split("\"")[0];
+ return authCode;
+ }
+ }
+ return Constants.EMPTY;
+ }
+
+ public static String codeFromUrl(String encodedUrl) {
+ final MultiMap<String> tokenMap = new MultiMap<String>();
+ UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
+ final StringBuilder codeFound = new StringBuilder();
+ tokenMap.forEach((key, value) -> {
+ if (!value.isEmpty()) {
+ String val = value.get(0);
+ if (key.endsWith(CODE)) {
+ codeFound.append(val);
+ }
+ }
+ });
+ return codeFound.toString();
+ }
+
+ @Test
+ public void testJWTDeserialze() {
+ 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 = JsonStringDeserializer.deserializeString(tokenJwtDecodeStr,
+ ChinaTokenExpiration.class);
+ Token t = new Token();
+ t.setToken(token);
+ t.setType(cat.data.tokenType);
+ t.setExpirationTotal(cte.exp);
+ assertEquals(
+ "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJEVU1NWSQxJEEkMTYzNzcwNzkxNjc4MiIsIm5iZiI6MTYzNzcwNzkxNiwiZXhwIjoxNjM3NzExMjE2LCJpYXQiOjE2Mzc3MDc5MTZ9.hpi-P97W68g7avGwu9dcBRapIsaG4F8MwOdPHe6PuTA",
+ t.getBearerToken(), "Token");
+ }
+
+ public void testChina() {
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HttpClient authHttpClient = new HttpClient(sslContextFactory);
+ try {
+ authHttpClient.start();
+ HttpClientFactory mockHCF = mock(HttpClientFactory.class);
+ when(mockHCF.getCommonHttpClient()).thenReturn(authHttpClient);
+ MyBMWBridgeConfiguration config = new MyBMWBridgeConfiguration();
+ config.region = BimmerConstants.REGION_CHINA;
+ config.userName = "Hello User";
+ config.password = "Hello Password";
+ MyBMWTokenController tokenHandler = new MyBMWTokenController(config, authHttpClient);
+ Token token = tokenHandler.getToken();
+ assertNotNull(token);
+ assertNotNull(token.getBearerToken());
+ } catch (Exception e) {
+ logger.warn("Exception: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void testPublicKey() {
+ 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", "")
+ .replace("\\n", "").trim();
+ byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
+ X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
+ KeyFactory kf;
+ try {
+ 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("Hello World".getBytes());
+ String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
+ logger.info(encodedPassword);
+ } catch (Exception e) {
+ assertTrue(false, "Excpetion: " + e.getMessage());
+ }
+ }
+
+ public void testChinaToken() {
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ HttpClient authHttpClient = new HttpClient(sslContextFactory);
+ try {
+ authHttpClient.start();
+ String url = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
+ + BimmerConstants.CHINA_PUBLIC_KEY;
+ Request oauthQueryRequest = authHttpClient.newRequest(url);
+ oauthQueryRequest.header(HEADER_X_USER_AGENT,
+ String.format(BimmerConstants.BRAND_BMW, BimmerConstants.BRAND_BMW, BimmerConstants.REGION_ROW));
+
+ ContentResponse publicKeyResponse = oauthQueryRequest.send();
+ 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 publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
+ .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "");
+ 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("Hello World".getBytes());
+ String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
+ logger.info(encodedPassword);
+ } catch (Exception e) {
+ assertTrue(false, "Excpetion: " + e.getMessage());
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<VehicleBase> 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);
+ }
+}
--- /dev/null
+/**
+ * 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<VehicleBase> 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<Vehicle> 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
*/
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;
* 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;
+ }
}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
+++ /dev/null
-[{
- "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
-}
-]
-
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[\r
+ {\r
+ "vin": "anonymousBEV",\r
+ "mappingInfo": {\r
+ "isAssociated": true,\r
+ "isLmmEnabled": true,\r
+ "mappingStatus": "CONFIRMED",\r
+ "isPrimaryUser": true\r
+ },\r
+ "appVehicleType": "CONNECTED",\r
+ "attributes": {\r
+ "lastFetched": "2023-01-16T15:41:26.664Z",\r
+ "model": "iX xDrive40",\r
+ "year": 2022,\r
+ "color": 4282345065,\r
+ "brand": "BMW_I",\r
+ "driveTrain": "ELECTRIC",\r
+ "headUnitType": "MGU",\r
+ "headUnitRaw": "HU_MGU",\r
+ "hmiVersion": "ID8",\r
+ "softwareVersionCurrent": {\r
+ "puStep": {\r
+ "month": 7,\r
+ "year": 22\r
+ },\r
+ "iStep": 545,\r
+ "seriesCluster": "I020"\r
+ },\r
+ "softwareVersionExFactory": {\r
+ "puStep": {\r
+ "month": 7,\r
+ "year": 22\r
+ },\r
+ "iStep": 545,\r
+ "seriesCluster": "I020"\r
+ },\r
+ "telematicsUnit": "WAVE11",\r
+ "bodyType": "I20",\r
+ "countryOfOrigin": "BE",\r
+ "driverGuideInfo": {\r
+ "androidAppScheme": "com.bmwgroup.driversguide.row",\r
+ "iosAppScheme": "bmwdriversguide:///open",\r
+ "androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row",\r
+ "iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8"\r
+ }\r
+ }\r
+ }\r
+]
\ No newline at end of file
--- /dev/null
+{\r
+ "state": {\r
+ "isLeftSteering": true,\r
+ "lastFetched": "2023-01-16T15:25:49.307Z",\r
+ "lastUpdatedAt": "2023-01-16T12:32:48Z",\r
+ "isLscSupported": true,\r
+ "range": 191,\r
+ "doorsState": {\r
+ "combinedSecurityState": "SECURED",\r
+ "leftFront": "CLOSED",\r
+ "leftRear": "CLOSED",\r
+ "rightFront": "CLOSED",\r
+ "rightRear": "CLOSED",\r
+ "combinedState": "CLOSED",\r
+ "hood": "CLOSED",\r
+ "trunk": "CLOSED"\r
+ },\r
+ "windowsState": {\r
+ "leftFront": "CLOSED",\r
+ "leftRear": "CLOSED",\r
+ "rightFront": "CLOSED",\r
+ "rightRear": "CLOSED",\r
+ "combinedState": "CLOSED"\r
+ },\r
+ "tireState": {\r
+ "frontLeft": {\r
+ "details": {\r
+ "identificationInProgress": false\r
+ },\r
+ "status": {\r
+ "currentPressure": 230,\r
+ "targetPressure": 220,\r
+ "wearStatus": 0\r
+ }\r
+ },\r
+ "frontRight": {\r
+ "details": {\r
+ "identificationInProgress": false\r
+ },\r
+ "status": {\r
+ "currentPressure": 230,\r
+ "targetPressure": 220,\r
+ "wearStatus": 0\r
+ }\r
+ },\r
+ "rearLeft": {\r
+ "details": {\r
+ "identificationInProgress": false\r
+ },\r
+ "status": {\r
+ "currentPressure": 260,\r
+ "targetPressure": 260,\r
+ "wearStatus": 0\r
+ }\r
+ },\r
+ "rearRight": {\r
+ "details": {\r
+ "identificationInProgress": false\r
+ },\r
+ "status": {\r
+ "currentPressure": 280,\r
+ "targetPressure": 260,\r
+ "wearStatus": 0\r
+ }\r
+ }\r
+ },\r
+ "location": {\r
+ "coordinates": {\r
+ "latitude": 1.1,\r
+ "longitude": 2.2\r
+ },\r
+ "address": {\r
+ "formatted": "anonymousAddress"\r
+ },\r
+ "heading": -1\r
+ },\r
+ "currentMileage": 2686,\r
+ "climateControlState": {\r
+ "activity": "INACTIVE"\r
+ },\r
+ "requiredServices": [\r
+ {\r
+ "dateTime": "2024-11-01T00:00:00.000Z",\r
+ "type": "BRAKE_FLUID",\r
+ "status": "OK",\r
+ "description": "Volgende vervanging uiterlijk op het aangegeven tijdstip."\r
+ },\r
+ {\r
+ "dateTime": "2024-11-01T00:00:00.000Z",\r
+ "type": "VEHICLE_CHECK",\r
+ "status": "OK",\r
+ "description": "Volgende visuele controle op de aangegeven datum of na afloop van de evt. aangegeven afstand."\r
+ },\r
+ {\r
+ "type": "TIRE_WEAR_REAR",\r
+ "status": "OK"\r
+ },\r
+ {\r
+ "type": "TIRE_WEAR_FRONT",\r
+ "status": "OK"\r
+ }\r
+ ],\r
+ "checkControlMessages": [\r
+ {\r
+ "type": "TIRE_PRESSURE",\r
+ "severity": "LOW"\r
+ }\r
+ ],\r
+ "chargingProfile": {\r
+ "chargingControlType": "WEEKLY_PLANNER",\r
+ "reductionOfChargeCurrent": {\r
+ "start": {\r
+ "hour": 21,\r
+ "minute": 0\r
+ },\r
+ "end": {\r
+ "hour": 6,\r
+ "minute": 0\r
+ }\r
+ },\r
+ "chargingMode": "DELAYED_CHARGING",\r
+ "chargingPreference": "CHARGING_WINDOW",\r
+ "departureTimes": [\r
+ {\r
+ "id": 1,\r
+ "timeStamp": {\r
+ "hour": 7,\r
+ "minute": 0\r
+ },\r
+ "action": "ACTIVATE",\r
+ "timerWeekDays": [\r
+ "MONDAY",\r
+ "TUESDAY",\r
+ "WEDNESDAY",\r
+ "THURSDAY",\r
+ "FRIDAY",\r
+ "SATURDAY",\r
+ "SUNDAY"\r
+ ]\r
+ },\r
+ {\r
+ "id": 2,\r
+ "timeStamp": {\r
+ "hour": 23,\r
+ "minute": 59\r
+ },\r
+ "action": "DEACTIVATE",\r
+ "timerWeekDays": []\r
+ },\r
+ {\r
+ "id": 3,\r
+ "timeStamp": {\r
+ "hour": 23,\r
+ "minute": 59\r
+ },\r
+ "action": "DEACTIVATE",\r
+ "timerWeekDays": []\r
+ },\r
+ {\r
+ "id": 4,\r
+ "timeStamp": {\r
+ "hour": 7,\r
+ "minute": 30\r
+ },\r
+ "action": "DEACTIVATE",\r
+ "timerWeekDays": []\r
+ }\r
+ ],\r
+ "climatisationOn": false,\r
+ "chargingSettings": {\r
+ "targetSoc": 80,\r
+ "acCurrentLimit": 32,\r
+ "idcc": "AUTOMATIC_INTELLIGENT",\r
+ "hospitality": "HOSP_INACTIVE",\r
+ "isAcCurrentLimitActive": false\r
+ }\r
+ },\r
+ "electricChargingState": {\r
+ "chargingLevelPercent": 46,\r
+ "remainingChargingMinutes": 178,\r
+ "range": 159,\r
+ "isChargerConnected": true,\r
+ "chargingConnectionType": "UNKNOWN",\r
+ "chargingStatus": "CHARGING",\r
+ "chargingTarget": 80\r
+ },\r
+ "combustionFuelLevel": {\r
+ "range": 191\r
+ },\r
+ "driverPreferences": {\r
+ "lscPrivacyMode": "OFF"\r
+ },\r
+ "isDeepSleepModeActive": false,\r
+ "climateTimers": [\r
+ {\r
+ "isWeeklyTimer": false,\r
+ "timerAction": "DEACTIVATE",\r
+ "timerWeekDays": [],\r
+ "departureTime": {\r
+ "hour": 7,\r
+ "minute": 0\r
+ }\r
+ },\r
+ {\r
+ "isWeeklyTimer": true,\r
+ "timerAction": "DEACTIVATE",\r
+ "timerWeekDays": [\r
+ "MONDAY"\r
+ ],\r
+ "departureTime": {\r
+ "hour": 7,\r
+ "minute": 0\r
+ }\r
+ },\r
+ {\r
+ "isWeeklyTimer": true,\r
+ "timerAction": "DEACTIVATE",\r
+ "timerWeekDays": [\r
+ "MONDAY"\r
+ ],\r
+ "departureTime": {\r
+ "hour": 7,\r
+ "minute": 0\r
+ }\r
+ }\r
+ ]\r
+ },\r
+ "capabilities": {\r
+ "a4aType": "NOT_SUPPORTED",\r
+ "climateNow": true,\r
+ "climateFunction": "AIR_CONDITIONING",\r
+ "horn": true,\r
+ "isBmwChargingSupported": true,\r
+ "isCarSharingSupported": false,\r
+ "isChargeNowForBusinessSupported": false,\r
+ "isChargingHistorySupported": true,\r
+ "isChargingHospitalityEnabled": true,\r
+ "isChargingLoudnessEnabled": true,\r
+ "isChargingPlanSupported": true,\r
+ "isChargingPowerLimitEnabled": true,\r
+ "isChargingSettingsEnabled": true,\r
+ "isChargingTargetSocEnabled": true,\r
+ "isCustomerEsimSupported": true,\r
+ "isDataPrivacyEnabled": false,\r
+ "isDCSContractManagementSupported": true,\r
+ "isEasyChargeEnabled": false,\r
+ "isMiniChargingSupported": false,\r
+ "isEvGoChargingSupported": false,\r
+ "isRemoteHistoryDeletionSupported": false,\r
+ "isRemoteEngineStartSupported": false,\r
+ "isRemoteServicesActivationRequired": false,\r
+ "isRemoteServicesBookingRequired": false,\r
+ "isScanAndChargeSupported": true,\r
+ "lastStateCallState": "ACTIVATED",\r
+ "lights": true,\r
+ "lock": true,\r
+ "remote360": true,\r
+ "remoteSoftwareUpgrade": true,\r
+ "sendPoi": true,\r
+ "surroundViewRecorder": true,\r
+ "unlock": true,\r
+ "vehicleFinder": true,\r
+ "vehicleStateSource": "LAST_STATE_CALL",\r
+ "isRemoteHistorySupported": true,\r
+ "isWifiHotspotServiceSupported": false,\r
+ "isNonLscFeatureEnabled": false,\r
+ "isSustainabilitySupported": false,\r
+ "isSustainabilityAccumulatedViewEnabled": false,\r
+ "checkSustainabilityDPP": false,\r
+ "specialThemeSupport": [],\r
+ "isRemoteParkingSupported": false,\r
+ "remoteChargingCommands": {\r
+ "chargingControl": [\r
+ "START",\r
+ "STOP"\r
+ ],\r
+ "plugControl": [\r
+ "NOT_SUPPORTED"\r
+ ],\r
+ "flapControl": [\r
+ "COUPLE_FLAP",\r
+ "DECOUPLE_FLAP"\r
+ ]\r
+ },\r
+ "isClimateTimerWeeklyActive": false,\r
+ "digitalKey": {\r
+ "bookedServicePackage": "SMACC_2_UWB",\r
+ "readerGraphics": "000200000001",\r
+ "state": "ACTIVATED"\r
+ }\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
- }
-]
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "description": "November 2021",
- "optStateType": "OPT_IN_WITH_SESSIONS",
- "statistics": {
- "numberOfChargingSessions": 11,
- "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics",
- "symbol": "~",
- "totalEnergyCharged": 87,
- "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics"
- }
-}
\ No newline at end of file
+++ /dev/null
-{
- "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
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "description": "November 2021",
- "optStateType": "OPT_IN_WITH_SESSIONS",
- "statistics": {
- "numberOfChargingSessions": 6,
- "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics",
- "symbol": "~",
- "totalEnergyCharged": 71,
- "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics"
- }
-}
\ No newline at end of file
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "description": "November 2021",
- "optStateType": "OPT_IN_WITH_SESSIONS",
- "statistics": {
- "numberOfChargingSessions": 1,
- "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics",
- "symbol": "~",
- "totalEnergyCharged": 36,
- "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics"
- }
-}
\ No newline at end of file
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "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
+++ /dev/null
-{
- "description": "November 2021",
- "optStateType": "OPT_IN_WITH_SESSIONS",
- "statistics": {
- "numberOfChargingSessions": 15,
- "numberOfChargingSessionsSemantics": "mobile20chsChargingSessionNumberSemantics",
- "symbol": "~",
- "totalEnergyCharged": 144,
- "totalEnergyChargedSemantics": "mobile20chsApproximatelyTotalChargedSemantics"
- }
-}
\ No newline at end of file
+++ /dev/null
-[
- {
- "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
+++ /dev/null
- [
- {
- "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"
- }
-]
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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"
- }
-]
+++ /dev/null
-[
- {
- "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "eventId": "4131c5b8-4b55-42a3-8682-0dcef479c2dc",
+ "creationTime": "2022-12-31T21:49:29.423034Z"
+}
\ No newline at end of file
--- /dev/null
+{
+ "eventStatus": "ERROR",
+ "errorDetails": {
+ "title": null,
+ "description": null,
+ "presentationType": "DIALOG",
+ "iconId": null,
+ "isRetriable": false,
+ "errorDetails": "<no error returned>"
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "eventStatus": "EXECUTED"
+}
\ No newline at end of file
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+{
+ "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
--- /dev/null
+[
+ {
+ "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
--- /dev/null
+{
+ "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
+++ /dev/null
- {
- "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
-}
-
+++ /dev/null
- {
- "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
-}
+++ /dev/null
-[
- {
- "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
- }
-]
+++ /dev/null
-[
- {
- "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
+++ /dev/null
-[
- {
- "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"
- }
-]
"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,
--- /dev/null
+[
+ {
+ "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