remove mybmw binding (#20371)

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege
2026-03-13 09:47:19 +01:00
committed by GitHub
parent 2ce65962f5
commit b139d78e3c
224 changed files with 0 additions and 20042 deletions
-1
View File
@@ -263,7 +263,6 @@
/bundles/org.openhab.binding.mqtt.fpp/ @computergeek1507
/bundles/org.openhab.binding.mqtt.ruuvigateway/ @ssalonen
/bundles/org.openhab.binding.mspa/ @weymann
/bundles/org.openhab.binding.mybmw/ @ntruchsess @mherwege @martingrassl
/bundles/org.openhab.binding.mycroft/ @dalgwen
/bundles/org.openhab.binding.mynice/ @clinique
/bundles/org.openhab.binding.mystrom/ @pail23
-5
View File
@@ -1301,11 +1301,6 @@
<artifactId>org.openhab.binding.mycroft</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.mybmw</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.mynice</artifactId>
-13
View File
@@ -1,13 +0,0 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
-864
View File
@@ -1,864 +0,0 @@
# MyBMW Binding
The binding provides access like [MyBMW App](https://www.bmw.com/en/footer/mybmw-app.html) to openHAB.
All vehicles connected to an account will be detected by the discovery with the correct type:
- Conventional Fuel Vehicle
- Plugin-Hybrid Electrical Vehicle
- Battery Electric Vehicle with Range Extender
- Battery Electric Vehicle
In addition properties are attached with information and services provided by this vehicle.
The provided data depends on
1. the [Thing Type](#things) and
1. the [Properties](#properties) mentioned in Services
Different channel groups are clustering all information.
Check for each group if it's supported by your vehicle.
Please note **this isn't a real-time binding**.
If a door is opened the state isn't transmitted and changed immediately.
It's not a flaw in the binding itself because the state in BMW's own MyBMW App is also updated with some delay.
This binding does not support the region: China.
## Supported Things
### Bridge
The bridge establishes the connection between BMW API and openHAB.
| Name | Bridge Type ID | Description |
|----------------------------|----------------|------------------------------------------|
| MyBMW Account | `account` | Access to BMW API for a specific user |
### Things
Four different vehicle types are provided.
They differ in the supported channel groups & channels.
Conventional Fuel Vehicles don't provide e.g. _Charging Profile_, Electric Vehicles don't provide a _Fuel Range_.
For hybrid vehicles in addition to _Fuel and Electric Range_ the _Hybrid Range_ is shown.
| Name | Thing Type ID | Supported Channel Groups |
|-------------------------------------|---------------|---------------------------------------------------------------------|
| BMW Electric Vehicle | `bev` | Vehicle with electric drive train |
| BMW Electric Vehicle with REX | `bev_rex` | Vehicle with electric drive train plus fuel powered range extender |
| BMW Plug-In-Hybrid Electric Vehicle | `phev` | Vehicle with combustion and electric drive train |
| BMW Conventional Vehicle | `conv` | Vehicle with combustion drive train |
#### Properties
<img align="right" src="doc/vehicle-properties.png" alt="Vehicle properties overview" width="500" height="350"/>
For each vehicle properties are available.
Basic information is given regarding
- Vehicle properties like model type, drive train and construction year
- Which services are available / not available
In the right picture can see in _remoteServicesEnabled_ e.g. the _Door Lock_ and _Door Unlock_ services are mentioned.
This ensures channel group [Remote Services](#remote-services) is supporting door lock and unlock remote control.
In _Services Supported_ the entry _ChargingHistory_ is mentioned.
So it's valid to connect channel group [Charge Sessions](#charge-sessions) in order to display your last charging sessions.
| Property Key | Property Value | Supported Channel Groups |
|------------------------|---------------------|------------------------------|
| servicesSupported | ChargingHistory | session |
| remoteServicesEnabled | _list of services_ | remote |
## Discovery
Auto discovery is starting after the bridge is created.
A list of your registered vehicles is queried and all found things are added in the inbox.
Unique identifier is the _Vehicle Identification Number_ (VIN).
If a Thing is already declared in a _.things_ configuration, discovery won't highlight it again.
Properties will be attached to predefined vehicles if the VIN is matching.
## Configuration
### Bridge Configuration
| Parameter | Type | Description |
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
| userName | text | MyBMW Username |
| password | text | MyBMW Password |
| region | text | Select region in order to connect to the appropriate BMW server. |
The region Configuration has 2 different options
- _NORTH_AMERICA_
- _ROW_ (Rest of World)
At first initialization, follow the online instructions for login into the BMW API.
#### Advanced Configuration
| Parameter | Type | Description |
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
| language | text | Channel data can be returned in the desired language |
| hcaptchatoken | text | HCaptcha-Token for initial login (see <https://bimmer-connected.readthedocs.io/en/stable/captcha.html>) |
| callbackIP | text | IP address for openHAB callback URL, defaults to IP of openHAB host |
| callbackPort | integer | Port Number for openHAB callback URL, default 8090 |
Language is predefined as _AUTODETECT_.
Some textual descriptions, date and times are delivered based on your local language.
You can overwrite this setting with lowercase 2-letter [language code reagrding ISO 639](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html)
So if want your UI in english language place _en_ as desired language.
The initial login to the BMW API requires a Captcha Token.
At first configuration, you can set the Captcha Token as a configuration parameter manually.
To set the Captcha Token online, a webpage is presented and a callback to the bridge is created temporarily on the hosts IP address and a default port.
If the port is already in use, or you have a complex network setup, you may have to override the defaults provided.
### Thing Configuration
Same configuration is needed for all things
| Parameter | Type | Description |
|-----------------|---------|-------------------------------------------------------------------------------------------------------------------------|
| vin | text | Vehicle Identification Number (VIN) |
| refreshInterval | integer | Refresh Interval in Minutes (by default set to 60; to be set to 0 if no automated refresh should be triggered) |
#### Advanced Configuration
| Parameter | Type | Description |
|-----------------|---------|-----------------------------------|
| vehicleBrand | text | Vehicle Brand like BMW or Mini |
The _vehicleBrand_ is automatically obtained by the discovery service and shall not be changed.
If Thing is defined manually via *.things file following brands are supported
- BMW
- MINI
## Channels
There are many channels available for each vehicle.
For better overview they are clustered in different channel groups.
They differ for each vehicle type, build-in sensors and activated services.
### Thing Channel Groups
| Channel Group ID | Description | conv | phev | bev_rex | bev |
|----------------------------------|---------------------------------------------------|------|------|---------|-----|
| [update](#vehicle-update) | Overall vehicle status | X | X | X | X |
| [status](#vehicle-status) | Overall vehicle status | X | X | X | X |
| [doors](#doors-details) | Details of all doors and windows | X | X | X | X |
| [range](#range-data) | Provides mileage, range and charge / fuel levels | X | X | X | X |
| [check](#check-control) | Shows current active CheckControl messages | X | X | X | X |
| [service](#services) | Future vehicle service schedules | X | X | X | X |
| [location](#location) | Coordinates and heading of the vehicle | X | X | X | X |
| [remote](#remote-services) | Remote control of the vehicle | X | X | X | X |
| [profile](#charge-profile) | Scheduled charging profiles of vehicle | | X | X | X |
| [statistic](#charge-statistics) | Charging statistics of current month | | X | X | X |
| [session](#charge-sessions) | Past charging sessions | | X | X | X |
| [tires](#tire-pressure) | Current and wanted pressure for all tires | X | X | X | X |
| [image](#image) | Provides an image of your vehicle | X | X | X | X |
#### Vehicle Update
The BMW API has limits in the requests per time period.
This leads to unexpected errors stating that some quota is reached and the next successful request can be triggered in X minutes.
In this case the bridge as well as the vehicle things can be set to offline and nothing can be done with them anymore until the next successful refresh.
To reduce the probability of the error, the default automated API update has been set to 60 Minutes, but this is often not sufficient to retrieve continuous range or charging updates.
These channels can be used to control the update behavior from openHAB, e.g. via rules.
- Channel Group ID is status
- Available for all vehicles (charging channel only for xEV)
- switches which can be triggered by a command
- if the switches are set to ON, then immediately they will be set to OFF again for being able to trigger the next update
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|-----------------|--------|----------------------------------------------------------------------------------------------------------|------|------|---------|-----|
| state-update | Switch | When set to ON, the state channels of the vehicle will be updated | X | X | X | X |
| charging-update | Switch | When set to ON, the charging statistics and charging sessions channels of the vehicle will be updated | | X | X | X |
| image-update | Switch | When set to ON, the image of the vehicle will be updated | X | X | X | X |
#### Vehicle Status
Reflects overall status of the vehicle.
- Channel Group ID is **status**
- Available for all vehicles
- Read-only values
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|---------------------|---------------|------------------------------------------------|------|------|---------|-----|
| doors | String | Combined status for all doors | X | X | X | X |
| windows | String | Combined status for all windows | X | X | X | X |
| lock | String | Status if vehicle is secured | X | X | X | X |
| service-date | DateTime | Date of next upcoming service | X | X | X | X |
| service-mileage | Number:Length | Mileage till upcoming service | X | X | X | X |
| check-control | String | Presence of active warning messages | X | X | X | X |
| plug-connection | String | Plug is _Connected_ or _Not connected_ | | X | X | X |
| charge | String | Current charging status | | X | X | X |
| charge-remaining | Number:Time | Remaining time for current charging session | | X | X | X |
| last-update | DateTime | Date and time of last status update | X | X | X | X |
| last-fetched | DateTime | Date and time of last time status fetched | X | X | X | X |
Overall Door Status values
- _Closed_ - all doors closed
- _Open_ - at least one door is open
- _Undef_ - no door data delivered at all
Overall Windows Status values
- _Closed_ - all windows closed
- _Open_ - at least one window is completely open
- _Intermediate_ - at least one window is partially open
- _Undef_ - no window data delivered at all
Check Control values
Localized String of current active warnings.
Examples:
- No Issues
- Multiple Issues
Charging Status values
- _Not Charging_
- _Charging_
- _Plugged In_
- _Fully Charged_
Charging Information values
Localized String of current active charging session
Examples
- 100% at ~00:43
- Starts at ~09:00
##### Vehicle Status Raw Data
The _raw data channel_ is marked as _advanced_ and isn't shown by default.
Target are advanced users to derive even more data out of BMW API replies.
As the replies are formatted as JSON use the [JsonPath Transformation Service](https://www.openhab.org/addons/transformations/jsonpath/) to extract data for an item,
| Channel ID | Type | Description |
|---------------------|---------------|------------------------------------------------|
| raw | String | Unfiltered JSON String of vehicle data |
<img align="right" src="doc/RawData.png" alt="Raw data example" width="400" height="125"/>
Examples:
###### Country ISO Code
```json
$.properties.originCountryISO
```
###### Drivers Guide URL
```json
$.driverGuideInfo.androidStoreUrl
```
#### Range Data
Based on vehicle type some channels are present or not.
Conventional fuel vehicles don't provide _Electric Range_ and battery electric vehicles don't show _Fuel Range_.
Hybrid vehicles have both and in addition _Hybrid Range_.
See description [Range vs Range Radius](#range-vs-range-radius) to get more information.
- Channel Group ID is **range**
- Availability according to table
- Read-only values
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|----------------------------|----------------------|-----------------------------------------------|------|------|---------|-----|
| mileage | Number:Length | Current mileage of the vehicle | X | X | X | X |
| range-fuel | Number:Length | Fuel range | X | X | X | |
| range-electric | Number:Length | Electric range | | X | X | X |
| range-hybrid | Number:Length | Combined hybrid range | | X | X | |
| soc | Number:Dimensionless | State of charge | | X | X | X |
| remaining-fuel | Number:Volume | Remaining fuel in l | X | X | X | |
| estimated-fuel-l-100km | Number | Estimated fuel consumption in l | X | X | X | |
| estimated-fuel-mpg | Number | Estimated fuel consumption in mpg | X | X | X | |
| range-radius-fuel | Number:Length | The calculated range radius combustion | X | X | X | |
| range-radius-electric | Number:Length | The calculated range radius electric | | X | X | X |
| range-radius-hybrid | Number:Length | The calculated range radius hybrid combined | | X | X | |
#### Doors Details
Detailed status of all doors and windows.
- Channel Group ID is **doors**
- Available for all vehicles if corresponding sensors are built-in
- Read-only values
| Channel ID | Type | Description |
|-------------------------|---------------|-----------------------------------------|
| driver-front | String | Status of front door driver's side |
| driver-rear | String | Status of rear door driver's side |
| passenger-front | String | Status of front door passenger's side |
| passenger-rear | String | Status of rear door passenger's side |
| trunk | String | Status of trunk |
| hood | String | Status of hood |
| win-driver-front | String | Status of front window driver's side |
| win-driver-rear | String | Status of rear window driver's side |
| win-passenger-front | String | Status of front window passenger's side |
| win-passenger-rear | String | Status of rear window passenger's side |
| win-rear | String | Status of rear window |
| sunroof | String | Status of sunroof |
Possible states
- _Undef_ - no status data available
- _Invalid_ - this door / window isn't applicable for this vehicle
- _Closed_ - the door / window is closed
- _Open_ - the door / window is open
- _Intermediate_ - window in intermediate position, not applicable for doors
#### Check Control
Group for all current active Check Control messages.
If more than one message is active the channel _name_ contains all active messages as options.
- Channel Group ID is **check**
- Available for all vehicles
- Read/Write access
| Channel ID | Type | Access |
|---------------------|----------------|------------|
| name | String | Read/Write |
| details | String | Read |
| severity | String | Read |
Severity Levels
- Ok
- Low
- Medium
#### Services
Group for all upcoming services with description, service date and/or service mileage.
If more than one service is scheduled in the future the channel _name_ contains all future services as options.
- Channel Group ID is **service**
- Available for all vehicles
- Read/Write access
| Channel ID | Type | Access |
|---------------------|----------------|------------|
| name | String | Read/Write |
| details | String | Read |
| date | DateTime | Read |
| mileage | Number:Length | Read |
#### Location
GPS location and heading of the vehicle.
- Channel Group ID is **location**
- Available for all vehicles with built-in GPS sensor. Function can be enabled/disabled in the head unit
- Read-only values
| Channel ID | Type | Description |
|---------------------|---------------|--------------------------------------------------------------|
| gps | Location | Current GPS coordinates of the vehicle |
| heading | Number:Angle | Current direction of the vehicle |
| address | String | Current address |
| home-distance | Number:Length | Calculated distance from configured home position of openHAB |
#### Remote Services
Remote control of the vehicle.
Send a _command_ to the vehicle and the _state_ is reporting the execution progress.
Only one command can be executed each time.
Parallel execution isn't supported.
- Channel Group ID is **remote**
- Available for all commands mentioned in _Services Activated_. See [Vehicle Properties](#properties) for further details
- Read/Write access
| Channel ID | Type | Access |
|---------------------|---------|--------|
| command | String | Write |
| state | String | Read |
The channel _command_ provides options
- _light-flash_
- _vehicle-finder_
- _door-lock_
- _door-unlock_
- _horn-blow_
- _climate-now-start_
- _climate-now-stop_
- _start-charging_
- _stop-charging_
The channel _state_ shows the progress of the command execution in the following order
1. _initiated_
1. _pending_
1. _delivered_
1. _executed_
#### Charge Profile
Charging options with date and time for preferred time windows and charging modes.
- Channel Group ID is **profile**
- Available for electric and hybrid vehicles
- Read access for UI.
- There are 4 timers _T1, T2, T3 and T4_ available. Replace _X_ with number 1,2 or 3 to target the correct timer
| Channel ID | Type |
|---------------------------|----------|
| mode | String |
| prefs | String |
| control | String |
| target | String |
| limit | Switch |
| window-start | DateTime |
| window-end | DateTime |
| climate | Switch |
| timer_X_-enabled | Switch |
| timer_X_-departure | DateTime |
| timer_X_-day-mon | Switch |
| timer_X_-day-tue | Switch |
| timer_X_-day-wed | Switch |
| timer_X_-day-thu | Switch |
| timer_X_-day-fri | Switch |
| timer_X_-day-sat | Switch |
| timer_X_-day-sun | Switch |
The channel _profile-mode_ supports
- _immediateCharging_
- _delayedCharging_
The channel _profile-prefs_ supports
- _noPreSelection_
- _chargingWindow_
#### Charge Statistics
Shows charge statistics of the current month
- Channel Group ID is **statistic**
- Available for electric and hybrid vehicles
- Read-only values
| Channel ID | Type | Description |
|-------------------------|----------------|-------------------------|
| title | String | Title of the statistics |
| energy | Number:Energy | Consumed energy |
| sessions | Number | Number of sessions |
#### Charge Sessions
Group for past charging sessions.
If more than one message is active the channel _name_ contains all active messages as options.
- Channel Group ID is **session**
- Available for electric and hybrid vehicles
- Read-only values
| Channel ID | Type | Description |
|--------------|----------|-------------------------|
| title | String | Title of the session |
| subtitle | String | Subtitle of the session |
| energy | String | Consumed energy |
| issue | String | If an issue occurred |
| status | String | Status of the session |
#### Tire Pressure
Current and target tire pressure values
- Channel Group ID is **tires**
- Available for all vehicles if corresponding sensors are built-in
- Read-only values
| Channel ID | Type | Description |
|-------------------------|------------------|------------------------------|
| fl-current | Number:Pressure | Current pressure front left |
| fl-target | Number:Pressure | Target pressure front left |
| fr-current | Number:Pressure | Current pressure front right |
| fr-target | Number:Pressure | Target pressure front right |
| rl-current | Number:Pressure | Current pressure rear left |
| rl-target | Number:Pressure | Target pressure rear left |
| rr-current | Number:Pressure | Current pressure rear right |
| rr-target | Number:Pressure | Target pressure rear right |
#### Image
Image representation of the vehicle.
- Channel Group ID is **image**
- Available for all vehicles
- Read/Write access
| Channel ID | Type | Access | Description |
|---------------------|--------|----------|---------------------------|
| png | Image | Read | The image as png |
| view | String | Write | The view port of the car |
Possible view ports:
- _VehicleStatus_ Front Left Side View
- _FrontView_ Front View
- _FrontLeft_ Front Left Side View
- _FrontRight_ Front Right Side View
- _RearView_ Rear View
## Further Descriptions
### Dynamic Data
<img align="right" src="doc/SessionOptions.png" alt="Session options example" width="400" height="250"/>
There are 3 occurrences of dynamic data delivered
- Upcoming Services delivered in group [Services](#services)
- Check Control Messages delivered in group [Check Control](#check-control)
- Charging Session data delivered in group [Charge Sessions](#charge-sessions)
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.
### TroubleShooting
BMW has a high range of vehicles supported by their API.
In case of any issues with this binding help to resolve it!
Please perform the following steps:
- Can you log into MyBMW App with your credentials?
- Is the vehicle listed in your account?
- Is the [MyBMW Brige](#bridge) status _Online_?
If these preconditions are fulfilled proceed with the fingerprint generation.
#### Generate Debug Fingerprint
Login to the openHAB console and use the `mybmw fingerprint` command.
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.
You can restrict the accounts and vehicles for the fingerprint generation.
Full syntax is available through the `mybmw help` console command.
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 in the JSON response and URL's.
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!
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" alt="Range radius example" width="400" height="350"/>
You will observe differences in the vehicle range and range radius values.
While range is indicating the possible distance to be driven on roads the range radius indicates the reachable range on the map.
The right picture shows the distance between Kassel and Frankfurt in Germany.
While the air-line distance is 145 kilometers the route distance is 192 kilometers.
So range value is the normal remaining range while the range radius values can be used e.g. on [Mapview](https://www.openhab.org/docs/ui/sitemaps.html#element-type-mapview) to indicate the reachable range on map.
Please note this is just an indicator of the effective range.
Especially for electric vehicles it depends on many factors like driving style and usage of electric consumers.
## Full Example
The example is based on a BMW i3 with range extender (REX).
Exchange configuration parameters in the Things section
- 4711 - any id you want
- YOUR_USERNAME - with your MyBMW login username
- YOUR_PASSWORD - with your MyBMW password credentials
- VEHICLE_VIN - the vehicle identification number
In addition search for all occurrences of _i3_ and replace it with your Vehicle Identification like _x3_ or _535d_ and you're ready to go!
### Things File
```java
Bridge mybmw:account:4711 "MyBMW Account" [userName="YOUR_USERNAME",password="YOUR_PASSWORD",region="ROW"] {
Thing bev_rex i3 "BMW i3 94h REX" [ vin="VEHICLE_VIN",refreshInterval=5,vehicleBrand="BMW"]
}
```
### Items File
```java
Number:Length i3Mileage "Odometer [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:range#mileage" }
Number:Length i3Range "Range [%d %unit%]" <motion> (i3) {channel="mybmw:bev_rex:4711:i3:range#hybrid"}
Number:Length i3RangeElectric "Electric Range [%d %unit%]" <motion> (i3,long) {channel="mybmw:bev_rex:4711:i3:range#electric"}
Number:Length i3RangeFuel "Fuel Range [%d %unit%]" <motion> (i3) {channel="mybmw:bev_rex:4711:i3:range#fuel"}
Number:Dimensionless i3BatterySoc "Battery Charge [%.1f %%]" <battery> (i3,long) {channel="mybmw:bev_rex:4711:i3:range#soc"}
Number:Volume i3Fuel "Fuel [%.1f %unit%]" <oil> (i3) {channel="mybmw:bev_rex:4711:i3:range#remaining-fuel"}
Number:Length i3RadiusElectric "Electric Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-electric" }
Number:Length i3RadiusFuel "Fuel Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-fuel" }
Number:Length i3RadiusHybrid "Hybrid Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-hybrid" }
String i3DoorStatus "Door Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#doors" }
String i3WindowStatus "Window Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#windows" }
String i3LockStatus "Lock Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#lock" }
DateTime i3NextServiceDate "Next Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:status#service-date" }
String i3NextServiceMileage "Next Service Mileage [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:status#service-mileage" }
String i3CheckControl "Check Control [%s]" <error> (i3) {channel="mybmw:bev_rex:4711:i3:status#check-control" }
String i3PlugConnection "Plug [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#plug-connection" }
String i3ChargingStatus "[%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#charge" }
String i3ChargingInfo "[%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#charge-info" }
DateTime i3LastUpdate "Update [%1$tA, %1$td.%1$tm. %1$tH:%1$tM]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:status#last-update"}
Location i3Location "Location [%s]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:location#gps" }
Number:Angle i3Heading "Heading [%.1f %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:location#heading" }
String i3RemoteCommand "Command [%s]" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:remote#command" }
String i3RemoteState "Remote Execution State [%s]" <status> (i3) {channel="mybmw:bev_rex:4711:i3:remote#state" }
String i3DriverDoor "Driver Door [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#driver-front" }
String i3DriverDoorRear "Driver Door Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#driver-rear" }
String i3PassengerDoor "Passenger Door [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#passenger-front" }
String i3PassengerDoorRear "Passenger Door Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#passenger-rear" }
String i3Hood "Hood [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#hood" }
String i3Trunk "Trunk [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#trunk" }
String i3DriverWindow "Driver Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-driver-front" }
String i3DriverWindowRear "Driver Window Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-driver-rear" }
String i3PassengerWindow "Passenger Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-passenger-front" }
String i3PassengerWindowRear "Passenger Window Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-passenger-rear" }
String i3RearWindow "Rear Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-rear" }
String i3Sunroof "Sunroof [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#sunroof" }
String i3ServiceName "Service Name [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:service#name" }
String i3ServiceDetails "Service Details [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:service#details" }
Number:Length i3ServiceMileage "Service Mileage [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:service#mileage" }
DateTime i3ServiceDate "Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:service#date" }
String i3CCName "CheckControl Name [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:check#name" }
String i3CCDetails "CheckControl Details [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:check#details" }
String i3CCSeverity "CheckControl Severity [%s]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:check#severity" }
Switch i3ChargeProfileClimate "Charge Profile Climatization" <temperature> (i3) {channel="mybmw:bev_rex:4711:i3:profile#climate" }
String i3ChargeProfileMode "Charge Profile Mode [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#mode" }
String i3ChargeProfilePrefs "Charge Profile Preference [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#prefs" }
String i3ChargeProfileCtrl "Charge Profile Control [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#control" }
Number i3ChargeProfileTarget "Charge Profile SoC Target [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#target" }
Switch i3ChargeProfileLimit "Charge Profile limited" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#limit" }
DateTime i3ChargeWindowStart "Charge Window Start [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#window-start" }
DateTime i3ChargeWindowEnd "Charge Window End [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#window-end" }
DateTime i3Timer1Departure "Timer 1 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-departure" }
String i3Timer1Days "Timer 1 Days [%s]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-days" }
Switch i3Timer1DayMon "Timer 1 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-mon" }
Switch i3Timer1DayTue "Timer 1 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-tue" }
Switch i3Timer1DayWed "Timer 1 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-wed" }
Switch i3Timer1DayThu "Timer 1 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-thu" }
Switch i3Timer1DayFri "Timer 1 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-fri" }
Switch i3Timer1DaySat "Timer 1 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-sat" }
Switch i3Timer1DaySun "Timer 1 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-sun" }
Switch i3Timer1Enabled "Timer 1 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-enabled" }
DateTime i3Timer2Departure "Timer 2 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-departure" }
Switch i3Timer2DayMon "Timer 2 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-mon" }
Switch i3Timer2DayTue "Timer 2 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-tue" }
Switch i3Timer2DayWed "Timer 2 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-wed" }
Switch i3Timer2DayThu "Timer 2 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-thu" }
Switch i3Timer2DayFri "Timer 2 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-fri" }
Switch i3Timer2DaySat "Timer 2 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-sat" }
Switch i3Timer2DaySun "Timer 2 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-sun" }
Switch i3Timer2Enabled "Timer 2 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-enabled" }
DateTime i3Timer3Departure "Timer 3 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-departure" }
Switch i3Timer3DayMon "Timer 3 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-mon" }
Switch i3Timer3DayTue "Timer 3 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-tue" }
Switch i3Timer3DayWed "Timer 3 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-wed" }
Switch i3Timer3DayThu "Timer 3 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-thu" }
Switch i3Timer3DayFri "Timer 3 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-fri" }
Switch i3Timer3DaySat "Timer 3 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-sat" }
Switch i3Timer3DaySun "Timer 3 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-sun" }
Switch i3Timer3Enabled "Timer 3 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-enabled" }
DateTime i3Timer4Departure "Timer 4 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-departure" }
Switch i3Timer4DayMon "Timer 4 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-mon" }
Switch i3Timer4DayTue "Timer 4 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-tue" }
Switch i3Timer4DayWed "Timer 4 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-wed" }
Switch i3Timer4DayThu "Timer 4 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-thu" }
Switch i3Timer4DayFri "Timer 4 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-fri" }
Switch i3Timer4DaySat "Timer 4 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-sat" }
Switch i3Timer4DaySun "Timer 4 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-sun" }
Switch i3Timer4Enabled "Timer 4 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-enabled" }
String i3StatisticsTitle "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#title" }
Number:Energy i3StatisticsEnergy "Charged [%d %unit%]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#energy" }
Number i3StatisticsSessions "Sessions [%d]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#sessions" }
String i3SessionTitle "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#title" }
String i3SessionDetails "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#subtitle" }
String i3SessionCharged "Energy Charged [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:session#energy" }
String i3SessionProblems "Problems [%s]" <error> (i3) {channel="mybmw:bev_rex:4711:i3:session#issue" }
String i3SessionStatus "Session status [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#status" }
Number:Pressure i3TireFLCurrent "Tire Front Left [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fl-current" }
Number:Pressure i3TireFLTarget "Tire Front Left Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fl-target" }
Number:Pressure i3TireFRCurrent "Tire Front Right [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fr-current" }
Number:Pressure i3TireFRTarget "Tire Front Right Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fr-target" }
Number:Pressure i3TireRLCurrent "Tire Rear Left [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rl-current" }
Number:Pressure i3TireRLTarget "Tire Rear Left Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rl-target" }
Number:Pressure i3TireRRCurrent "Tire Rear Right [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rr-current" }
Number:Pressure i3TireRRTarget "Tire Rear Right Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rr-target" }
Image i3Image "Image" (i3) {channel="mybmw:bev_rex:4711:i3:image#png" }
String i3ImageViewport "Image Viewport [%s]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:image#view" }
```
### Sitemap File
```perl
sitemap BMW label="BMW" {
Frame label="BMW i3" {
Image item=i3Image
}
Frame label="Status" {
Text item=i3DoorStatus
Text item=i3WindowStatus
Text item=i3LockStatus
Text item=i3NextServiceDate
Text item=i3NextServiceMileage
Text item=i3CheckControl
Text item=i3ChargingStatus
Text item=i3LastUpdate
}
Frame label="Range" {
Text item=i3Mileage
Text item=i3Range
Text item=i3RangeElectric
Text item=i3RangeFuel
Text item=i3BatterySoc
Text item=i3Fuel
Text item=i3RadiusElectric
Text item=i3RadiusHybrid
}
Frame label="Remote Services" {
Selection item=i3RemoteCommand
Text item=i3RemoteState
}
Frame label="Services" {
Selection item=i3ServiceName
Text item=i3ServiceDetails
Text item=i3ServiceMileage
Text item=i3ServiceDate
}
Frame label="CheckControl" {
Selection item=i3CCName
Text item=i3CCDetails
Text item=i3CCSeverity
}
Frame label="Door Details" {
Text item=i3DriverDoor visibility=[i3DriverDoor!="INVALID"]
Text item=i3DriverDoorRear visibility=[i3DriverDoorRear!="INVALID"]
Text item=i3PassengerDoor visibility=[i3PassengerDoor!="INVALID"]
Text item=i3PassengerDoorRear visibility=[i3PassengerDoorRear!="INVALID"]
Text item=i3Hood visibility=[i3Hood!="INVALID"]
Text item=i3Trunk visibility=[i3Trunk!="INVALID"]
Text item=i3DriverWindow visibility=[i3DriverWindow!="INVALID"]
Text item=i3DriverWindowRear visibility=[i3DriverWindowRear!="INVALID"]
Text item=i3PassengerWindow visibility=[i3PassengerWindow!="INVALID"]
Text item=i3PassengerWindowRear visibility=[i3PassengerWindowRear!="INVALID"]
Text item=i3RearWindow visibility=[i3RearWindow!="INVALID"]
Text item=i3Sunroof visibility=[i3Sunroof!="INVALID"]
}
Frame label="Location" {
Text item=i3Location
Text item=i3Heading
}
Frame label="Charge Profile" {
Switch item=i3ChargeProfileClimate
Selection item=i3ChargeProfileMode
Text item=i3ChargeWindowStart
Text item=i3ChargeWindowEnd
Text item=i3Timer1Departure
Switch item=i3Timer1DayMon
Switch item=i3Timer1DayTue
Switch item=i3Timer1DayWed
Switch item=i3Timer1DayThu
Switch item=i3Timer1DayFri
Switch item=i3Timer1DaySat
Switch item=i3Timer1DaySun
Switch item=i3Timer1Enabled
Text item=i3Timer2Departure
Switch item=i3Timer2DayMon
Switch item=i3Timer2DayTue
Switch item=i3Timer2DayWed
Switch item=i3Timer2DayThu
Switch item=i3Timer2DayFri
Switch item=i3Timer2DaySat
Switch item=i3Timer2DaySun
Switch item=i3Timer2Enabled
Text item=i3Timer3Departure
Switch item=i3Timer3DayMon
Switch item=i3Timer3DayTue
Switch item=i3Timer3DayWed
Switch item=i3Timer3DayThu
Switch item=i3Timer3DayFri
Switch item=i3Timer3DaySat
Switch item=i3Timer3DaySun
Switch item=i3Timer3Enabled
Text item=i3Timer4Departure
Switch item=i3Timer4DayMon
Switch item=i3Timer4DayTue
Switch item=i3Timer4DayWed
Switch item=i3Timer4DayThu
Switch item=i3Timer4DayFri
Switch item=i3Timer4DaySat
Switch item=i3Timer4DaySun
Switch item=i3Timer4Enabled
}
Frame label="Charge Statistics" {
Text item=i3StatisticsTitle
Text item=i3StatisticsEnergy
Text item=i3StatisticsSessions
}
Frame label="Charge Sessions" {
Selection item=i3SessionTitle
Text item=i3SessionDetails
Text item=i3SessionCharged
Text item=i3SessionProblems
Text item=i3SessionStatus
}
Frame label="Tires" {
Text item=i3TireFLCurrent
Text item=i3TireFLTarget
Text item=i3TireFRCurrent
Text item=i3TireFRTarget
Text item=i3TireRLCurrent
Text item=i3TireRLTarget
Text item=i3TireRRCurrent
Text item=i3TireRRTarget
}
Frame label="Image Properties" {
Selection item=i3ImageViewport
}
}
```
## Credits
This work is based on the great work of the project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

-182
View File
@@ -1,182 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.mybmw</artifactId>
<name>openHAB Add-ons :: Bundles :: MyBMW Binding</name>
<profiles>
<profile>
<id>test-coverage</id>
<dependencies>
<dependency>
<!-- must be on the classpath -->
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>0.8.14</version>
<classifier>runtime</classifier>
<scope>test</scope>
</dependency>
</dependencies>
<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.14</version>
<executions>
<execution>
<id>default-instrument</id>
<goals>
<goal>instrument</goal>
</goals>
</execution>
<execution>
<id>default-restore-instrumented-classes</id>
<goals>
<goal>restore-instrumented-classes</goal>
</goals>
<phase>test</phase>
</execution>
<execution>
<id>default-report</id>
<goals>
<goal>report</goal>
</goals>
<phase>test</phase>
</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>
</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.5.5</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>
<executions>
<execution>
<id>copy-resources</id>
<goals>
<goal>copy-resources</goal>
</goals>
<!-- here the phase you need -->
<phase>package</phase>
<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>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
<phase>package</phase>
<configuration>
<classifier>testenv</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.mybmw-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${project.version}/xml/features</repository>
<feature name="openhab-binding-mybmw" description="MyBMW Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mybmw/${project.version}</bundle>
</feature>
</features>
@@ -1,125 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 and added hcaptchastring
* @author Mark Herwege - added authorisation servlet
*/
@NonNullByDefault
public class MyBMWBridgeConfiguration {
/**
* Depending on the location the correct server needs to be called
*/
private String region = Constants.EMPTY;
/**
* MyBMW App Username
*/
private String userName = Constants.EMPTY;
/**
* MyBMW App Password
*/
private String password = Constants.EMPTY;
/**
* Preferred Locale language
*/
private String language = Constants.LANGUAGE_AUTODETECT;
/**
* the hCaptcha string
*/
private String hcaptchatoken = Constants.EMPTY;
/**
* the callback IP address for the authorisation servlet
*/
private String callbackIP = Constants.EMPTY;
/**
* the callback port for the authorisation servlet
*/
private int callbackPort = 8090;
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getHCaptchaToken() {
return hcaptchatoken;
}
public void setHCaptchaToken(String hcaptchatoken) {
this.hcaptchatoken = hcaptchatoken;
}
public String getCallbackIP() {
return Constants.EMPTY.equals(callbackIP) ? "" : callbackIP;
}
public void setCallbackIP(String callbackIP) {
this.callbackIP = callbackIP;
}
public int getCallbackPort() {
return callbackPort;
}
public void setCallbackPort(int callbackPort) {
this.callbackPort = callbackPort;
}
@Override
public String toString() {
return "MyBMWBridgeConfiguration [region=" + region + ", userName=" + userName + ", password=" + password
+ ", language=" + language + ", hcaptchatoken=" + hcaptchatoken + ", callbackAddress=" + callbackIP
+ ":" + callbackPort + "]";
}
}
@@ -1,256 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link MyBMWConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit and send of charge profile
* @author Martin Grassl - updated enum values
*/
@NonNullByDefault
public interface MyBMWConstants {
static final String BINDING_ID = "mybmw";
static final String VIN = "vin";
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 = 60;
// Captcha servlet constants
static final String LOCAL_OPENHAB_BASE_PATH = "/" + BINDING_ID + "/";
static final String CAPTCHA_URL_ROOT = "captcha/";
static final String NORTH_AMERICA = "NORTH_AMERICA";
static final String ROW = "ROW";
static final Map<String, String> CAPTCHA_HTML = Map.of(NORTH_AMERICA, CAPTCHA_URL_ROOT + "north_america_form.html",
ROW, CAPTCHA_URL_ROOT + "rest_of_world_form.html");
// Thing status detail messages
static final String STATUS_AUTH_NEEDED = "@text/mybmw.status.authorization-needed";
static final String STATUS_USER_DETAILS_MISSING = "@text/mybmw.status.user-details-missing";
static final String STATUS_REGION_MISSING = "@text/mybmw.status.region-missing";
static final String STATUS_IP_MISSING = "@text/mybmw.status.ip-missing";
static final String STATUS_VEHICLE_RETRIEVAL_ERROR = "@text/mybmw.status.vehicle-retrieval-error";
static final String STATUS_NETWORK_ERROR = "@text/mybmw.status.network-error";
static final String STATUS_QUOTA_ERROR = "@text/mybmw.status.quota-error";
// See constants from bimmer-connected
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
enum VehicleType {
CONVENTIONAL("conv"),
PLUGIN_HYBRID("phev"),
MILD_HYBRID("hybrid"),
ELECTRIC_REX("bev_rex"),
ELECTRIC("bev"),
UNKNOWN("unknown");
private final String type;
VehicleType(String s) {
type = s;
}
@Override
public String toString() {
return type;
}
}
enum ChargingMode {
IMMEDIATE_CHARGING,
DELAYED_CHARGING
}
enum ChargingPreference {
NO_PRESELECTION,
CHARGING_WINDOW
}
static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.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
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
static final String CHANNEL_GROUP_UPDATE = "update";
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";
// types of updates
static final String STATE_UPDATE = "state-update";
static final String CHARGING_UPDATE = "charging-update";
static final String IMAGE_UPDATE = "image-update";
// Charge Statistics & Sessions
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
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
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
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
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
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
static final String IMAGE_FORMAT = "png";
static final String IMAGE_VIEWPORT = "view";
// Remote Services
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_START_CHARGING = "start-charging";
static final String REMOTE_SERVICE_STOP_CHARGING = "stop-charging";
static final String REMOTE_SERVICE_COMMAND = "command";
static final String REMOTE_STATE = "state";
// TIRES
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";
}
@@ -1,92 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
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.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
/**
* The {@link MyBMWHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - changed localeProvider handling
*/
@NonNullByDefault
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
private final HttpClientFactory httpClientFactory;
private final OAuthFactory oAuthFactory;
private final MyBMWCommandOptionProvider commandOptionProvider;
private final NetworkAddressService networkAddressService;
private final HttpService httpService;
private final LocationProvider locationProvider;
private final TimeZoneProvider timeZoneProvider;
private final LocaleProvider localeProvider;
@Activate
public MyBMWHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference OAuthFactory oAuthFactory,
final @Reference MyBMWCommandOptionProvider commandOptionProvider,
final @Reference NetworkAddressService networkAddressService, final @Reference HttpService httpService,
final @Reference LocaleProvider localeProvider, final @Reference LocationProvider locationProvider,
final @Reference TimeZoneProvider timeZoneProvider) {
this.httpClientFactory = httpClientFactory;
this.oAuthFactory = oAuthFactory;
this.commandOptionProvider = commandOptionProvider;
this.networkAddressService = networkAddressService;
this.httpService = httpService;
this.locationProvider = locationProvider;
this.timeZoneProvider = timeZoneProvider;
this.localeProvider = localeProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_SET.contains(thingTypeUID);
}
@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, oAuthFactory, httpService,
networkAddressService, localeProvider);
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
return new VehicleHandler(thing, commandOptionProvider, locationProvider, timeZoneProvider,
thingTypeUID.getId());
}
return null;
}
}
@@ -1,97 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mybmw.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mybmw.internal.utils.Constants;
/**
* The {@link MyBMWVehicleConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - renaming and refactoring to Java Beans
*/
@NonNullByDefault
public class MyBMWVehicleConfiguration {
/**
* Vehicle Identification Number (VIN)
*/
private String vin = Constants.EMPTY;
/**
* Vehicle brand
* - bmw
* - bmw_i
* - mini
*/
private String vehicleBrand = Constants.EMPTY;
/**
* Data refresh rate in minutes
*/
private int refreshInterval = MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
/**
* @return the vin
*/
public String getVin() {
return vin;
}
/**
* @param vin the vin to set
*/
public void setVin(String vin) {
this.vin = vin;
}
/**
* @return the vehicleBrand
*/
public String getVehicleBrand() {
return vehicleBrand;
}
/**
* @param vehicleBrand the vehicleBrand to set
*/
public void setVehicleBrand(String vehicleBrand) {
this.vehicleBrand = vehicleBrand;
}
/**
* @return the refreshInterval
*/
public int getRefreshInterval() {
return refreshInterval;
}
/**
* @param refreshInterval the refreshInterval to set
*/
public void setRefreshInterval(int refreshInterval) {
this.refreshInterval = refreshInterval;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "MyBMWVehicleConfiguration [vin=" + vin + ", vehicleBrand=" + vehicleBrand + ", refreshInterval="
+ refreshInterval + "]";
}
}
@@ -1,325 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.*;
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.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<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);
Objects.requireNonNull(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;
}
}
@@ -1,235 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
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.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.AbstractThingHandlerDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VehicleDiscovery} requests data from BMW API and is identifying
* the Vehicles after response
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - refactoring
*/
@Component(scope = ServiceScope.PROTOTYPE, service = VehicleDiscovery.class)
@NonNullByDefault
public class VehicleDiscovery extends AbstractThingHandlerDiscoveryService<MyBMWBridgeHandler> {
private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
private static final int DISCOVERY_TIMEOUT = 10;
private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
private @NonNullByDefault({}) ThingUID bridgeUid;
public VehicleDiscovery() {
super(MyBMWBridgeHandler.class, MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
}
/**
* Constructor for tests only.
*
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
*/
VehicleDiscovery(ScheduledExecutorService scheduler) {
super(scheduler, MyBMWBridgeHandler.class, MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false, null,
null);
}
@Override
public void initialize() {
thingHandler.setVehicleDiscovery(this);
bridgeUid = thingHandler.getThing().getUID();
super.initialize();
}
@Override
protected void startScan() {
logger.trace("VehicleDiscovery.startScan");
discoverVehicles();
}
public void discoverVehicles() {
logger.trace("VehicleDiscovery.discoverVehicles");
myBMWProxy = thingHandler.getMyBmwProxy();
try {
Optional<List<Vehicle>> vehicleList = myBMWProxy.map(prox -> {
try {
return prox.requestVehicles();
} catch (NetworkException e) {
throw new IllegalStateException(e);
}
});
vehicleList.ifPresentOrElse(vehicles -> {
if (!vehicles.isEmpty()) {
thingHandler.vehicleDiscoverySuccess();
processVehicles(vehicles);
} else {
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
}
}, () -> thingHandler.vehicleDiscoveryError(Constants.EMPTY));
} catch (IllegalStateException ex) {
NetworkException ne = (NetworkException) ex.getCause();
if (ne != null && (ne.getStatus() == 403 || ne.getStatus() == 429)) {
thingHandler.vehicleQuotaDiscoveryError(myBMWProxy.get().getNextQuota());
} else {
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
}
}
}
/**
* 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
*/
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.getId());
Map<String, String> properties = generateProperties(vehicle);
boolean thingFound = false;
// Update Properties for already created Things
List<Thing> vehicleThings = thingHandler.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<>(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)
.withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
.withProperties(convertedProperties).build());
}
}
});
});
}
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);
}
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;
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.auth;
import org.openhab.binding.mybmw.internal.utils.Constants;
import com.google.gson.annotations.SerializedName;
/**
* The {@link AuthResponse} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - extracted from myBmwProxy
*/
public class AuthResponse {
@SerializedName("access_token")
public String accessToken = Constants.EMPTY;
@SerializedName("refresh_token")
public String refreshToken = Constants.EMPTY;
@SerializedName("token_type")
public String tokenType = Constants.EMPTY;
@SerializedName("gcid")
public String gcid = Constants.EMPTY;
@SerializedName("expires_in")
public int expiresIn = -1;
@Override
public String toString() {
return "AuthResponse [accessToken=" + accessToken + ", refreshToken=" + refreshToken + ", tokenType="
+ tokenType + ", gcid=" + gcid + ", expiresIn=" + expiresIn + "]";
}
}
@@ -1,70 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.auth;
import java.util.List;
import org.openhab.binding.mybmw.internal.utils.Constants;
/**
* The {@link OAuthSettingsQueryResponse} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - add toString for debugging
*/
public class OAuthSettingsQueryResponse {
public String clientName;// ": "mybmwapp",
public String clientSecret;// ": "c0e3393d-70a2-4f6f-9d3c-8530af64d552",
public String clientId;// ": "31c357a0-7a1d-4590-aa99-33b97244d048",
public String gcdmBaseUrl;// ": "https://customer.bmwgroup.com",
public String returnUrl;// ": "com.bmw.connected://oauth",
public String brand;// ": "bmw",
public String language;// ": "en",
public String country;// ": "US",
public String authorizationEndpoint;// ": "https://customer.bmwgroup.com/oneid/login",
public String tokenEndpoint;// ": "https://customer.bmwgroup.com/gcdm/oauth/token",
private List<String> scopes;// ;": [
// "openid",
// "profile",
// "email",
// "offline_access",
// "smacc",
// "vehicle_data",
// "perseus",
// "dlm",
// "svds",
// "cesim",
// "vsapi",
// "remote_services",
// "fupo",
// "authenticate_user"
// ],
public List<String> promptValues; // ": ["login"]
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String scopes() {
return String.join(Constants.SPACE, scopes);
}
@Override
public String toString() {
return "AuthQueryResponse [clientName=" + clientName + ", clientSecret=" + clientSecret + ", clientId="
+ clientId + ", gcdmBaseUrl=" + gcdmBaseUrl + ", returnUrl=" + returnUrl + ", brand=" + brand
+ ", language=" + language + ", country=" + country + ", authorizationEndpoint=" + authorizationEndpoint
+ ", tokenEndpoint=" + tokenEndpoint + ", scopes=" + scopes + ", promptValues=" + promptValues + "]";
}
}
@@ -1,80 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,96 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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
+ "]";
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,23 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
}
@@ -1,73 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 ChargingSettings} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - refactored to Java Bean
*/
public class ChargingSettings {
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 + "]";
}
}
@@ -1,110 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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
+ "]";
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 ChargingWindow} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - refactored to Java Bean
*/
public class ChargingWindow {
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 + "]";
}
}
@@ -1,81 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 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 {
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 "Time [hour=" + hour + ", minute=" + minute + "]";
}
}
@@ -1,35 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
import org.openhab.binding.mybmw.internal.utils.Constants;
/**
* The {@link Timer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit and send of charge profile
*/
public class Timer {
public int id = -1;// ": 1,
public String action;// ": "deactivate",
public Time timeStamp;
public List<String> timerWeekDays;
@Override
public String toString() {
return id + Constants.COLON + action + Constants.COLON + timeStamp + Constants.COLON + timerWeekDays;
}
}
@@ -1,31 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.remote;
/**
* The {@link ExecutionError} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class ExecutionError {
public String title;// ": "Etwas ist schiefgelaufen",
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
// ausgeführt werden, wenn die Fahrertür geschlossen und der Türstatus bekannt ist.",
public String presentationType;// ": "PAGE",
public int iconId;// ": 60217,
public boolean isRetriable;// ": true,
public String errorDetails;// ": "NACK"
}
@@ -1,64 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.remote;
/**
* The {@link ExecutionStatusContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - refactored to Java Bean
*/
public class ExecutionStatusContainer {
private String eventId = "";
private String creationTime = "";
private String eventStatus = "";
private ExecutionError errorDetails = null;
public String getEventId() {
return eventId;
}
public void setEventId(String eventId) {
this.eventId = eventId;
}
public String getCreationTime() {
return creationTime;
}
public void setCreationTime(String creationTime) {
this.creationTime = creationTime;
}
public String getEventStatus() {
return eventStatus;
}
public void setEventStatus(String eventStatus) {
this.eventStatus = eventStatus;
}
public ExecutionError getErrorDetails() {
return errorDetails;
}
public void setErrorDetails(ExecutionError errorDetails) {
this.errorDetails = errorDetails;
}
@Override
public String toString() {
return "ExecutionStatusContainer [eventId=" + eventId + ", creationTime=" + creationTime + ", eventStatus="
+ eventStatus + ", errorDetails=" + errorDetails + "]";
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,75 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,36 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,67 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,55 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,55 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,36 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,142 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,73 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 Vehicle} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - refactored for v2 API
*/
public class Vehicle {
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 + "]";
}
}
@@ -1,148 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,224 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,101 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,54 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,231 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
}
}
@@ -1,55 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,121 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,46 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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
+ "]";
}
}
@@ -1,64 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,82 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 + "]";
}
}
@@ -1,265 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
import org.openhab.binding.mybmw.internal.MyBMWConstants;
import org.openhab.binding.mybmw.internal.discovery.VehicleDiscovery;
import org.openhab.binding.mybmw.internal.handler.auth.MyBMWAuthServlet;
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.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.NetUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 {
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 final HttpClient httpClient;
private final OAuthFactory oAuthFactory;
private final HttpService httpService;
private final NetworkAddressService networkAddressService;
private Optional<MyBMWProxy> myBmwProxy = Optional.empty();
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
private Optional<VehicleDiscovery> vehicleDiscovery = Optional.empty();
private LocaleProvider localeProvider;
private CompletableFuture<Boolean> isInitialized = new CompletableFuture<>();
private Optional<MyBMWAuthServlet> authServlet = Optional.empty();
private boolean tokenInitError = false;
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory, OAuthFactory oAuthFactory,
HttpService httpService, NetworkAddressService networkAddressService, LocaleProvider localeProvider) {
super(bridge);
this.httpClient = httpClientFactory.getCommonHttpClient();
this.oAuthFactory = oAuthFactory;
this.httpService = httpService;
this.networkAddressService = networkAddressService;
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() {
isInitialized = new CompletableFuture<>();
tokenInitError = false;
logger.trace("MyBMWBridgeHandler.initialize");
updateStatus(ThingStatus.UNKNOWN);
MyBMWBridgeConfiguration localBridgeConfiguration = getConfigAs(MyBMWBridgeConfiguration.class);
if (Constants.EMPTY.equals(localBridgeConfiguration.getUserName())
|| Constants.EMPTY.equals(localBridgeConfiguration.getPassword())) {
logger.warn("username or password no set");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
MyBMWConstants.STATUS_USER_DETAILS_MISSING);
return;
}
if (Constants.EMPTY.equals(localBridgeConfiguration.getRegion())) {
logger.warn("region not set");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
MyBMWConstants.STATUS_REGION_MISSING);
return;
}
Configuration config = super.editConfiguration();
if (Constants.LANGUAGE_AUTODETECT.equals(localBridgeConfiguration.getLanguage())) {
config.put("language", localeProvider.getLocale().getLanguage().toLowerCase());
}
String ipConfig = localBridgeConfiguration.getCallbackIP();
if (Constants.EMPTY.equals(ipConfig) || NetUtil.getAllInterfaceAddresses().stream()
.map(cidr -> cidr.getAddress().getHostAddress()).noneMatch(a -> ipConfig.equals(a))) {
String ip = networkAddressService.getPrimaryIpv4HostAddress();
if (ip != null) {
config.put("callbackIP", ipConfig);
} else {
logger.warn("the callback IP address could not be retrieved");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
MyBMWConstants.STATUS_IP_MISSING);
return;
}
}
// Update the central configuration and get the updates configuration back
super.updateConfiguration(config);
localBridgeConfiguration = getConfigAs(MyBMWBridgeConfiguration.class);
// 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(localBridgeConfiguration, environment);
initializerJob = Optional.of(scheduler.schedule(this::discoverVehicles, 2, TimeUnit.SECONDS));
isInitialized.complete(true);
}
private void createMyBmwProxy(MyBMWBridgeConfiguration config, String environment) {
if (!myBmwProxy.isPresent()) {
if (!(TEST.equals(environment) && TESTUSER.equals(config.getUserName()))) {
myBmwProxy = Optional.of(new MyBMWHttpProxy(this, httpClient, oAuthFactory, config));
} else {
myBmwProxy = Optional.of(new MyBMWFileProxy(httpClient, config));
}
logger.trace("MyBMWBridgeHandler proxy set");
} else {
myBmwProxy.get().setBridgeConfiguration(config);
logger.trace("MyBMWBridgeHandler update proxy with bridge configuration");
}
}
@Override
public void dispose() {
logger.trace("MyBMWBridgeHandler.dispose");
initializerJob.ifPresent(job -> job.cancel(true));
authServlet.ifPresent(servlet -> servlet.dispose());
authServlet = Optional.empty();
isInitialized.cancel(true);
}
public void vehicleDiscoveryError(String message) {
logger.trace("MyBMWBridgeHandler.vehicleDiscoveryError");
if (!tokenInitError) {
String errorMessage = message.isEmpty() ? MyBMWConstants.STATUS_VEHICLE_RETRIEVAL_ERROR : message;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
}
}
public void vehicleQuotaDiscoveryError(Instant nextQuota) {
logger.trace("MyBMWBridgeHandler.vehicleQuotaDiscoveryError");
if (!tokenInitError) {
String timeString = DateTimeFormatter.ofPattern("HH:mm:ss")
.format(LocalDateTime.ofInstant(nextQuota, ZoneId.systemDefault()));
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
(MyBMWConstants.STATUS_QUOTA_ERROR + " [%s]").formatted(timeString));
}
}
public void vehicleDiscoverySuccess() {
logger.trace("MyBMWBridgeHandler.vehicleDiscoverySuccess");
updateStatus(ThingStatus.ONLINE);
}
private void discoverVehicles() {
logger.trace("MyBMWBridgeHandler.requestVehicles");
vehicleDiscovery.ifPresent(discovery -> discovery.discoverVehicles());
}
public void tokenInitError() {
Configuration config = super.editConfiguration();
config.remove("hcaptchatoken");
super.updateConfiguration(config);
authServlet.ifPresent(servlet -> servlet.dispose());
MyBMWAuthServlet servlet = new MyBMWAuthServlet(this, getConfigAs(MyBMWBridgeConfiguration.class).getRegion(),
httpService);
servlet.startListening();
this.authServlet = Optional.of(servlet);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
(MyBMWConstants.STATUS_AUTH_NEEDED + " [ \"http(s)://<YOUROPENHAB>:<YOURPORT>%s\" ]")
.formatted(servlet.getPath()));
tokenInitError = true;
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
logger.trace("MyBMWBridgeHandler.getServices");
return List.of(VehicleDiscovery.class);
}
public Optional<MyBMWProxy> getMyBmwProxy() {
// wait for initialization to complete
try {
isInitialized.get();
} catch (InterruptedException | ExecutionException e) {
logger.debug("exception waiting for bridge initialization: {}", e.toString());
}
return myBmwProxy;
}
public void setHCaptchaToken(String hCaptchaToken) {
Configuration config = super.editConfiguration();
config.put("hcaptchatoken", hCaptchaToken);
super.updateConfiguration(config);
if (!hCaptchaToken.isEmpty()) {
initializerJob.ifPresent(job -> job.cancel(true));
authServlet.ifPresent(servlet -> servlet.dispose());
authServlet = Optional.empty();
initialize();
}
}
}
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.core.events.EventPublisher;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of command options while leaving other state description fields as original.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
@Component(service = { DynamicCommandDescriptionProvider.class, MyBMWCommandOptionProvider.class })
public class MyBMWCommandOptionProvider extends BaseDynamicCommandDescriptionProvider {
@Activate
public MyBMWCommandOptionProvider(final @Reference EventPublisher eventPublisher, //
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}
@@ -1,164 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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(Constants.EMPTY),
ExecutionState.TIMEOUT.name().toLowerCase());
reset();
// immediately refresh data
handler.updateData();
} 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(Constants.EMPTY),
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(Constants.EMPTY),
ExecutionState.INITIATED.name().toLowerCase());
} else if (!executionStatusContainer.getEventStatus().isEmpty()) {
// service status updated
synchronized (this) {
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
executionStatusContainer.getEventStatus().toLowerCase());
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
|| ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
// refresh loop ends - update of status handled in the normal refreshInterval.
// Earlier update doesn't show better results!
reset();
return;
}
}
}
// schedule even if no result is present until retries exceeded
synchronized (this) {
stateJob.ifPresent(job -> {
if (!job.isDone()) {
job.cancel(true);
}
});
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
}
}
private void reset() {
serviceExecuting = Optional.empty();
executingEventId = Optional.empty();
counter = 0;
}
public void cancel() {
synchronized (this) {
stateJob.ifPresent(action -> {
if (!action.isDone()) {
action.cancel(true);
}
stateJob = Optional.empty();
});
}
}
}
@@ -1,949 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 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.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 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 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<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();
private ThingStatus currentStatus = ThingStatus.UNKNOWN;
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");
}
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;
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");
currentStatus = ThingStatus.UNKNOWN;
updateStatus(currentStatus);
vehicleConfiguration = Optional.of(getConfigAs(MyBMWVehicleConfiguration.class));
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler != null) {
// Can be long running if we have to wait for the bridge to be initialized
scheduler.submit(() -> {
proxy = ((MyBMWBridgeHandler) handler).getMyBmwProxy();
remote = Optional.of(new RemoteServiceExecutor(this, proxy.get()));
imageProperties = new ImageProperties();
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT,
Converter.toTitleCase(imageProperties.viewport), null);
startSchedule(vehicleConfiguration.get().getRefreshInterval());
});
} else {
logger.debug("Bridge Handler null");
}
} else {
logger.debug("Bridge null");
}
}
private void startSchedule(int interval) {
// start update schedule only if the refreshInterval is not 0
if (interval > 0) {
logger.info("VehicleHandler.startSchedule with interval {}min", interval);
refreshJob.ifPresentOrElse(job -> {
if (job.isCancelled()) {
refreshJob = Optional
.of(scheduler.scheduleWithFixedDelay(this::updateData, 0, interval, TimeUnit.MINUTES));
} // else - scheduler is already running!
}, () -> {
refreshJob = Optional
.of(scheduler.scheduleWithFixedDelay(this::updateData, 0, interval, TimeUnit.MINUTES));
});
} else {
logger.info("VehicleHandler initialize: don't start schedule as interval is 0");
updateData();
}
}
@Override
public void dispose() {
logger.trace("VehicleHandler.dispose");
refreshJob.ifPresent(job -> job.cancel(true));
editTimeout.ifPresent(job -> job.cancel(true));
remote.ifPresent(RemoteServiceExecutor::cancel);
}
/**
* update all data
*/
void updateData() {
logger.trace("VehicleHandler.updateData");
updateVehicleStatus();
if (isElectric) {
updateCharging();
}
updateImage();
}
private void updateVehicleStatus() {
proxy.ifPresentOrElse(prox -> {
vehicleConfiguration.ifPresentOrElse(config -> {
try {
VehicleStateContainer vehicleState = prox.requestVehicleState(config.getVin(),
config.getVehicleBrand());
triggerVehicleStatusUpdate(vehicleState, null);
currentStatus = ThingStatus.ONLINE;
updateStatus(currentStatus);
} catch (NetworkException e) {
logger.debug("{}", e.toString());
currentStatus = ThingStatus.OFFLINE;
updateStatus(currentStatus, ThingStatusDetail.COMMUNICATION_ERROR, "Vehicle State Update failed");
}
}, () -> {
logger.warn("MyBMW Vehicle Configuration isn't present");
});
}, () -> {
logger.warn("MyBMWProxy isn't present");
});
}
private void updateCharging() {
proxy.ifPresentOrElse(prox -> {
vehicleConfiguration.ifPresentOrElse(config -> {
if (isElectric && ThingStatus.ONLINE.equals(currentStatus)) {
try {
updateChargingStatistics(
prox.requestChargeStatistics(config.getVin(), config.getVehicleBrand()), null);
updateChargingSessions(prox.requestChargeSessions(config.getVin(), config.getVehicleBrand()),
null);
} catch (NetworkException e) {
logger.debug("{}", e.toString());
}
}
}, () -> {
logger.warn("MyBMW Vehicle Configuration isn't present");
});
}, () -> {
logger.warn("MyBMWProxy isn't present");
});
}
private void updateImage() {
proxy.ifPresentOrElse(prox -> {
vehicleConfiguration.ifPresentOrElse(config -> {
if (!imageCache.isPresent() && !imageProperties.failLimitReached()
&& ThingStatus.ONLINE.equals(currentStatus)) {
try {
updateImage(prox.requestImage(config.getVin(), config.getVehicleBrand(), imageProperties));
} catch (NetworkException e) {
logger.debug("{}", e.toString());
}
}
}, () -> {
logger.warn("MyBMW Vehicle Configuration isn't present");
});
}, () -> {
logger.warn("MyBMWProxy 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);
}
} else {
logger.debug("configuration not present");
}
}
public void updateRemoteExecutionStatus(@Nullable String service, String status) {
updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
(service == null ? "-" : service) + Constants.SPACE + status.toLowerCase(), null);
}
public Optional<MyBMWVehicleConfiguration> getVehicleConfiguration() {
logger.trace("VehicleHandler.getVehicleConfiguration");
return vehicleConfiguration;
}
public ScheduledExecutorService getScheduler() {
logger.trace("VehicleHandler.getScheduler");
return scheduler;
}
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 {
logger.trace("updating channel {}, {}, {}", group, id, "not printed");
}
updateState(new ChannelUID(thing.getUID(), group, id), state);
}
}
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);
}
}
/**
* updates the channels with the current state of the vehicle
*
* @param vehicleStateState
*/
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_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);
// if current selected item isn't anymore in the list select first entry
if (!isSelectedElementIn) {
selectCheckControl(0, channelToBeUpdated);
}
}
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 {
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);
// if current selected item isn't anymore in the list select first entry
if (!isSelectedElementIn) {
selectSession(0, channelToBeUpdated);
}
}
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 {
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);
}
} else if (command instanceof OnOffType) {
if (CHANNEL_GROUP_UPDATE.equals(group) && OnOffType.ON.equals(command)) {
// triggering the update of the respective channel
switch (channelUID.getIdWithoutGroup()) {
case STATE_UPDATE:
updateVehicleStatus();
break;
case CHARGING_UPDATE:
updateCharging();
break;
case IMAGE_UPDATE:
updateImage();
break;
default:
break;
}
}
}
}
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();
}
}
}
}
@@ -1,110 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mybmw.internal.MyBMWConstants;
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link MyBMWAuthServlet} provides captcha html pages
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class MyBMWAuthServlet extends HttpServlet {
private static final long serialVersionUID = 3817341543768551687L;
private static final String CONTENT_TYPE = "text/html;charset=UTF-8";
private final Logger logger = LoggerFactory.getLogger(MyBMWAuthServlet.class);
private final @NonNullByDefault({}) ClassLoader classLoader = MyBMWAuthServlet.class.getClassLoader();
private final HttpService httpService;
private final MyBMWBridgeHandler bridgeHandler;
private String path = "";
private String captchaHtml = "";
public MyBMWAuthServlet(MyBMWBridgeHandler bridgeHandler, String region, HttpService httpService) {
this.httpService = httpService;
this.bridgeHandler = bridgeHandler;
this.path = MyBMWConstants.LOCAL_OPENHAB_BASE_PATH + bridgeHandler.getThing().getUID().getAsString();
String captchaTemplate = MyBMWConstants.CAPTCHA_HTML.get(region);
if (captchaTemplate != null) {
try (InputStream stream = classLoader.getResourceAsStream(captchaTemplate)) {
captchaHtml = stream != null ? new String(stream.readAllBytes(), StandardCharsets.UTF_8) : "";
} catch (IOException e) {
throw new IllegalArgumentException("No captcha html found for region " + region);
}
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.debug("myBMW auth servlet received GET request {}.", request.getRequestURI());
StringBuffer requestUrl = request.getRequestURL();
if (requestUrl != null) {
response.setContentType(CONTENT_TYPE);
response.getWriter().append(captchaHtml);
response.getWriter().close();
} else {
logger.warn("Unexpected: GET requestUrl is null");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
StringBuilder buffer = new StringBuilder();
String line;
try (BufferedReader reader = request.getReader()) {
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
logger.trace("myBMW auth servlet received POST content: {}", buffer.toString());
this.bridgeHandler.setHCaptchaToken(buffer.toString().trim());
}
}
public void startListening() {
try {
httpService.registerServlet(path, this, null, httpService.createDefaultHttpContext());
logger.info("Registered myBMW servlet at '{}'", path);
} catch (NamespaceException | ServletException e) {
logger.warn("Registering servlet failed:{}", e.getMessage());
}
}
public void dispose() {
logger.debug("Stopping myBMW Servlet {}", path);
httpService.unregister(path);
this.destroy();
}
public String getPath() {
return path;
}
}
@@ -1,326 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.*;
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.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.OAuthSettingsQueryResponse;
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
import org.openhab.binding.mybmw.internal.utils.Constants;
import org.openhab.binding.mybmw.internal.utils.RandomUserAgentVersionGenerator;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthClientService;
import org.openhab.core.auth.client.oauth2.OAuthException;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
import org.openhab.core.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
* @author Mark Herwege - refactor to use OAuthFactory
* @author Mark Herwege - remove China
*/
@NonNullByDefault
public class MyBMWTokenController {
private final Logger logger = LoggerFactory.getLogger(MyBMWTokenController.class);
private MyBMWBridgeHandler bridgeHandler;
private MyBMWBridgeConfiguration bridgeConfiguration;
private final HttpClient httpClient;
private final OAuthFactory oAuthFactory;
private String oAuthClientServiceId;
private @Nullable OAuthClientService oAuthClientService = null;
private AccessTokenResponse tokenResponse = new AccessTokenResponse();
private boolean waitingForInitialToken = false;
private static final String SESSION_ID = UUID.randomUUID().toString();
public MyBMWTokenController(MyBMWBridgeHandler bridgeHandler, MyBMWBridgeConfiguration configuration,
HttpClient httpClient, OAuthFactory oAuthFactory) {
this.bridgeHandler = bridgeHandler;
this.bridgeConfiguration = configuration;
this.httpClient = httpClient;
this.oAuthFactory = oAuthFactory;
this.oAuthClientServiceId = bridgeHandler.getThing().getUID().getAsString();
this.oAuthClientService = oAuthFactory.getOAuthClientService(oAuthClientServiceId);
}
public synchronized void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
this.bridgeConfiguration = bridgeConfiguration;
}
/**
* 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 synchronized AccessTokenResponse getToken() {
logger.trace("getToken");
if (waitingForInitialToken && !bridgeConfiguration.getHCaptchaToken().isBlank()) {
// if the hCaptchaToken is available, then a new login is triggered
logger.trace("initial login, using captchatoken {}", bridgeConfiguration.getHCaptchaToken());
boolean tokenCreationSuccess = loginROW();
if (tokenCreationSuccess) {
waitingForInitialToken = false;
logger.trace("get inital token success");
} else {
logger.warn(
"initial Authentication failed, maybe request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!");
bridgeHandler.tokenInitError();
logger.trace("get inital token failed");
}
// reset the token as it times out
bridgeHandler.setHCaptchaToken(Constants.EMPTY);
} else if (!waitingForInitialToken && tokenResponse.isExpired(Instant.now(), 5)) {
// try to refresh the token
boolean tokenUpdateSuccess = refreshTokenROW();
logger.trace("update token {}", tokenUpdateSuccess ? "success" : "failed");
if (!tokenUpdateSuccess) {
logger.warn("Updating token failed!");
waitingForInitialToken = true;
if (bridgeConfiguration.getHCaptchaToken().isBlank()) {
logger.warn(
"initial Authentication failed, request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!");
bridgeHandler.tokenInitError();
} else {
getToken();
}
}
}
return tokenResponse;
}
/**
* @return true if the token was successfully retrieved
*/
private boolean loginROW() {
logger.trace("get initial token");
try {
/**
* Step 1) Get OAuth2 settings for further queries
*/
OAuthSettingsQueryResponse aqr = getOAuthSettings();
/**
* Step 2) Calculate values for oauth base parameters
*/
String codeVerifier = generateToken(86);
String codeChallenge = generateCodeChallenge(codeVerifier);
String state = generateToken(22);
String nonce = generateToken(22);
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, nonce);
baseParams.put(SCOPE, aqr.scopes());
baseParams.put(CODE_CHALLENGE, codeChallenge);
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
/**
* Step 3) Authentication with username and password, get authentication code
*/
String loginUrl = aqr.gcdmBaseUrl + OAUTH_ENDPOINT;
Request loginRequest = httpClient.POST(loginUrl);
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
loginRequest.header(HCAPTCHA_TOKEN, bridgeConfiguration.getHCaptchaToken());
MultiMap<@Nullable String> loginParams = new MultiMap<>(baseParams);
loginParams.put(GRANT_TYPE, AUTHORIZATION_CODE);
loginParams.put(USERNAME, bridgeConfiguration.getUserName());
loginParams.put(PASSWORD, bridgeConfiguration.getPassword());
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());
logger.trace("authentication code: {}", authCode);
/**
* Step 4) With authentication code get 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));
logger.trace("code: {}", code);
/**
* Step 5) With code get token
*/
if (oAuthFactory.getOAuthClientService(oAuthClientServiceId) != null) {
oAuthFactory.ungetOAuthService(oAuthClientServiceId);
}
OAuthClientService oAuthClientService = oAuthFactory.createOAuthClientService(oAuthClientServiceId,
aqr.tokenEndpoint, loginUrl, aqr.clientId, aqr.clientSecret, aqr.scopes(), false);
this.oAuthClientService = oAuthClientService;
oAuthClientService.addExtraAuthField(CODE_VERIFIER, codeVerifier);
tokenResponse = oAuthClientService.getAccessTokenResponseByAuthorizationCode(code, aqr.returnUrl);
return true;
} catch (ExecutionException | OAuthException | IOException | OAuthResponseException | InterruptedException
| HttpResponseException | TimeoutException | NoSuchAlgorithmException e) {
logger.warn("Exception at login: {}", e.getMessage());
}
return false;
}
/**
* refresh the existing token
*
* @return true if token has successfully been refreshed
*/
private boolean refreshTokenROW() {
logger.trace("refreshToken");
OAuthClientService oAuthClientService = this.oAuthClientService;
if (oAuthClientService == null) {
return false;
}
try {
AccessTokenResponse tokenResponse;
tokenResponse = oAuthClientService.getAccessTokenResponse();
if (tokenResponse != null) {
this.tokenResponse = tokenResponse;
return true;
}
} catch (OAuthException | IOException | OAuthResponseException e) {
logger.warn("Exception refreshing token: ", e);
}
return false;
}
private OAuthSettingsQueryResponse getOAuthSettings()
throws InterruptedException, TimeoutException, ExecutionException {
String uuidString = UUID.randomUUID().toString();
String oAuthSettingsUrl = "https://" + EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
+ API_OAUTH_CONFIG;
Request oAuthSettingsRequest = httpClient.newRequest(oAuthSettingsUrl);
oAuthSettingsRequest.header(HEADER_ACP_SUBSCRIPTION_KEY, OCP_APIM_KEYS.get(bridgeConfiguration.getRegion()));
oAuthSettingsRequest.header(HEADER_X_USER_AGENT,
String.format(X_USER_AGENT, RandomUserAgentVersionGenerator.getRandomUserAgentVersion(), BRAND_BMW,
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
oAuthSettingsRequest.header(HEADER_X_IDENTITY_PROVIDER, AUTH_PROVIDER);
oAuthSettingsRequest.header(HEADER_X_CORRELATION_ID, uuidString);
oAuthSettingsRequest.header(HEADER_BMW_CORRELATION_ID, uuidString);
oAuthSettingsRequest.header(HEADER_BMW_SESSION_ID, SESSION_ID);
ContentResponse oAuthSettingsRepsonse = oAuthSettingsRequest.send();
if (oAuthSettingsRepsonse.getStatus() != 200) {
throw new HttpResponseException("URL: " + oAuthSettingsRequest.getURI() + ", Error: "
+ oAuthSettingsRepsonse.getStatus() + ", Message: " + oAuthSettingsRepsonse.getContentAsString(),
oAuthSettingsRepsonse);
}
OAuthSettingsQueryResponse aqr = JsonStringDeserializer
.deserializeString(oAuthSettingsRepsonse.getContentAsString(), OAuthSettingsQueryResponse.class);
return aqr;
}
private String generateToken(int length) {
String bytes = StringUtils.getRandomAlphabetic(length).toLowerCase();
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes.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 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.isEmpty()) {
String val = value.get(0);
if (key.endsWith(CODE) && (val != null)) {
codeFound.append(val.toString());
}
}
});
return codeFound.toString();
}
}
@@ -1,98 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.NonNull;
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<>();
}
}
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<@NonNull T> deserializedClass) {
return GSON.fromJson(toBeDeserialized, deserializedClass);
}
}
@@ -1,221 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
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.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(HttpClient httpClient, MyBMWBridgeConfiguration bridgeConfiguration) {
logger.trace("MyBMWFileProxy - initialize");
vehicleToBeTested = bridgeConfiguration.getPassword();
}
@Override
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
logger.trace("MyBMWFileProxy - update bridge");
vehicleToBeTested = bridgeConfiguration.getPassword();
}
@Override
public List<Vehicle> requestVehicles() throws NetworkException {
List<Vehicle> vehicles = new ArrayList<>();
List<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
*/
@Override
public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
String vehicleResponseString = requestVehiclesBaseJson(brand);
return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
}
@Override
public String requestVehiclesBaseJson(String brand) throws NetworkException {
String vehicleResponseString = fileToString(VEHICLES_BASE);
return vehicleResponseString;
}
/**
* request vehicles for all possible brands
*
* @param callback
*/
@Override
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
*/
@Override
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
return "".getBytes();
}
/**
* request the state for one specific vehicle
*
* @param baseVehicle
* @return
*/
@Override
public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
}
@Override
public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
String vehicleStateResponseString = fileToString(VEHICLES_STATE);
return vehicleStateResponseString;
}
/**
* request charge statistics for electric vehicles
*
*/
@Override
public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
}
@Override
public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
String chargeStatisticsResponseString = fileToString(CHARGING_STATISTICS);
return chargeStatisticsResponseString;
}
/**
* request charge sessions for electric vehicles
*
*/
@Override
public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
}
@Override
public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
String chargeSessionsResponseString = fileToString(CHARGING_SESSIONS);
return chargeSessionsResponseString;
}
@Override
public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
throws NetworkException {
return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_CALL));
}
@Override
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(Objects.requireNonNull(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 "";
}
}
@Override
public Instant getNextQuota() {
return Instant.now();
}
}
@@ -1,501 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.MyBMWBridgeHandler;
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.binding.mybmw.internal.utils.RandomUserAgentVersionGenerator;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.auth.client.oauth2.OAuthFactory;
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 static final Pattern TIME_PATTERN = Pattern.compile("\\b(\\d{1,2}:\\d{2}:\\d{2})\\b");
private static final Pattern DURATION_PATTERN = Pattern.compile("\\b(\\d+)");
private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxy.class);
private final HttpClient httpClient;
private MyBMWBridgeConfiguration bridgeConfiguration;
MyBMWTokenController myBMWTokenController;
private Instant nextQuota = Instant.now();
/**
* 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(MyBMWBridgeHandler bridgeHandler, HttpClient httpClient, OAuthFactory oAuthFactory,
MyBMWBridgeConfiguration bridgeConfiguration) {
logger.trace("MyBMWHttpProxy - initialize");
this.httpClient = httpClient;
myBMWTokenController = new MyBMWTokenController(bridgeHandler, bridgeConfiguration, httpClient, oAuthFactory);
this.bridgeConfiguration = bridgeConfiguration;
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
+ BimmerConstants.API_VEHICLES;
vehicleStateUrl = vehicleUrl + "/state";
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
remoteStatusUrl = remoteCommandUrl + "eventStatus";
logger.trace("MyBMWHttpProxy - ready");
}
@Override
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
this.bridgeConfiguration = bridgeConfiguration;
myBMWTokenController.setBridgeConfiguration(bridgeConfiguration);
}
/**
* requests all vehicles
*
* @return list of vehicles
*/
@Override
public List<Vehicle> requestVehicles() throws NetworkException {
List<Vehicle> vehicles = new ArrayList<>();
List<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
*/
@Override
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
*/
@Override
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
*/
@Override
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 (NetworkException ne) {
logger.warn("error retrieving the base vehicles for brand {}: {}", brand, ne.getMessage());
throw ne;
} catch (InterruptedException ie) {
throw new NetworkException(ie);
}
}
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
*/
@Override
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
+ BimmerConstants.IMAGE_URL.replace(BimmerConstants.PARAM_VIN, vin) + 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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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.getRegion())
+ BimmerConstants.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
*/
@Override
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
*/
@Override
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.getRegion())
+ BimmerConstants.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
*/
@Override
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
*/
@Override
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();
if (Instant.now().isBefore(nextQuota)) {
throw new NetworkException(url, -1, "Quota Exceeded, waiting for quota renewal", body);
}
// return in case of unknown brand
if (!BimmerConstants.REQUESTED_BRANDS.contains(brand.toLowerCase())) {
logger.warn("Unknown Brand {}", brand);
throw new NetworkException("Unknown Brand " + brand);
}
// if no token is available, no request can be triggered
AccessTokenResponse tokenResponse = myBMWTokenController.getToken();
String bearerToken = tokenResponse.getAccessToken();
bearerToken = (bearerToken == null) || bearerToken.isEmpty() ? Constants.EMPTY
: new StringBuilder(tokenResponse.getTokenType()).append(Constants.SPACE).append(bearerToken)
.toString();
if (tokenResponse.isExpired(Instant.now(), 1)) {
logger.warn("The login failed, no token is available");
throw new NetworkException("The login failed, no token is available");
}
final Request req;
if (post) {
req = httpClient.POST(url);
} else {
req = httpClient.newRequest(url);
}
req.header(HttpHeader.AUTHORIZATION, bearerToken);
req.header(HTTPConstants.HEADER_X_USER_AGENT,
String.format(BimmerConstants.X_USER_AGENT, RandomUserAgentVersionGenerator.getRandomUserAgentVersion(),
brand.toLowerCase(), BimmerConstants.APP_VERSIONS.get(bridgeConfiguration.getRegion()),
bridgeConfiguration.getRegion()));
req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.getLanguage());
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) {
setNextQuotaAttempt(response);
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 setNextQuotaAttempt(ContentResponse response) {
int status = response.getStatus();
String message = JsonStringDeserializer.deserializeString(response.getContentAsString(),
ResponseErrorContainer.class).message;
Duration delay = Duration.ofSeconds(5); // minimum wait of 5 seconds
switch (status) {
case 403:
// response contains the amount of time before quota is replenished
try {
Matcher matcher = TIME_PATTERN.matcher(message);
if (matcher.find()) {
LocalTime time = LocalTime.parse(matcher.group(1), DateTimeFormatter.ISO_LOCAL_TIME);
delay = Duration.between(LocalTime.MIDNIGHT, time);
}
} catch (DateTimeParseException e) {
// we couldn't parse, continue with default
}
break;
case 429:
// response may contain the number of seconds to wait before quota is replenished
Matcher matcher = DURATION_PATTERN.matcher(message);
if (matcher.find()) {
delay = Duration.ofSeconds(Long.valueOf(matcher.group(1)));
} else {
delay = Duration.ofMinutes(5);
}
break;
default:
return;
}
logger.debug("next call allowed in {}:{}:{}", delay.toHours(), delay.toMinutesPart(), delay.toSecondsPart());
nextQuota = Instant.now().plus(delay);
}
@Override
public Instant getNextQuota() {
return nextQuota;
}
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 ######");
}
}
@@ -1,98 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.time.Instant;
import java.util.List;
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<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;
Instant getNextQuota();
}
@@ -1,103 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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) {
super(reason);
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 + "]";
}
}
@@ -1,245 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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.openhab.binding.mybmw.internal.utils.Constants;
/**
* The {@link ResponseErrorContainer} Data Transfer Object
*
* @author Mark Herwege - Initial contribution
*/
@NonNullByDefault
public class ResponseErrorContainer {
public String status = Constants.EMPTY;
public String message = Constants.EMPTY;
@Override
public String toString() {
return "ResponseError [status=" + status + ", message=" + message + "]";
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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
}
@@ -1,73 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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_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_START_CHARGING;
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_STOP_CHARGING;
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\"}"),
START_CHARGING("Start Charging", REMOTE_SERVICE_START_CHARGING, "start-charging", ""),
STOP_CHARGING("Stop Charging", REMOTE_SERVICE_STOP_CHARGING, "stop-charging", "");
private final String label;
private final String id;
private final String command;
private final String body;
RemoteService(final String label, final String id, String command, String body) {
this.label = label;
this.id = id;
this.command = command;
this.body = body;
}
public String getLabel() {
return label;
}
public String getId() {
return id;
}
public String getCommand() {
return command;
}
public String getBody() {
return body;
}
}
@@ -1,83 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link BimmerConstants} 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 Martin Grassl - update to v2 API
* @author Mark Herwege - remove China
*/
@NonNullByDefault
public interface BimmerConstants {
static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
static final String REGION_ROW = "ROW";
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);
static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
static final String AUTH_PROVIDER = "gcdm";
static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, EADRAX_SERVER_NORTH_AMERICA,
REGION_ROW, EADRAX_SERVER_ROW);
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);
// Http variables
static final String APP_VERSION_NORTH_AMERICA = "4.9.2(36892)";
static final String APP_VERSION_ROW = "4.9.2(36892)";
static final Map<String, String> APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW,
APP_VERSION_ROW);
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(AP2A.%s);%s;%s;%s";
static final String AUTHORIZATION_CODE = "authorization_code";
static final String REFRESH_TOKEN = "refresh_token";
// Parameters for API Requests
static final String TIRE_GUARD_MODE = "tireGuardMode";
static final String APP_DATE_TIME = "appDateTime";
static final String APP_TIMEZONE = "apptimezone";
// API endpoints
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";
static final String CHARGING_STATISTICS = "/eadrax-chs/v2/charging-statistics?";
static final String CHARGING_SESSIONS = "/eadrax-chs/v2/charging-sessions?";
static final String PARAM_VIN = "$vin$";
static final String IMAGE_URL = "/eadrax-ics/v3/presentation/vehicles/" + PARAM_VIN + "/images?carView=";
}
@@ -1,137 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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));
}
}
@@ -1,202 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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);
}
}
}
}
}
@@ -1,107 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.UnDefType;
/**
* The {@link Constants} General Constant Definitions
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - contributor
* @author Martin Grassl - rename drivetrain options
*/
@NonNullByDefault
public class Constants {
// For Vehicle Status
public static final String NO_ENTRIES = "-";
public static final String OPEN = "Open";
public static final String CLOSED = "Closed";
public static final String LOCKED = "Locked";
public static final String UNLOCKED = "Unlocked";
public static final String CONNECTED = "Connected";
public static final String UNCONNECTED = "Not connected";
public static final String UNDEF = UnDefType.UNDEF.toFullString();
public static final String NULL_TIME = "00:00";
public static final String KILOMETERS_JSON = "KILOMETERS";
public static final String KM_JSON = "km";
public static final String MI_JSON = "mi";
public static final String UNIT_PRECENT_JSON = "%";
public static final String UNIT_LITER_JSON = "l";
public static final Unit<Length> KILOMETRE_UNIT = MetricPrefix.KILO(SIUnits.METRE);
public static final int INT_UNDEF = -1;
// Services in Discovery
public static final String ENABLED = "ENABLED";
// General Constants for String concatenation
public static final String NULL = "null";
public static final String SPACE = " ";
public static final String UNDERLINE = "_";
public static final String HYPHEN = " - ";
public static final String PLUS = "+";
public static final String EMPTY = "";
public static final String COMMA = ",";
public static final String QUESTION = "?";
public static final String COLON = ":";
public static final String SEMICOLON = ";";
public static final String TILDE = "~";
public static final String ANONYMOUS = "anonymous";
public static final String EMPTY_JSON = "{}";
public static final String LANGUAGE_AUTODETECT = "AUTODETECT";
// Time Constants for DateTime channels
public static final LocalDate EPOCH_DAY = LocalDate.ofEpochDay(0);
public static final DateTimeFormatter TIME_FORMATER = DateTimeFormatter.ofPattern("HH:mm");
public static final LocalTime NULL_LOCAL_TIME = LocalTime.parse(NULL_TIME, TIME_FORMATER);
@SuppressWarnings("serial")
public static final Map<DayOfWeek, String> DAYS = new HashMap<>() {
{
put(DayOfWeek.MONDAY, "Mon");
put(DayOfWeek.TUESDAY, "Tue");
put(DayOfWeek.WEDNESDAY, "Wed");
put(DayOfWeek.THURSDAY, "Thu");
put(DayOfWeek.FRIDAY, "Fri");
put(DayOfWeek.SATURDAY, "Sat");
put(DayOfWeek.SUNDAY, "Sun");
}
};
// Drive Train definitions from json
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";
public static final String NOT_CHARGING_STATE = "NOT_CHARGING";
public static final String CHARGING_STATE = "CHARGING";
public static final String PLUGGED_STATE = "PLUGGED_IN";
}
@@ -1,184 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mybmw.internal.dto.charge.Time;
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;
/**
* The {@link Converter} Conversion Helpers
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - extract some methods to other classes
*/
@NonNullByDefault
public interface Converter {
static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
static final String SPLIT_HYPHEN = "-";
static final String SPLIT_BRACKET = "\\(";
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;
}
}
/**
* 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);
return converted;
}
}
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) {
sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
}
sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
}
return sb.toString().trim();
}
/**
* Measure distance between 2 coordinates
*
* @param sourceLatitude
* @param sourceLongitude
* @param destinationLatitude
* @param destinationLongitude
* @return distance
*/
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;
double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
* Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadius * c;
}
/**
* 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
* line from Location A to B.
* 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
*
* 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.
*
* @param range
* @return mapping from air-line distance to "real road" distance
*/
static int guessRangeRadius(double range) {
return (int) (range * 0.8);
}
/**
* checks if a string is a valid integer
*
* @param fullString
* @return
*/
static int parseIntegerString(String fullString) {
int index = -1;
try {
index = Integer.parseInt(fullString);
} catch (NumberFormatException nfe) {
}
return index;
}
static State getConnectionState(boolean connected) {
if (connected) {
return StringType.valueOf(Constants.CONNECTED);
} else {
return StringType.valueOf(Constants.UNCONNECTED);
}
}
static String getCurrentISOTime() {
return ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
}
static String getTime(Time t) {
StringBuffer time = new StringBuffer();
if (t.getHour() < 10) {
time.append("0");
}
time.append(Integer.toString(t.getHour())).append(":");
if (t.getMinute() < 10) {
time.append("0");
}
time.append(Integer.toString(t.getMinute()));
return time.toString();
}
}
@@ -1,62 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
/**
* The {@link HTTPConstants} interface contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
* @author Martin Grassl - added image content type
*/
@NonNullByDefault
public interface HTTPConstants {
static final int HTTP_TIMEOUT_SEC = 10;
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 HCAPTCHA_TOKEN = "hcaptchatoken";
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";
// 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_SESSION_ID = "bmw-session-id";
static final String HEADER_BMW_VIN = "bmw-vin";
}
@@ -1,48 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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;
/**
* 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 = "VehicleStatus"; // default view
public ImageProperties(String viewport) {
this.viewport = viewport;
}
public ImageProperties() {
}
public void failed() {
failCounter++;
}
public boolean failLimitReached() {
return failCounter > RETRY_COUNTER;
}
@Override
public String toString() {
return viewport;
}
}
@@ -1,43 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.util.Locale;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link RandomUserAgentVersionGenerator} generates random version numbers for the X-User-Agent in the format
* xxxxxx.xxx
*
* @author Martin Grassl - Initial contribution
*/
@NonNullByDefault
public interface RandomUserAgentVersionGenerator {
/**
* this method generates the random UserAgent version
*
* @return formatted String xxxxxx.xxx
*/
static String getRandomUserAgentVersion() {
int decimalPlaces = 3;
int min = 250000;
int max = 999999;
double random = min + (max - min) * Math.random();
// with the US locale we ensure that the decimal separator is a dot
return String.format(Locale.US, "%." + decimalPlaces + "f", random);
}
}
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
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> getRemoteServiceFromCommand(final String command) {
return Optional.ofNullable(COMMAND_SERVICES.get(command));
}
public static List<CommandOption> getOptions(final boolean isElectric) {
return Stream.of(RemoteService.values()).map(service -> new CommandOption(service.getId(), service.getLabel()))
.collect(Collectors.toUnmodifiableList());
}
}
@@ -1,108 +0,0 @@
/*
* Copyright (c) 2010-2026 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 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 java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
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.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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);
/**
* 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 (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(DateTimeFormatter.ISO_INSTANT));
}
}
/**
* the mileage can be empty
*
* @param requiredServices
* @return
*/
public static State getNextServiceMileage(List<RequiredService> requiredServices) {
int serviceMileage = Integer.MAX_VALUE;
for (RequiredService requiredService : requiredServices) {
if (requiredService.getMileage() > 0) {
if (requiredService.getMileage() < serviceMileage) {
serviceMileage = requiredService.getMileage();
}
}
}
if (serviceMileage != Integer.MAX_VALUE) {
return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
} else {
return UnDefType.UNDEF;
}
}
/**
* calculates the mapping of thing type
*
* @param driveTrain
* @param model
* @return
*/
public static VehicleType vehicleType(String driveTrain, String model) {
if (Constants.DRIVETRAIN_BEV.equals(driveTrain)) {
if (model.endsWith(Constants.DRIVETRAIN_REX_EXTENSION)) {
return VehicleType.ELECTRIC_REX;
} else {
return VehicleType.ELECTRIC;
}
} else if (Constants.DRIVETRAIN_PHEV.equals(driveTrain)) {
return VehicleType.PLUGIN_HYBRID;
} 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;
}
}
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon:addon id="mybmw" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
<name>MyBMW</name>
<description>Provides access to your Vehicle Data like MyBMW App</description>
<connection>cloud</connection>
</addon:addon>
@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:mybmw:bridge">
<parameter name="userName" type="text" required="true">
<label>Username</label>
<description>MyBMW Username</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>MyBMW Password</description>
<context>password</context>
</parameter>
<parameter name="region" type="text" required="true">
<label>Region</label>
<description>Select Region in order to connect to the appropriate BMW Server</description>
<options>
<option value="NORTH_AMERICA">North America</option>
<option value="ROW">Rest of the World</option>
</options>
<default>ROW</default>
</parameter>
<parameter name="language" type="text">
<label>Language Settings</label>
<description>Channel data can be returned in the desired language like en, de, fr ...</description>
<advanced>true</advanced>
<default>AUTODETECT</default>
</parameter>
<parameter name="hcaptchatoken" type="text">
<label>Captcha-Token</label>
<description>Captcha-Token for login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)</description>
<advanced>true</advanced>
</parameter>
<parameter name="callbackIP" type="text">
<label>Callback IP Address</label>
<description>IP address for openHAB callback URL, defaults to IP of openHAB host</description>
<context>network-address</context>
<advanced>true</advanced>
</parameter>
<parameter name="callbackPort" type="integer">
<label>Callback Port Number</label>
<description>Port Number for openHAB callback URL, default 8090</description>
<default>8090</default>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:mybmw:vehicle">
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number (VIN)</label>
<description>Unique VIN given by BMW</description>
</parameter>
<parameter name="refreshInterval" type="integer" min="0" unit="min" required="true">
<label>Refresh Interval</label>
<description>Data refresh rate for your vehicle data</description>
<default>5</default>
</parameter>
<parameter name="vehicleBrand" type="text" required="true">
<label>Brand of the Vehicle</label>
<description>Vehicle brand like BMW or Mini</description>
<advanced>true</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>
@@ -1,275 +0,0 @@
# add-on
addon.mybmw.name = MyBMW
addon.mybmw.description = Provides access to your Vehicle Data like MyBMW App
# thing types
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.language.label = Language Settings
thing-type.config.mybmw.bridge.password.description = MyBMW Password
thing-type.config.mybmw.bridge.password.label = Password
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token for the Login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
thing-type.config.mybmw.bridge.region.description = Select Region in order to connect to the appropriate BMW Server
thing-type.config.mybmw.bridge.region.label = Region
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.description = MyBMW Username
thing-type.config.mybmw.bridge.userName.label = Username
thing-type.config.mybmw.vehicle.refreshInterval.description = Data refresh rate for your vehicle data. If set to 0 no refresh will be triggered automatically.
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.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.description = Charging statistics of current month
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.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.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.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-range-values.label = Range and Charge Data
channel-group-type.mybmw.ev-vehicle-status.description = Overall vehicle status
channel-group-type.mybmw.ev-vehicle-status.label = Vehicle Status
channel-group-type.mybmw.ev-vehicle-update.description = Force the update of the vehicle data
channel-group-type.mybmw.ev-vehicle-update.label = Force Vehicle Update
channel-group-type.mybmw.hybrid-range-values.description = Provides mileage, remaining fuel and range data for hybrid vehicles
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.image-values.label = Vehicle Image
channel-group-type.mybmw.location-values.description = Coordinates and heading of the vehicle
channel-group-type.mybmw.location-values.label = Vehicle Location
channel-group-type.mybmw.profile-values.description = Scheduled charging profiles
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.remote-services.label = Remote Services
channel-group-type.mybmw.service-values.description = Future vehicle service schedules
channel-group-type.mybmw.service-values.label = Vehicle Services
channel-group-type.mybmw.session-values.description = Past charging sessions
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.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-group-type.mybmw.vehicle-update.description = Force the update of the vehicle data
channel-group-type.mybmw.vehicle-update.label = Force Vehicle Update
# 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.charging-update-channel.label = Force update of the charging data
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.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-update-channel.label = Force update of the image
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.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.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-front-channel.label = Passenger Door
channel-type.mybmw.passenger-rear-channel.label = Passenger Door Rear
channel-type.mybmw.plug-connection-channel.label = Plug Connection Status
channel-type.mybmw.png-channel.label = Rendered Vehicle Image
channel-type.mybmw.profile-climate-channel.label = A/C at Departure Time
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Weekly Schedule
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-limit-channel.label = Charging Energy Limited
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Use Charging Preference
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-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-electric-channel.label = Electric Range Radius
channel-type.mybmw.range-radius-fuel-channel.label = Fuel Range Radius
channel-type.mybmw.range-radius-hybrid-channel.label = Hybrid Range Radius
channel-type.mybmw.raw-channel.label = Raw Data
channel-type.mybmw.rear-left-current-channel.label = Tire Pressure Rear Left
channel-type.mybmw.rear-left-target-channel.label = Tire Pressure Rear Left Target
channel-type.mybmw.rear-right-current-channel.label = Tire Pressure Rear Right
channel-type.mybmw.rear-right-target-channel.label = Tire Pressure Rear Right Target
channel-type.mybmw.remaining-fuel-channel.label = Remaining Fuel
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Start Climate now
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Stop Climate now
channel-type.mybmw.remote-command-channel.command.option.door-lock = Lock vehicle
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Unlock vehicle
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Blow horn
channel-type.mybmw.remote-command-channel.command.option.light-flash = Flash lights
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Find vehicle
channel-type.mybmw.remote-command-channel.label = Remote Command
channel-type.mybmw.remote-state-channel.label = Service Execution State
channel-type.mybmw.service-date-channel.label = Service Date
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.service-details-channel.label = Service Details
channel-type.mybmw.service-mileage-channel.label = Mileage 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-status-channel.label = Session Status
channel-type.mybmw.session-subtitle-channel.label = Session Details
channel-type.mybmw.session-title-channel.label = Session Title
channel-type.mybmw.soc-channel.label = Battery Charge Level
channel-type.mybmw.state-update-channel.label = Force update of the vehicle state data
channel-type.mybmw.statistic-energy-channel.description = Total energy charged in current month
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.description = Friday scheduled for timer 1
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-mon-channel.label = T1 Monday
channel-type.mybmw.timer1-day-sat-channel.description = Saturday scheduled for timer 1
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-sun-channel.label = T1 Sunday
channel-type.mybmw.timer1-day-thu-channel.description = Thursday scheduled for timer 1
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-tue-channel.label = T1 Tuesday
channel-type.mybmw.timer1-day-wed-channel.description = Wednesday scheduled for timer 1
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.description = Timer 1 enabled
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-fri-channel.label = T2 Friday
channel-type.mybmw.timer2-day-mon-channel.description = Monday scheduled for timer 2
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-sat-channel.label = T2 Saturday
channel-type.mybmw.timer2-day-sun-channel.description = Sunday scheduled for timer 2
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-thu-channel.label = T2 Thursday
channel-type.mybmw.timer2-day-tue-channel.description = Tuesday scheduled for timer 2
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-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.description = Timer 2 enabled
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-fri-channel.label = T3 Friday
channel-type.mybmw.timer3-day-mon-channel.description = Monday scheduled for timer 3
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-sat-channel.label = T3 Saturday
channel-type.mybmw.timer3-day-sun-channel.description = Sunday scheduled for timer 3
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-thu-channel.label = T3 Thursday
channel-type.mybmw.timer3-day-tue-channel.description = Tuesday scheduled for timer 3
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-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.description = Timer 3 enabled
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-fri-channel.label = T4 Friday
channel-type.mybmw.timer4-day-mon-channel.description = Monday scheduled for timer 4
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-sat-channel.label = T4 Saturday
channel-type.mybmw.timer4-day-sun-channel.description = Sunday scheduled for timer 4
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-thu-channel.label = T4 Thursday
channel-type.mybmw.timer4-day-tue-channel.description = Tuesday scheduled for timer 4
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-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.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.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.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
mybmw.status.authorization-needed = You need to complete a captcha to continue: {0}
mybmw.status.user-details-missing = Username or password configuration is missing
mybmw.status.region-missing = Region configuration is missing
mybmw.status.ip-missing = The callback ip of the the openHAB host could not be retrieved
mybmw.status.vehicle-retrieval-error = Retrieving vehicles failed
mybmw.status.network-error = Retrieving vehicles failed, possible network error
mybmw.status.quota-error = Out of quota, refresh at {0}
@@ -1,275 +0,0 @@
# add-on
addon.mybmw.name = MyBMW
addon.mybmw.description = Fahrzeugdaten über die MyBMW App
# thing types
thing-type.config.mybmw.bridge.language.description = Daten werden für die gewünschte Sprache angefordert (en, de, fr ...)
thing-type.config.mybmw.bridge.language.label = Sprachauswahl
thing-type.config.mybmw.bridge.password.description = Passwort für die MyBMW App
thing-type.config.mybmw.bridge.password.label = Passwort
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token für den Login (siehe https\://bimmer-connected.readthedocs.io/en/stable/captcha.html)
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
thing-type.config.mybmw.bridge.region.description = Auswahl Ihrer Region
thing-type.config.mybmw.bridge.region.label = Region
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Nordamerika
thing-type.config.mybmw.bridge.region.option.ROW = Rest der Welt
thing-type.config.mybmw.bridge.userName.description = Benutzername für die MyBMW App
thing-type.config.mybmw.bridge.userName.label = Benutzername
thing-type.config.mybmw.vehicle.refreshInterval.description = Aktualisierungsrate für Ihre Fahrzeugdaten. Wenn auf 0 gesetzt, wird keine Aktualisierung automatisch ausgelöst.
thing-type.config.mybmw.vehicle.refreshInterval.label = Datenaktualisierung in Minuten
thing-type.config.mybmw.vehicle.vehicleBrand.description = Fahrzeugmarke wie z.B. BMW oder Mini.
thing-type.config.mybmw.vehicle.vehicleBrand.label = Marke des Fahrzeugs
thing-type.config.mybmw.vehicle.vin.description = VIN des Fahrzeugs
thing-type.config.mybmw.vehicle.vin.label = Fahrzeug Identifikationsnummer (VIN)
thing-type.mybmw.account.description = Kontodaten für das BMW Benutzerkonto
thing-type.mybmw.account.label = MyBMW Benutzerkonto
thing-type.mybmw.bev_rex.description = Elektrofahrzeug mit Range Extender (bev_rex)
thing-type.mybmw.bev_rex.label = Elektrofahrzeug mit REX
thing-type.mybmw.bev.description = Batterieelektrisches Fahrzeug (bev)
thing-type.mybmw.bev.label = Elektrofahrzeug
thing-type.mybmw.conv.description = Konventionelles Benzin/Diesel Fahrzeug (conv)
thing-type.mybmw.conv.label = Konventionelles Fahrzeug
thing-type.mybmw.phev.description = Konventionelles Fahrzeug mit Elektromotor (phev)
thing-type.mybmw.phev.label = Plug-in-Hybrid Elektrofahrzeug
# channel group types
channel-group-type.mybmw.charge-statistic.description = Statistik der Ladevorgänge im Monat
channel-group-type.mybmw.charge-statistic.label = Elektrische Ladestatistik
channel-group-type.mybmw.check-control-values.description = Aktuelle Warnungen des Fahrzeugs
channel-group-type.mybmw.check-control-values.label = Warnungen
channel-group-type.mybmw.conv-range-values.description = Tachostand, Reichweite und Tankfüllung des Fahrzeugs
channel-group-type.mybmw.conv-range-values.label = Verbrenner Reichweiten und Füllstände
channel-group-type.mybmw.door-values.description = Zeigt die Details der Türen und Fenster des Fahrzeugs
channel-group-type.mybmw.door-values.label = Details aller Türen
channel-group-type.mybmw.ev-range-values.description = Tachostand, Reichweiten und Ladestand des Fahrzeugs
channel-group-type.mybmw.ev-range-values.label = Elektro- Reichweiten und Batterieladung
channel-group-type.mybmw.ev-vehicle-status.description = Gesamtzustand des Fahrzeugs
channel-group-type.mybmw.ev-vehicle-status.label = Fahrzeug Zustand
channel-group-type.mybmw.ev-vehicle-update.description = Die Fahrzeugdaten manuell aktualisieren
channel-group-type.mybmw.ev-vehicle-update.label = Fahrzeugaktualisierung erzwingen
channel-group-type.mybmw.hybrid-range-values.description = Tachostand, Reichweite, Ladezustand und Tankfüllung für hybride Fahrzeuge
channel-group-type.mybmw.hybrid-range-values.label = Hybride Reichweiten und Füllstände
channel-group-type.mybmw.image-values.description = Bild des Fahrzeug basierend auf der ausgewählten Ansicht
channel-group-type.mybmw.image-values.label = Fahrzeug Bild
channel-group-type.mybmw.location-values.description = Koordinaten und Ausrichtung des Fahrzeugs
channel-group-type.mybmw.location-values.label = Fahrzeug Standort
channel-group-type.mybmw.profile-values.description = Zeitplanung der Ladevorgänge
channel-group-type.mybmw.profile-values.label = Elektrisches Ladeprofil
channel-group-type.mybmw.remote-services.description = Fernsteuerung des Fahrzeugs
channel-group-type.mybmw.remote-services.label = Fernsteuerung
channel-group-type.mybmw.service-values.description = Anstehende Wartungstermine des Fahrzeugs
channel-group-type.mybmw.service-values.label = Wartung
channel-group-type.mybmw.session-values.description = Liste der letzten Ladevorgänge
channel-group-type.mybmw.session-values.label = Elektrische Ladevorgänge
channel-group-type.mybmw.tire-pressures.description = Reifen Luftdruck Ist und Sollwerte
channel-group-type.mybmw.tire-pressures.label = Reifen Luftdruck
channel-group-type.mybmw.vehicle-status.description = Gesamtzustand des Fahrzeugs
channel-group-type.mybmw.vehicle-status.label = Fahrzeug Zustand
channel-group-type.mybmw.vehicle-update.description = Aktualisierung der Fahrzeugdaten erzwingen
channel-group-type.mybmw.vehicle-update.label = Fahrzeugaktualisierung erzwingen
# channel types
channel-type.mybmw.address-channel.label = Adresse
channel-type.mybmw.charging-info-channel.label = Ladeinformationen
channel-type.mybmw.charging-remaining-channel.label = Verbleibende Ladezeit
channel-type.mybmw.charging-status-channel.label = Ladezustand
channel-type.mybmw.charging-update-channel.label = Update der Ladedaten erzwingen
channel-type.mybmw.check-control-channel.label = Warnung Aktiv
channel-type.mybmw.checkcontrol-details-channel.label = Warnung Details
channel-type.mybmw.checkcontrol-name-channel.label = Warnung
channel-type.mybmw.checkcontrol-severity-channel.label = Warnung Priorität
channel-type.mybmw.doors-channel.label = Gesamtzustand der Türen
channel-type.mybmw.driver-front-channel.label = Fahrertür
channel-type.mybmw.driver-rear-channel.label = Fahrertür Hinten
channel-type.mybmw.estimated-fuel-l-100km-channel.label = Geschätzter Verbrauch l/100km
channel-type.mybmw.estimated-fuel-mpg-channel.label = Geschätzter Verbrauch mpg
channel-type.mybmw.front-left-current-channel.label = Reifen Luftdruck Vorne Links
channel-type.mybmw.front-left-target-channel.label = Reifen Luftdruck vorne links
channel-type.mybmw.front-right-current-channel.label = Reifen Luftdruck Vorne Rechts
channel-type.mybmw.front-right-target-channel.label = Reifen Luftdruck vorne rechts
channel-type.mybmw.gps-channel.label = Koordinaten
channel-type.mybmw.heading-channel.label = Ausrichtung
channel-type.mybmw.home-distance-channel.description = Berechnete Entfernung zwischen Fahrzeug und Heimatort
channel-type.mybmw.home-distance-channel.label = Entfernung von Zuhause
channel-type.mybmw.hood-channel.label = Frontklappe
channel-type.mybmw.image-update-channel.label = Update des Bildes erzwingen
channel-type.mybmw.image-view-channel.command.option.FrontLeft = Linke Seitenansicht
channel-type.mybmw.image-view-channel.command.option.FrontRight = Rechte Seitenansicht
channel-type.mybmw.image-view-channel.command.option.FrontView = Frontansicht
channel-type.mybmw.image-view-channel.command.option.RearView = Rückansicht
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Seitenansicht
channel-type.mybmw.image-view-channel.label = Fahrzeug Ansicht
channel-type.mybmw.last-fetched-channel.label = Zeitstempel letztes openHAB Update
channel-type.mybmw.last-update-channel.label = Zeitstempel letzter Fahrzeugstatus
channel-type.mybmw.lock-channel.label = Fahrzeug Abgeschlossen
channel-type.mybmw.mileage-channel.label = Tachostand
channel-type.mybmw.next-service-date-channel.label = Nächster Service Termin
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.next-service-mileage-channel.label = Nächster Service in Kilometern
channel-type.mybmw.passenger-front-channel.label = Beifahrertür
channel-type.mybmw.passenger-rear-channel.label = Beifahrertür Hinten
channel-type.mybmw.plug-connection-channel.label = Ladestecker
channel-type.mybmw.png-channel.label = Fahrzeug Bild
channel-type.mybmw.profile-climate-channel.label = Klimatisierung bei Abfahrt
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Wochenplan
channel-type.mybmw.profile-control-channel.description = Ladeplan Auswahl
channel-type.mybmw.profile-control-channel.label = Ladeplan
channel-type.mybmw.profile-limit-channel.description = Limitiertes Laden aktiviert
channel-type.mybmw.profile-limit-channel.label = Ladung Limitiert
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Ladeverzögerung
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Sofort Laden
channel-type.mybmw.profile-mode-channel.description = Modus für sofortiges oder verzögertes Laden
channel-type.mybmw.profile-mode-channel.label = Ladeprofil
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Laden im Zeitfenster
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = Keine Präferenz
channel-type.mybmw.profile-prefs-channel.description = Einstellungen für verzögerte Ladung
channel-type.mybmw.profile-prefs-channel.label = Ladeprofil Präferenz
channel-type.mybmw.profile-target-channel.description = Erwünschter Batterie Ladezustand
channel-type.mybmw.profile-target-channel.label = Ziel Ladezustand
channel-type.mybmw.range-electric-channel.label = Elektrische Reichweite
channel-type.mybmw.range-fuel-channel.label = Verbrenner Reichweite
channel-type.mybmw.range-hybrid-channel.label = Hybride Reichweite
channel-type.mybmw.range-radius-electric-channel.label = Elektrischer Reichweiten-Radius
channel-type.mybmw.range-radius-fuel-channel.label = Verbrenner Reichweiten-Radius
channel-type.mybmw.range-radius-hybrid-channel.label = Hybrider Reichweiten-Radius
channel-type.mybmw.raw-channel.label = Rohdaten
channel-type.mybmw.rear-left-current-channel.label = Reifen Luftdruck Hinten Links
channel-type.mybmw.rear-left-target-channel.label = Reifen Luftdruck hinten links
channel-type.mybmw.rear-right-current-channel.label = Reifen Luftdruck Hinten Rechts
channel-type.mybmw.rear-right-target-channel.label = Reifen Luftdruck hinten rechts
channel-type.mybmw.remaining-fuel-channel.label = Tankstand
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Klimaanlage jetzt einschalten
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Klimaanlage jetzt ausschalten
channel-type.mybmw.remote-command-channel.command.option.door-lock = Fahrzeug verriegeln
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Fahrzeug entriegeln
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Hupe betätigen
channel-type.mybmw.remote-command-channel.command.option.light-flash = Lampen blinken lassen
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Fahrzeug finden
channel-type.mybmw.remote-command-channel.label = Kommando Auswahl
channel-type.mybmw.remote-state-channel.label = Ausführungszustand
channel-type.mybmw.service-date-channel.label = Service Termin
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 = Fahrstrecke bis zum nächsten Service
channel-type.mybmw.service-name-channel.label = Service
channel-type.mybmw.session-energy-channel.label = Energie Geladen
channel-type.mybmw.session-issue-channel.label = Ladevorgang Probleme
channel-type.mybmw.session-status-channel.label = Ladevorgang Zustand
channel-type.mybmw.session-subtitle-channel.label = Ladevorgang Details
channel-type.mybmw.session-title-channel.label = Ladevorgang Beschreibung
channel-type.mybmw.soc-channel.label = Batterie Ladestand
channel-type.mybmw.state-update-channel.label = Aktualisierung der Fahrzeugzustandsdaten erzwingen
channel-type.mybmw.statistic-energy-channel.description = Geladene Energie in diesem Monat
channel-type.mybmw.statistic-energy-channel.label = Energie Geladen Monat
channel-type.mybmw.statistic-sessions-channel.description = Anzahl der Ladevorgänge in diesem Monat
channel-type.mybmw.statistic-sessions-channel.label = Ladevorgänge Monat
channel-type.mybmw.statistic-title-channel.label = Ladestatistik Monat
channel-type.mybmw.sunroof-channel.label = Schiebedach
channel-type.mybmw.timer1-day-fri-channel.description = Freitags Planung für Timer 1
channel-type.mybmw.timer1-day-fri-channel.label = Zeitprofil 1 - Freitag
channel-type.mybmw.timer1-day-mon-channel.description = Montags Planung für Timer 1
channel-type.mybmw.timer1-day-mon-channel.label = Zeitprofil 1 - Montag
channel-type.mybmw.timer1-day-sat-channel.description = Samstags Planung für Timer 1
channel-type.mybmw.timer1-day-sat-channel.label = Zeitprofil 1 - Samstag
channel-type.mybmw.timer1-day-sun-channel.description = Sonntags Planung für Timer 1
channel-type.mybmw.timer1-day-sun-channel.label = Zeitprofil 1 - Sonntag
channel-type.mybmw.timer1-day-thu-channel.description = Donnerstags Planung für Timer 1
channel-type.mybmw.timer1-day-thu-channel.label = Zeitprofil 1 - Donnerstag
channel-type.mybmw.timer1-day-tue-channel.description = Dienstags Planung für Timer 1
channel-type.mybmw.timer1-day-tue-channel.label = Zeitprofil 1 - Dienstag
channel-type.mybmw.timer1-day-wed-channel.description = Mittwochs Planung für Timer 1
channel-type.mybmw.timer1-day-wed-channel.label = Zeitprofil 1 - Mittwoch
channel-type.mybmw.timer1-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 1
channel-type.mybmw.timer1-departure-channel.label = Zeitprofil 1 - Abfahrtszeit
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer1-enabled-channel.description = Timer 1 aktiviert
channel-type.mybmw.timer1-enabled-channel.label = Zeitprofil 1 - Aktiviert
channel-type.mybmw.timer2-day-fri-channel.description = Freitags Planung für Timer 2
channel-type.mybmw.timer2-day-fri-channel.label = Zeitprofil 2 - Freitag
channel-type.mybmw.timer2-day-mon-channel.description = Montags Planung für Timer 2
channel-type.mybmw.timer2-day-mon-channel.label = Zeitprofil 2 - Montag
channel-type.mybmw.timer2-day-sat-channel.description = Samstags Planung für Timer 2
channel-type.mybmw.timer2-day-sat-channel.label = Zeitprofil 2 - Samstag
channel-type.mybmw.timer2-day-sun-channel.description = Sonntags Planung für Timer 2
channel-type.mybmw.timer2-day-sun-channel.label = Zeitprofil 2 - Sonntag
channel-type.mybmw.timer2-day-thu-channel.description = Donnerstags Planung für Timer 2
channel-type.mybmw.timer2-day-thu-channel.label = Zeitprofil 2 - Donnerstag
channel-type.mybmw.timer2-day-tue-channel.description = Dienstags Planung für Timer 2
channel-type.mybmw.timer2-day-tue-channel.label = Zeitprofil 2 - Dienstag
channel-type.mybmw.timer2-day-wed-channel.description = Mittwochs Planung für Timer 2
channel-type.mybmw.timer2-day-wed-channel.label = Zeitprofil 2 - Mittwoch
channel-type.mybmw.timer2-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 2
channel-type.mybmw.timer2-departure-channel.label = Zeitprofil 2 - Abfahrtszeit
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer2-enabled-channel.description = Timer 2 aktiviert
channel-type.mybmw.timer2-enabled-channel.label = Zeitprofil 2 - Aktiviert
channel-type.mybmw.timer3-day-fri-channel.description = Freitags Planung für Timer 3
channel-type.mybmw.timer3-day-fri-channel.label = Zeitprofil 3 - Freitag
channel-type.mybmw.timer3-day-mon-channel.description = Montags Planung für Timer 3
channel-type.mybmw.timer3-day-mon-channel.label = Zeitprofil 3 - Montag
channel-type.mybmw.timer3-day-sat-channel.description = Samstags Planung für Timer 3
channel-type.mybmw.timer3-day-sat-channel.label = Zeitprofil 3 - Samstag
channel-type.mybmw.timer3-day-sun-channel.description = Sonntags Planung für Timer 3
channel-type.mybmw.timer3-day-sun-channel.label = Zeitprofil 3 - Sonntag
channel-type.mybmw.timer3-day-thu-channel.description = Donnerstags Planung für Timer 3
channel-type.mybmw.timer3-day-thu-channel.label = Zeitprofil 3 - Donnerstag
channel-type.mybmw.timer3-day-tue-channel.description = Dienstags Planung für Timer 3
channel-type.mybmw.timer3-day-tue-channel.label = Zeitprofil 3 - Dienstag
channel-type.mybmw.timer3-day-wed-channel.description = Mittwochs Planung für Timer 3
channel-type.mybmw.timer3-day-wed-channel.label = Zeitprofil 3 - Mittwoch
channel-type.mybmw.timer3-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 3
channel-type.mybmw.timer3-departure-channel.label = Zeitprofil 3 - Abfahrtszeit
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer3-enabled-channel.description = Timer 3 aktiviert
channel-type.mybmw.timer3-enabled-channel.label = Zeitprofil 3 - Aktiviert
channel-type.mybmw.timer4-day-fri-channel.description = Freitags Planung für Timer 4
channel-type.mybmw.timer4-day-fri-channel.label = Zeitprofil 4 - Freitag
channel-type.mybmw.timer4-day-mon-channel.description = Montags Planung für Timer 4
channel-type.mybmw.timer4-day-mon-channel.label = Zeitprofil 4 - Montag
channel-type.mybmw.timer4-day-sat-channel.description = Samstags Planung für Timer 4
channel-type.mybmw.timer4-day-sat-channel.label = Zeitprofil 4 - Samstag
channel-type.mybmw.timer4-day-sun-channel.description = Sonntags Planung für Timer 4
channel-type.mybmw.timer4-day-sun-channel.label = Zeitprofil 4 - Sonntag
channel-type.mybmw.timer4-day-thu-channel.description = Donnerstags Planung für Timer 4
channel-type.mybmw.timer4-day-thu-channel.label = Zeitprofil 4 - Donnerstag
channel-type.mybmw.timer4-day-tue-channel.description = Dienstags Planung für Timer 4
channel-type.mybmw.timer4-day-tue-channel.label = Zeitprofil 4 - Dienstag
channel-type.mybmw.timer4-day-wed-channel.description = Mittwochs Planung für Timer 4
channel-type.mybmw.timer4-day-wed-channel.label = Zeitprofil 4 - Mittwoch
channel-type.mybmw.timer4-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 4
channel-type.mybmw.timer4-departure-channel.label = Zeitprofil 4 - Abfahrtszeit
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer4-enabled-channel.description = Timer 4 aktiviert
channel-type.mybmw.timer4-enabled-channel.label = Zeitprofil 4 - Aktiviert
channel-type.mybmw.trunk-channel.label = Heckklappe
channel-type.mybmw.window-driver-front-channel.label = Fahrertür Fenster
channel-type.mybmw.window-driver-rear-channel.label = Fahrertür Hinten Fenster
channel-type.mybmw.window-end-channel.description = Endzeit des Ladefensters
channel-type.mybmw.window-end-channel.label = Ladefenster Endzeit
channel-type.mybmw.window-end-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.window-passenger-front-channel.label = Beifahrertür Fenster
channel-type.mybmw.window-passenger-rear-channel.label = Beifahrertür Hinten Fenster
channel-type.mybmw.window-start-channel.description = Startzeit des Ladefensters
channel-type.mybmw.window-start-channel.label = Ladefenster Startzeit
channel-type.mybmw.window-start-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.windows-channel.label = Gesamtzustand der Fenster
mybmw.status.authorization-needed = Bitte ein Captcha abschließen um fortzufahren\: {0}
mybmw.status.user-details-missing = Benutzername oder Passwortkonfiguration fehlt
mybmw.status.region-missing = Konfiguration der Region fehlt
mybmw.status.ip-missing = Die Callback IP des openHAB Hosts konnte nicht abgerufen werden
mybmw.status.vehicle-retrieval-error = Abrufen von Fahrzeugen fehlgeschlagen
mybmw.status.network-error = Abrufen der Fahrzeuge fehlgeschlagen, möglicher Netzwerkfehler
mybmw.status.quota-error = Fehlendes Kontingent, aktualisieren bei {0}
@@ -1,266 +0,0 @@
# add-on
addon.mybmw.name = MyBMW
addon.mybmw.description = Provides access to your Vehicle Data like MyBMW App
# thing types
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.language.label = Kieliasetukset
thing-type.config.mybmw.bridge.password.description = MyBMW Salasana
thing-type.config.mybmw.bridge.password.label = Salasana
thing-type.config.mybmw.bridge.region.description = Valitse alue muodostaaksesi yhteyden sopivaan BMW-palvelimeen
thing-type.config.mybmw.bridge.region.label = Alue
thing-type.config.mybmw.bridge.region.option.CHINA = Kiina
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Pohjois-Amerikka
thing-type.config.mybmw.bridge.region.option.ROW = Muu maailma
thing-type.config.mybmw.bridge.userName.description = MyBMW Käyttäjätunnus
thing-type.config.mybmw.bridge.userName.label = Käyttäjätunnus
thing-type.config.mybmw.vehicle.refreshInterval.description = Tietojen päivittämisnopeus ajoneuvotiedoissa. Jos arvo on 0, päivitystä ei tapahdu automaattisesti.
thing-type.config.mybmw.vehicle.refreshInterval.label = Päivitysväli
thing-type.config.mybmw.vehicle.vehicleBrand.description = Automerkki kuten BMW tai Mini
thing-type.config.mybmw.vehicle.vehicleBrand.label = Ajoneuvon merkki
thing-type.config.mybmw.vehicle.vin.description = BMW\:n antama VIN
thing-type.config.mybmw.vehicle.vin.label = Ajoneuvon valmistenumero (VIN)
thing-type.mybmw.account.description = BMW-tilisi tiedot
thing-type.mybmw.account.label = MyBMW tili
thing-type.mybmw.bev_rex.description = Akkukäyttöinen sähköajoneuvo kantaman pidentäjällä (BEV_REX)
thing-type.mybmw.bev_rex.label = Sähköauto REX\: llä
thing-type.mybmw.bev.description = Akkukäyttöinen sähköajoneuvo (BEV)
thing-type.mybmw.bev.label = Sähköajoneuvo
thing-type.mybmw.conv.description = Perinteisellä polttoaineella varustettu ajoneuvo (CONV)
thing-type.mybmw.conv.label = Perinteinen ajoneuvo
thing-type.mybmw.phev.description = Ladattava hybridi sähköajoneuvo
thing-type.mybmw.phev.label = Ladattava hybridi (PHEV)
# channel group types
channel-group-type.mybmw.charge-statistic.description = Kuluvan kuukauden lataustilastot
channel-group-type.mybmw.charge-statistic.label = Lataustilastot
channel-group-type.mybmw.check-control-values.description = Näyttää nykyiset aktiiviset CheckControl-viestit
channel-group-type.mybmw.check-control-values.label = CheckControl-viestit
channel-group-type.mybmw.conv-range-values.description = Näyttää ajokilometrimäärän, jäljellä olevan toimintamatkan ja polttoainetason arvot
channel-group-type.mybmw.conv-range-values.label = Toimintamatka ja polttoainetiedot
channel-group-type.mybmw.door-values.description = Kaikkien ovien ja ikkunoiden yksityiskohtainen tila
channel-group-type.mybmw.door-values.label = Yksityiskohtainen oven tila
channel-group-type.mybmw.ev-range-values.description = Näyttää mittarilukeman, jäljellä oleva toimintamatkan ja lataustason arvot
channel-group-type.mybmw.ev-range-values.label = Kantama- ja lataustiedot
channel-group-type.mybmw.ev-vehicle-status.description = Ajoneuvon yleistila
channel-group-type.mybmw.ev-vehicle-status.label = Ajoneuvon tila
channel-group-type.mybmw.ev-vehicle-update.description = Pakota ajoneuvon tietojen päivitys
channel-group-type.mybmw.ev-vehicle-update.label = Pakota Ajoneuvon Päivitys
channel-group-type.mybmw.hybrid-range-values.description = Näyttää ajokilometrit, jäljellä olevan polttoaineen ja ajomatkan hybridiautoille
channel-group-type.mybmw.hybrid-range-values.label = Toimintamatka, lataus/polttoainetiedot
channel-group-type.mybmw.image-values.description = Näyttää ajoneuvosi kuvan
channel-group-type.mybmw.image-values.label = Ajoneuvon kuva
channel-group-type.mybmw.location-values.description = Ajoneuvon koordinaatit ja suunta
channel-group-type.mybmw.location-values.label = Ajoneuvon sijainti
channel-group-type.mybmw.profile-values.description = Ajoitetut latausprofiilit
channel-group-type.mybmw.profile-values.label = Sähkölatausprofiili
channel-group-type.mybmw.remote-services.description = Ajoneuvon etäohjaus
channel-group-type.mybmw.remote-services.label = Etäpalvelut
channel-group-type.mybmw.service-values.description = Auton tulevat huoltoaikataulut
channel-group-type.mybmw.service-values.label = Ajoneuvopalvelut
channel-group-type.mybmw.session-values.description = Aiemmat lataustapahtumat
channel-group-type.mybmw.session-values.label = Lataustapahtumat
channel-group-type.mybmw.tire-pressures.description = Nykyiset ja halutut paineet kaikkiin renkaisiin
channel-group-type.mybmw.tire-pressures.label = Rengaspaine
channel-group-type.mybmw.vehicle-status.description = Ajoneuvon yleistila
channel-group-type.mybmw.vehicle-status.label = Ajoneuvon Tila
channel-group-type.mybmw.vehicle-update.description = Pakota ajoneuvon tietojen päivitys
channel-group-type.mybmw.vehicle-update.label = Pakota Ajoneuvon Päivitys
# channel types
channel-type.mybmw.address-channel.label = Osoite
channel-type.mybmw.charging-info-channel.label = Lataustiedot
channel-type.mybmw.charging-remaining-channel.label = Jäljellä Oleva Maksuaika
channel-type.mybmw.charging-status-channel.label = Lataustila
channel-type.mybmw.charging-update-channel.label = Pakota lataustietojen päivitys
channel-type.mybmw.check-control-channel.label = Check Control
channel-type.mybmw.checkcontrol-details-channel.label = Check Control tiedot
channel-type.mybmw.checkcontrol-name-channel.label = Check Control kuvaus
channel-type.mybmw.checkcontrol-severity-channel.label = Vakavuustaso
channel-type.mybmw.doors-channel.label = Oven yleistila
channel-type.mybmw.driver-front-channel.label = Kuljettajan ovi
channel-type.mybmw.driver-rear-channel.label = Kuljettajan puoleinen takaovi
channel-type.mybmw.estimated-fuel-l-100km-channel.label = Arvioitu kulutus l/100 km
channel-type.mybmw.estimated-fuel-mpg-channel.label = Arvioitu kulutus mpg
channel-type.mybmw.front-left-current-channel.label = Rengaspaine vasen etu
channel-type.mybmw.front-left-target-channel.label = Rengaspaine vasen etu tavoite
channel-type.mybmw.front-right-current-channel.label = Rengaspaine oikea etu
channel-type.mybmw.front-right-target-channel.label = Rengaspaine oikea etu tavoite
channel-type.mybmw.gps-channel.label = GPS koordinaatit
channel-type.mybmw.heading-channel.label = Suuntakulma
channel-type.mybmw.home-distance-channel.description = Ajoneuvon ja kotipaikan välinen laskennallinen etäisyys
channel-type.mybmw.home-distance-channel.label = Etäisyys Kotipaikasta
channel-type.mybmw.hood-channel.label = Konepelti
channel-type.mybmw.image-update-channel.label = Pakota kuvan päivitys
channel-type.mybmw.image-view-channel.command.option.FrontLeft = Vasen Sivunäkymä
channel-type.mybmw.image-view-channel.command.option.FrontRight = Oikea Sivunäkymä
channel-type.mybmw.image-view-channel.command.option.FrontView = Etu Näkymä
channel-type.mybmw.image-view-channel.command.option.RearView = Taka-Näkymä
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Näkymä edestä
channel-type.mybmw.image-view-channel.label = Kuvan katseluportti
channel-type.mybmw.last-fetched-channel.label = Viimeisin Openhab Päivityksen Aikaleima
channel-type.mybmw.last-update-channel.label = Viimeisen Auton Tilan Aikaleima
channel-type.mybmw.lock-channel.label = Ovet lukittu
channel-type.mybmw.mileage-channel.label = Ajettu kokonaismatka
channel-type.mybmw.next-service-date-channel.label = Seuraava huoltopäivämäärä
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.next-service-mileage-channel.label = Kilometrit seuraavaan huoltoon
channel-type.mybmw.passenger-front-channel.label = Matkustajan ovi
channel-type.mybmw.passenger-rear-channel.label = Matkustajan puoleinen takaovi
channel-type.mybmw.plug-connection-channel.label = Pistokeyhteyden tila
channel-type.mybmw.png-channel.label = Renderoidun ajoneuvon kuva
channel-type.mybmw.profile-climate-channel.label = Ilmastointi lähtöaikana
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Viikkoaikataulu
channel-type.mybmw.profile-control-channel.description = Lataussuunnitelman valinta
channel-type.mybmw.profile-control-channel.label = Lataussuunnitelma
channel-type.mybmw.profile-limit-channel.description = Rajoitettu lataus aktivoitu
channel-type.mybmw.profile-limit-channel.label = Latausenergia rajoitettu
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Käytä latausasetusta
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Välitön lataus
channel-type.mybmw.profile-mode-channel.description = Välittömän tai viivästetyn latauksen tilan valinta
channel-type.mybmw.profile-mode-channel.label = Lataustila
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Latausikkuna
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = Ei valintaa
channel-type.mybmw.profile-prefs-channel.description = Viivästetyn latauksen asetukset
channel-type.mybmw.profile-prefs-channel.label = Latausasetukset
channel-type.mybmw.profile-target-channel.description = Lataustilan tavoite
channel-type.mybmw.profile-target-channel.label = Lataustilan tavoite
channel-type.mybmw.range-electric-channel.label = Sähköinen toimintamatka
channel-type.mybmw.range-fuel-channel.label = Toimintamatka polttoaineella
channel-type.mybmw.range-hybrid-channel.label = Yhdistetty toimintamatka
channel-type.mybmw.range-radius-electric-channel.label = Sähköinen toimintasäde
channel-type.mybmw.range-radius-fuel-channel.label = Toimintamatka polttoaineella
channel-type.mybmw.range-radius-hybrid-channel.label = Yhdistetty toimintamatka
channel-type.mybmw.raw-channel.label = Raakadata
channel-type.mybmw.rear-left-current-channel.label = Rengaspaine vasen taka
channel-type.mybmw.rear-left-target-channel.label = Rengaspaine vasen taka tavoite
channel-type.mybmw.rear-right-current-channel.label = Rengaspaine oikea taka
channel-type.mybmw.rear-right-target-channel.label = Rengaspaine oikea taka tavoite
channel-type.mybmw.remaining-fuel-channel.label = Jäljellä oleva polttoaine
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Aloita ilmasto nyt
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Pysäytä ilmasto nyt
channel-type.mybmw.remote-command-channel.command.option.door-lock = Lukitse ajoneuvo
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Avaa ajoneuvon lukitus
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Soita äänitorvea
channel-type.mybmw.remote-command-channel.command.option.light-flash = Väläytä valoja
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Etsi ajoneuvo
channel-type.mybmw.remote-command-channel.label = Etäkomento
channel-type.mybmw.remote-state-channel.label = Etäkomenmon tila
channel-type.mybmw.service-date-channel.label = Huollon päiväys
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
channel-type.mybmw.service-details-channel.label = Huollon yksityiskohdat
channel-type.mybmw.service-mileage-channel.label = Kilometrit huoltoon asti
channel-type.mybmw.service-name-channel.label = Palvelun nimi
channel-type.mybmw.session-energy-channel.label = Ladattu energia
channel-type.mybmw.session-issue-channel.label = Ongelmia latauksen aikana
channel-type.mybmw.session-status-channel.label = Latauksen tila
channel-type.mybmw.session-subtitle-channel.label = Latauksen yksityiskohdat
channel-type.mybmw.session-title-channel.label = Latauksen nimi
channel-type.mybmw.soc-channel.label = Akun lataustaso
channel-type.mybmw.state-update-channel.label = Pakota ajoneuvon tietojen päivitys
channel-type.mybmw.statistic-energy-channel.description = Kuluvan kuukauden ladattu kokonaisenergia
channel-type.mybmw.statistic-energy-channel.label = Latauskustannus
channel-type.mybmw.statistic-sessions-channel.description = Latauskertojen määrä tässä kuussa
channel-type.mybmw.statistic-sessions-channel.label = Latauskerrat
channel-type.mybmw.statistic-title-channel.label = Lataustilastokuukausi
channel-type.mybmw.sunroof-channel.label = Kattoluukku
channel-type.mybmw.timer1-day-fri-channel.description = Perjantaina ajastin 1
channel-type.mybmw.timer1-day-fri-channel.label = T1 Perjantai
channel-type.mybmw.timer1-day-mon-channel.description = Maanantaina ajastin 1
channel-type.mybmw.timer1-day-mon-channel.label = T1 Maanantai
channel-type.mybmw.timer1-day-sat-channel.description = Lauantaina ajastin 1
channel-type.mybmw.timer1-day-sat-channel.label = T1 Lauantai
channel-type.mybmw.timer1-day-sun-channel.description = Sunnuntaina ajastin 1
channel-type.mybmw.timer1-day-sun-channel.label = T1 Sunnuntai
channel-type.mybmw.timer1-day-thu-channel.description = Torstaina ajastin 1
channel-type.mybmw.timer1-day-thu-channel.label = T1 Torstai
channel-type.mybmw.timer1-day-tue-channel.description = Tiistaina ajastin 1
channel-type.mybmw.timer1-day-tue-channel.label = T1 Tiistai
channel-type.mybmw.timer1-day-wed-channel.description = Keskiviikkona ajastin 1
channel-type.mybmw.timer1-day-wed-channel.label = T1 Keskiviikko
channel-type.mybmw.timer1-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 1
channel-type.mybmw.timer1-departure-channel.label = T1 Lähtöaika
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer1-enabled-channel.description = Ajastin 1 käytössä
channel-type.mybmw.timer1-enabled-channel.label = T1 Käytössä
channel-type.mybmw.timer2-day-fri-channel.description = Perjantaina ajastin 2
channel-type.mybmw.timer2-day-fri-channel.label = T2 Perjantai
channel-type.mybmw.timer2-day-mon-channel.description = Maanantaina ajastin 2
channel-type.mybmw.timer2-day-mon-channel.label = T2 Maanantai
channel-type.mybmw.timer2-day-sat-channel.description = Lauantaina ajastin 2
channel-type.mybmw.timer2-day-sat-channel.label = T2 Lauantai
channel-type.mybmw.timer2-day-sun-channel.description = Sunnuntaina ajastin 2
channel-type.mybmw.timer2-day-sun-channel.label = T2 Sunnuntai
channel-type.mybmw.timer2-day-thu-channel.description = Torstaina ajastin 2
channel-type.mybmw.timer2-day-thu-channel.label = T2 Torstai
channel-type.mybmw.timer2-day-tue-channel.description = Tiistaina ajastin 2
channel-type.mybmw.timer2-day-tue-channel.label = T2 Tiistai
channel-type.mybmw.timer2-day-wed-channel.description = Keskiviikkona ajastin 2
channel-type.mybmw.timer2-day-wed-channel.label = T2 Keskiviikko
channel-type.mybmw.timer2-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 2
channel-type.mybmw.timer2-departure-channel.label = T2 Lähtöaika
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer2-enabled-channel.description = Ajastin 2 käytössä
channel-type.mybmw.timer2-enabled-channel.label = T2 Käytössä
channel-type.mybmw.timer3-day-fri-channel.description = Perjantaina ajastin 3
channel-type.mybmw.timer3-day-fri-channel.label = T3 Perjantai
channel-type.mybmw.timer3-day-mon-channel.description = Maanantaina ajastin 3
channel-type.mybmw.timer3-day-mon-channel.label = T3 Maanantai
channel-type.mybmw.timer3-day-sat-channel.description = Lauantaina ajastin 3
channel-type.mybmw.timer3-day-sat-channel.label = T3 Lauantai
channel-type.mybmw.timer3-day-sun-channel.description = Sunnuntaina ajastin 3
channel-type.mybmw.timer3-day-sun-channel.label = T3 Sunnuntai
channel-type.mybmw.timer3-day-thu-channel.description = Torstaina ajastin 3
channel-type.mybmw.timer3-day-thu-channel.label = T3 Torstai
channel-type.mybmw.timer3-day-tue-channel.description = Tiistaina ajastin 3
channel-type.mybmw.timer3-day-tue-channel.label = T3 Tiistai
channel-type.mybmw.timer3-day-wed-channel.description = Keskiviikkona ajastin 3
channel-type.mybmw.timer3-day-wed-channel.label = T3 Keskiviikko
channel-type.mybmw.timer3-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 3
channel-type.mybmw.timer3-departure-channel.label = T3 Lähtöaika
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer3-enabled-channel.description = Ajastin 3 käytössä
channel-type.mybmw.timer3-enabled-channel.label = T3 Käytössä
channel-type.mybmw.timer4-day-fri-channel.description = Perjantaina ajastin 4
channel-type.mybmw.timer4-day-fri-channel.label = T4 Perjantai
channel-type.mybmw.timer4-day-mon-channel.description = Maanantaina ajastin 4
channel-type.mybmw.timer4-day-mon-channel.label = T4 Maanantai
channel-type.mybmw.timer4-day-sat-channel.description = Lauantaina ajastin 4
channel-type.mybmw.timer4-day-sat-channel.label = T4 Lauantai
channel-type.mybmw.timer4-day-sun-channel.description = Sunnuntaina ajastin 4
channel-type.mybmw.timer4-day-sun-channel.label = T4 Sunnuntai
channel-type.mybmw.timer4-day-thu-channel.description = Torstaina ajastin 4
channel-type.mybmw.timer4-day-thu-channel.label = T4 Torstai
channel-type.mybmw.timer4-day-tue-channel.description = Tiistaina ajastin 4
channel-type.mybmw.timer4-day-tue-channel.label = T4 Tiistai
channel-type.mybmw.timer4-day-wed-channel.description = Keskiviikkona ajastin 4
channel-type.mybmw.timer4-day-wed-channel.label = T4 Keskiviikko
channel-type.mybmw.timer4-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 4
channel-type.mybmw.timer4-departure-channel.label = T4 Lähtöaika
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.timer4-enabled-channel.description = Ajastin 4 käytössä
channel-type.mybmw.timer4-enabled-channel.label = T4 Käytössä
channel-type.mybmw.trunk-channel.label = Tavaratila
channel-type.mybmw.window-driver-front-channel.label = Kuljettajan ikkuna
channel-type.mybmw.window-driver-rear-channel.label = Kuljettajan puoleinen takaikkuna
channel-type.mybmw.window-end-channel.description = Lataustapahtuman päättymisaika
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 = Matkustajan ikkuna
channel-type.mybmw.window-passenger-rear-channel.label = Matkustajan puoleinen takaikkuna
channel-type.mybmw.window-start-channel.description = Lataustapahtuman alkamisaika
channel-type.mybmw.window-start-channel.label = Latauksen aloitusaika
channel-type.mybmw.window-start-channel.state.pattern = %1$tH\:%1$tM
channel-type.mybmw.windows-channel.label = Overall Window Status
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mybmw"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="account">
<label>MyBMW Account</label>
<description>Your BMW account data</description>
<semantic-equipment-tag>WebService</semantic-equipment-tag>
<config-description-ref uri="thing-type:mybmw:bridge"/>
</bridge-type>
</thing:thing-descriptions>
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mybmw"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="charge-statistic">
<label>Charging Statistics</label>
<description>Charging statistics of current month</description>
<channels>
<channel id="title" typeId="statistic-title-channel"/>
<channel id="energy" typeId="statistic-energy-channel"/>
<channel id="sessions" typeId="statistic-sessions-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mybmw"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="statistic-title-channel">
<item-type>String</item-type>
<label>Charge Statistic Month</label>
</channel-type>
<channel-type id="statistic-energy-channel">
<item-type>Number:Energy</item-type>
<label>Energy Charged</label>
<description>Total energy charged in current month</description>
<tags>
<tag>Measurement</tag>
<tag>Energy</tag>
</tags>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="statistic-sessions-channel">
<item-type>Number</item-type>
<label>Charge Sessions</label>
<description>Number of charging sessions this month</description>
<tags>
<tag>Status</tag>
</tags>
</channel-type>
</thing:thing-descriptions>

Some files were not shown because too many files have changed in this diff Show More