[mybmw] new binding contribution (#12006)
* solve pom.xml conflict Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * authorization working Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * Fully Authorization integration & cleanup Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * make project compilable Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * make code compilable & buildable Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix discovery test Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix property test Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix unit tests Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * unit tests fixed Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * solve checkstyle high & medium Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * include all status update channel calls Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * correct Closed/Connected/Locked states Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add charge statistics Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add ChargingProfile channels Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add charging sessions Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add discovery properties Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * provide general check-control info Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add test data for different vehicles Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix some checkstyle Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add more vehicles to unit test Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add F11 test Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add service mileage Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add check controls Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add fingerprint mechanism Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * cleanup channels after rework Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfixe requesting vehicles Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix brand handling Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * anonymous fingerprint Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add charge statistics channels Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add tire channels Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * remove range max channels Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * resolve last checkstyle issues Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix chrge profile updates Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix remote service execution Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix image handling Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * adapt service & checkcontrol handling Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfix session selection Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * rework km / mi handling Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * introduce fallbacks for range calculations Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * unit tests for all available vehicle fingerprints Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfixes during creation of HMI and translations Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfix translation and language selection Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * introduce climate-now start / stop Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfixes translation Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add all fuelindicator fields Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * correct remote service ids, commands and translations Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * cleanup remote requsts and responses Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add remote response examples Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * rework command options Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * remove unused timezoneprovider Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * locale language debugging Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * fix range value calculation Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * introduce charge-info channel Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * localize charge info string Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * smaller charge status fixes Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * 1st readme adaptions plus corresponding bugfixes Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfix date time conversion Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * readme channel group update Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add motion status Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * raw test anonymous fingerprint Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * revert motion status Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfixes todo sections Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * china login Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * remove unnecessary info logging Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * spell check and example update Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * resolve codeowner conflicts Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * delete rex responses Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfix linux characters Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * bugfix unit test with static time comparison Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * Copyright header adaption Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add raw data channel Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add address channel Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add motion channel Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * add mild hybrid vehicle support Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * review comment corrections Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * correct review comments Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> * resolve bom conflict Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
@ -202,6 +202,7 @@
|
|||||||
/bundles/org.openhab.binding.mqtt.homeassistant/ @davidgraeff @antroids
|
/bundles/org.openhab.binding.mqtt.homeassistant/ @davidgraeff @antroids
|
||||||
/bundles/org.openhab.binding.mqtt.homie/ @davidgraeff
|
/bundles/org.openhab.binding.mqtt.homie/ @davidgraeff
|
||||||
/bundles/org.openhab.binding.mycroft/ @dalgwen
|
/bundles/org.openhab.binding.mycroft/ @dalgwen
|
||||||
|
/bundles/org.openhab.binding.mybmw/ @weymann @ntruchsess
|
||||||
/bundles/org.openhab.binding.myq/ @digitaldan
|
/bundles/org.openhab.binding.myq/ @digitaldan
|
||||||
/bundles/org.openhab.binding.mystrom/ @pail23
|
/bundles/org.openhab.binding.mystrom/ @pail23
|
||||||
/bundles/org.openhab.binding.nanoleaf/ @raepple @stefan-hoehn
|
/bundles/org.openhab.binding.nanoleaf/ @raepple @stefan-hoehn
|
||||||
|
@ -1001,6 +1001,11 @@
|
|||||||
<artifactId>org.openhab.binding.mycroft</artifactId>
|
<artifactId>org.openhab.binding.mycroft</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.mybmw</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.myq</artifactId>
|
<artifactId>org.openhab.binding.myq</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.mybmw/NOTICE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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
|
846
bundles/org.openhab.binding.mybmw/README.md
Normal file
@ -0,0 +1,846 @@
|
|||||||
|
# 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
|
||||||
|
2. 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.
|
||||||
|
|
||||||
|
## 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" 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 3 different options
|
||||||
|
|
||||||
|
* _NORTH_AMERICA_
|
||||||
|
* _CHINA_
|
||||||
|
* _ROW_ (Rest of World)
|
||||||
|
|
||||||
|
|
||||||
|
#### Advanced Configuration
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------------|---------|---------------------------------------------------------|
|
||||||
|
| language | text | Channel data can be returned in the desired language |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Thing Configuration
|
||||||
|
|
||||||
|
Same configuration is needed for all things
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------------|---------|---------------------------------------|
|
||||||
|
| vin | text | Vehicle Identification Number (VIN) |
|
||||||
|
| refreshInterval | integer | Refresh Interval in Minutes |
|
||||||
|
|
||||||
|
|
||||||
|
#### 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 |
|
||||||
|
|----------------------------------|---------------------------------------------------|------|------|---------|-----|
|
||||||
|
| [status](#vehicle-status) | Overall vehicle status | X | X | X | X |
|
||||||
|
| [range](#range-data) | Provides mileage, range and charge / fuel levels | X | X | X | X |
|
||||||
|
| [doors](#doors-details) | Detials of all doors and windows | 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 Status
|
||||||
|
|
||||||
|
Reflects overall status of the vehicle.
|
||||||
|
|
||||||
|
* Channel Group ID is **status**
|
||||||
|
* Available for all vehicles
|
||||||
|
* Read-only values
|
||||||
|
|
||||||
|
| Channel Label | Channel ID | Type | Description | conv | phev | bev_rex | bev |
|
||||||
|
|---------------------------|---------------------|---------------|------------------------------------------------|------|------|---------|-----|
|
||||||
|
| Overall Door Status | doors | String | Combined status for all doors | X | X | X | X |
|
||||||
|
| Overall Window Status | windows | String | Combined status for all windows | X | X | X | X |
|
||||||
|
| Doors Locked | lock | String | Status if vehicle is secured | X | X | X | X |
|
||||||
|
| Next Service Date | service-date | DateTime | Date of next upcoming service | X | X | X | X |
|
||||||
|
| Mileage till Next Service | service-mileage | Number:Length | Mileage till upcoming service | X | X | X | X |
|
||||||
|
| Check Control | check-control | String | Presence of active warning messages | X | X | X | X |
|
||||||
|
| Plug Connection Status | plug-connection | String | Plug is _Connected_ or _Not connected_ | | X | X | X |
|
||||||
|
| Charging Status | charge | String | Current charging status | | X | X | X |
|
||||||
|
| Charging Information | charge-info | String | Information regarding current charging session | | X | X | X |
|
||||||
|
| Motion Status | motion | Switch | Driving state - depends on vehicle hardware | X | X | X | X |
|
||||||
|
| Last Status Timestamp | last-update | DateTime | Date and time of last status update | 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 Label | Channel ID | Type | Description |
|
||||||
|
|---------------------------|---------------------|---------------|------------------------------------------------|
|
||||||
|
| Raw Data | raw | String | Unfiltered JSON String of vehicle data |
|
||||||
|
|
||||||
|
<img align="right" src="./doc/RawData.png" width="400" height="125"/>
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
_Country ISO Code_
|
||||||
|
|
||||||
|
```
|
||||||
|
$.properties.originCountryISO
|
||||||
|
```
|
||||||
|
|
||||||
|
_Drivers Guide URL_
|
||||||
|
|
||||||
|
```
|
||||||
|
$.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 Label | Channel ID | Type | conv | phev | bev_rex | bev |
|
||||||
|
|---------------------------|-------------------------|----------------------|------|------|---------|-----|
|
||||||
|
| Mileage | mileage | Number:Length | X | X | X | X |
|
||||||
|
| Fuel Range | range-fuel | Number:Length | X | X | X | |
|
||||||
|
| Electric Range | range-electric | Number:Length | | X | X | X |
|
||||||
|
| Hybrid Range | range-hybrid | Number:Length | | X | X | |
|
||||||
|
| Battery Charge Level | soc | Number:Dimensionless | | X | X | X |
|
||||||
|
| Remaining Fuel | remaining-fuel | Number:Volume | X | X | X | |
|
||||||
|
| Fuel Range Radius | range-radius-fuel | Number:Length | X | X | X | |
|
||||||
|
| Electric Range Radius | range-radius-electric | Number:Length | | X | X | X |
|
||||||
|
| Hybrid Range Radius | range-radius-hybrid | Number:Length | | X | X | |
|
||||||
|
|
||||||
|
|
||||||
|
#### 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 Label | Channel ID | Type |
|
||||||
|
|----------------------------|-------------------------|---------------|
|
||||||
|
| Driver Door | driver-front | String |
|
||||||
|
| Driver Door Rear | driver-rear | String |
|
||||||
|
| Passenger Door | passenger-front | String |
|
||||||
|
| Passenger Door Rear | passenger-rear | String |
|
||||||
|
| Trunk | trunk | String |
|
||||||
|
| Hood | hood | String |
|
||||||
|
| Driver Window | win-driver-front | String |
|
||||||
|
| Driver Rear Window | win-driver-rear | String |
|
||||||
|
| Passenger Window | win-passenger-front | String |
|
||||||
|
| Passenger Rear Window | win-passenger-rear | String |
|
||||||
|
| Rear Window | win-rear | String |
|
||||||
|
| Sunroof | sunroof | String |
|
||||||
|
|
||||||
|
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 Label | Channel ID | Type | Access |
|
||||||
|
|---------------------------------|---------------------|----------------|------------|
|
||||||
|
| Check Control Description | name | String | Read/Write |
|
||||||
|
| Check Control Details | details | String | Read |
|
||||||
|
| Severity Level | 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 Label | Channel ID | Type | Access |
|
||||||
|
|--------------------------------|---------------------|----------------|------------|
|
||||||
|
| Service Name | name | String | Read/Write |
|
||||||
|
| Service Details | details | String | Read |
|
||||||
|
| Service Date | date | DateTime | Read |
|
||||||
|
| Mileage till Service | 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 Label | Channel ID | Type |
|
||||||
|
|-----------------|---------------------|--------------|
|
||||||
|
| GPS Coordinates | gps | Location |
|
||||||
|
| Heading | heading | Number:Angle |
|
||||||
|
| Address | address | String |
|
||||||
|
|
||||||
|
|
||||||
|
#### 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 Label | Channel ID | Type | Access |
|
||||||
|
|-------------------------|---------------------|---------|--------|
|
||||||
|
| Remote Service Command | command | String | Write |
|
||||||
|
| Service Execution State | state | String | Read |
|
||||||
|
|
||||||
|
The channel _command_ provides options
|
||||||
|
|
||||||
|
* _flash-lights_
|
||||||
|
* _vehicle-finder_
|
||||||
|
* _door-lock_
|
||||||
|
* _door-unlock_
|
||||||
|
* _horn-low_
|
||||||
|
* _climate-now-start_
|
||||||
|
* _climate-now-stop_
|
||||||
|
|
||||||
|
The channel _state_ shows the progress of the command execution in the following order
|
||||||
|
|
||||||
|
1) _initiated_
|
||||||
|
2) _pending_
|
||||||
|
3) _delivered_
|
||||||
|
4) _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 Label | Channel ID | Type |
|
||||||
|
|----------------------------|---------------------------|----------|
|
||||||
|
| Charge Mode | mode | String |
|
||||||
|
| Charge Preferences | prefs | String |
|
||||||
|
| Charging Plan | control | String |
|
||||||
|
| SoC Target | target | String |
|
||||||
|
| Charging Energy Limited | limit | Switch |
|
||||||
|
| Window Start Time | window-start | DateTime |
|
||||||
|
| Window End Time | window-end | DateTime |
|
||||||
|
| A/C at Departure | climate | Switch |
|
||||||
|
| T*X* Enabled | timer*X*-enabled | Switch |
|
||||||
|
| T*X* Departure Time | timer*X*-departure | DateTime |
|
||||||
|
| T*X* Monday | timer*X*-day-mon | Switch |
|
||||||
|
| T*X* Tuesday | timer*X*-day-tue | Switch |
|
||||||
|
| T*X* Wednesday | timer*X*-day-wed | Switch |
|
||||||
|
| T*X* Thursday | timer*X*-day-thu | Switch |
|
||||||
|
| T*X* Friday | timer*X*-day-fri | Switch |
|
||||||
|
| T*X* Saturday | timer*X*-day-sat | Switch |
|
||||||
|
| T*X* Sunday | 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 Label | Channel ID | Type |
|
||||||
|
|----------------------------|-------------------------|----------------|
|
||||||
|
| Charge Statistic Month | title | String |
|
||||||
|
| Energy Charged | energy | Number:Energy |
|
||||||
|
| Charge Sessions | sessions | Number |
|
||||||
|
|
||||||
|
|
||||||
|
#### 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 Label | Channel ID | Type |
|
||||||
|
|---------------------------------|--------------|----------|
|
||||||
|
| Session Title | title | String |
|
||||||
|
| Session Details | subtitle | String |
|
||||||
|
| Charged Energy in Session | energy | String |
|
||||||
|
| Issues during Session | issue | String |
|
||||||
|
| Session Status | status | String |
|
||||||
|
|
||||||
|
|
||||||
|
#### 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 Label | Channel ID | Type |
|
||||||
|
|----------------------------|-------------------------|------------------|
|
||||||
|
| Front Left | fl-current | Number:Pressure |
|
||||||
|
| Front Left Target | fl-target | Number:Pressure |
|
||||||
|
| Front Right | fr-current | Number:Pressure |
|
||||||
|
| Front Right Target | fr-target | Number:Pressure |
|
||||||
|
| Rear Left | rl-current | Number:Pressure |
|
||||||
|
| Rear Left Target | rl-target | Number:Pressure |
|
||||||
|
| Rear Right | rr-current | Number:Pressure |
|
||||||
|
| Rear Right Target | rr-target | Number:Pressure |
|
||||||
|
|
||||||
|
|
||||||
|
#### Image
|
||||||
|
|
||||||
|
Image representation of the vehicle.
|
||||||
|
|
||||||
|
* Channel Group ID is **image**
|
||||||
|
* Available for all vehicles
|
||||||
|
* Read/Write access
|
||||||
|
|
||||||
|
| Channel Label | Channel ID | Type | Access |
|
||||||
|
|----------------------------|---------------------|--------|----------|
|
||||||
|
| Rendered Vehicle Image | png | Image | Read |
|
||||||
|
| Image Viewport | view | String | Write |
|
||||||
|
|
||||||
|
Possible view ports:
|
||||||
|
|
||||||
|
* _VehicleStatus_ Front Side View
|
||||||
|
* _VehicleInfo_ Front View
|
||||||
|
* _ChargingHistory_ Side View
|
||||||
|
* _Default_ Front Side View
|
||||||
|
|
||||||
|
|
||||||
|
## Further Descriptions
|
||||||
|
|
||||||
|
### Dynamic Data
|
||||||
|
|
||||||
|
<img align="right" src="./doc/SessionOptions.png" 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
|
||||||
|
|
||||||
|
<img align="right" src="./doc/DiscoveryScan.png" width="400" height="350"/>
|
||||||
|
|
||||||
|
|
||||||
|
First [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) for the binding.
|
||||||
|
|
||||||
|
```
|
||||||
|
log:set DEBUG org.openhab.binding.mybmw
|
||||||
|
```
|
||||||
|
|
||||||
|
The debug fingerprint is generated every time the discovery is executed.
|
||||||
|
To force a new fingerprint perform a _Scan_ for MyBMW things.
|
||||||
|
Personal data is eliminated from the log entries so it should be possible to share them in public.
|
||||||
|
Data like
|
||||||
|
|
||||||
|
* Vehicle Identification Number (VIN)
|
||||||
|
* Location data
|
||||||
|
|
||||||
|
are anonymized.
|
||||||
|
You'll find the fingerprint in the logs with the command
|
||||||
|
|
||||||
|
```
|
||||||
|
grep "Discovery Fingerprint Data" openhab.log
|
||||||
|
```
|
||||||
|
|
||||||
|
After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint data!
|
||||||
|
Your feedback is highly appreciated!
|
||||||
|
|
||||||
|
|
||||||
|
### Range vs Range Radius
|
||||||
|
|
||||||
|
<img align="right" src="./doc/range-radius.png" 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
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
|
||||||
|
```
|
||||||
|
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 project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
|
||||||
|
|
BIN
bundles/org.openhab.binding.mybmw/doc/AwayImage.png
Normal file
After Width: | Height: | Size: 211 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/CarStatusImages.png
Normal file
After Width: | Height: | Size: 460 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/ChargingImage.png
Normal file
After Width: | Height: | Size: 188 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/CheckControlImage.png
Normal file
After Width: | Height: | Size: 337 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/DiscoveryScan.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/RawData.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/RawDataItems.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/SessionOptions.png
Normal file
After Width: | Height: | Size: 238 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/UnlockedImage.png
Normal file
After Width: | Height: | Size: 345 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/panel.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
bundles/org.openhab.binding.mybmw/doc/properties.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/range-radius.png
Normal file
After Width: | Height: | Size: 740 KiB |
BIN
bundles/org.openhab.binding.mybmw/doc/vehicle-properties.png
Normal file
After Width: | Height: | Size: 97 KiB |
17
bundles/org.openhab.binding.mybmw/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<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 http://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>3.3.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.mybmw</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: MyBMW Binding</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?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>
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MyBMWConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MyBMWConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on the location the correct server needs to be called
|
||||||
|
*/
|
||||||
|
public String region = Constants.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBMW App Username
|
||||||
|
*/
|
||||||
|
public String userName = Constants.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MyBMW App Password
|
||||||
|
*/
|
||||||
|
public String password = Constants.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preferred Locale language
|
||||||
|
*/
|
||||||
|
public String language = Constants.LANGUAGE_AUTODETECT;
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.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 & send of charge profile
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MyBMWConstants {
|
||||||
|
|
||||||
|
private static final String BINDING_ID = "mybmw";
|
||||||
|
|
||||||
|
public static final String VIN = "vin";
|
||||||
|
|
||||||
|
public static final int DEFAULT_IMAGE_SIZE_PX = 1024;
|
||||||
|
public static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 5;
|
||||||
|
|
||||||
|
// See constants from bimmer-connected
|
||||||
|
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
|
||||||
|
public enum VehicleType {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ChargingMode {
|
||||||
|
immediateCharging,
|
||||||
|
delayedCharging
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ChargingPreference {
|
||||||
|
noPreSelection,
|
||||||
|
chargingWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
|
||||||
|
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||||
|
public static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
|
||||||
|
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||||
|
public static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID,
|
||||||
|
VehicleType.CONVENTIONAL.toString());
|
||||||
|
public static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID,
|
||||||
|
VehicleType.PLUGIN_HYBRID.toString());
|
||||||
|
public static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID,
|
||||||
|
VehicleType.ELECTRIC_REX.toString());
|
||||||
|
public static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT,
|
||||||
|
THING_TYPE_CONV, THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
|
||||||
|
|
||||||
|
// Thing Group definitions
|
||||||
|
public static final String CHANNEL_GROUP_STATUS = "status";
|
||||||
|
public static final String CHANNEL_GROUP_SERVICE = "service";
|
||||||
|
public static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
|
||||||
|
public static final String CHANNEL_GROUP_DOORS = "doors";
|
||||||
|
public static final String CHANNEL_GROUP_RANGE = "range";
|
||||||
|
public static final String CHANNEL_GROUP_LOCATION = "location";
|
||||||
|
public static final String CHANNEL_GROUP_REMOTE = "remote";
|
||||||
|
public static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
|
||||||
|
public static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
|
||||||
|
public static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
|
||||||
|
public static final String CHANNEL_GROUP_TIRES = "tires";
|
||||||
|
public static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
|
||||||
|
|
||||||
|
// Charge Statistics & Sessions
|
||||||
|
public static final String SESSIONS = "sessions";
|
||||||
|
public static final String ENERGY = "energy";
|
||||||
|
public static final String TITLE = "title";
|
||||||
|
public static final String SUBTITLE = "subtitle";
|
||||||
|
public static final String ISSUE = "issue";
|
||||||
|
public static final String STATUS = "status";
|
||||||
|
|
||||||
|
// Generic Constants for several groups
|
||||||
|
public static final String NAME = "name";
|
||||||
|
public static final String DETAILS = "details";
|
||||||
|
public static final String SEVERITY = "severity";
|
||||||
|
public static final String DATE = "date";
|
||||||
|
public static final String MILEAGE = "mileage";
|
||||||
|
public static final String GPS = "gps";
|
||||||
|
public static final String HEADING = "heading";
|
||||||
|
public static final String ADDRESS = "address";
|
||||||
|
|
||||||
|
// Status
|
||||||
|
public static final String DOORS = "doors";
|
||||||
|
public static final String WINDOWS = "windows";
|
||||||
|
public static final String LOCK = "lock";
|
||||||
|
public static final String SERVICE_DATE = "service-date";
|
||||||
|
public static final String SERVICE_MILEAGE = "service-mileage";
|
||||||
|
public static final String CHECK_CONTROL = "check-control";
|
||||||
|
public static final String PLUG_CONNECTION = "plug-connection";
|
||||||
|
public static final String CHARGE_STATUS = "charge";
|
||||||
|
public static final String CHARGE_INFO = "charge-info";
|
||||||
|
public static final String MOTION = "motion";
|
||||||
|
public static final String LAST_UPDATE = "last-update";
|
||||||
|
public static final String RAW = "raw";
|
||||||
|
|
||||||
|
// Door Details
|
||||||
|
public static final String DOOR_DRIVER_FRONT = "driver-front";
|
||||||
|
public static final String DOOR_DRIVER_REAR = "driver-rear";
|
||||||
|
public static final String DOOR_PASSENGER_FRONT = "passenger-front";
|
||||||
|
public static final String DOOR_PASSENGER_REAR = "passenger-rear";
|
||||||
|
public static final String HOOD = "hood";
|
||||||
|
public static final String TRUNK = "trunk";
|
||||||
|
public static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
|
||||||
|
public static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
|
||||||
|
public static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
|
||||||
|
public static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
|
||||||
|
public static final String WINDOW_REAR = "win-rear";
|
||||||
|
public static final String SUNROOF = "sunroof";
|
||||||
|
|
||||||
|
// Charge Profile
|
||||||
|
public static final String CHARGE_PROFILE_CLIMATE = "climate";
|
||||||
|
public static final String CHARGE_PROFILE_MODE = "mode";
|
||||||
|
public static final String CHARGE_PROFILE_PREFERENCE = "prefs";
|
||||||
|
public static final String CHARGE_PROFILE_CONTROL = "control";
|
||||||
|
public static final String CHARGE_PROFILE_TARGET = "target";
|
||||||
|
public static final String CHARGE_PROFILE_LIMIT = "limit";
|
||||||
|
public static final String CHARGE_WINDOW_START = "window-start";
|
||||||
|
public static final String CHARGE_WINDOW_END = "window-end";
|
||||||
|
public static final String CHARGE_TIMER1 = "timer1";
|
||||||
|
public static final String CHARGE_TIMER2 = "timer2";
|
||||||
|
public static final String CHARGE_TIMER3 = "timer3";
|
||||||
|
public static final String CHARGE_TIMER4 = "timer4";
|
||||||
|
public static final String CHARGE_DEPARTURE = "-departure";
|
||||||
|
public static final String CHARGE_ENABLED = "-enabled";
|
||||||
|
public static final String CHARGE_DAY_MON = "-day-mon";
|
||||||
|
public static final String CHARGE_DAY_TUE = "-day-tue";
|
||||||
|
public static final String CHARGE_DAY_WED = "-day-wed";
|
||||||
|
public static final String CHARGE_DAY_THU = "-day-thu";
|
||||||
|
public static final String CHARGE_DAY_FRI = "-day-fri";
|
||||||
|
public static final String CHARGE_DAY_SAT = "-day-sat";
|
||||||
|
public static final String CHARGE_DAY_SUN = "-day-sun";
|
||||||
|
|
||||||
|
// Range
|
||||||
|
public static final String RANGE_ELECTRIC = "electric";
|
||||||
|
public static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
|
||||||
|
public static final String RANGE_FUEL = "fuel";
|
||||||
|
public static final String RANGE_RADIUS_FUEL = "radius-fuel";
|
||||||
|
public static final String RANGE_HYBRID = "hybrid";
|
||||||
|
public static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
|
||||||
|
public static final String REMAINING_FUEL = "remaining-fuel";
|
||||||
|
public static final String SOC = "soc";
|
||||||
|
|
||||||
|
// Image
|
||||||
|
public static final String IMAGE_FORMAT = "png";
|
||||||
|
public static final String IMAGE_VIEWPORT = "view";
|
||||||
|
|
||||||
|
// Remote Services
|
||||||
|
public static final String REMOTE_SERVICE_LIGHT_FLASH = "light-flash";
|
||||||
|
public static final String REMOTE_SERVICE_VEHICLE_FINDER = "vehicle-finder";
|
||||||
|
public static final String REMOTE_SERVICE_DOOR_LOCK = "door-lock";
|
||||||
|
public static final String REMOTE_SERVICE_DOOR_UNLOCK = "door-unlock";
|
||||||
|
public static final String REMOTE_SERVICE_HORN = "horn-blow";
|
||||||
|
public static final String REMOTE_SERVICE_AIR_CONDITIONING_START = "climate-now-start";
|
||||||
|
public static final String REMOTE_SERVICE_AIR_CONDITIONING_STOP = "climate-now-stop";
|
||||||
|
|
||||||
|
public static final String REMOTE_SERVICE_COMMAND = "command";
|
||||||
|
public static final String REMOTE_STATE = "state";
|
||||||
|
|
||||||
|
// TIRES
|
||||||
|
public static final String FRONT_LEFT_CURRENT = "fl-current";
|
||||||
|
public static final String FRONT_LEFT_TARGET = "fl-target";
|
||||||
|
public static final String FRONT_RIGHT_CURRENT = "fr-current";
|
||||||
|
public static final String FRONT_RIGHT_TARGET = "fr-target";
|
||||||
|
public static final String REAR_LEFT_CURRENT = "rl-current";
|
||||||
|
public static final String REAR_LEFT_TARGET = "rl-target";
|
||||||
|
public static final String REAR_RIGHT_CURRENT = "rr-current";
|
||||||
|
public static final String REAR_RIGHT_TARGET = "rr-target";
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.i18n.LocaleProvider;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MyBMWHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
|
||||||
|
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
private final MyBMWCommandOptionProvider commandOptionProvider;
|
||||||
|
private String localeLanguage;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public MyBMWHandlerFactory(final @Reference HttpClientFactory hcf, final @Reference MyBMWCommandOptionProvider cop,
|
||||||
|
final @Reference LocaleProvider lp) {
|
||||||
|
httpClientFactory = hcf;
|
||||||
|
commandOptionProvider = cop;
|
||||||
|
localeLanguage = lp.getLocale().getLanguage().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@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, localeLanguage);
|
||||||
|
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
|
||||||
|
VehicleHandler vh = new VehicleHandler(thing, commandOptionProvider, thingTypeUID.getId());
|
||||||
|
return vh;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link VehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VehicleConfiguration {
|
||||||
|
/**
|
||||||
|
* Vehicle Identification Number (VIN)
|
||||||
|
*/
|
||||||
|
public String vin = Constants.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vehicle brand
|
||||||
|
* - bmw
|
||||||
|
* - mini
|
||||||
|
*/
|
||||||
|
public String vehicleBrand = Constants.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data refresh rate in minutes
|
||||||
|
*/
|
||||||
|
public int refreshInterval = MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
|
||||||
|
}
|
@ -0,0 +1,223 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.discovery;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUPPORTED_THING_SET;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||||
|
import org.openhab.binding.mybmw.internal.handler.RemoteServiceHandler;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||||
|
import org.openhab.core.config.core.Configuration;
|
||||||
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryService;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(VehicleDiscovery.class);
|
||||||
|
public static final String SUPPORTED_SUFFIX = "Supported";
|
||||||
|
public static final String ENABLE_SUFFIX = "Enable";
|
||||||
|
public static final String ENABLED_SUFFIX = "Enabled";
|
||||||
|
private static final int DISCOVERY_TIMEOUT = 10;
|
||||||
|
private Optional<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
|
||||||
|
|
||||||
|
public VehicleDiscovery() {
|
||||||
|
super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResponse(List<Vehicle> vehicleList) {
|
||||||
|
bridgeHandler.ifPresent(bridge -> {
|
||||||
|
final ThingUID bridgeUID = bridge.getThing().getUID();
|
||||||
|
vehicleList.forEach(vehicle -> {
|
||||||
|
// the DriveTrain field in the delivered json is defining the Vehicle Type
|
||||||
|
String vehicleType = VehicleStatusUtils.vehicleType(vehicle.driveTrain, vehicle.model).toString();
|
||||||
|
SUPPORTED_THING_SET.forEach(entry -> {
|
||||||
|
if (entry.getId().equals(vehicleType)) {
|
||||||
|
ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
|
||||||
|
Map<String, String> properties = new HashMap<>();
|
||||||
|
// Vehicle Properties
|
||||||
|
properties.put("vehicleModel", vehicle.model);
|
||||||
|
properties.put("vehicleDriveTrain", vehicle.driveTrain);
|
||||||
|
properties.put("vehicleConstructionYear", Integer.toString(vehicle.year));
|
||||||
|
properties.put("vehicleBodytype", vehicle.bodyType);
|
||||||
|
|
||||||
|
properties.put("servicesSupported", getServices(vehicle, SUPPORTED_SUFFIX, true));
|
||||||
|
properties.put("servicesUnsupported", getServices(vehicle, SUPPORTED_SUFFIX, false));
|
||||||
|
String servicesEnabled = getServices(vehicle, ENABLED_SUFFIX, true) + Constants.SEMICOLON
|
||||||
|
+ getServices(vehicle, ENABLE_SUFFIX, true);
|
||||||
|
properties.put("servicesEnabled", servicesEnabled.trim());
|
||||||
|
String servicesDisabled = getServices(vehicle, ENABLED_SUFFIX, false) + Constants.SEMICOLON
|
||||||
|
+ getServices(vehicle, ENABLE_SUFFIX, false);
|
||||||
|
properties.put("servicesDisabled", servicesDisabled.trim());
|
||||||
|
|
||||||
|
// For RemoteServices we need to do it step-by-step
|
||||||
|
StringBuffer remoteServicesEnabled = new StringBuffer();
|
||||||
|
StringBuffer remoteServicesDisabled = new StringBuffer();
|
||||||
|
if (vehicle.capabilities.lock.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
if (vehicle.capabilities.unlock.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
if (vehicle.capabilities.lights.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
if (vehicle.capabilities.horn.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
if (vehicle.capabilities.vehicleFinder.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled.append(
|
||||||
|
RemoteServiceHandler.RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
if (vehicle.capabilities.climateNow.isEnabled) {
|
||||||
|
remoteServicesEnabled.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
|
||||||
|
+ Constants.SEMICOLON);
|
||||||
|
} else {
|
||||||
|
remoteServicesDisabled
|
||||||
|
.append(RemoteServiceHandler.RemoteService.CLIMATE_NOW_START.getLabel()
|
||||||
|
+ Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
properties.put("remoteServicesEnabled", remoteServicesEnabled.toString().trim());
|
||||||
|
properties.put("remoteServicesDisabled", remoteServicesDisabled.toString().trim());
|
||||||
|
|
||||||
|
// Update Properties for already created Things
|
||||||
|
bridge.getThing().getThings().forEach(vehicleThing -> {
|
||||||
|
Configuration c = vehicleThing.getConfiguration();
|
||||||
|
if (c.containsKey(MyBMWConstants.VIN)) {
|
||||||
|
String thingVIN = c.get(MyBMWConstants.VIN).toString();
|
||||||
|
if (vehicle.vin.equals(thingVIN)) {
|
||||||
|
vehicleThing.setProperties(properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Properties needed for functional Thing
|
||||||
|
properties.put(MyBMWConstants.VIN, vehicle.vin);
|
||||||
|
properties.put("vehicleBrand", vehicle.brand);
|
||||||
|
properties.put("refreshInterval",
|
||||||
|
Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
|
||||||
|
|
||||||
|
String vehicleLabel = vehicle.brand + " " + vehicle.model;
|
||||||
|
Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
|
||||||
|
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
|
||||||
|
.withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
|
||||||
|
.withProperties(convertedProperties).build());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThingHandler(ThingHandler handler) {
|
||||||
|
if (handler instanceof MyBMWBridgeHandler) {
|
||||||
|
bridgeHandler = Optional.of((MyBMWBridgeHandler) handler);
|
||||||
|
bridgeHandler.get().setDiscoveryService(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingHandler getThingHandler() {
|
||||||
|
return bridgeHandler.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void startScan() {
|
||||||
|
bridgeHandler.ifPresent(MyBMWBridgeHandler::requestVehicles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deactivate() {
|
||||||
|
super.deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getServices(Vehicle vehicle, String suffix, boolean enabled) {
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
List<String> l = getObject(vehicle.capabilities, enabled);
|
||||||
|
for (String capEntry : l) {
|
||||||
|
// remove "is" prefix
|
||||||
|
String cut = capEntry.substring(2);
|
||||||
|
if (cut.endsWith(suffix)) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(Constants.SEMICOLON);
|
||||||
|
}
|
||||||
|
sb.append(cut.substring(0, cut.length() - suffix.length()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all field names from a DTO with a specific value
|
||||||
|
* Used to get e.g. all services which are "ACTIVATED"
|
||||||
|
*
|
||||||
|
* @param DTO Object
|
||||||
|
* @param compare String which needs to map with the value
|
||||||
|
* @return String with all field names matching this value separated with Spaces
|
||||||
|
*/
|
||||||
|
public static List<String> getObject(Object dto, Object compare) {
|
||||||
|
List<String> l = new ArrayList<String>();
|
||||||
|
for (Field field : dto.getClass().getDeclaredFields()) {
|
||||||
|
try {
|
||||||
|
Object value = field.get(dto);
|
||||||
|
if (compare.equals(value)) {
|
||||||
|
l.add(field.getName());
|
||||||
|
}
|
||||||
|
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||||
|
LOGGER.debug("Field {} not found {}", compare, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AuthQueryResponse} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class AuthQueryResponse {
|
||||||
|
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",
|
||||||
|
public 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"]
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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
|
||||||
|
*/
|
||||||
|
public class AuthResponse {
|
||||||
|
@SerializedName("access_token")
|
||||||
|
public String accessToken = Constants.EMPTY;
|
||||||
|
@SerializedName("token_type")
|
||||||
|
public String tokenType = Constants.EMPTY;
|
||||||
|
@SerializedName("expires_in")
|
||||||
|
public int expiresIn = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Token " + accessToken + " type " + tokenType + " expires in " + expiresIn;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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 ChinaAccessToken} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChinaAccessToken {
|
||||||
|
@SerializedName("access_token")
|
||||||
|
public String accessToken = Constants.EMPTY;
|
||||||
|
@SerializedName("token_type")
|
||||||
|
public String tokenType = Constants.EMPTY;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChinaPublicKey} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChinaPublicKey {
|
||||||
|
public String value;// ": "-----BEGIN PUBLIC
|
||||||
|
// KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCteEZFIGa2z5cj7sAmX40y8/ige01T2r+VUzkMshAYwotZFvrVWZLQ6W9+ltvINJoRfZEZkmdP2lsidhqj1H1+RWyC78ear7Fm6xd9Gp9LnKtVVBJRM/9cBRg0AGiTJ7IO/x6MpKkBxxHmProFqPI40hueunV85RlaPBrjZVNIpQIDAQAB\r\n-----END
|
||||||
|
// PUBLIC KEY-----",
|
||||||
|
public String expires;// ": "3600"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChinaPublicKeyResponse} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChinaPublicKeyResponse {
|
||||||
|
public ChinaPublicKey data;
|
||||||
|
public int code;// ":200,
|
||||||
|
public String error;// ":false,
|
||||||
|
public String description;// ":"ok"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChinaTokenExpiration} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChinaTokenExpiration {
|
||||||
|
public String jti;// ":"DUMMY$1$A$1637707916782",
|
||||||
|
public long nbf;// ":1637707916,
|
||||||
|
public long exp;// ":1637711216,
|
||||||
|
public long iat;// ":1637707916}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChinaTokenResponse} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChinaTokenResponse {
|
||||||
|
public ChinaAccessToken data;
|
||||||
|
public int code;// ":200,
|
||||||
|
public String error;// ":false,
|
||||||
|
public String description;// ":"ok"
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeProfile} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - edit & send of charge profile
|
||||||
|
*/
|
||||||
|
public class ChargeProfile {
|
||||||
|
public static final Timer INVALID_TIMER = new Timer();
|
||||||
|
|
||||||
|
public ChargingWindow reductionOfChargeCurrent;
|
||||||
|
public String chargingMode;// ": "immediateCharging",
|
||||||
|
public String chargingPreference;// ": "chargingWindow",
|
||||||
|
public String chargingControlType;// ": "weeklyPlanner",
|
||||||
|
public List<Timer> departureTimes;
|
||||||
|
public boolean climatisationOn;// ": false,
|
||||||
|
public ChargingSettings chargingSettings;
|
||||||
|
|
||||||
|
public Timer getTimerId(int id) {
|
||||||
|
if (departureTimes != null) {
|
||||||
|
for (Timer t : departureTimes) {
|
||||||
|
if (t.id == id) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INVALID_TIMER;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeSession} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargeSession {
|
||||||
|
public String id;// ": "2021-12-26T16:57:20Z_128fa4af",
|
||||||
|
public String title;// ": "Gestern 17:57",
|
||||||
|
public String subtitle;// ": "Uferstraße 4B • 7h 45min • -- EUR",
|
||||||
|
public String energyCharged;// ": "~ 31 kWh",
|
||||||
|
public String sessionStatus;// ": "FINISHED",
|
||||||
|
public String issues;// ": "2 Probleme",
|
||||||
|
public String isPublic;// ": false
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeSessions} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargeSessions {
|
||||||
|
public String total;// ": "~ 218 kWh",
|
||||||
|
public String numberOfSessions;// ": "17",
|
||||||
|
public String chargingListState;// ": "HAS_SESSIONS",
|
||||||
|
public List<ChargeSession> sessions;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeSessionsContainer} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargeSessionsContainer {
|
||||||
|
public Object paginationInfo;
|
||||||
|
public ChargeSessions chargingSessions;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeStatistics} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargeStatistics {
|
||||||
|
public int totalEnergyCharged;// ": 173,
|
||||||
|
public String totalEnergyChargedSemantics;// ": "Insgesamt circa 173 Kilowattstunden geladen",
|
||||||
|
public String symbol;// ": "~",
|
||||||
|
public int numberOfChargingSessions;// ": 13,
|
||||||
|
public String numberOfChargingSessionsSemantics;// ": "13 Ladevorgänge"
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeStatisticsContainer} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargeStatisticsContainer {
|
||||||
|
public String description;// ": "Dezember 2021",
|
||||||
|
public String optStateType;// ": "OPT_IN_WITH_SESSIONS",
|
||||||
|
public ChargeStatistics statistics;// ": {
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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
|
||||||
|
*/
|
||||||
|
public class ChargingSettings {
|
||||||
|
public int targetSoc;// ": 100,
|
||||||
|
public boolean isAcCurrentLimitActive;// ": false,
|
||||||
|
public String hospitality;// ": "NO_ACTION",
|
||||||
|
public String idcc;// ": "NO_ACTION"
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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
|
||||||
|
*/
|
||||||
|
public class ChargingWindow {
|
||||||
|
public Time start;
|
||||||
|
public Time end;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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 org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Time} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - edit & send of charge profile
|
||||||
|
*/
|
||||||
|
public class Time {
|
||||||
|
public int hour;// ": 11,
|
||||||
|
public int minute;// ": 0
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Converter.getTime(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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 & 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.network;
|
||||||
|
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link NetworkError} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class NetworkError {
|
||||||
|
public String url;
|
||||||
|
public int status;
|
||||||
|
public String reason;
|
||||||
|
public String params;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder(url).append(Constants.HYPHEN).append(status).append(Constants.HYPHEN).append(reason)
|
||||||
|
.append(params).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toJson() {
|
||||||
|
return Converter.getGson().toJson(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Address} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Address {
|
||||||
|
public String formatted;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CBS} Data Transfer Object ConditionBasedService
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CBS {
|
||||||
|
public String type = Constants.NO_ENTRIES;// ": "BRAKE_FLUID",
|
||||||
|
public String status = Constants.NO_ENTRIES;// ": "OK",
|
||||||
|
public String dateTime;// ": "2023-11-01T00:00:00.000Z"
|
||||||
|
public Distance distance;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CCM} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CCM {
|
||||||
|
// [todo] [todo] definition currently unknown
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargingState} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class ChargingState {
|
||||||
|
public int chargePercentage;// ": 74,
|
||||||
|
public String state;// ": "NOT_CHARGING",
|
||||||
|
public String type;// ": "NOT_AVAILABLE",
|
||||||
|
public boolean isChargerConnected;// ": false
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Coordinates} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Coordinates {
|
||||||
|
public double latitude;// ": 50.556049,
|
||||||
|
public double longitude;// ": 8.495669
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Distance} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Distance {
|
||||||
|
public int value;// ": 31,
|
||||||
|
public String units;// ": "KILOMETERS"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Doors} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Doors {
|
||||||
|
public String driverFront;// ": "CLOSED",
|
||||||
|
public String driverRear;// ": "CLOSED",
|
||||||
|
public String passengerFront;// ": "CLOSED",
|
||||||
|
public String passengerRear;// ": "CLOSED"
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link DoorsWindows} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class DoorsWindows {
|
||||||
|
public Doors doors;
|
||||||
|
public Windows windows;
|
||||||
|
public String trunk;// ": "CLOSED",
|
||||||
|
public String hood;// ": "CLOSED",
|
||||||
|
public String moonroof;// ": "CLOSED"
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FuelLevel} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class FuelLevel {
|
||||||
|
public int value;// ": 4,
|
||||||
|
public String units;// ": "LITERS"
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Location} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Location {
|
||||||
|
public Coordinates coordinates;
|
||||||
|
public Address address;
|
||||||
|
public int heading;// ": 222
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Properties} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Properties {
|
||||||
|
public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
|
||||||
|
public boolean inMotion;// ": false,
|
||||||
|
public boolean areDoorsLocked;// ": true,
|
||||||
|
public String originCountryISO;// ": "DE",
|
||||||
|
public boolean areDoorsClosed;// ": true,
|
||||||
|
public boolean areDoorsOpen;// ": false,
|
||||||
|
public boolean areWindowsClosed;// ": true,
|
||||||
|
public DoorsWindows doorsAndWindows;// ":
|
||||||
|
public boolean isServiceRequired;// ":false
|
||||||
|
public FuelLevel fuelLevel;
|
||||||
|
public ChargingState chargingState;// ":
|
||||||
|
public Range combustionRange;
|
||||||
|
public Range combinedRange;
|
||||||
|
public Range electricRange;
|
||||||
|
public Range electricRangeAndStatus;
|
||||||
|
public List<CCM> checkControlMessages;
|
||||||
|
public List<CBS> serviceRequired;
|
||||||
|
public Location vehicleLocation;
|
||||||
|
public Tires tires;
|
||||||
|
// "climateControl":{} [todo] definition currently unknown
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Range} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Range {
|
||||||
|
public int chargePercentage;
|
||||||
|
public Distance distance;
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Tire} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Tire {
|
||||||
|
public TireStatus status;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link TireStatus} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class TireStatus {
|
||||||
|
public double currentPressure;// ": 220,
|
||||||
|
public String localizedCurrentPressure;// ": "2.2 bar",
|
||||||
|
public String localizedTargetPressure;// ": "2.3 bar",
|
||||||
|
public double targetPressure;// ": 230
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Tires} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Tires {
|
||||||
|
public Tire frontLeft;
|
||||||
|
public Tire frontRight;
|
||||||
|
public Tire rearLeft;
|
||||||
|
public Tire rearRight;
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Windows} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Windows {
|
||||||
|
public String driverFront;// ": "CLOSED",
|
||||||
|
public String driverRear;// ": "CLOSED",
|
||||||
|
public String passengerFront;// ": "CLOSED",
|
||||||
|
public String passengerRear;// ": "CLOSED"
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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"
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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
|
||||||
|
*/
|
||||||
|
public class ExecutionStatusContainer {
|
||||||
|
public String eventId;
|
||||||
|
public String creationTime;
|
||||||
|
public String eventStatus;
|
||||||
|
public ExecutionError errorDetails;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CBSMessage} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CBSMessage {
|
||||||
|
public String id;// ": "BrakeFluid",
|
||||||
|
public String title;// ": "Brake fluid",
|
||||||
|
public int iconId;// ": 60223,
|
||||||
|
public String longDescription;// ": "Next service due by the specified date.",
|
||||||
|
public String subtitle;// ": "Due in November 2023",
|
||||||
|
public String criticalness;// ": "nonCritical"
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CCMMessage} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class CCMMessage {
|
||||||
|
public String criticalness;// ": "semiCritical",
|
||||||
|
public int iconId;// ": 60217,
|
||||||
|
public String state = Constants.NO_ENTRIES;// ": "Medium",
|
||||||
|
public String title = Constants.NO_ENTRIES;// ": "Battery discharged: Start engine"
|
||||||
|
public String id;// ": "229",
|
||||||
|
public String longDescription = Constants.NO_ENTRIES;// ": "Charge by driving for longer periods or use external
|
||||||
|
// charger. Functions requiring battery will be switched off.
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link DoorWindow} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class DoorWindow {
|
||||||
|
public int iconId;// ": 59757,
|
||||||
|
public String title;// ": "Lock status",
|
||||||
|
public String state;// ": "Locked",
|
||||||
|
public String criticalness;// ": "nonCritical"
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link FuelIndicator} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class FuelIndicator {
|
||||||
|
public int mainBarValue;// ": 74,
|
||||||
|
public String rangeUnits;// ": "km",
|
||||||
|
public String rangeValue;// ": "76",
|
||||||
|
public String levelUnits;// ": "%",
|
||||||
|
public String levelValue;// ": "74",
|
||||||
|
|
||||||
|
public int secondaryBarValue;// ": 0,
|
||||||
|
public int infoIconId;// ": 59694,
|
||||||
|
public int rangeIconId;// ": 59683,
|
||||||
|
public int levelIconId;// ": 59694,
|
||||||
|
public boolean showsBar;// ": true,
|
||||||
|
public boolean showBarGoal;// ": false,
|
||||||
|
public String barType;// ": null,
|
||||||
|
public String infoLabel;// ": "State of Charge",
|
||||||
|
public boolean isInaccurate;// ": false,
|
||||||
|
public boolean isCircleIcon;// ": false,
|
||||||
|
public String iconOpacity;// ": "high",
|
||||||
|
public String chargingType;// ": null,
|
||||||
|
public String chargingStatusType;// ": "DEFAULT",
|
||||||
|
public String chargingStatusIndicatorType;// ": "DEFAULT"
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Issues} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Issues {
|
||||||
|
// [todo] definition currently unknown
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Mileage} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Mileage {
|
||||||
|
public int mileage;// ": 31537,
|
||||||
|
public String units;// ": "km",
|
||||||
|
public String formattedMileage;// ": "31537"
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.status;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Status} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Status {
|
||||||
|
public String lastUpdatedAt;// ": "2021-12-21T16:46:02Z",
|
||||||
|
public Mileage currentMileage;
|
||||||
|
public Issues issues;
|
||||||
|
public String doorsGeneralState;// ":"Locked",
|
||||||
|
public String checkControlMessagesGeneralState;// ":"No Issues",
|
||||||
|
public List<DoorWindow> doorsAndWindows;// ":[
|
||||||
|
public List<CCMMessage> checkControlMessages;//
|
||||||
|
public List<CBSMessage> requiredServices;//
|
||||||
|
// "recallMessages":[],
|
||||||
|
// "recallExternalUrl":null,
|
||||||
|
public List<FuelIndicator> fuelIndicators;
|
||||||
|
public String timestampMessage;// ":"Updated from vehicle 12/21/2021 05:46 PM",
|
||||||
|
public ChargeProfile chargingProfile;
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Capabilities} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Capabilities {
|
||||||
|
public boolean isRemoteServicesBookingRequired;
|
||||||
|
public boolean isRemoteServicesActivationRequired;
|
||||||
|
public boolean isRemoteHistorySupported;
|
||||||
|
public boolean canRemoteHistoryBeDeleted;
|
||||||
|
public boolean isChargingHistorySupported;
|
||||||
|
public boolean isScanAndChargeSupported;
|
||||||
|
public boolean isDCSContractManagementSupported;
|
||||||
|
public boolean isBmwChargingSupported;
|
||||||
|
public boolean isMiniChargingSupported;
|
||||||
|
public boolean isChargeNowForBusinessSupported;
|
||||||
|
public boolean isDataPrivacyEnabled;
|
||||||
|
public boolean isChargingPlanSupported;
|
||||||
|
public boolean isChargingPowerLimitEnable;
|
||||||
|
public boolean isChargingTargetSocEnable;
|
||||||
|
public boolean isChargingLoudnessEnable;
|
||||||
|
public boolean isChargingSettingsEnabled;
|
||||||
|
public boolean isChargingHospitalityEnabled;
|
||||||
|
public boolean isEvGoChargingSupported;
|
||||||
|
public boolean isFindChargingEnabled;
|
||||||
|
public boolean isCustomerEsimSupported;
|
||||||
|
public boolean isCarSharingSupported;
|
||||||
|
public boolean isEasyChargeSupported;
|
||||||
|
|
||||||
|
public RemoteService lock;
|
||||||
|
public RemoteService unlock;
|
||||||
|
public RemoteService lights;
|
||||||
|
public RemoteService horn;
|
||||||
|
public RemoteService vehicleFinder;
|
||||||
|
public RemoteService sendPoi;
|
||||||
|
public RemoteService climateNow;
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RemoteService} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class RemoteService {
|
||||||
|
public boolean isEnabled;// ": true,
|
||||||
|
public boolean isPinAuthenticationRequired;// ": false,
|
||||||
|
public String executionMessage;// ": "Lock your vehicle now? Remote functions may take a few seconds."
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which 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.dto.properties.Properties;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.status.Status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Vehicle} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
public class Vehicle {
|
||||||
|
public String vin;// ": "WBY1Z81040V905639",
|
||||||
|
public String model;// ": "i3 94 (+ REX)",
|
||||||
|
public int year;// ": 2017,
|
||||||
|
public String brand;// ": "BMW",
|
||||||
|
public String headUnit;// ": "ID5",
|
||||||
|
public boolean isLscSupported;// ": true,
|
||||||
|
public String driveTrain;// ": "ELECTRIC",
|
||||||
|
public String puStep;// ": "0321",
|
||||||
|
public String iStep;// ": "I001-21-03-530",
|
||||||
|
public String telematicsUnit;// ": "TCB1",
|
||||||
|
public String hmiVersion;// ": "ID4",
|
||||||
|
public String bodyType;// ": "I01",
|
||||||
|
public String a4aType;// ": "USB_ONLY",
|
||||||
|
public String exFactoryPUStep;// ": "0717",
|
||||||
|
public String exFactoryILevel;// ": "I001-17-07-500"
|
||||||
|
public Capabilities capabilities;
|
||||||
|
// "connectedDriveServices": [] currently no clue how to resolve,
|
||||||
|
public Properties properties;
|
||||||
|
public boolean isMappingPending;// ":false,"
|
||||||
|
public boolean isMappingUnconfirmed;// ":false,
|
||||||
|
public Status status;
|
||||||
|
public boolean valid = false;
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ByteResponseCallback} Interface for all raw byte results from ASYNC REST API
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface ByteResponseCallback extends ResponseCallback {
|
||||||
|
|
||||||
|
public void onResponse(byte[] result);
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
|
||||||
|
import org.openhab.binding.mybmw.internal.discovery.VehicleDiscovery;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
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.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
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MyBMWBridgeHandler extends BaseBridgeHandler implements StringResponseCallback {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(MyBMWBridgeHandler.class);
|
||||||
|
private HttpClientFactory httpClientFactory;
|
||||||
|
private Optional<VehicleDiscovery> discoveryService = Optional.empty();
|
||||||
|
private Optional<MyBMWProxy> proxy = Optional.empty();
|
||||||
|
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
|
||||||
|
private Optional<String> troubleshootFingerprint = Optional.empty();
|
||||||
|
private String localeLanguage;
|
||||||
|
|
||||||
|
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory hcf, String language) {
|
||||||
|
super(bridge);
|
||||||
|
httpClientFactory = hcf;
|
||||||
|
localeLanguage = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
// no commands available
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
troubleshootFingerprint = Optional.empty();
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
MyBMWConfiguration config = getConfigAs(MyBMWConfiguration.class);
|
||||||
|
if (config.language.equals(Constants.LANGUAGE_AUTODETECT)) {
|
||||||
|
config.language = localeLanguage;
|
||||||
|
}
|
||||||
|
if (!checkConfiguration(config)) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||||
|
} else {
|
||||||
|
proxy = Optional.of(new MyBMWProxy(httpClientFactory, config));
|
||||||
|
initializerJob = Optional.of(scheduler.schedule(this::requestVehicles, 2, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkConfiguration(MyBMWConfiguration config) {
|
||||||
|
if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return BimmerConstants.EADRAX_SERVER_MAP.containsKey(config.region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
initializerJob.ifPresent(job -> job.cancel(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestVehicles() {
|
||||||
|
proxy.ifPresent(prox -> prox.requestVehicles(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logFingerPrint() {
|
||||||
|
logger.debug("###### Discovery Fingerprint Data - BEGIN ######");
|
||||||
|
logger.debug("{}", troubleshootFingerprint.get());
|
||||||
|
logger.debug("###### Discovery Fingerprint Data - END ######");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response for vehicle request
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public synchronized void onResponse(@Nullable String response) {
|
||||||
|
if (response != null) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
List<Vehicle> vehicleList = Converter.getVehicleList(response);
|
||||||
|
discoveryService.get().onResponse(vehicleList);
|
||||||
|
troubleshootFingerprint = Optional.of(Converter.anonymousFingerprint(response));
|
||||||
|
logFingerPrint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
troubleshootFingerprint = Optional.of(error.toJson());
|
||||||
|
logFingerPrint();
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||||
|
return Collections.singleton(VehicleDiscovery.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<MyBMWProxy> getProxy() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDiscoveryService(VehicleDiscovery discoveryService) {
|
||||||
|
this.discoveryService = Optional.of(discoveryService);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,510 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpResponseException;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
|
import org.eclipse.jetty.util.UrlEncoded;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConfiguration;
|
||||||
|
import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.auth.AuthQueryResponse;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.auth.AuthResponse;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||||
|
import org.openhab.binding.mybmw.internal.handler.simulation.Injector;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MyBMWProxy} This class holds the important constants for the BMW Connected Drive Authorization.
|
||||||
|
* They
|
||||||
|
* are taken from the Bimmercode from github {@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 & send of charge profile
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class MyBMWProxy {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(MyBMWProxy.class);
|
||||||
|
private Optional<RemoteServiceHandler> remoteServiceHandler = Optional.empty();
|
||||||
|
private final Token token = new Token();
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private final MyBMWConfiguration configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URLs taken from https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
|
||||||
|
*/
|
||||||
|
final String vehicleUrl;
|
||||||
|
final String remoteCommandUrl;
|
||||||
|
final String remoteStatusUrl;
|
||||||
|
final String serviceExecutionAPI = "/executeService";
|
||||||
|
final String serviceExecutionStateAPI = "/serviceExecutionStatus";
|
||||||
|
final String remoteServiceEADRXstatusUrl = BimmerConstants.API_REMOTE_SERVICE_BASE_URL
|
||||||
|
+ "eventStatus?eventId={event_id}";
|
||||||
|
|
||||||
|
public MyBMWProxy(HttpClientFactory httpClientFactory, MyBMWConfiguration config) {
|
||||||
|
httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
configuration = config;
|
||||||
|
|
||||||
|
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ BimmerConstants.API_VEHICLES;
|
||||||
|
|
||||||
|
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
|
||||||
|
remoteStatusUrl = remoteCommandUrl + "eventStatus";
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void call(final String url, final boolean post, final @Nullable String encoding,
|
||||||
|
final @Nullable String params, final String brand, final ResponseCallback callback) {
|
||||||
|
// only executed in "simulation mode"
|
||||||
|
// SimulationTest.testSimulationOff() assures Injector is off when releasing
|
||||||
|
if (Injector.isActive()) {
|
||||||
|
if (url.equals(vehicleUrl)) {
|
||||||
|
((StringResponseCallback) callback).onResponse(Injector.getDiscovery());
|
||||||
|
} else if (url.endsWith(vehicleUrl)) {
|
||||||
|
((StringResponseCallback) callback).onResponse(Injector.getStatus());
|
||||||
|
} else {
|
||||||
|
logger.debug("Simulation of {} not supported", url);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return in case of unknown brand
|
||||||
|
String userAgent = BimmerConstants.BRAND_USER_AGENTS_MAP.get(brand.toLowerCase());
|
||||||
|
if (userAgent == null) {
|
||||||
|
logger.warn("Unknown Brand {}", brand);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Request req;
|
||||||
|
final String completeUrl;
|
||||||
|
|
||||||
|
if (post) {
|
||||||
|
completeUrl = url;
|
||||||
|
req = httpClient.POST(url);
|
||||||
|
if (encoding != null) {
|
||||||
|
req.header(HttpHeader.CONTENT_TYPE, encoding);
|
||||||
|
if (CONTENT_TYPE_URL_ENCODED.equals(encoding)) {
|
||||||
|
req.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
|
||||||
|
} else if (CONTENT_TYPE_JSON_ENCODED.equals(encoding)) {
|
||||||
|
req.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completeUrl = params == null ? url : url + Constants.QUESTION + params;
|
||||||
|
req = httpClient.newRequest(completeUrl);
|
||||||
|
}
|
||||||
|
req.header(HttpHeader.AUTHORIZATION, getToken().getBearerToken());
|
||||||
|
req.header(HTTPConstants.X_USER_AGENT, userAgent);
|
||||||
|
req.header(HttpHeader.ACCEPT_LANGUAGE, configuration.language);
|
||||||
|
if (callback instanceof ByteResponseCallback) {
|
||||||
|
req.header(HttpHeader.ACCEPT, "image/png");
|
||||||
|
} else {
|
||||||
|
req.header(HttpHeader.ACCEPT, CONTENT_TYPE_JSON_ENCODED);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send(new BufferingResponseListener() {
|
||||||
|
@NonNullByDefault({})
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result) {
|
||||||
|
if (result.getResponse().getStatus() != 200) {
|
||||||
|
NetworkError error = new NetworkError();
|
||||||
|
error.url = completeUrl;
|
||||||
|
error.status = result.getResponse().getStatus();
|
||||||
|
if (result.getResponse().getReason() != null) {
|
||||||
|
error.reason = result.getResponse().getReason();
|
||||||
|
} else {
|
||||||
|
error.reason = result.getFailure().getMessage();
|
||||||
|
}
|
||||||
|
error.params = result.getRequest().getParams().toString();
|
||||||
|
logger.debug("HTTP Error {}", error.toString());
|
||||||
|
callback.onError(error);
|
||||||
|
} else {
|
||||||
|
if (callback instanceof StringResponseCallback) {
|
||||||
|
((StringResponseCallback) callback).onResponse(getContentAsString());
|
||||||
|
} else if (callback instanceof ByteResponseCallback) {
|
||||||
|
((ByteResponseCallback) callback).onResponse(getContent());
|
||||||
|
} else {
|
||||||
|
logger.error("unexpected reponse type {}", callback.getClass().getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void get(String url, @Nullable String coding, @Nullable String params, final String brand,
|
||||||
|
ResponseCallback callback) {
|
||||||
|
call(url, false, coding, params, brand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void post(String url, @Nullable String coding, @Nullable String params, final String brand,
|
||||||
|
ResponseCallback callback) {
|
||||||
|
call(url, true, coding, params, brand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request all vehicles for one specific brand
|
||||||
|
*
|
||||||
|
* @param brand
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void requestVehicles(String brand, StringResponseCallback callback) {
|
||||||
|
// calculate necessary parameters for query
|
||||||
|
MultiMap<String> vehicleParams = new MultiMap<String>();
|
||||||
|
vehicleParams.put(BimmerConstants.TIRE_GUARD_MODE, Constants.ENABLED);
|
||||||
|
vehicleParams.put(BimmerConstants.APP_DATE_TIME, Long.toString(System.currentTimeMillis()));
|
||||||
|
vehicleParams.put(BimmerConstants.APP_TIMEZONE, Integer.toString(Converter.getOffsetMinutes()));
|
||||||
|
String params = UrlEncoded.encode(vehicleParams, StandardCharsets.UTF_8, false);
|
||||||
|
get(vehicleUrl + "?" + params, null, null, brand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request vehicles for all possible brands
|
||||||
|
*
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void requestVehicles(StringResponseCallback callback) {
|
||||||
|
BimmerConstants.ALL_BRANDS.forEach(brand -> {
|
||||||
|
requestVehicles(brand, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requestImage(VehicleConfiguration config, ImageProperties props, ByteResponseCallback callback) {
|
||||||
|
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ "/eadrax-ics/v3/presentation/vehicles/" + config.vin + "/images?carView=" + props.viewport;
|
||||||
|
get(localImageUrl, null, null, config.vehicleBrand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request charge statistics for electric vehicles
|
||||||
|
*
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void requestChargeStatistics(VehicleConfiguration config, StringResponseCallback callback) {
|
||||||
|
MultiMap<String> chargeStatisticsParams = new MultiMap<String>();
|
||||||
|
chargeStatisticsParams.put("vin", config.vin);
|
||||||
|
chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
|
||||||
|
String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
|
||||||
|
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ "/eadrax-chs/v1/charging-statistics?" + params;
|
||||||
|
get(chargeStatisticsUrl, null, null, config.vehicleBrand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request charge statistics for electric vehicles
|
||||||
|
*
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
public void requestChargeSessions(VehicleConfiguration config, StringResponseCallback callback) {
|
||||||
|
MultiMap<String> chargeSessionsParams = new MultiMap<String>();
|
||||||
|
chargeSessionsParams.put("vin", "WBY1Z81040V905639");
|
||||||
|
chargeSessionsParams.put("maxResults", "40");
|
||||||
|
chargeSessionsParams.put("include_date_picker", "true");
|
||||||
|
String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
|
||||||
|
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ "/eadrax-chs/v1/charging-sessions?" + params;
|
||||||
|
|
||||||
|
get(chargeSessionsUrl, null, null, config.vehicleBrand, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteServiceHandler getRemoteServiceHandler(VehicleHandler vehicleHandler) {
|
||||||
|
remoteServiceHandler = Optional.of(new RemoteServiceHandler(vehicleHandler, this));
|
||||||
|
return remoteServiceHandler.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token handling
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets new token if old one is expired or invalid. In case of error the token remains.
|
||||||
|
* So if token refresh fails the corresponding requests will also fail and update the
|
||||||
|
* Thing status accordingly.
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
public Token getToken() {
|
||||||
|
if (!token.isValid()) {
|
||||||
|
boolean tokenUpdateSuccess = false;
|
||||||
|
switch (configuration.region) {
|
||||||
|
case BimmerConstants.REGION_CHINA:
|
||||||
|
tokenUpdateSuccess = updateTokenChina();
|
||||||
|
break;
|
||||||
|
case BimmerConstants.REGION_NORTH_AMERICA:
|
||||||
|
tokenUpdateSuccess = updateToken();
|
||||||
|
break;
|
||||||
|
case BimmerConstants.REGION_ROW:
|
||||||
|
tokenUpdateSuccess = updateToken();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.warn("Region {} not supported", configuration.region);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!tokenUpdateSuccess) {
|
||||||
|
logger.debug("Authorization failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Everything is catched by surroundig try catch
|
||||||
|
* - HTTP Exceptions
|
||||||
|
* - JSONSyntax Exceptions
|
||||||
|
* - potential NullPointer Exceptions
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
public synchronized boolean updateToken() {
|
||||||
|
try {
|
||||||
|
/*
|
||||||
|
* Step 1) Get basic values for further queries
|
||||||
|
*/
|
||||||
|
String authValuesUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
|
||||||
|
+ BimmerConstants.API_OAUTH_CONFIG;
|
||||||
|
Request authValuesRequest = httpClient.newRequest(authValuesUrl);
|
||||||
|
authValuesRequest.header(ACP_SUBSCRIPTION_KEY, BimmerConstants.OCP_APIM_KEYS.get(configuration.region));
|
||||||
|
authValuesRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
|
||||||
|
ContentResponse authValuesResponse = authValuesRequest.send();
|
||||||
|
if (authValuesResponse.getStatus() != 200) {
|
||||||
|
throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
|
||||||
|
+ authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
|
||||||
|
authValuesResponse);
|
||||||
|
}
|
||||||
|
AuthQueryResponse aqr = Converter.getGson().fromJson(authValuesResponse.getContentAsString(),
|
||||||
|
AuthQueryResponse.class);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Step 2) Calculate values for base parameters
|
||||||
|
*/
|
||||||
|
String verfifierBytes = Converter.getRandomString(64);
|
||||||
|
String codeVerifier = Base64.getUrlEncoder().withoutPadding().encodeToString(verfifierBytes.getBytes());
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
|
||||||
|
String codeChallange = Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
||||||
|
String stateBytes = Converter.getRandomString(16);
|
||||||
|
String state = Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
|
||||||
|
|
||||||
|
MultiMap<String> baseParams = new MultiMap<String>();
|
||||||
|
baseParams.put(CLIENT_ID, aqr.clientId);
|
||||||
|
baseParams.put(RESPONSE_TYPE, CODE);
|
||||||
|
baseParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||||
|
baseParams.put(STATE, state);
|
||||||
|
baseParams.put(NONCE, BimmerConstants.LOGIN_NONCE);
|
||||||
|
baseParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
|
||||||
|
baseParams.put(CODE_CHALLENGE, codeChallange);
|
||||||
|
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3) Authorization with username and password
|
||||||
|
*/
|
||||||
|
String loginUrl = aqr.gcdmBaseUrl + BimmerConstants.OAUTH_ENDPOINT;
|
||||||
|
Request loginRequest = httpClient.POST(loginUrl);
|
||||||
|
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||||
|
|
||||||
|
MultiMap<String> loginParams = new MultiMap<String>(baseParams);
|
||||||
|
loginParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
|
||||||
|
loginParams.put(USERNAME, configuration.userName);
|
||||||
|
loginParams.put(PASSWORD, configuration.password);
|
||||||
|
loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||||
|
UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||||
|
ContentResponse loginResponse = loginRequest.send();
|
||||||
|
if (loginResponse.getStatus() != 200) {
|
||||||
|
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||||
|
+ loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
|
||||||
|
loginResponse);
|
||||||
|
}
|
||||||
|
String authCode = getAuthCode(loginResponse.getContentAsString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 4) Authorize with code
|
||||||
|
*/
|
||||||
|
Request authRequest = httpClient.POST(loginUrl).followRedirects(false);
|
||||||
|
MultiMap<String> authParams = new MultiMap<String>(baseParams);
|
||||||
|
authParams.put(AUTHORIZATION, authCode);
|
||||||
|
authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||||
|
authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||||
|
UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||||
|
ContentResponse authResponse = authRequest.send();
|
||||||
|
if (authResponse.getStatus() != 302) {
|
||||||
|
throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
|
||||||
|
+ ", Message: " + authResponse.getContentAsString(), authResponse);
|
||||||
|
}
|
||||||
|
String code = MyBMWProxy.codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 5) Request token
|
||||||
|
*/
|
||||||
|
Request codeRequest = httpClient.POST(aqr.tokenEndpoint);
|
||||||
|
String basicAuth = "Basic "
|
||||||
|
+ Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
|
||||||
|
codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||||
|
codeRequest.header(AUTHORIZATION, basicAuth);
|
||||||
|
|
||||||
|
MultiMap<String> codeParams = new MultiMap<String>();
|
||||||
|
codeParams.put(CODE, code);
|
||||||
|
codeParams.put(CODE_VERIFIER, codeVerifier);
|
||||||
|
codeParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||||
|
codeParams.put(GRANT_TYPE, BimmerConstants.AUTHORIZATION_CODE);
|
||||||
|
codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||||
|
UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||||
|
ContentResponse codeResponse = codeRequest.send();
|
||||||
|
if (codeResponse.getStatus() != 200) {
|
||||||
|
throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
|
||||||
|
+ ", Message: " + codeResponse.getContentAsString(), codeResponse);
|
||||||
|
}
|
||||||
|
AuthResponse ar = Converter.getGson().fromJson(codeResponse.getContentAsString(), AuthResponse.class);
|
||||||
|
token.setType(ar.tokenType);
|
||||||
|
token.setToken(ar.accessToken);
|
||||||
|
token.setExpiration(ar.expiresIn);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getAuthCode(String response) {
|
||||||
|
String[] keys = response.split("&");
|
||||||
|
for (int i = 0; i < keys.length; i++) {
|
||||||
|
if (keys[i].startsWith(AUTHORIZATION)) {
|
||||||
|
String authCode = keys[i].split("=")[1];
|
||||||
|
authCode = authCode.split("\"")[0];
|
||||||
|
return authCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Constants.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String codeFromUrl(String encodedUrl) {
|
||||||
|
final MultiMap<String> tokenMap = new MultiMap<String>();
|
||||||
|
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
|
||||||
|
final StringBuilder codeFound = new StringBuilder();
|
||||||
|
tokenMap.forEach((key, value) -> {
|
||||||
|
if (value.size() > 0) {
|
||||||
|
String val = value.get(0);
|
||||||
|
if (key.endsWith(CODE)) {
|
||||||
|
codeFound.append(val.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return codeFound.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("null")
|
||||||
|
public synchronized boolean updateTokenChina() {
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Step 1) get public key
|
||||||
|
*/
|
||||||
|
String publicKeyUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
|
||||||
|
+ BimmerConstants.CHINA_PUBLIC_KEY;
|
||||||
|
Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl);
|
||||||
|
oauthQueryRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
|
||||||
|
ContentResponse publicKeyResponse = oauthQueryRequest.send();
|
||||||
|
if (publicKeyResponse.getStatus() != 200) {
|
||||||
|
throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
|
||||||
|
+ publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(),
|
||||||
|
publicKeyResponse);
|
||||||
|
}
|
||||||
|
ChinaPublicKeyResponse pkr = Converter.getGson().fromJson(publicKeyResponse.getContentAsString(),
|
||||||
|
ChinaPublicKeyResponse.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 2) Encode password with public key
|
||||||
|
*/
|
||||||
|
// https://www.baeldung.com/java-read-pem-file-keys
|
||||||
|
String publicKeyStr = pkr.data.value;
|
||||||
|
String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||||
|
.replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "")
|
||||||
|
.replace("\\n", "").trim();
|
||||||
|
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
|
||||||
|
X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
|
||||||
|
KeyFactory kf = KeyFactory.getInstance("RSA");
|
||||||
|
PublicKey publicKey = kf.generatePublic(spec);
|
||||||
|
// https://www.thexcoders.net/java-ciphers-rsa/
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||||
|
byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
|
||||||
|
String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3) Send Auth with encoded password
|
||||||
|
*/
|
||||||
|
String tokenUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(BimmerConstants.REGION_CHINA)
|
||||||
|
+ BimmerConstants.CHINA_LOGIN;
|
||||||
|
Request loginRequest = httpClient.POST(tokenUrl);
|
||||||
|
loginRequest.header(X_USER_AGENT, BimmerConstants.USER_AGENT_BMW);
|
||||||
|
String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
|
||||||
|
+ "\"}";
|
||||||
|
loginRequest.content(new StringContentProvider(jsonContent));
|
||||||
|
ContentResponse tokenResponse = loginRequest.send();
|
||||||
|
if (tokenResponse.getStatus() != 200) {
|
||||||
|
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||||
|
+ tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(),
|
||||||
|
tokenResponse);
|
||||||
|
}
|
||||||
|
String authCode = getAuthCode(tokenResponse.getContentAsString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 4) Decode access token
|
||||||
|
*/
|
||||||
|
ChinaTokenResponse cat = Converter.getGson().fromJson(authCode, ChinaTokenResponse.class);
|
||||||
|
String token = cat.data.accessToken;
|
||||||
|
// https://www.baeldung.com/java-jwt-token-decode
|
||||||
|
String[] chunks = token.split("\\.");
|
||||||
|
String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1]));
|
||||||
|
ChinaTokenExpiration cte = Converter.getGson().fromJson(tokenJwtDecodeStr, ChinaTokenExpiration.class);
|
||||||
|
Token t = new Token();
|
||||||
|
t.setToken(token);
|
||||||
|
t.setType(cat.data.tokenType);
|
||||||
|
t.setExpirationTotal(cte.exp);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,227 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||||
|
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_JSON_ENCODED;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.util.MultiMap;
|
||||||
|
import org.eclipse.jetty.util.UrlEncoded;
|
||||||
|
import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RemoteServiceHandler} handles executions of remote services towards your Vehicle
|
||||||
|
*
|
||||||
|
* @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - edit & send of charge profile
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RemoteServiceHandler implements StringResponseCallback {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(RemoteServiceHandler.class);
|
||||||
|
|
||||||
|
private static final String EVENT_ID = "eventId";
|
||||||
|
private static final String DATA = "data";
|
||||||
|
private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
|
||||||
|
private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
|
||||||
|
|
||||||
|
private final MyBMWProxy proxy;
|
||||||
|
private final VehicleHandler handler;
|
||||||
|
private final String serviceExecutionAPI;
|
||||||
|
private final String serviceExecutionStateAPI;
|
||||||
|
|
||||||
|
private int counter = 0;
|
||||||
|
private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
|
||||||
|
private Optional<String> serviceExecuting = Optional.empty();
|
||||||
|
private Optional<String> executingEventId = Optional.empty();
|
||||||
|
|
||||||
|
public enum ExecutionState {
|
||||||
|
READY,
|
||||||
|
INITIATED,
|
||||||
|
PENDING,
|
||||||
|
DELIVERED,
|
||||||
|
EXECUTED,
|
||||||
|
ERROR,
|
||||||
|
TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RemoteService {
|
||||||
|
LIGHT_FLASH("Flash Lights", REMOTE_SERVICE_LIGHT_FLASH, REMOTE_SERVICE_LIGHT_FLASH),
|
||||||
|
VEHICLE_FINDER("Vehicle Finder", REMOTE_SERVICE_VEHICLE_FINDER, REMOTE_SERVICE_VEHICLE_FINDER),
|
||||||
|
DOOR_LOCK("Door Lock", REMOTE_SERVICE_DOOR_LOCK, REMOTE_SERVICE_DOOR_LOCK),
|
||||||
|
DOOR_UNLOCK("Door Unlock", REMOTE_SERVICE_DOOR_UNLOCK, REMOTE_SERVICE_DOOR_UNLOCK),
|
||||||
|
HORN_BLOW("Horn Blow", REMOTE_SERVICE_HORN, REMOTE_SERVICE_HORN),
|
||||||
|
CLIMATE_NOW_START("Start Climate", REMOTE_SERVICE_AIR_CONDITIONING_START, "climate-now?action=START"),
|
||||||
|
CLIMATE_NOW_STOP("Stop Climate", REMOTE_SERVICE_AIR_CONDITIONING_STOP, "climate-now?action=STOP");
|
||||||
|
|
||||||
|
private final String label;
|
||||||
|
private final String id;
|
||||||
|
private final String command;
|
||||||
|
|
||||||
|
RemoteService(final String label, final String id, String command) {
|
||||||
|
this.label = label;
|
||||||
|
this.id = id;
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCommand() {
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteServiceHandler(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
|
||||||
|
handler = vehicleHandler;
|
||||||
|
proxy = myBmwProxy;
|
||||||
|
final VehicleConfiguration config = handler.getConfiguration().get();
|
||||||
|
serviceExecutionAPI = proxy.remoteCommandUrl + config.vin + "/";
|
||||||
|
serviceExecutionStateAPI = proxy.remoteStatusUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean execute(RemoteService service, String... data) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (serviceExecuting.isPresent()) {
|
||||||
|
logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
|
||||||
|
// only one service executing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
serviceExecuting = Optional.of(service.getId());
|
||||||
|
}
|
||||||
|
final MultiMap<String> dataMap = new MultiMap<String>();
|
||||||
|
if (data.length > 0) {
|
||||||
|
dataMap.add(DATA, data[0]);
|
||||||
|
proxy.post(serviceExecutionAPI + service.getCommand(), CONTENT_TYPE_JSON_ENCODED, data[0],
|
||||||
|
handler.getConfiguration().get().vehicleBrand, this);
|
||||||
|
} else {
|
||||||
|
proxy.post(serviceExecutionAPI + service.getCommand(), null, null,
|
||||||
|
handler.getConfiguration().get().vehicleBrand, this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getState() {
|
||||||
|
synchronized (this) {
|
||||||
|
serviceExecuting.ifPresentOrElse(service -> {
|
||||||
|
if (counter >= GIVEUP_COUNTER) {
|
||||||
|
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
|
||||||
|
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||||
|
ExecutionState.TIMEOUT.name().toLowerCase());
|
||||||
|
reset();
|
||||||
|
// immediately refresh data
|
||||||
|
handler.getData();
|
||||||
|
} else {
|
||||||
|
counter++;
|
||||||
|
final MultiMap<String> dataMap = new MultiMap<String>();
|
||||||
|
dataMap.add(EVENT_ID, executingEventId.get());
|
||||||
|
final String encoded = UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false);
|
||||||
|
proxy.post(serviceExecutionStateAPI + Constants.QUESTION + encoded, null, null,
|
||||||
|
handler.getConfiguration().get().vehicleBrand, this);
|
||||||
|
}
|
||||||
|
}, () -> {
|
||||||
|
logger.warn("No Service executed to get state");
|
||||||
|
});
|
||||||
|
stateJob = Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@Nullable String result) {
|
||||||
|
if (result != null) {
|
||||||
|
try {
|
||||||
|
ExecutionStatusContainer esc = Converter.getGson().fromJson(result, ExecutionStatusContainer.class);
|
||||||
|
if (esc != null) {
|
||||||
|
if (esc.eventId != null) {
|
||||||
|
// service initiated - store event id for further MyBMW updates
|
||||||
|
executingEventId = Optional.of(esc.eventId);
|
||||||
|
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||||
|
ExecutionState.INITIATED.name().toLowerCase());
|
||||||
|
} else if (esc.eventStatus != null) {
|
||||||
|
// service status updated
|
||||||
|
synchronized (this) {
|
||||||
|
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||||
|
esc.eventStatus.toLowerCase());
|
||||||
|
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(esc.eventStatus)
|
||||||
|
|| ExecutionState.ERROR.name().equalsIgnoreCase(esc.eventStatus)) {
|
||||||
|
// refresh loop ends - update of status handled in the normal refreshInterval.
|
||||||
|
// Earlier update doesn't show better results!
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException jse) {
|
||||||
|
logger.debug("RemoteService response is unparseable: {} {}", result, jse.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// schedule even if no result is present until retries exceeded
|
||||||
|
synchronized (this) {
|
||||||
|
stateJob.ifPresent(job -> {
|
||||||
|
if (!job.isDone()) {
|
||||||
|
job.cancel(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
synchronized (this) {
|
||||||
|
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||||
|
ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(error.status));
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
serviceExecuting = Optional.empty();
|
||||||
|
executingEventId = Optional.empty();
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
synchronized (this) {
|
||||||
|
stateJob.ifPresent(action -> {
|
||||||
|
if (!action.isDone()) {
|
||||||
|
action.cancel(true);
|
||||||
|
}
|
||||||
|
stateJob = Optional.empty();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ResponseCallback} Marker Interface for all ASYNC REST API callbacks
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface ResponseCallback {
|
||||||
|
public void onError(NetworkError error);
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link StringResponseCallback} Interface for all String results from ASYNC REST API
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface StringResponseCallback extends ResponseCallback {
|
||||||
|
|
||||||
|
public void onResponse(@Nullable String result);
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Token} MyBMW Token storage
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Token {
|
||||||
|
private String token = Constants.EMPTY;
|
||||||
|
private String tokenType = Constants.EMPTY;
|
||||||
|
private long expiration = 0;
|
||||||
|
|
||||||
|
public String getBearerToken() {
|
||||||
|
return new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpiration(int expiration) {
|
||||||
|
this.expiration = System.currentTimeMillis() / 1000 + expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpirationTotal(long expiration) {
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(String type) {
|
||||||
|
tokenType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return (!token.equals(Constants.EMPTY) && !tokenType.equals(Constants.EMPTY)
|
||||||
|
&& (this.expiration - System.currentTimeMillis() / 1000) > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return tokenType + Constants.COLON + token + Constants.COLON + isValid();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,456 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.measure.Unit;
|
||||||
|
import javax.measure.quantity.Length;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeSession;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.CBS;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.DoorsWindows;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Location;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Tires;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.status.CCMMessage;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils.TimedChannel;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||||
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PointType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.ImperialUnits;
|
||||||
|
import org.openhab.core.library.unit.Units;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.types.CommandOption;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link VehicleChannelHandler} handles Channel updates
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - edit & send of charge profile
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class VehicleChannelHandler extends BaseThingHandler {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
|
||||||
|
protected boolean hasFuel = false;
|
||||||
|
protected boolean isElectric = false;
|
||||||
|
protected boolean isHybrid = false;
|
||||||
|
|
||||||
|
// List Interfaces
|
||||||
|
protected List<CBS> serviceList = new ArrayList<CBS>();
|
||||||
|
protected String selectedService = Constants.UNDEF;
|
||||||
|
protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
|
||||||
|
protected String selectedCC = Constants.UNDEF;
|
||||||
|
protected List<ChargeSession> sessionList = new ArrayList<ChargeSession>();
|
||||||
|
protected String selectedSession = Constants.UNDEF;
|
||||||
|
|
||||||
|
protected MyBMWCommandOptionProvider commandOptionProvider;
|
||||||
|
|
||||||
|
// Data Caches
|
||||||
|
protected Optional<String> vehicleStatusCache = Optional.empty();
|
||||||
|
protected Optional<byte[]> imageCache = Optional.empty();
|
||||||
|
|
||||||
|
public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, String type) {
|
||||||
|
super(thing);
|
||||||
|
commandOptionProvider = cop;
|
||||||
|
|
||||||
|
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||||
|
|| type.equals(VehicleType.ELECTRIC_REX.toString());
|
||||||
|
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||||
|
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
|
||||||
|
isHybrid = hasFuel && isElectric;
|
||||||
|
|
||||||
|
setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOptions(final String group, final String id, List<CommandOption> options) {
|
||||||
|
commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateChannel(final String group, final String id, final State state) {
|
||||||
|
updateState(new ChannelUID(thing.getUID(), group, id), state);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateChargeStatistics(ChargeStatisticsContainer csc) {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, StringType.valueOf(csc.description));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY,
|
||||||
|
QuantityType.valueOf(csc.statistics.totalEnergyCharged, Units.KILOWATT_HOUR));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
|
||||||
|
DecimalType.valueOf(Integer.toString(csc.statistics.numberOfChargingSessions)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateVehicle(Vehicle v) {
|
||||||
|
updateVehicleStatus(v);
|
||||||
|
updateRange(v);
|
||||||
|
updateDoors(v.properties.doorsAndWindows);
|
||||||
|
updateWindows(v.properties.doorsAndWindows);
|
||||||
|
updatePosition(v.properties.vehicleLocation);
|
||||||
|
updateServices(v.properties.serviceRequired);
|
||||||
|
updateCheckControls(v.status.checkControlMessages);
|
||||||
|
updateTires(v.properties.tires);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTires(@Nullable Tires tires) {
|
||||||
|
if (tires == null) {
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF);
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF);
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
|
||||||
|
QuantityType.valueOf(tires.frontLeft.status.currentPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
|
||||||
|
QuantityType.valueOf(tires.frontLeft.status.targetPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
|
||||||
|
QuantityType.valueOf(tires.frontRight.status.currentPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
|
||||||
|
QuantityType.valueOf(tires.frontRight.status.targetPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
|
||||||
|
QuantityType.valueOf(tires.rearLeft.status.currentPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
|
||||||
|
QuantityType.valueOf(tires.rearLeft.status.targetPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
|
||||||
|
QuantityType.valueOf(tires.rearRight.status.currentPressure / 100, Units.BAR));
|
||||||
|
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
|
||||||
|
QuantityType.valueOf(tires.rearRight.status.targetPressure / 100, Units.BAR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateVehicleStatus(Vehicle v) {
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, LOCK, Converter.getLockState(v.properties.areDoorsLocked));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
|
||||||
|
VehicleStatusUtils.getNextServiceDate(v.properties.serviceRequired));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
|
||||||
|
VehicleStatusUtils.getNextServiceMileage(v.properties.serviceRequired));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
|
||||||
|
StringType.valueOf(v.status.checkControlMessagesGeneralState));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, MOTION, OnOffType.from(v.properties.inMotion));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
|
||||||
|
DateTimeType.valueOf(Converter.zonedToLocalDateTime(v.properties.lastUpdatedAt)));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, DOORS, Converter.getClosedState(v.properties.areDoorsClosed));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, WINDOWS, Converter.getClosedState(v.properties.areWindowsClosed));
|
||||||
|
|
||||||
|
if (isElectric) {
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
|
||||||
|
Converter.getConnectionState(v.properties.chargingState.isChargerConnected));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(v))));
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_INFO,
|
||||||
|
StringType.valueOf(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(v))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateRange(Vehicle v) {
|
||||||
|
// get the right unit
|
||||||
|
Unit<Length> lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
|
||||||
|
if (lengthUnit == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isElectric) {
|
||||||
|
int rangeElectric = VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, v);
|
||||||
|
QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
|
||||||
|
QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
|
||||||
|
lengthUnit);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius);
|
||||||
|
}
|
||||||
|
if (hasFuel) {
|
||||||
|
int rangeFuel = VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, v);
|
||||||
|
QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
|
||||||
|
QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius);
|
||||||
|
}
|
||||||
|
if (isHybrid) {
|
||||||
|
int rangeCombined = VehicleStatusUtils.getRange(Constants.PHEV, v);
|
||||||
|
QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
|
||||||
|
QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
|
||||||
|
lengthUnit);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange);
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius);
|
||||||
|
}
|
||||||
|
if (v.status.currentMileage.mileage == Constants.INT_UNDEF) {
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF);
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
|
||||||
|
QuantityType.valueOf(v.status.currentMileage.mileage, lengthUnit));
|
||||||
|
}
|
||||||
|
if (isElectric) {
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, SOC,
|
||||||
|
QuantityType.valueOf(v.properties.chargingState.chargePercentage, Units.PERCENT));
|
||||||
|
}
|
||||||
|
if (hasFuel) {
|
||||||
|
updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
|
||||||
|
QuantityType.valueOf(v.properties.fuelLevel.value, Units.LITRE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateCheckControls(List<CCMMessage> ccl) {
|
||||||
|
if (ccl.isEmpty()) {
|
||||||
|
// No Check Control available - show not active
|
||||||
|
CCMMessage ccm = new CCMMessage();
|
||||||
|
ccm.title = Constants.NO_ENTRIES;
|
||||||
|
ccm.longDescription = Constants.NO_ENTRIES;
|
||||||
|
ccm.state = Constants.NO_ENTRIES;
|
||||||
|
ccl.add(ccm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all elements to options
|
||||||
|
checkControlList = ccl;
|
||||||
|
List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
|
||||||
|
boolean isSelectedElementIn = false;
|
||||||
|
int index = 0;
|
||||||
|
for (CCMMessage ccEntry : checkControlList) {
|
||||||
|
ccmDescriptionOptions.add(new CommandOption(Integer.toString(index), ccEntry.title));
|
||||||
|
if (selectedCC.equals(ccEntry.title)) {
|
||||||
|
isSelectedElementIn = true;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
|
||||||
|
|
||||||
|
// if current selected item isn't anymore in the list select first entry
|
||||||
|
if (!isSelectedElementIn) {
|
||||||
|
selectCheckControl(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void selectCheckControl(int index) {
|
||||||
|
if (index >= 0 && index < checkControlList.size()) {
|
||||||
|
CCMMessage ccEntry = checkControlList.get(index);
|
||||||
|
selectedCC = ccEntry.title;
|
||||||
|
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.title));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.longDescription));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY, StringType.valueOf(ccEntry.state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateServices(List<CBS> sl) {
|
||||||
|
// if list is empty add "undefined" element
|
||||||
|
if (sl.isEmpty()) {
|
||||||
|
CBS cbsm = new CBS();
|
||||||
|
cbsm.type = Constants.NO_ENTRIES;
|
||||||
|
sl.add(cbsm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all elements to options
|
||||||
|
serviceList = sl;
|
||||||
|
List<CommandOption> serviceNameOptions = new ArrayList<>();
|
||||||
|
boolean isSelectedElementIn = false;
|
||||||
|
int index = 0;
|
||||||
|
for (CBS serviceEntry : serviceList) {
|
||||||
|
// create StateOption with "value = list index" and "label = human readable string"
|
||||||
|
serviceNameOptions.add(new CommandOption(Integer.toString(index), serviceEntry.type));
|
||||||
|
if (selectedService.equals(serviceEntry.type)) {
|
||||||
|
isSelectedElementIn = true;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
|
||||||
|
|
||||||
|
// if current selected item isn't anymore in the list select first entry
|
||||||
|
if (!isSelectedElementIn) {
|
||||||
|
selectService(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void selectService(int index) {
|
||||||
|
if (index >= 0 && index < serviceList.size()) {
|
||||||
|
CBS serviceEntry = serviceList.get(index);
|
||||||
|
selectedService = serviceEntry.type;
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, NAME, StringType.valueOf(Converter.toTitleCase(serviceEntry.type)));
|
||||||
|
if (serviceEntry.dateTime != null) {
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, DATE,
|
||||||
|
DateTimeType.valueOf(Converter.zonedToLocalDateTime(serviceEntry.dateTime)));
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, DATE, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
if (serviceEntry.distance != null) {
|
||||||
|
if (Constants.KILOMETERS_JSON.equals(serviceEntry.distance.units)) {
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||||
|
QuantityType.valueOf(serviceEntry.distance.value, Constants.KILOMETRE_UNIT));
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||||
|
QuantityType.valueOf(serviceEntry.distance.value, ImperialUnits.MILE));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||||
|
QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateSessions(List<ChargeSession> sl) {
|
||||||
|
// if list is empty add "undefined" element
|
||||||
|
if (sl.isEmpty()) {
|
||||||
|
ChargeSession cs = new ChargeSession();
|
||||||
|
cs.title = Constants.NO_ENTRIES;
|
||||||
|
sl.add(cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add all elements to options
|
||||||
|
sessionList = sl;
|
||||||
|
List<CommandOption> sessionNameOptions = new ArrayList<>();
|
||||||
|
boolean isSelectedElementIn = false;
|
||||||
|
int index = 0;
|
||||||
|
for (ChargeSession session : sessionList) {
|
||||||
|
// create StateOption with "value = list index" and "label = human readable string"
|
||||||
|
sessionNameOptions.add(new CommandOption(Integer.toString(index), session.title));
|
||||||
|
if (selectedService.equals(session.title)) {
|
||||||
|
isSelectedElementIn = true;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
|
||||||
|
|
||||||
|
// if current selected item isn't anymore in the list select first entry
|
||||||
|
if (!isSelectedElementIn) {
|
||||||
|
selectSession(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void selectSession(int index) {
|
||||||
|
if (index >= 0 && index < sessionList.size()) {
|
||||||
|
ChargeSession sessionEntry = sessionList.get(index);
|
||||||
|
selectedService = sessionEntry.title;
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.title));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.subtitle));
|
||||||
|
if (sessionEntry.energyCharged != null) {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.energyCharged));
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF));
|
||||||
|
}
|
||||||
|
if (sessionEntry.issues != null) {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.issues));
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN));
|
||||||
|
}
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.sessionStatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateChargeProfile(ChargeProfile cp) {
|
||||||
|
ChargeProfileWrapper cpw = new ChargeProfileWrapper(cp);
|
||||||
|
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()));
|
||||||
|
ChargingSettings cs = cpw.getChargeSettings();
|
||||||
|
if (cs != null) {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
|
||||||
|
DecimalType.valueOf(Integer.toString(cs.targetSoc)));
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
|
||||||
|
OnOffType.from(cs.isAcCurrentLimitActive));
|
||||||
|
}
|
||||||
|
final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
|
||||||
|
climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
|
||||||
|
updateTimedState(cpw, ProfileKey.WINDOWSTART);
|
||||||
|
updateTimedState(cpw, ProfileKey.WINDOWEND);
|
||||||
|
updateTimedState(cpw, ProfileKey.TIMER1);
|
||||||
|
updateTimedState(cpw, ProfileKey.TIMER2);
|
||||||
|
updateTimedState(cpw, ProfileKey.TIMER3);
|
||||||
|
updateTimedState(cpw, ProfileKey.TIMER4);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
|
||||||
|
final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
|
||||||
|
if (timed != null) {
|
||||||
|
final LocalTime time = profile.getTime(key);
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
|
||||||
|
time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
|
||||||
|
: new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
|
||||||
|
if (timed.timer != null) {
|
||||||
|
final Boolean enabled = profile.isEnabled(key);
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
|
||||||
|
enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
|
||||||
|
if (timed.hasDays) {
|
||||||
|
final Set<DayOfWeek> days = profile.getDays(key);
|
||||||
|
EnumSet.allOf(DayOfWeek.class).forEach(day -> {
|
||||||
|
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
|
||||||
|
timed.timer + ChargeProfileUtils.getDaysChannel(day),
|
||||||
|
days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateDoors(DoorsWindows dw) {
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.doors.driverFront)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.doors.driverRear)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.doors.passengerFront)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.doors.passengerRear)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(dw.trunk)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(dw.hood)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateWindows(DoorsWindows dw) {
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.windows.driverFront)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.windows.driverRear)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.windows.passengerFront)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
|
||||||
|
StringType.valueOf(Converter.toTitleCase(dw.windows.passengerRear)));
|
||||||
|
updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(dw.moonroof)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updatePosition(Location pos) {
|
||||||
|
updateChannel(CHANNEL_GROUP_LOCATION, GPS, PointType
|
||||||
|
.valueOf(Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude)));
|
||||||
|
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
|
||||||
|
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,351 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.util.Optional;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.VehicleConfiguration;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeSessionsContainer;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.network.NetworkError;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.binding.mybmw.internal.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.core.io.net.http.HttpUtil;
|
||||||
|
import org.openhab.core.library.types.RawType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
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.BridgeHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link VehicleHandler} handles responses from BMW API
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - edit & send charge profile
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VehicleHandler extends VehicleChannelHandler {
|
||||||
|
private Optional<MyBMWProxy> proxy = Optional.empty();
|
||||||
|
private Optional<RemoteServiceHandler> remote = Optional.empty();
|
||||||
|
public Optional<VehicleConfiguration> configuration = Optional.empty();
|
||||||
|
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
|
||||||
|
private Optional<ScheduledFuture<?>> editTimeout = Optional.empty();
|
||||||
|
|
||||||
|
private ImageProperties imageProperties = new ImageProperties();
|
||||||
|
VehicleStatusCallback vehicleStatusCallback = new VehicleStatusCallback();
|
||||||
|
ChargeStatisticsCallback chargeStatisticsCallback = new ChargeStatisticsCallback();
|
||||||
|
ChargeSessionsCallback chargeSessionCallback = new ChargeSessionsCallback();
|
||||||
|
ByteResponseCallback imageCallback = new ImageCallback();
|
||||||
|
|
||||||
|
public VehicleHandler(Thing thing, MyBMWCommandOptionProvider cop, String driveTrain) {
|
||||||
|
super(thing, cop, driveTrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
String group = channelUID.getGroupId();
|
||||||
|
|
||||||
|
// Refresh of Channels with cached values
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
if (CHANNEL_GROUP_STATUS.equals(group)) {
|
||||||
|
vehicleStatusCache.ifPresent(vehicleStatus -> vehicleStatusCallback.onResponse(vehicleStatus));
|
||||||
|
} else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
|
||||||
|
imageCache.ifPresent(image -> imageCallback.onResponse(image));
|
||||||
|
}
|
||||||
|
// Check for Channel Group and corresponding Actions
|
||||||
|
} else if (CHANNEL_GROUP_REMOTE.equals(group)) {
|
||||||
|
// Executing Remote Services
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
String serviceCommand = ((StringType) command).toFullString();
|
||||||
|
remote.ifPresent(remot -> {
|
||||||
|
switch (serviceCommand) {
|
||||||
|
case REMOTE_SERVICE_LIGHT_FLASH:
|
||||||
|
case REMOTE_SERVICE_DOOR_LOCK:
|
||||||
|
case REMOTE_SERVICE_DOOR_UNLOCK:
|
||||||
|
case REMOTE_SERVICE_HORN:
|
||||||
|
case REMOTE_SERVICE_VEHICLE_FINDER:
|
||||||
|
RemoteServiceUtils.getRemoteService(serviceCommand)
|
||||||
|
.ifPresentOrElse(service -> remot.execute(service), () -> {
|
||||||
|
logger.debug("Remote service execution {} unknown", serviceCommand);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case REMOTE_SERVICE_AIR_CONDITIONING_START:
|
||||||
|
RemoteServiceUtils.getRemoteService(serviceCommand)
|
||||||
|
.ifPresentOrElse(service -> remot.execute(service), () -> {
|
||||||
|
logger.debug("Remote service execution {} unknown", serviceCommand);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case REMOTE_SERVICE_AIR_CONDITIONING_STOP:
|
||||||
|
RemoteServiceUtils.getRemoteService(serviceCommand)
|
||||||
|
.ifPresentOrElse(service -> remot.execute(service), () -> {
|
||||||
|
logger.debug("Remote service execution {} unknown", serviceCommand);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.debug("Remote service execution {} unknown", serviceCommand);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
|
||||||
|
// Image Change
|
||||||
|
configuration.ifPresent(config -> {
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
|
||||||
|
String newViewport = command.toString();
|
||||||
|
synchronized (imageProperties) {
|
||||||
|
if (!imageProperties.viewport.equals(newViewport)) {
|
||||||
|
imageProperties = new ImageProperties(newViewport);
|
||||||
|
imageCache = Optional.empty();
|
||||||
|
proxy.ifPresent(prox -> prox.requestImage(config, imageProperties, imageCallback));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (CHANNEL_GROUP_SERVICE.equals(group)) {
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
int index = Converter.getIndex(command.toFullString());
|
||||||
|
if (index != -1) {
|
||||||
|
selectService(index);
|
||||||
|
} else {
|
||||||
|
logger.debug("Cannot select Service index {}", command.toFullString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (CHANNEL_GROUP_CHECK_CONTROL.equals(group)) {
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
int index = Converter.getIndex(command.toFullString());
|
||||||
|
if (index != -1) {
|
||||||
|
selectCheckControl(index);
|
||||||
|
} else {
|
||||||
|
logger.debug("Cannot select CheckControl index {}", command.toFullString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (CHANNEL_GROUP_CHARGE_SESSION.equals(group)) {
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
int index = Converter.getIndex(command.toFullString());
|
||||||
|
if (index != -1) {
|
||||||
|
selectSession(index);
|
||||||
|
} else {
|
||||||
|
logger.debug("Cannot select Session index {}", command.toFullString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
final VehicleConfiguration config = getConfigAs(VehicleConfiguration.class);
|
||||||
|
configuration = Optional.of(config);
|
||||||
|
Bridge bridge = getBridge();
|
||||||
|
if (bridge != null) {
|
||||||
|
BridgeHandler handler = bridge.getHandler();
|
||||||
|
if (handler != null) {
|
||||||
|
proxy = ((MyBMWBridgeHandler) handler).getProxy();
|
||||||
|
remote = proxy.map(prox -> prox.getRemoteServiceHandler(this));
|
||||||
|
} else {
|
||||||
|
logger.debug("Bridge Handler null");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Bridge null");
|
||||||
|
}
|
||||||
|
|
||||||
|
imageProperties = new ImageProperties();
|
||||||
|
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(imageProperties.viewport));
|
||||||
|
|
||||||
|
// start update schedule
|
||||||
|
startSchedule(config.refreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startSchedule(int interval) {
|
||||||
|
refreshJob.ifPresentOrElse(job -> {
|
||||||
|
if (job.isCancelled()) {
|
||||||
|
refreshJob = Optional
|
||||||
|
.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
|
||||||
|
} // else - scheduler is already running!
|
||||||
|
}, () -> {
|
||||||
|
refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
refreshJob.ifPresent(job -> job.cancel(true));
|
||||||
|
editTimeout.ifPresent(job -> job.cancel(true));
|
||||||
|
remote.ifPresent(RemoteServiceHandler::cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getData() {
|
||||||
|
proxy.ifPresentOrElse(prox -> {
|
||||||
|
configuration.ifPresentOrElse(config -> {
|
||||||
|
prox.requestVehicles(config.vehicleBrand, vehicleStatusCallback);
|
||||||
|
if (isElectric) {
|
||||||
|
prox.requestChargeStatistics(config, chargeStatisticsCallback);
|
||||||
|
prox.requestChargeSessions(config, chargeSessionCallback);
|
||||||
|
}
|
||||||
|
if (!imageCache.isPresent() && !imageProperties.failLimitReached()) {
|
||||||
|
prox.requestImage(config, imageProperties, imageCallback);
|
||||||
|
}
|
||||||
|
}, () -> {
|
||||||
|
logger.warn("MyBMW Vehicle Configuration isn't present");
|
||||||
|
});
|
||||||
|
}, () -> {
|
||||||
|
logger.warn("MyBMWProxy isn't present");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateRemoteExecutionStatus(@Nullable String service, String status) {
|
||||||
|
updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
|
||||||
|
StringType.valueOf((service == null ? "-" : service) + Constants.SPACE + status.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<VehicleConfiguration> getConfiguration() {
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScheduledExecutorService getScheduler() {
|
||||||
|
return scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageCallback implements ByteResponseCallback {
|
||||||
|
@Override
|
||||||
|
public void onResponse(byte[] content) {
|
||||||
|
if (content.length > 0) {
|
||||||
|
imageCache = Optional.of(content);
|
||||||
|
String contentType = HttpUtil.guessContentTypeFromData(content);
|
||||||
|
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(content, contentType));
|
||||||
|
} else {
|
||||||
|
synchronized (imageProperties) {
|
||||||
|
imageProperties.failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
logger.debug("{}", error.toString());
|
||||||
|
synchronized (imageProperties) {
|
||||||
|
imageProperties.failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The VehicleStatus is supported by all Vehicle Types so it's used to reflect the Thing Status
|
||||||
|
*/
|
||||||
|
public class VehicleStatusCallback implements StringResponseCallback {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@Nullable String content) {
|
||||||
|
if (content != null) {
|
||||||
|
if (getConfiguration().isPresent()) {
|
||||||
|
Vehicle v = Converter.getVehicle(configuration.get().vin, content);
|
||||||
|
if (v.valid) {
|
||||||
|
vehicleStatusCache = Optional.of(content);
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, RAW,
|
||||||
|
StringType.valueOf(Converter.getRawVehicleContent(configuration.get().vin, content)));
|
||||||
|
updateVehicle(v);
|
||||||
|
if (isElectric) {
|
||||||
|
updateChargeProfile(v.status.chargingProfile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Vehicle {} not valid", configuration.get().vin);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("configuration not present");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateChannel(CHANNEL_GROUP_STATUS, RAW, StringType.valueOf(Constants.EMPTY_JSON));
|
||||||
|
logger.debug("Content not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
logger.debug("{}", error.toString());
|
||||||
|
vehicleStatusCache = Optional.of(Converter.getGson().toJson(error));
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChargeStatisticsCallback implements StringResponseCallback {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@Nullable String content) {
|
||||||
|
if (content != null) {
|
||||||
|
try {
|
||||||
|
ChargeStatisticsContainer csc = Converter.getGson().fromJson(content,
|
||||||
|
ChargeStatisticsContainer.class);
|
||||||
|
if (csc != null) {
|
||||||
|
updateChargeStatistics(csc);
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException jse) {
|
||||||
|
logger.warn("{}", jse.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Content not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
logger.debug("{}", error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChargeSessionsCallback implements StringResponseCallback {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@Nullable String content) {
|
||||||
|
if (content != null) {
|
||||||
|
try {
|
||||||
|
ChargeSessionsContainer csc = Converter.getGson().fromJson(content, ChargeSessionsContainer.class);
|
||||||
|
if (csc != null) {
|
||||||
|
if (csc.chargingSessions != null) {
|
||||||
|
updateSessions(csc.chargingSessions.sessions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException jse) {
|
||||||
|
logger.warn("{}", jse.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Content not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(NetworkError error) {
|
||||||
|
logger.debug("{}", error.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.handler.simulation;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Injector} Simulates feedback of the BMW API
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Injector {
|
||||||
|
private static boolean active = false;
|
||||||
|
|
||||||
|
// copy discovery json here
|
||||||
|
private static String discovery = "";
|
||||||
|
|
||||||
|
// copy vehicle status json here
|
||||||
|
private static String status = "";
|
||||||
|
|
||||||
|
public static boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getDiscovery() {
|
||||||
|
return discovery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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 {@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
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BimmerConstants {
|
||||||
|
|
||||||
|
public static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
|
||||||
|
public static final String REGION_CHINA = "CHINA";
|
||||||
|
public static final String REGION_ROW = "ROW";
|
||||||
|
|
||||||
|
public static final String BRAND_BMW = "bmw";
|
||||||
|
public static final String BRAND_MINI = "mini";
|
||||||
|
public static final List<String> ALL_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
|
||||||
|
|
||||||
|
public static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
|
||||||
|
|
||||||
|
public static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
|
||||||
|
public static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
|
||||||
|
public static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn";
|
||||||
|
public static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA,
|
||||||
|
EADRAX_SERVER_NORTH_AMERICA, REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
|
||||||
|
|
||||||
|
public static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
|
||||||
|
public static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
|
||||||
|
public static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
|
||||||
|
REGION_ROW, OCP_APIM_KEY_ROW);
|
||||||
|
|
||||||
|
public static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey";
|
||||||
|
public static final String CHINA_LOGIN = "/eadrax-coas/v1/login/pwd";
|
||||||
|
|
||||||
|
// Http variables
|
||||||
|
public static final String USER_AGENT_BMW = "android(v1.07_20200330);bmw;1.7.0(11152)";
|
||||||
|
public static final String USER_AGENT_MINI = "android(v1.07_20200330);mini;1.7.0(11152)";
|
||||||
|
public static final Map<String, String> BRAND_USER_AGENTS_MAP = Map.of(BRAND_BMW, USER_AGENT_BMW, BRAND_MINI,
|
||||||
|
USER_AGENT_MINI);
|
||||||
|
|
||||||
|
public static final String LOGIN_NONCE = "login_nonce";
|
||||||
|
public static final String AUTHORIZATION_CODE = "authorization_code";
|
||||||
|
|
||||||
|
// Parameters for API Requests
|
||||||
|
public static final String TIRE_GUARD_MODE = "tireGuardMode";
|
||||||
|
public static final String APP_DATE_TIME = "appDateTime";
|
||||||
|
public static final String APP_TIMEZONE = "apptimezone";
|
||||||
|
|
||||||
|
// API endpoints
|
||||||
|
public static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
|
||||||
|
public static final String API_VEHICLES = "/eadrax-vcs/v1/vehicles";
|
||||||
|
public static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v2/presentation/remote-commands/"; // '/{vin}/{service_type}'
|
||||||
|
public static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.utils;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeProfileUtils} utility functions for charging profiles
|
||||||
|
*
|
||||||
|
* @author Norbert Truchsess - initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ChargeProfileUtils {
|
||||||
|
|
||||||
|
// Charging
|
||||||
|
public static class TimedChannel {
|
||||||
|
public final String time;
|
||||||
|
public final @Nullable String timer;
|
||||||
|
public final boolean hasDays;
|
||||||
|
|
||||||
|
TimedChannel(final String time, @Nullable final String timer, final boolean hasDays) {
|
||||||
|
this.time = time;
|
||||||
|
this.timer = timer;
|
||||||
|
this.hasDays = hasDays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static final Map<ProfileKey, TimedChannel> TIMED_CHANNELS = new HashMap<>() {
|
||||||
|
{
|
||||||
|
put(ProfileKey.WINDOWSTART, new TimedChannel(CHARGE_WINDOW_START, null, false));
|
||||||
|
put(ProfileKey.WINDOWEND, new TimedChannel(CHARGE_WINDOW_END, null, false));
|
||||||
|
put(ProfileKey.TIMER1, new TimedChannel(CHARGE_TIMER1 + CHARGE_DEPARTURE, CHARGE_TIMER1, true));
|
||||||
|
put(ProfileKey.TIMER2, new TimedChannel(CHARGE_TIMER2 + CHARGE_DEPARTURE, CHARGE_TIMER2, true));
|
||||||
|
put(ProfileKey.TIMER3, new TimedChannel(CHARGE_TIMER3 + CHARGE_DEPARTURE, CHARGE_TIMER3, true));
|
||||||
|
put(ProfileKey.TIMER4, new TimedChannel(CHARGE_TIMER4 + CHARGE_DEPARTURE, CHARGE_TIMER4, true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static final Map<DayOfWeek, String> DAY_CHANNELS = new HashMap<>() {
|
||||||
|
{
|
||||||
|
put(DayOfWeek.MONDAY, CHARGE_DAY_MON);
|
||||||
|
put(DayOfWeek.TUESDAY, CHARGE_DAY_TUE);
|
||||||
|
put(DayOfWeek.WEDNESDAY, CHARGE_DAY_WED);
|
||||||
|
put(DayOfWeek.THURSDAY, CHARGE_DAY_THU);
|
||||||
|
put(DayOfWeek.FRIDAY, CHARGE_DAY_FRI);
|
||||||
|
put(DayOfWeek.SATURDAY, CHARGE_DAY_SAT);
|
||||||
|
put(DayOfWeek.SUNDAY, CHARGE_DAY_SUN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static class ChargeKeyDay {
|
||||||
|
public final ProfileKey key;
|
||||||
|
public final DayOfWeek day;
|
||||||
|
|
||||||
|
ChargeKeyDay(final ProfileKey key, final DayOfWeek day) {
|
||||||
|
this.key = key;
|
||||||
|
this.day = day;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static final Map<String, ProfileKey> CHARGE_ENABLED_CHANNEL_KEYS = new HashMap<>() {
|
||||||
|
{
|
||||||
|
TIMED_CHANNELS.forEach((key, channel) -> {
|
||||||
|
put(channel.timer + CHARGE_ENABLED, key);
|
||||||
|
});
|
||||||
|
put(CHARGE_PROFILE_CLIMATE, ProfileKey.CLIMATE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static final Map<String, ProfileKey> CHARGE_TIME_CHANNEL_KEYS = new HashMap<>() {
|
||||||
|
{
|
||||||
|
TIMED_CHANNELS.forEach((key, channel) -> {
|
||||||
|
put(channel.time, key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
private static final Map<String, ChargeKeyDay> CHARGE_DAYS_CHANNEL_KEYS = new HashMap<>() {
|
||||||
|
{
|
||||||
|
DAY_CHANNELS.forEach((dayOfWeek, dayChannel) -> {
|
||||||
|
put(CHARGE_TIMER1 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER1, dayOfWeek));
|
||||||
|
put(CHARGE_TIMER2 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER2, dayOfWeek));
|
||||||
|
put(CHARGE_TIMER3 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
|
||||||
|
put(CHARGE_TIMER4 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static @Nullable TimedChannel getTimedChannel(ProfileKey key) {
|
||||||
|
return TIMED_CHANNELS.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable String getDaysChannel(DayOfWeek day) {
|
||||||
|
return DAY_CHANNELS.get(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable ProfileKey getEnableKey(final String id) {
|
||||||
|
return CHARGE_ENABLED_CHANNEL_KEYS.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable ChargeKeyDay getKeyDay(final String id) {
|
||||||
|
return CHARGE_DAYS_CHANNEL_KEYS.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable ProfileKey getTimeKey(final String id) {
|
||||||
|
return CHARGE_TIME_CHANNEL_KEYS.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatDays(final Set<DayOfWeek> weekdays) {
|
||||||
|
return weekdays.stream().map(day -> Constants.DAYS.get(day)).collect(Collectors.joining(Constants.COMMA));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mybmw.internal.utils;
|
||||||
|
|
||||||
|
import static org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey.*;
|
||||||
|
import static org.openhab.binding.mybmw.internal.utils.Constants.*;
|
||||||
|
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingMode;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingPreference;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.ChargingWindow;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.Time;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.Timer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ChargeProfileWrapper} Wrapper for ChargeProfiles
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
* @author Norbert Truchsess - add ChargeProfileActions
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ChargeProfileWrapper {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ChargeProfileWrapper.class);
|
||||||
|
|
||||||
|
private static final String CHARGING_WINDOW = "chargingWindow";
|
||||||
|
private static final String WEEKLY_PLANNER = "weeklyPlanner";
|
||||||
|
private static final String ACTIVATE = "activate";
|
||||||
|
private static final String DEACTIVATE = "deactivate";
|
||||||
|
|
||||||
|
public enum ProfileKey {
|
||||||
|
CLIMATE,
|
||||||
|
TIMER1,
|
||||||
|
TIMER2,
|
||||||
|
TIMER3,
|
||||||
|
TIMER4,
|
||||||
|
WINDOWSTART,
|
||||||
|
WINDOWEND
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<ChargingMode> mode = Optional.empty();
|
||||||
|
private Optional<ChargingPreference> preference = Optional.empty();
|
||||||
|
private Optional<String> controlType = Optional.empty();
|
||||||
|
private Optional<ChargingSettings> chargeSettings = Optional.empty();
|
||||||
|
|
||||||
|
private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
|
||||||
|
private final Map<ProfileKey, LocalTime> times = new HashMap<>();
|
||||||
|
private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
|
||||||
|
|
||||||
|
public ChargeProfileWrapper(final ChargeProfile profile) {
|
||||||
|
setPreference(profile.chargingPreference);
|
||||||
|
setMode(profile.chargingMode);
|
||||||
|
controlType = Optional.of(profile.chargingControlType);
|
||||||
|
chargeSettings = Optional.of(profile.chargingSettings);
|
||||||
|
setEnabled(CLIMATE, profile.climatisationOn);
|
||||||
|
|
||||||
|
addTimer(TIMER1, profile.getTimerId(1));
|
||||||
|
addTimer(TIMER2, profile.getTimerId(2));
|
||||||
|
if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
|
||||||
|
addTimer(TIMER3, profile.getTimerId(3));
|
||||||
|
addTimer(TIMER4, profile.getTimerId(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CHARGING_WINDOW.equals(profile.chargingPreference)) {
|
||||||
|
addTime(WINDOWSTART, profile.reductionOfChargeCurrent.start);
|
||||||
|
addTime(WINDOWEND, profile.reductionOfChargeCurrent.end);
|
||||||
|
} else {
|
||||||
|
preference.ifPresent(pref -> {
|
||||||
|
if (ChargingPreference.chargingWindow.equals(pref)) {
|
||||||
|
addTime(WINDOWSTART, null);
|
||||||
|
addTime(WINDOWEND, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Boolean isEnabled(final ProfileKey key) {
|
||||||
|
return enabled.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(final ProfileKey key, @Nullable final Boolean enabled) {
|
||||||
|
if (enabled == null) {
|
||||||
|
this.enabled.remove(key);
|
||||||
|
} else {
|
||||||
|
this.enabled.put(key, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getMode() {
|
||||||
|
return mode.map(m -> m.name()).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getControlType() {
|
||||||
|
return controlType.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ChargingSettings getChargeSettings() {
|
||||||
|
return chargeSettings.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(final @Nullable String mode) {
|
||||||
|
if (mode != null) {
|
||||||
|
try {
|
||||||
|
this.mode = Optional.of(ChargingMode.valueOf(mode));
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
LOGGER.warn("unexpected value for chargingMode: {}", mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mode = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getPreference() {
|
||||||
|
return preference.map(pref -> pref.name()).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreference(final @Nullable String preference) {
|
||||||
|
if (preference != null) {
|
||||||
|
try {
|
||||||
|
this.preference = Optional.of(ChargingPreference.valueOf(preference));
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
LOGGER.warn("unexpected value for chargingPreference: {}", preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.preference = Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
|
||||||
|
return daysOfWeek.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDays(final ProfileKey key, final @Nullable Set<DayOfWeek> days) {
|
||||||
|
if (days == null) {
|
||||||
|
daysOfWeek.remove(key);
|
||||||
|
} else {
|
||||||
|
daysOfWeek.put(key, days);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDayEnabled(final ProfileKey key, final DayOfWeek day, final boolean enabled) {
|
||||||
|
final Set<DayOfWeek> days = daysOfWeek.get(key);
|
||||||
|
if (days == null) {
|
||||||
|
daysOfWeek.put(key, enabled ? EnumSet.of(day) : EnumSet.noneOf(DayOfWeek.class));
|
||||||
|
} else {
|
||||||
|
if (enabled) {
|
||||||
|
days.add(day);
|
||||||
|
} else {
|
||||||
|
days.remove(day);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalTime getTime(final ProfileKey key) {
|
||||||
|
LocalTime t = times.get(key);
|
||||||
|
if (t != null) {
|
||||||
|
return t;
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("Profile not valid - Key {} doesn't contain boolean value", key);
|
||||||
|
return Constants.NULL_LOCAL_TIME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(final ProfileKey key, @Nullable LocalTime time) {
|
||||||
|
if (time == null) {
|
||||||
|
times.remove(key);
|
||||||
|
} else {
|
||||||
|
times.put(key, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJson() {
|
||||||
|
final ChargeProfile profile = new ChargeProfile();
|
||||||
|
|
||||||
|
preference.ifPresent(pref -> profile.chargingPreference = pref.name());
|
||||||
|
profile.chargingControlType = controlType.get();
|
||||||
|
Boolean enabledBool = isEnabled(CLIMATE);
|
||||||
|
profile.climatisationOn = enabledBool == null ? false : enabledBool;
|
||||||
|
preference.ifPresent(pref -> {
|
||||||
|
if (ChargingPreference.chargingWindow.equals(pref)) {
|
||||||
|
profile.chargingMode = getMode();
|
||||||
|
final LocalTime start = getTime(WINDOWSTART);
|
||||||
|
final LocalTime end = getTime(WINDOWEND);
|
||||||
|
if (!start.equals(Constants.NULL_LOCAL_TIME) && !end.equals(Constants.NULL_LOCAL_TIME)) {
|
||||||
|
ChargingWindow cw = new ChargingWindow();
|
||||||
|
profile.reductionOfChargeCurrent = cw;
|
||||||
|
cw.start = new Time();
|
||||||
|
cw.start.hour = start.getHour();
|
||||||
|
cw.start.minute = start.getMinute();
|
||||||
|
cw.end = new Time();
|
||||||
|
cw.end.hour = end.getHour();
|
||||||
|
cw.end.minute = end.getMinute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
profile.departureTimes = new ArrayList<Timer>();
|
||||||
|
profile.departureTimes.add(getTimer(TIMER1));
|
||||||
|
profile.departureTimes.add(getTimer(TIMER2));
|
||||||
|
if (profile.chargingControlType.equals(WEEKLY_PLANNER)) {
|
||||||
|
profile.departureTimes.add(getTimer(TIMER3));
|
||||||
|
profile.departureTimes.add(getTimer(TIMER4));
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.chargingSettings = chargeSettings.get();
|
||||||
|
return Converter.getGson().toJson(profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTime(final ProfileKey key, @Nullable final Time time) {
|
||||||
|
try {
|
||||||
|
times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(Converter.getTime(time), TIME_FORMATER));
|
||||||
|
} catch (DateTimeParseException dtpe) {
|
||||||
|
LOGGER.warn("unexpected value for {} time: {}", key.name(), time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTimer(final ProfileKey key, @Nullable final Timer timer) {
|
||||||
|
if (timer == null) {
|
||||||
|
enabled.put(key, false);
|
||||||
|
addTime(key, null);
|
||||||
|
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||||
|
} else {
|
||||||
|
enabled.put(key, ACTIVATE.equals(timer.action));
|
||||||
|
addTime(key, timer.timeStamp);
|
||||||
|
final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
|
||||||
|
if (timer.timerWeekDays != null) {
|
||||||
|
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||||
|
for (String day : timer.timerWeekDays) {
|
||||||
|
try {
|
||||||
|
daySet.add(DayOfWeek.valueOf(day.toUpperCase()));
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
LOGGER.warn("unexpected value for {} day: {}", key.name(), day);
|
||||||
|
}
|
||||||
|
daysOfWeek.put(key, daySet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timer getTimer(final ProfileKey key) {
|
||||||
|
final Timer timer = new Timer();
|
||||||
|
switch (key) {
|
||||||
|
case TIMER1:
|
||||||
|
timer.id = 1;
|
||||||
|
break;
|
||||||
|
case TIMER2:
|
||||||
|
timer.id = 2;
|
||||||
|
break;
|
||||||
|
case TIMER3:
|
||||||
|
timer.id = 3;
|
||||||
|
break;
|
||||||
|
case TIMER4:
|
||||||
|
timer.id = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// timer id stays -1
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Boolean enabledBool = isEnabled(key);
|
||||||
|
if (enabledBool != null) {
|
||||||
|
timer.action = enabledBool ? ACTIVATE : DEACTIVATE;
|
||||||
|
} else {
|
||||||
|
timer.action = DEACTIVATE;
|
||||||
|
}
|
||||||
|
final LocalTime time = getTime(key);
|
||||||
|
if (!time.equals(Constants.NULL_LOCAL_TIME)) {
|
||||||
|
timer.timeStamp = new Time();
|
||||||
|
timer.timeStamp.hour = time.getHour();
|
||||||
|
timer.timeStamp.minute = time.getMinute();
|
||||||
|
}
|
||||||
|
final Set<DayOfWeek> days = daysOfWeek.get(key);
|
||||||
|
if (days != null) {
|
||||||
|
timer.timerWeekDays = new ArrayList<>();
|
||||||
|
for (DayOfWeek day : days) {
|
||||||
|
timer.timerWeekDays.add(day.name().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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
|
||||||
|
*/
|
||||||
|
@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 BEV = "ELECTRIC";
|
||||||
|
public static final String REX_EXTENSION = "(+ REX)";
|
||||||
|
public static final String HYBRID = "HYBRID";
|
||||||
|
public static final String CONV = "COMBUSTION";
|
||||||
|
public static final String PHEV = "PLUGIN_HYBRID";
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
}
|
@ -0,0 +1,371 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.lang.reflect.Type;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.charge.Time;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Address;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Coordinates;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Distance;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Location;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.Range;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.status.Mileage;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Converter} Conversion Helpers
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class Converter {
|
||||||
|
public static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
|
||||||
|
|
||||||
|
public static final String DATE_INPUT_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ss";
|
||||||
|
public static final DateTimeFormatter DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern(DATE_INPUT_PATTERN_STRING);
|
||||||
|
public static final DateTimeFormatter LOCALE_ENGLISH_TIMEFORMATTER = DateTimeFormatter.ofPattern("hh:mm a",
|
||||||
|
Locale.ENGLISH);
|
||||||
|
public static final SimpleDateFormat ISO_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
private static final Vehicle INVALID_VEHICLE = new Vehicle();
|
||||||
|
private static final String SPLIT_HYPHEN = "-";
|
||||||
|
private static final String SPLIT_BRACKET = "\\(";
|
||||||
|
private static final String VIN_PATTERN = "\"vin\":";
|
||||||
|
private static final String VEHICLE_LOCATION_PATTERN = "\"vehicleLocation\":";
|
||||||
|
private static final String VEHICLE_LOCATION_REPLACEMENT = "\"vehicleLocation\": {\"coordinates\": {\"latitude\": 1.1,\"longitude\": 2.2},\"address\": {\"formatted\": \"anonymous\"},\"heading\": -1}";
|
||||||
|
private static final char OPEN_BRACKET = "{".charAt(0);
|
||||||
|
private static final char CLOSING_BRACKET = "}".charAt(0);
|
||||||
|
|
||||||
|
// https://www.baeldung.com/gson-list
|
||||||
|
public static final Type VEHICLE_LIST_TYPE = new TypeToken<ArrayList<Vehicle>>() {
|
||||||
|
}.getType();
|
||||||
|
public static int offsetMinutes = -1;
|
||||||
|
|
||||||
|
public static String zonedToLocalDateTime(String input) {
|
||||||
|
try {
|
||||||
|
ZonedDateTime d = ZonedDateTime.parse(input).withZoneSameInstant(ZoneId.systemDefault());
|
||||||
|
return d.toLocalDateTime().format(Converter.DATE_INPUT_PATTERN);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toTitleCase(@Nullable String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return toTitleCase(Constants.UNDEF);
|
||||||
|
} else if (input.length() == 1) {
|
||||||
|
return input;
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String capitalizeFirst(String str) {
|
||||||
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gson getGson() {
|
||||||
|
return GSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measure distance between 2 coordinates
|
||||||
|
*
|
||||||
|
* @param sourceLatitude
|
||||||
|
* @param sourceLongitude
|
||||||
|
* @param destinationLatitude
|
||||||
|
* @param destinationLongitude
|
||||||
|
* @return distance
|
||||||
|
*/
|
||||||
|
public 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
|
||||||
|
*/
|
||||||
|
public static int guessRangeRadius(double range) {
|
||||||
|
return (int) (range * 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getIndex(String fullString) {
|
||||||
|
int index = -1;
|
||||||
|
try {
|
||||||
|
index = Integer.parseInt(fullString);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of found vehicles
|
||||||
|
* In case of errors return empty list
|
||||||
|
*
|
||||||
|
* @param json
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Vehicle> getVehicleList(String json) {
|
||||||
|
try {
|
||||||
|
List<Vehicle> l = GSON.fromJson(json, VEHICLE_LIST_TYPE);
|
||||||
|
if (l != null) {
|
||||||
|
return l;
|
||||||
|
} else {
|
||||||
|
return new ArrayList<Vehicle>();
|
||||||
|
}
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
LOGGER.warn("JsonSyntaxException {}", e.getMessage());
|
||||||
|
return new ArrayList<Vehicle>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vehicle getVehicle(String vin, String json) {
|
||||||
|
List<Vehicle> l = getVehicleList(json);
|
||||||
|
for (Vehicle vehicle : l) {
|
||||||
|
if (vin.equals(vehicle.vin)) {
|
||||||
|
// declare vehicle as valid
|
||||||
|
vehicle.valid = true;
|
||||||
|
return getConsistentVehcile(vehicle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INVALID_VEHICLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRawVehicleContent(String vin, String json) {
|
||||||
|
JsonArray jArr = JsonParser.parseString(json).getAsJsonArray();
|
||||||
|
for (int i = 0; i < jArr.size(); i++) {
|
||||||
|
JsonObject jo = jArr.get(i).getAsJsonObject();
|
||||||
|
String jsonVin = jo.getAsJsonPrimitive(MyBMWConstants.VIN).getAsString();
|
||||||
|
if (vin.equals(jsonVin)) {
|
||||||
|
return jo.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Constants.EMPTY_JSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ensure basic data like mileage and location data are available every time
|
||||||
|
*
|
||||||
|
* @param v
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Vehicle getConsistentVehcile(Vehicle v) {
|
||||||
|
if (v.status.currentMileage == null) {
|
||||||
|
v.status.currentMileage = new Mileage();
|
||||||
|
v.status.currentMileage.mileage = -1;
|
||||||
|
v.status.currentMileage.units = "km";
|
||||||
|
}
|
||||||
|
if (v.properties.combustionRange == null) {
|
||||||
|
v.properties.combustionRange = new Range();
|
||||||
|
v.properties.combustionRange.distance = new Distance();
|
||||||
|
v.properties.combustionRange.distance.value = -1;
|
||||||
|
v.properties.combustionRange.distance.units = Constants.EMPTY;
|
||||||
|
}
|
||||||
|
if (v.properties.vehicleLocation == null) {
|
||||||
|
v.properties.vehicleLocation = new Location();
|
||||||
|
v.properties.vehicleLocation.heading = -1;
|
||||||
|
v.properties.vehicleLocation.coordinates = new Coordinates();
|
||||||
|
v.properties.vehicleLocation.coordinates.latitude = -1.234;
|
||||||
|
v.properties.vehicleLocation.coordinates.longitude = -9.876;
|
||||||
|
v.properties.vehicleLocation.address = new Address();
|
||||||
|
v.properties.vehicleLocation.address.formatted = Constants.UNDEF;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRandomString(int size) {
|
||||||
|
int leftLimit = 97; // letter 'a'
|
||||||
|
int rightLimit = 122; // letter 'z'
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
String generatedString = random.ints(leftLimit, rightLimit + 1).limit(size)
|
||||||
|
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
|
||||||
|
|
||||||
|
return generatedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State getLockState(boolean lock) {
|
||||||
|
if (lock) {
|
||||||
|
return StringType.valueOf(Constants.LOCKED);
|
||||||
|
} else {
|
||||||
|
return StringType.valueOf(Constants.UNLOCKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State getClosedState(boolean close) {
|
||||||
|
if (close) {
|
||||||
|
return StringType.valueOf(Constants.CLOSED);
|
||||||
|
} else {
|
||||||
|
return StringType.valueOf(Constants.OPEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State getConnectionState(boolean connected) {
|
||||||
|
if (connected) {
|
||||||
|
return StringType.valueOf(Constants.CONNECTED);
|
||||||
|
} else {
|
||||||
|
return StringType.valueOf(Constants.UNCONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCurrentISOTime() {
|
||||||
|
Date date = new Date(System.currentTimeMillis());
|
||||||
|
synchronized (ISO_FORMATTER) {
|
||||||
|
ISO_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
return ISO_FORMATTER.format(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTime(Time t) {
|
||||||
|
StringBuffer time = new StringBuffer();
|
||||||
|
if (t.hour < 10) {
|
||||||
|
time.append("0");
|
||||||
|
}
|
||||||
|
time.append(Integer.toString(t.hour)).append(":");
|
||||||
|
if (t.minute < 10) {
|
||||||
|
time.append("0");
|
||||||
|
}
|
||||||
|
time.append(Integer.toString(t.minute));
|
||||||
|
return time.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getOffsetMinutes() {
|
||||||
|
if (offsetMinutes == -1) {
|
||||||
|
ZoneOffset zo = ZonedDateTime.now().getOffset();
|
||||||
|
offsetMinutes = zo.getTotalSeconds() / 60;
|
||||||
|
}
|
||||||
|
return offsetMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int stringToInt(String intStr) {
|
||||||
|
int integer = Constants.INT_UNDEF;
|
||||||
|
try {
|
||||||
|
integer = Integer.parseInt(intStr);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.debug("Unable to convert range {} into int value", intStr);
|
||||||
|
}
|
||||||
|
return integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLocalTime(String chrageInfoLabel) {
|
||||||
|
String[] timeSplit = chrageInfoLabel.split(Constants.TILDE);
|
||||||
|
if (timeSplit.length == 2) {
|
||||||
|
try {
|
||||||
|
LocalTime timeL = LocalTime.parse(timeSplit[1].trim(), LOCALE_ENGLISH_TIMEFORMATTER);
|
||||||
|
return timeSplit[0] + Constants.TILDE + timeL.toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.debug("Unable to parse date {} - {}", timeSplit[1], e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chrageInfoLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String anonymousFingerprint(String raw) {
|
||||||
|
String anonymousFingerprintString = raw;
|
||||||
|
int vinStartIndex = raw.indexOf(VIN_PATTERN);
|
||||||
|
if (vinStartIndex != -1) {
|
||||||
|
String[] arr = raw.substring(vinStartIndex + VIN_PATTERN.length()).trim().split("\"");
|
||||||
|
String vin = arr[1].trim();
|
||||||
|
anonymousFingerprintString = raw.replace(vin, "anonymous");
|
||||||
|
}
|
||||||
|
|
||||||
|
int locationStartIndex = raw.indexOf(VEHICLE_LOCATION_PATTERN);
|
||||||
|
int bracketCounter = -1;
|
||||||
|
if (locationStartIndex != -1) {
|
||||||
|
int endLocationIndex = 0;
|
||||||
|
for (int i = locationStartIndex; i < raw.length() && bracketCounter != 0; i++) {
|
||||||
|
endLocationIndex = i;
|
||||||
|
if (raw.charAt(i) == OPEN_BRACKET) {
|
||||||
|
if (bracketCounter == -1) {
|
||||||
|
// start point
|
||||||
|
bracketCounter = 1;
|
||||||
|
} else {
|
||||||
|
bracketCounter++;
|
||||||
|
}
|
||||||
|
} else if (raw.charAt(i) == CLOSING_BRACKET) {
|
||||||
|
bracketCounter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String locationReplacement = raw.substring(locationStartIndex, endLocationIndex + 1);
|
||||||
|
anonymousFingerprintString = anonymousFingerprintString.replace(locationReplacement,
|
||||||
|
VEHICLE_LOCATION_REPLACEMENT);
|
||||||
|
}
|
||||||
|
return anonymousFingerprintString;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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} class contains fields mapping thing configuration parameters.
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HTTPConstants {
|
||||||
|
public static final int HTTP_TIMEOUT_SEC = 10;
|
||||||
|
|
||||||
|
public static final String CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||||
|
public static final String CONTENT_TYPE_JSON_ENCODED = "application/json";
|
||||||
|
public static final String KEEP_ALIVE = "Keep-Alive";
|
||||||
|
public static final String CLIENT_ID = "client_id";
|
||||||
|
public static final String RESPONSE_TYPE = "response_type";
|
||||||
|
public static final String TOKEN = "token";
|
||||||
|
public static final String CODE = "code";
|
||||||
|
public static final String CODE_VERIFIER = "code_verifier";
|
||||||
|
public static final String STATE = "state";
|
||||||
|
public static final String NONCE = "nonce";
|
||||||
|
public static final String REDIRECT_URI = "redirect_uri";
|
||||||
|
public static final String AUTHORIZATION = "authorization";
|
||||||
|
public static final String GRANT_TYPE = "grant_type";
|
||||||
|
public static final String SCOPE = "scope";
|
||||||
|
public static final String CREDENTIALS = "Credentials";
|
||||||
|
public static final String USERNAME = "username";
|
||||||
|
public static final String PASSWORD = "password";
|
||||||
|
public static final String CONTENT_LENGTH = "Content-Length";
|
||||||
|
public static final String CODE_CHALLENGE = "code_challenge";
|
||||||
|
public static final String CODE_CHALLENGE_METHOD = "code_challenge_method";
|
||||||
|
public static final String ACCESS_TOKEN = "access_token";
|
||||||
|
public static final String TOKEN_TYPE = "token_type";
|
||||||
|
public static final String EXPIRES_IN = "expires_in";
|
||||||
|
public static final String CHUNKED = "chunked";
|
||||||
|
|
||||||
|
public static final String ACP_SUBSCRIPTION_KEY = "ocp-apim-subscription-key";
|
||||||
|
public static final String X_USER_AGENT = "x-user-agent";
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class ImageProperties {
|
||||||
|
public static final int RETRY_COUNTER = 5;
|
||||||
|
public int failCounter = 0;
|
||||||
|
public String viewport = "Default";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.RemoteServiceHandler.RemoteService;
|
||||||
|
import org.openhab.core.types.CommandOption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for Remote Service Commands
|
||||||
|
*
|
||||||
|
* @author Norbert Truchsess - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RemoteServiceUtils {
|
||||||
|
|
||||||
|
private static final Map<String, RemoteService> COMMAND_SERVICES = Stream.of(RemoteService.values())
|
||||||
|
.collect(Collectors.toUnmodifiableMap(RemoteService::getId, service -> service));
|
||||||
|
|
||||||
|
public static Optional<RemoteService> getRemoteService(final String command) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 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.util.List;
|
||||||
|
|
||||||
|
import javax.measure.Unit;
|
||||||
|
import javax.measure.quantity.Length;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.properties.CBS;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.status.FuelIndicator;
|
||||||
|
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||||
|
import org.openhab.core.library.types.DateTimeType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.unit.ImperialUnits;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link VehicleStatusUtils} Data Transfer Object
|
||||||
|
*
|
||||||
|
* @author Bernd Weymann - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class VehicleStatusUtils {
|
||||||
|
public static final Logger LOGGER = LoggerFactory.getLogger(VehicleStatusUtils.class);
|
||||||
|
|
||||||
|
public static State getNextServiceDate(List<CBS> cbsMessageList) {
|
||||||
|
ZonedDateTime farFuture = ZonedDateTime.now().plusYears(100);
|
||||||
|
ZonedDateTime serviceDate = farFuture;
|
||||||
|
for (CBS service : cbsMessageList) {
|
||||||
|
if (service.dateTime != null) {
|
||||||
|
ZonedDateTime d = ZonedDateTime.parse(service.dateTime);
|
||||||
|
if (d.isBefore(serviceDate)) {
|
||||||
|
serviceDate = d;
|
||||||
|
} // else skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (serviceDate.equals(farFuture)) {
|
||||||
|
return UnDefType.UNDEF;
|
||||||
|
} else {
|
||||||
|
DateTimeType dt = DateTimeType.valueOf(serviceDate.format(Converter.DATE_INPUT_PATTERN));
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static State getNextServiceMileage(List<CBS> cbsMessageList) {
|
||||||
|
boolean imperial = false;
|
||||||
|
int serviceMileage = Integer.MAX_VALUE;
|
||||||
|
for (CBS service : cbsMessageList) {
|
||||||
|
if (service.distance != null) {
|
||||||
|
if (service.distance.value < serviceMileage) {
|
||||||
|
serviceMileage = service.distance.value;
|
||||||
|
imperial = !Constants.KILOMETERS_JSON.equals(service.distance.units);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (serviceMileage != Integer.MAX_VALUE) {
|
||||||
|
if (imperial) {
|
||||||
|
return QuantityType.valueOf(serviceMileage, ImperialUnits.MILE);
|
||||||
|
} else {
|
||||||
|
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.BEV.equals(driveTrain)) {
|
||||||
|
if (model.endsWith(Constants.REX_EXTENSION)) {
|
||||||
|
return VehicleType.ELECTRIC_REX;
|
||||||
|
} else {
|
||||||
|
return VehicleType.ELECTRIC;
|
||||||
|
}
|
||||||
|
} else if (Constants.PHEV.equals(driveTrain)) {
|
||||||
|
return VehicleType.PLUGIN_HYBRID;
|
||||||
|
} else if (Constants.CONV.equals(driveTrain) || Constants.HYBRID.equals(driveTrain)) {
|
||||||
|
return VehicleType.CONVENTIONAL;
|
||||||
|
}
|
||||||
|
LOGGER.warn("Unknown Vehicle Type: {} | {}", model, driveTrain);
|
||||||
|
return VehicleType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @Nullable Unit<Length> getLengthUnit(List<FuelIndicator> indicators) {
|
||||||
|
Unit<Length> ret = null;
|
||||||
|
for (FuelIndicator fuelIndicator : indicators) {
|
||||||
|
String unitAbbrev = fuelIndicator.rangeUnits;
|
||||||
|
switch (unitAbbrev) {
|
||||||
|
case Constants.KM_JSON:
|
||||||
|
if (ret != null) {
|
||||||
|
if (!ret.equals(Constants.KILOMETRE_UNIT)) {
|
||||||
|
LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.KM_JSON);
|
||||||
|
} // else - fine!
|
||||||
|
} else {
|
||||||
|
ret = Constants.KILOMETRE_UNIT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Constants.MI_JSON:
|
||||||
|
if (ret != null) {
|
||||||
|
if (!ret.equals(ImperialUnits.MILE)) {
|
||||||
|
LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.MI_JSON);
|
||||||
|
} // else - fine!
|
||||||
|
} else {
|
||||||
|
ret = ImperialUnits.MILE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGGER.debug("Cannot evaluate Unit for {}", unitAbbrev);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The range values delivered by BMW are quite ambiguous!
|
||||||
|
* - status fuel indicators are missing a unique identifier
|
||||||
|
* - properties ranges delivering wrong values for hybrid and fuel range
|
||||||
|
* - properties ranges are not reflecting mi / km - every time km
|
||||||
|
*
|
||||||
|
* So getRange will try
|
||||||
|
* 1) fuel indicator
|
||||||
|
* 2) ranges from properties, except combined range
|
||||||
|
* 3) take a guess from fuel indicators
|
||||||
|
*
|
||||||
|
* @param unitJson
|
||||||
|
* @param indicators
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getRange(String unitJson, Vehicle vehicle) {
|
||||||
|
if (vehicle.status.fuelIndicators.size() == 1) {
|
||||||
|
return Converter.stringToInt(vehicle.status.fuelIndicators.get(0).rangeValue);
|
||||||
|
} else {
|
||||||
|
return guessRange(unitJson, vehicle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guesses the range from 3 fuelindicators
|
||||||
|
* - electric range calculation is correct
|
||||||
|
* - for the 2 other values:
|
||||||
|
* -- smaller one is assigned to fuel range
|
||||||
|
* -- bigger one is assigned to hybrid range
|
||||||
|
*
|
||||||
|
* @see VehicleStatusTest testGuessRange
|
||||||
|
*
|
||||||
|
* @param unitJson
|
||||||
|
* @param vehicle
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int guessRange(String unitJson, Vehicle vehicle) {
|
||||||
|
int electricGuess = Constants.INT_UNDEF;
|
||||||
|
int fuelGuess = Constants.INT_UNDEF;
|
||||||
|
int hybridGuess = Constants.INT_UNDEF;
|
||||||
|
for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
|
||||||
|
// electric range - this fits 100%
|
||||||
|
if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
|
||||||
|
&& fuelIndicator.chargingStatusType != null) {
|
||||||
|
// found electric
|
||||||
|
electricGuess = Converter.stringToInt(fuelIndicator.rangeValue);
|
||||||
|
} else {
|
||||||
|
if (fuelGuess == Constants.INT_UNDEF) {
|
||||||
|
// fuel not set? then assume it's fuel
|
||||||
|
fuelGuess = Converter.stringToInt(fuelIndicator.rangeValue);
|
||||||
|
} else {
|
||||||
|
// fuel already guessed - take smaller value for fuel, bigger for hybrid
|
||||||
|
int newGuess = Converter.stringToInt(fuelIndicator.rangeValue);
|
||||||
|
hybridGuess = Math.max(fuelGuess, newGuess);
|
||||||
|
fuelGuess = Math.min(fuelGuess, newGuess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (unitJson) {
|
||||||
|
case Constants.UNIT_PRECENT_JSON:
|
||||||
|
return electricGuess;
|
||||||
|
case Constants.UNIT_LITER_JSON:
|
||||||
|
return fuelGuess;
|
||||||
|
case Constants.PHEV:
|
||||||
|
return hybridGuess;
|
||||||
|
default:
|
||||||
|
return Constants.INT_UNDEF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getChargStatus(Vehicle vehicle) {
|
||||||
|
FuelIndicator fi = getElectricFuelIndicator(vehicle);
|
||||||
|
if (fi.chargingStatusType != null) {
|
||||||
|
if (fi.chargingStatusType.equals(Constants.DEFAULT)) {
|
||||||
|
return Constants.NOT_CHARGING_STATE;
|
||||||
|
} else {
|
||||||
|
return fi.chargingStatusType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Constants.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getChargeInfo(Vehicle vehicle) {
|
||||||
|
FuelIndicator fi = getElectricFuelIndicator(vehicle);
|
||||||
|
if (fi.chargingStatusType != null && fi.infoLabel != null) {
|
||||||
|
if (fi.chargingStatusType.equals(Constants.CHARGING_STATE)
|
||||||
|
|| fi.chargingStatusType.equals(Constants.PLUGGED_STATE)) {
|
||||||
|
return fi.infoLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Constants.HYPHEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FuelIndicator getElectricFuelIndicator(Vehicle vehicle) {
|
||||||
|
for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
|
||||||
|
if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
|
||||||
|
&& fuelIndicator.chargingStatusType != null) {
|
||||||
|
return fuelIndicator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new FuelIndicator();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="mybmw" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||||
|
|
||||||
|
<name>MyBMW</name>
|
||||||
|
<description>Provides access to your Vehicle Data like MyBMW App</description>
|
||||||
|
|
||||||
|
</binding:binding>
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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="CHINA">China</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>
|
||||||
|
</config-description>
|
||||||
|
</config-description:config-descriptions>
|
@ -0,0 +1,23 @@
|
|||||||
|
<?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="1" 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>
|
@ -0,0 +1,225 @@
|
|||||||
|
# Binding
|
||||||
|
binding.mybmw.name = MyBMW
|
||||||
|
binding.mybmw.description = Fahrzeugdaten über die MyBMW App
|
||||||
|
|
||||||
|
# bridge types
|
||||||
|
thing-type.mybmw.account.label = MyBMW Benutzerkonto
|
||||||
|
thing-type.mybmw.account.description = Kontodaten für das BMW Benutzerkonto
|
||||||
|
|
||||||
|
# bridge config
|
||||||
|
thing-type.config.mybmw.bridge.userName.label = Benutzername
|
||||||
|
thing-type.config.mybmw.bridge.userName.description = Benutzername für die MyBMW App
|
||||||
|
thing-type.config.mybmw.bridge.password.label = Passwort
|
||||||
|
thing-type.config.mybmw.bridge.password.description = Passwort für die MyBMW App
|
||||||
|
thing-type.config.mybmw.bridge.region.label = Region
|
||||||
|
thing-type.config.mybmw.bridge.region.description = Auswahl Ihrer Region
|
||||||
|
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Nordamerika
|
||||||
|
thing-type.config.mybmw.bridge.region.option.CHINA = China
|
||||||
|
thing-type.config.mybmw.bridge.region.option.ROW = Rest der Welt
|
||||||
|
thing-type.config.mybmw.bridge.language.label = Sprachauswahl
|
||||||
|
thing-type.config.mybmw.bridge.language.description = Daten werden für die gewünschte Sprache angefordert (en, de, fr ...)
|
||||||
|
|
||||||
|
# thing types
|
||||||
|
thing-type.mybmw.bev_rex.label = Elektrofahrzeug mit REX
|
||||||
|
thing-type.mybmw.bev_rex.description = Elektrofahrzeug mit Range Extender (bev_rex)
|
||||||
|
thing-type.mybmw.bev.label = Elektrofahrzeug
|
||||||
|
thing-type.mybmw.bev.description = Batterieelektrisches Fahrzeug (bev)
|
||||||
|
thing-type.mybmw.phev.label = Plug-in-Hybrid Elektrofahrzeug
|
||||||
|
thing-type.mybmw.phev.description = Konventionelles Fahrzeug mit Elektromotor (phev)
|
||||||
|
thing-type.mybmw.conv.label = Konventionelles Fahrzeug
|
||||||
|
thing-type.mybmw.conv.description = Konventionelles Benzin/Diesel Fahrzeug (conv)
|
||||||
|
|
||||||
|
# thing config
|
||||||
|
thing-type.config.mybmw.vehicle.vin.label = Fahrzeug Identifikationsnummer (VIN)
|
||||||
|
thing-type.config.mybmw.vehicle.vin.description = VIN des Fahrzeugs
|
||||||
|
thing-type.config.mybmw.vehicle.refreshInterval.label = Datenaktualisierung in Minuten
|
||||||
|
thing-type.config.mybmw.vehicle.refreshInterval.description = Rate der Datenaktualisierung Ihres Fahrzeugs
|
||||||
|
thing-type.config.mybmw.vehicle.vehicleBrand.label = Marke des Fahrzeugs
|
||||||
|
thing-type.config.mybmw.vehicle.vehicleBrand.description = Fahrzeugmarke wie z.B. BMW oder Mini.
|
||||||
|
|
||||||
|
# Channel Groups
|
||||||
|
channel-group-type.mybmw.ev-vehicle-status.label = Fahrzeug Zustand
|
||||||
|
channel-group-type.mybmw.ev-vehicle-status.description = Gesamtzustand des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.vehicle-status.label = Fahrzeug Zustand
|
||||||
|
channel-group-type.mybmw.vehicle-status.description = Gesamtzustand des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.ev-range-values.label = Elektro- Reichweiten und Batterieladung
|
||||||
|
channel-group-type.mybmw.ev-range-values.description = Tachostand, Reichweiten und Ladestand des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.hybrid-range-values.label = Hybride Reichweiten und Füllstände
|
||||||
|
channel-group-type.mybmw.hybrid-range-values.description = Tachostand, Reichweite, Ladezustand und Tankfüllung für hybride Fahrzeuge
|
||||||
|
channel-group-type.mybmw.conv-range-values.label = Verbrenner Reichweiten und Füllstände
|
||||||
|
channel-group-type.mybmw.conv-range-values.description = Tachostand, Reichweite und Tankfüllung des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.door-values.label = Details aller Türen
|
||||||
|
channel-group-type.mybmw.door-values.description = Zeigt die Details der Türen und Fenster des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.check-control-values.label = Warnungen
|
||||||
|
channel-group-type.mybmw.check-control-values.description = Aktuelle Warnungen des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.service-values.label = Wartung
|
||||||
|
channel-group-type.mybmw.service-values.description = Anstehende Wartungstermine des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.location-values.label = Fahrzeug Standort
|
||||||
|
channel-group-type.mybmw.location-values.description = Koordinaten und Ausrichtung des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.remote-services.label = Fernsteuerung
|
||||||
|
channel-group-type.mybmw.remote-services.description = Fernsteuerung des Fahrzeugs
|
||||||
|
channel-group-type.mybmw.profile-values.label = Elektrisches Ladeprofil
|
||||||
|
channel-group-type.mybmw.profile-values.description = Zeitplanung der Ladevorgänge
|
||||||
|
channel-group-type.mybmw.charge-statistic.label = Elektrische Ladestatistik
|
||||||
|
channel-group-type.mybmw.charge-statistic.description = Statistik der Ladevorgänge im Monat
|
||||||
|
channel-group-type.mybmw.session-values.label = Elektrische Ladevorgänge
|
||||||
|
channel-group-type.mybmw.session-values.description = Liste der letzten Ladevorgänge
|
||||||
|
channel-group-type.mybmw.tire-pressures.label = Reifen Luftdruck
|
||||||
|
channel-group-type.mybmw.tire-pressures.description = Reifen Luftdruck Ist und Sollwerte
|
||||||
|
channel-group-type.mybmw.image-values.label = Fahrzeug Bild
|
||||||
|
channel-group-type.mybmw.image-values.description = Bild des Fahrzeug basierend auf der ausgewählten Ansicht
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Channel Types
|
||||||
|
channel-type.mybmw.doors-channel.label = Gesamtzustand der Türen
|
||||||
|
channel-type.mybmw.windows-channel.label = Gesamtzustand der Fenster
|
||||||
|
channel-type.mybmw.lock-channel.label = Fahrzeug Abgeschlossen
|
||||||
|
channel-type.mybmw.next-service-date-channel.label = Nächster Service Termin
|
||||||
|
channel-type.mybmw.next-service-mileage-channel.label = Nächster Service in Kilometern
|
||||||
|
channel-type.mybmw.check-control-channel.label = Warnung Aktiv
|
||||||
|
channel-type.mybmw.plug-connection-channel.label = Ladestecker
|
||||||
|
channel-type.mybmw.charging-status-channel.label = Ladezustand
|
||||||
|
channel-type.mybmw.charging-info-channel.label = Ladeinformationen
|
||||||
|
channel-type.mybmw.motion-channel.label = Fahrzustand
|
||||||
|
channel-type.mybmw.last-update-channel.label = Letzte Aktualisierung
|
||||||
|
channel-type.mybmw.raw-channel.label = Rohdaten
|
||||||
|
|
||||||
|
channel-type.mybmw.driver-front-channel.label = Fahrertür
|
||||||
|
channel-type.mybmw.driver-rear-channel.label = Fahrertür Hinten
|
||||||
|
channel-type.mybmw.passenger-front-channel.label = Beifahrertür
|
||||||
|
channel-type.mybmw.passenger-rear-channel.label = Beifahrertür Hinten
|
||||||
|
channel-type.mybmw.hood-channel.label = Frontklappe
|
||||||
|
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-passenger-front-channel.label = Beifahrertür Fenster
|
||||||
|
channel-type.mybmw.window-passenger-rear-channel.label = Beifahrertür Hinten Fenster
|
||||||
|
channel-type.mybmw.window-rear-channel.label = Heckfenster
|
||||||
|
channel-type.mybmw.sunroof-channel.label = Schiebedach
|
||||||
|
|
||||||
|
channel-type.mybmw.mileage-channel.label = Tachostand
|
||||||
|
channel-type.mybmw.range-hybrid-channel.label = Hybride Reichweite
|
||||||
|
channel-type.mybmw.range-electric-channel.label = Elektrische Reichweite
|
||||||
|
channel-type.mybmw.soc-channel.label = Batterie Ladestand
|
||||||
|
channel-type.mybmw.range-fuel-channel.label = Verbrenner Reichweite
|
||||||
|
channel-type.mybmw.remaining-fuel-channel.label = Tankstand
|
||||||
|
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.service-name-channel.label = Service
|
||||||
|
channel-type.mybmw.service-details-channel.label = Service Details
|
||||||
|
channel-type.mybmw.service-date-channel.label = Service Termin
|
||||||
|
channel-type.mybmw.service-mileage-channel.label = Service in Kilometern
|
||||||
|
|
||||||
|
channel-type.mybmw.checkcontrol-name-channel.label = Warnung
|
||||||
|
channel-type.mybmw.checkcontrol-details-channel.label = Warnung Details
|
||||||
|
channel-type.mybmw.checkcontrol-severity-channel.label = Warnung Priorität
|
||||||
|
|
||||||
|
channel-type.mybmw.profile-climate-channel.label = Klimatisierung bei Abfahrt
|
||||||
|
channel-type.mybmw.profile-mode-channel.label = Ladeprofil
|
||||||
|
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Sofort Laden
|
||||||
|
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Ladeverzögerung
|
||||||
|
channel-type.mybmw.profile-prefs-channel.label = Ladeprofil Präferenz
|
||||||
|
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = Keine Präferenz
|
||||||
|
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Laden im Zeitfenster
|
||||||
|
channel-type.mybmw.profile-control-channel.label = Ladeplan
|
||||||
|
channel-type.mybmw.profile-control-channel.description = Ladeplan Auswahl
|
||||||
|
channel-type.mybmw.profile-target-channel.label = Ziel Ladezustand
|
||||||
|
channel-type.mybmw.profile-target-channel.description = Erwünschter Batterie Ladezustand
|
||||||
|
channel-type.mybmw.profile-limit-channel.label = Ladung Limitiert
|
||||||
|
channel-type.mybmw.profile-limit-channell.description = Limitierte Ladung aktiviert
|
||||||
|
|
||||||
|
|
||||||
|
channel-type.mybmw.window-start-channel.label = Ladefenster Startzeit
|
||||||
|
channel-type.mybmw.window-end-channel.label = Ladefenster Endzeit
|
||||||
|
channel-type.mybmw.timer1-enabled-channel.label = Zeitprofil 1 - Aktiviert
|
||||||
|
channel-type.mybmw.timer1-departure-channel.label = Zeitprofil 1 - Abfahrtszeit
|
||||||
|
channel-type.mybmw.timer1-days-channel.label = Zeitprofil 1 - Tage
|
||||||
|
channel-type.mybmw.timer1-day-mon-channel.label = Zeitprofil 1 - Montag
|
||||||
|
channel-type.mybmw.timer1-day-tue-channel.label = Zeitprofil 1 - Dienstag
|
||||||
|
channel-type.mybmw.timer1-day-wed-channel.label = Zeitprofil 1 - Mittwoch
|
||||||
|
channel-type.mybmw.timer1-day-thu-channel.label = Zeitprofil 1 - Donnerstag
|
||||||
|
channel-type.mybmw.timer1-day-fri-channel.label = Zeitprofil 1 - Freitag
|
||||||
|
channel-type.mybmw.timer1-day-sat-channel.label = Zeitprofil 1 - Samstag
|
||||||
|
channel-type.mybmw.timer1-day-sun-channel.label = Zeitprofil 1 - Sonntag
|
||||||
|
channel-type.mybmw.timer2-enabled-channel.label = Zeitprofil 2 - Aktiviert
|
||||||
|
channel-type.mybmw.timer2-departure-channel.label = Zeitprofil 2 - Abfahrtszeit
|
||||||
|
channel-type.mybmw.timer2-days-channel.label = Zeitprofil 2 - Tage
|
||||||
|
channel-type.mybmw.timer2-day-mon-channel.label = Zeitprofil 2 - Montag
|
||||||
|
channel-type.mybmw.timer2-day-tue-channel.label = Zeitprofil 2 - Dienstag
|
||||||
|
channel-type.mybmw.timer2-day-wed-channel.label = Zeitprofil 2 - Mittwoch
|
||||||
|
channel-type.mybmw.timer2-day-thu-channel.label = Zeitprofil 2 - Donnerstag
|
||||||
|
channel-type.mybmw.timer2-day-fri-channel.label = Zeitprofil 2 - Freitag
|
||||||
|
channel-type.mybmw.timer2-day-sat-channel.label = Zeitprofil 2 - Samstag
|
||||||
|
channel-type.mybmw.timer2-day-sun-channel.label = Zeitprofil 2 - Sonntag
|
||||||
|
channel-type.mybmw.timer3-enabled-channel.label = Zeitprofil 3 - Aktiviert
|
||||||
|
channel-type.mybmw.timer3-departure-channel.label = Zeitprofil 3 - Abfahrtszeit
|
||||||
|
channel-type.mybmw.timer3-days-channel.label = Zeitprofil 3 - Tage
|
||||||
|
channel-type.mybmw.timer3-day-mon-channel.label = Zeitprofil 3 - Montag
|
||||||
|
channel-type.mybmw.timer3-day-tue-channel.label = Zeitprofil 3 - Dienstag
|
||||||
|
channel-type.mybmw.timer3-day-wed-channel.label = Zeitprofil 3 - Mittwoch
|
||||||
|
channel-type.mybmw.timer3-day-thu-channel.label = Zeitprofil 3 - Donnerstag
|
||||||
|
channel-type.mybmw.timer3-day-fri-channel.label = Zeitprofil 3 - Freitag
|
||||||
|
channel-type.mybmw.timer3-day-sat-channel.label = Zeitprofil 3 - Samstag
|
||||||
|
channel-type.mybmw.timer3-day-sun-channel.label = Zeitprofil 3 - Sonntag
|
||||||
|
channel-type.mybmw.timer4-enabled-channel.label = Zeitprofil 4 - Aktiviert
|
||||||
|
channel-type.mybmw.timer4-departure-channel.label = Zeitprofil 4 - Abfahrtszeit
|
||||||
|
channel-type.mybmw.timer4-days-channel.label = Zeitprofil 4 - Tage
|
||||||
|
channel-type.mybmw.timer4-day-mon-channel.label = Zeitprofil 4 - Montag
|
||||||
|
channel-type.mybmw.timer4-day-tue-channel.label = Zeitprofil 4 - Dienstag
|
||||||
|
channel-type.mybmw.timer4-day-wed-channel.label = Zeitprofil 4 - Mittwoch
|
||||||
|
channel-type.mybmw.timer4-day-thu-channel.label = Zeitprofil 4 - Donnerstag
|
||||||
|
channel-type.mybmw.timer4-day-fri-channel.label = Zeitprofil 4 - Freitag
|
||||||
|
channel-type.mybmw.timer4-day-sat-channel.label = Zeitprofil 4 - Samstag
|
||||||
|
channel-type.mybmw.timer4-day-sun-channel.label = Zeitprofil 4 - Sonntag
|
||||||
|
|
||||||
|
# Location
|
||||||
|
channel-type.mybmw.gps-channel.label = Koordinaten
|
||||||
|
channel-type.mybmw.heading-channel.label = Ausrichtung
|
||||||
|
channel-type.mybmw.address-channel.label = Adresse
|
||||||
|
|
||||||
|
#Remote
|
||||||
|
channel-type.mybmw.remote-command-channel.label = Kommando Auswahl
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.light-flash = Lichthupe Ausführen
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Fahrzeug Lokalisieren
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.door-lock = Fahrzeug Abschließen
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Fahrzug Aufschließen
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Hupe Aktivieren
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Klimatisierung Ausführen
|
||||||
|
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Klimatisierung Beenden
|
||||||
|
channel-type.mybmw.remote-state-channel.label = Ausführungszustand
|
||||||
|
|
||||||
|
# Image
|
||||||
|
channel-type.mybmw.png-channel.label = Fahrzeug Bild
|
||||||
|
channel-type.mybmw.image-view-channel.label = Fahrzeug Ansicht
|
||||||
|
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Seitenansicht
|
||||||
|
channel-type.mybmw.image-view-channel.command.option.VehicleInfo = Frontansicht
|
||||||
|
channel-type.mybmw.image-view-channel.command.option.ChargingHistory = Seitenansicht
|
||||||
|
channel-type.mybmw.image-view-channel.command.option.Default = Standard Ansicht
|
||||||
|
|
||||||
|
# Charge Sessions
|
||||||
|
channel-type.mybmw.session-title-channel.label = Ladevorgang Beschreibung
|
||||||
|
channel-type.mybmw.session-subtitle-channel.label = Ladevorgang Details
|
||||||
|
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
|
||||||
|
|
||||||
|
# Charge Statistcis
|
||||||
|
channel-type.mybmw.statistic-title-channel.label = Ladestatistik Monat
|
||||||
|
channel-type.mybmw.statistic-energy-channel.label = Energie Geladen Monat
|
||||||
|
channel-type.mybmw.statistic-energy-channel.description = Geladene Energie in diesem Monat
|
||||||
|
channel-type.mybmw.statistic-sessions-channel.label = Ladevorgänge Monat
|
||||||
|
channel-type.mybmw.statistic-sessions-channel.description = Anzahl der Ladevorgänge in diesem Monat
|
||||||
|
|
||||||
|
#Tires
|
||||||
|
channel-type.mybmw.front-left-current-channel.label = Reifen Luftdruck Vorne Links
|
||||||
|
channel-type.mybmw.front-left-wanted-channel.label = Reifen Luftdruck Vorne Links Sollwert
|
||||||
|
channel-type.mybmw.front-right-current-channel.label = Reifen Luftdruck Vorne Rechts
|
||||||
|
channel-type.mybmw.front-right-wanted-channel.label = Reifen Luftdruck Vorne Rechts Sollwert
|
||||||
|
channel-type.mybmw.rear-left-current-channel.label = Reifen Luftdruck Hinten Links
|
||||||
|
channel-type.mybmw.rear-left-wanted-channel.label = Reifen Luftdruck Hinten Links Sollwert
|
||||||
|
channel-type.mybmw.rear-right-current-channel.label = Reifen Luftdruck Hinten Rechts
|
||||||
|
channel-type.mybmw.rear-right-wanted-channel.label = Reifen Luftdruck Hinten Rechts Sollwert
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
<?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>
|
||||||
|
<config-description-ref uri="thing-type:mybmw:bridge"/>
|
||||||
|
</bridge-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,15 @@
|
|||||||
|
<?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>
|
@ -0,0 +1,21 @@
|
|||||||
|
<?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>
|
||||||
|
<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>
|
||||||
|
</channel-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,18 @@
|
|||||||
|
<?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="checkcontrol-name-channel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>CheckControl Description</label>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="checkcontrol-details-channel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>CheckControl Details</label>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="checkcontrol-severity-channel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Severity Level</label>
|
||||||
|
</channel-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,15 @@
|
|||||||
|
<?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="check-control-values">
|
||||||
|
<label>Check Control Messages</label>
|
||||||
|
<description>Shows current active CheckControl messages</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="name" typeId="checkcontrol-name-channel"/>
|
||||||
|
<channel id="details" typeId="checkcontrol-details-channel"/>
|
||||||
|
<channel id="severity" typeId="checkcontrol-severity-channel"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,16 @@
|
|||||||
|
<?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="conv-range-values">
|
||||||
|
<label>Range and Fuel Data</label>
|
||||||
|
<description>Provides Mileage, remaining range and fuel level values</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="mileage" typeId="mileage-channel"/>
|
||||||
|
<channel id="fuel" typeId="range-fuel-channel"/>
|
||||||
|
<channel id="remaining-fuel" typeId="remaining-fuel-channel"/>
|
||||||
|
<channel id="radius-fuel" typeId="range-radius-fuel-channel"/>
|
||||||
|
</channels>
|
||||||
|
</channel-group-type>
|
||||||
|
</thing:thing-descriptions>
|