remove mybmw binding (#20371)
Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
@@ -263,7 +263,6 @@
|
||||
/bundles/org.openhab.binding.mqtt.fpp/ @computergeek1507
|
||||
/bundles/org.openhab.binding.mqtt.ruuvigateway/ @ssalonen
|
||||
/bundles/org.openhab.binding.mspa/ @weymann
|
||||
/bundles/org.openhab.binding.mybmw/ @ntruchsess @mherwege @martingrassl
|
||||
/bundles/org.openhab.binding.mycroft/ @dalgwen
|
||||
/bundles/org.openhab.binding.mynice/ @clinique
|
||||
/bundles/org.openhab.binding.mystrom/ @pail23
|
||||
|
||||
@@ -1301,11 +1301,6 @@
|
||||
<artifactId>org.openhab.binding.mycroft</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.mybmw</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.mynice</artifactId>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
@@ -1,864 +0,0 @@
|
||||
# MyBMW Binding
|
||||
|
||||
The binding provides access like [MyBMW App](https://www.bmw.com/en/footer/mybmw-app.html) to openHAB.
|
||||
All vehicles connected to an account will be detected by the discovery with the correct type:
|
||||
|
||||
- Conventional Fuel Vehicle
|
||||
- Plugin-Hybrid Electrical Vehicle
|
||||
- Battery Electric Vehicle with Range Extender
|
||||
- Battery Electric Vehicle
|
||||
|
||||
In addition properties are attached with information and services provided by this vehicle.
|
||||
The provided data depends on
|
||||
|
||||
1. the [Thing Type](#things) and
|
||||
1. the [Properties](#properties) mentioned in Services
|
||||
|
||||
Different channel groups are clustering all information.
|
||||
Check for each group if it's supported by your vehicle.
|
||||
|
||||
Please note **this isn't a real-time binding**.
|
||||
If a door is opened the state isn't transmitted and changed immediately.
|
||||
It's not a flaw in the binding itself because the state in BMW's own MyBMW App is also updated with some delay.
|
||||
|
||||
This binding does not support the region: China.
|
||||
|
||||
## Supported Things
|
||||
|
||||
### Bridge
|
||||
|
||||
The bridge establishes the connection between BMW API and openHAB.
|
||||
|
||||
| Name | Bridge Type ID | Description |
|
||||
|----------------------------|----------------|------------------------------------------|
|
||||
| MyBMW Account | `account` | Access to BMW API for a specific user |
|
||||
|
||||
### Things
|
||||
|
||||
Four different vehicle types are provided.
|
||||
They differ in the supported channel groups & channels.
|
||||
Conventional Fuel Vehicles don't provide e.g. _Charging Profile_, Electric Vehicles don't provide a _Fuel Range_.
|
||||
For hybrid vehicles in addition to _Fuel and Electric Range_ the _Hybrid Range_ is shown.
|
||||
|
||||
| Name | Thing Type ID | Supported Channel Groups |
|
||||
|-------------------------------------|---------------|---------------------------------------------------------------------|
|
||||
| BMW Electric Vehicle | `bev` | Vehicle with electric drive train |
|
||||
| BMW Electric Vehicle with REX | `bev_rex` | Vehicle with electric drive train plus fuel powered range extender |
|
||||
| BMW Plug-In-Hybrid Electric Vehicle | `phev` | Vehicle with combustion and electric drive train |
|
||||
| BMW Conventional Vehicle | `conv` | Vehicle with combustion drive train |
|
||||
|
||||
#### Properties
|
||||
|
||||
<img align="right" src="doc/vehicle-properties.png" alt="Vehicle properties overview" width="500" height="350"/>
|
||||
|
||||
For each vehicle properties are available.
|
||||
Basic information is given regarding
|
||||
|
||||
- Vehicle properties like model type, drive train and construction year
|
||||
- Which services are available / not available
|
||||
|
||||
In the right picture can see in _remoteServicesEnabled_ e.g. the _Door Lock_ and _Door Unlock_ services are mentioned.
|
||||
This ensures channel group [Remote Services](#remote-services) is supporting door lock and unlock remote control.
|
||||
|
||||
In _Services Supported_ the entry _ChargingHistory_ is mentioned.
|
||||
So it's valid to connect channel group [Charge Sessions](#charge-sessions) in order to display your last charging sessions.
|
||||
|
||||
| Property Key | Property Value | Supported Channel Groups |
|
||||
|------------------------|---------------------|------------------------------|
|
||||
| servicesSupported | ChargingHistory | session |
|
||||
| remoteServicesEnabled | _list of services_ | remote |
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto discovery is starting after the bridge is created.
|
||||
A list of your registered vehicles is queried and all found things are added in the inbox.
|
||||
Unique identifier is the _Vehicle Identification Number_ (VIN).
|
||||
If a Thing is already declared in a _.things_ configuration, discovery won't highlight it again.
|
||||
Properties will be attached to predefined vehicles if the VIN is matching.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Bridge Configuration
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
|
||||
| userName | text | MyBMW Username |
|
||||
| password | text | MyBMW Password |
|
||||
| region | text | Select region in order to connect to the appropriate BMW server. |
|
||||
|
||||
The region Configuration has 2 different options
|
||||
|
||||
- _NORTH_AMERICA_
|
||||
- _ROW_ (Rest of World)
|
||||
|
||||
At first initialization, follow the online instructions for login into the BMW API.
|
||||
|
||||
#### Advanced Configuration
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
|
||||
| language | text | Channel data can be returned in the desired language |
|
||||
| hcaptchatoken | text | HCaptcha-Token for initial login (see <https://bimmer-connected.readthedocs.io/en/stable/captcha.html>) |
|
||||
| callbackIP | text | IP address for openHAB callback URL, defaults to IP of openHAB host |
|
||||
| callbackPort | integer | Port Number for openHAB callback URL, default 8090 |
|
||||
|
||||
Language is predefined as _AUTODETECT_.
|
||||
Some textual descriptions, date and times are delivered based on your local language.
|
||||
You can overwrite this setting with lowercase 2-letter [language code reagrding ISO 639](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html)
|
||||
So if want your UI in english language place _en_ as desired language.
|
||||
|
||||
The initial login to the BMW API requires a Captcha Token.
|
||||
At first configuration, you can set the Captcha Token as a configuration parameter manually.
|
||||
|
||||
To set the Captcha Token online, a webpage is presented and a callback to the bridge is created temporarily on the hosts IP address and a default port.
|
||||
If the port is already in use, or you have a complex network setup, you may have to override the defaults provided.
|
||||
|
||||
### Thing Configuration
|
||||
|
||||
Same configuration is needed for all things
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|-------------------------------------------------------------------------------------------------------------------------|
|
||||
| vin | text | Vehicle Identification Number (VIN) |
|
||||
| refreshInterval | integer | Refresh Interval in Minutes (by default set to 60; to be set to 0 if no automated refresh should be triggered) |
|
||||
|
||||
#### Advanced Configuration
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|-----------------------------------|
|
||||
| vehicleBrand | text | Vehicle Brand like BMW or Mini |
|
||||
|
||||
The _vehicleBrand_ is automatically obtained by the discovery service and shall not be changed.
|
||||
If Thing is defined manually via *.things file following brands are supported
|
||||
|
||||
- BMW
|
||||
- MINI
|
||||
|
||||
## Channels
|
||||
|
||||
There are many channels available for each vehicle.
|
||||
For better overview they are clustered in different channel groups.
|
||||
They differ for each vehicle type, build-in sensors and activated services.
|
||||
|
||||
### Thing Channel Groups
|
||||
|
||||
| Channel Group ID | Description | conv | phev | bev_rex | bev |
|
||||
|----------------------------------|---------------------------------------------------|------|------|---------|-----|
|
||||
| [update](#vehicle-update) | Overall vehicle status | X | X | X | X |
|
||||
| [status](#vehicle-status) | Overall vehicle status | X | X | X | X |
|
||||
| [doors](#doors-details) | Details of all doors and windows | X | X | X | X |
|
||||
| [range](#range-data) | Provides mileage, range and charge / fuel levels | X | X | X | X |
|
||||
| [check](#check-control) | Shows current active CheckControl messages | X | X | X | X |
|
||||
| [service](#services) | Future vehicle service schedules | X | X | X | X |
|
||||
| [location](#location) | Coordinates and heading of the vehicle | X | X | X | X |
|
||||
| [remote](#remote-services) | Remote control of the vehicle | X | X | X | X |
|
||||
| [profile](#charge-profile) | Scheduled charging profiles of vehicle | | X | X | X |
|
||||
| [statistic](#charge-statistics) | Charging statistics of current month | | X | X | X |
|
||||
| [session](#charge-sessions) | Past charging sessions | | X | X | X |
|
||||
| [tires](#tire-pressure) | Current and wanted pressure for all tires | X | X | X | X |
|
||||
| [image](#image) | Provides an image of your vehicle | X | X | X | X |
|
||||
|
||||
#### Vehicle Update
|
||||
|
||||
The BMW API has limits in the requests per time period.
|
||||
This leads to unexpected errors stating that some quota is reached and the next successful request can be triggered in X minutes.
|
||||
In this case the bridge as well as the vehicle things can be set to offline and nothing can be done with them anymore until the next successful refresh.
|
||||
To reduce the probability of the error, the default automated API update has been set to 60 Minutes, but this is often not sufficient to retrieve continuous range or charging updates.
|
||||
These channels can be used to control the update behavior from openHAB, e.g. via rules.
|
||||
|
||||
- Channel Group ID is status
|
||||
- Available for all vehicles (charging channel only for xEV)
|
||||
- switches which can be triggered by a command
|
||||
- if the switches are set to ON, then immediately they will be set to OFF again for being able to trigger the next update
|
||||
|
||||
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|
||||
|-----------------|--------|----------------------------------------------------------------------------------------------------------|------|------|---------|-----|
|
||||
| state-update | Switch | When set to ON, the state channels of the vehicle will be updated | X | X | X | X |
|
||||
| charging-update | Switch | When set to ON, the charging statistics and charging sessions channels of the vehicle will be updated | | X | X | X |
|
||||
| image-update | Switch | When set to ON, the image of the vehicle will be updated | X | X | X | X |
|
||||
|
||||
#### Vehicle Status
|
||||
|
||||
Reflects overall status of the vehicle.
|
||||
|
||||
- Channel Group ID is **status**
|
||||
- Available for all vehicles
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|
||||
|---------------------|---------------|------------------------------------------------|------|------|---------|-----|
|
||||
| doors | String | Combined status for all doors | X | X | X | X |
|
||||
| windows | String | Combined status for all windows | X | X | X | X |
|
||||
| lock | String | Status if vehicle is secured | X | X | X | X |
|
||||
| service-date | DateTime | Date of next upcoming service | X | X | X | X |
|
||||
| service-mileage | Number:Length | Mileage till upcoming service | X | X | X | X |
|
||||
| check-control | String | Presence of active warning messages | X | X | X | X |
|
||||
| plug-connection | String | Plug is _Connected_ or _Not connected_ | | X | X | X |
|
||||
| charge | String | Current charging status | | X | X | X |
|
||||
| charge-remaining | Number:Time | Remaining time for current charging session | | X | X | X |
|
||||
| last-update | DateTime | Date and time of last status update | X | X | X | X |
|
||||
| last-fetched | DateTime | Date and time of last time status fetched | X | X | X | X |
|
||||
|
||||
Overall Door Status values
|
||||
|
||||
- _Closed_ - all doors closed
|
||||
- _Open_ - at least one door is open
|
||||
- _Undef_ - no door data delivered at all
|
||||
|
||||
Overall Windows Status values
|
||||
|
||||
- _Closed_ - all windows closed
|
||||
- _Open_ - at least one window is completely open
|
||||
- _Intermediate_ - at least one window is partially open
|
||||
- _Undef_ - no window data delivered at all
|
||||
|
||||
Check Control values
|
||||
|
||||
Localized String of current active warnings.
|
||||
Examples:
|
||||
|
||||
- No Issues
|
||||
- Multiple Issues
|
||||
|
||||
Charging Status values
|
||||
|
||||
- _Not Charging_
|
||||
- _Charging_
|
||||
- _Plugged In_
|
||||
- _Fully Charged_
|
||||
|
||||
Charging Information values
|
||||
Localized String of current active charging session
|
||||
Examples
|
||||
|
||||
- 100% at ~00:43
|
||||
- Starts at ~09:00
|
||||
|
||||
##### Vehicle Status Raw Data
|
||||
|
||||
The _raw data channel_ is marked as _advanced_ and isn't shown by default.
|
||||
Target are advanced users to derive even more data out of BMW API replies.
|
||||
As the replies are formatted as JSON use the [JsonPath Transformation Service](https://www.openhab.org/addons/transformations/jsonpath/) to extract data for an item,
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|---------------------|---------------|------------------------------------------------|
|
||||
| raw | String | Unfiltered JSON String of vehicle data |
|
||||
|
||||
<img align="right" src="doc/RawData.png" alt="Raw data example" width="400" height="125"/>
|
||||
|
||||
Examples:
|
||||
|
||||
###### Country ISO Code
|
||||
|
||||
```json
|
||||
$.properties.originCountryISO
|
||||
```
|
||||
|
||||
###### Drivers Guide URL
|
||||
|
||||
```json
|
||||
$.driverGuideInfo.androidStoreUrl
|
||||
```
|
||||
|
||||
#### Range Data
|
||||
|
||||
Based on vehicle type some channels are present or not.
|
||||
Conventional fuel vehicles don't provide _Electric Range_ and battery electric vehicles don't show _Fuel Range_.
|
||||
Hybrid vehicles have both and in addition _Hybrid Range_.
|
||||
See description [Range vs Range Radius](#range-vs-range-radius) to get more information.
|
||||
|
||||
- Channel Group ID is **range**
|
||||
- Availability according to table
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description | conv | phev | bev_rex | bev |
|
||||
|----------------------------|----------------------|-----------------------------------------------|------|------|---------|-----|
|
||||
| mileage | Number:Length | Current mileage of the vehicle | X | X | X | X |
|
||||
| range-fuel | Number:Length | Fuel range | X | X | X | |
|
||||
| range-electric | Number:Length | Electric range | | X | X | X |
|
||||
| range-hybrid | Number:Length | Combined hybrid range | | X | X | |
|
||||
| soc | Number:Dimensionless | State of charge | | X | X | X |
|
||||
| remaining-fuel | Number:Volume | Remaining fuel in l | X | X | X | |
|
||||
| estimated-fuel-l-100km | Number | Estimated fuel consumption in l | X | X | X | |
|
||||
| estimated-fuel-mpg | Number | Estimated fuel consumption in mpg | X | X | X | |
|
||||
| range-radius-fuel | Number:Length | The calculated range radius combustion | X | X | X | |
|
||||
| range-radius-electric | Number:Length | The calculated range radius electric | | X | X | X |
|
||||
| range-radius-hybrid | Number:Length | The calculated range radius hybrid combined | | X | X | |
|
||||
|
||||
#### Doors Details
|
||||
|
||||
Detailed status of all doors and windows.
|
||||
|
||||
- Channel Group ID is **doors**
|
||||
- Available for all vehicles if corresponding sensors are built-in
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|-------------------------|---------------|-----------------------------------------|
|
||||
| driver-front | String | Status of front door driver's side |
|
||||
| driver-rear | String | Status of rear door driver's side |
|
||||
| passenger-front | String | Status of front door passenger's side |
|
||||
| passenger-rear | String | Status of rear door passenger's side |
|
||||
| trunk | String | Status of trunk |
|
||||
| hood | String | Status of hood |
|
||||
| win-driver-front | String | Status of front window driver's side |
|
||||
| win-driver-rear | String | Status of rear window driver's side |
|
||||
| win-passenger-front | String | Status of front window passenger's side |
|
||||
| win-passenger-rear | String | Status of rear window passenger's side |
|
||||
| win-rear | String | Status of rear window |
|
||||
| sunroof | String | Status of sunroof |
|
||||
|
||||
Possible states
|
||||
|
||||
- _Undef_ - no status data available
|
||||
- _Invalid_ - this door / window isn't applicable for this vehicle
|
||||
- _Closed_ - the door / window is closed
|
||||
- _Open_ - the door / window is open
|
||||
- _Intermediate_ - window in intermediate position, not applicable for doors
|
||||
|
||||
#### Check Control
|
||||
|
||||
Group for all current active Check Control messages.
|
||||
If more than one message is active the channel _name_ contains all active messages as options.
|
||||
|
||||
- Channel Group ID is **check**
|
||||
- Available for all vehicles
|
||||
- Read/Write access
|
||||
|
||||
| Channel ID | Type | Access |
|
||||
|---------------------|----------------|------------|
|
||||
| name | String | Read/Write |
|
||||
| details | String | Read |
|
||||
| severity | String | Read |
|
||||
|
||||
Severity Levels
|
||||
|
||||
- Ok
|
||||
- Low
|
||||
- Medium
|
||||
|
||||
#### Services
|
||||
|
||||
Group for all upcoming services with description, service date and/or service mileage.
|
||||
If more than one service is scheduled in the future the channel _name_ contains all future services as options.
|
||||
|
||||
- Channel Group ID is **service**
|
||||
- Available for all vehicles
|
||||
- Read/Write access
|
||||
|
||||
| Channel ID | Type | Access |
|
||||
|---------------------|----------------|------------|
|
||||
| name | String | Read/Write |
|
||||
| details | String | Read |
|
||||
| date | DateTime | Read |
|
||||
| mileage | Number:Length | Read |
|
||||
|
||||
#### Location
|
||||
|
||||
GPS location and heading of the vehicle.
|
||||
|
||||
- Channel Group ID is **location**
|
||||
- Available for all vehicles with built-in GPS sensor. Function can be enabled/disabled in the head unit
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|---------------------|---------------|--------------------------------------------------------------|
|
||||
| gps | Location | Current GPS coordinates of the vehicle |
|
||||
| heading | Number:Angle | Current direction of the vehicle |
|
||||
| address | String | Current address |
|
||||
| home-distance | Number:Length | Calculated distance from configured home position of openHAB |
|
||||
|
||||
#### Remote Services
|
||||
|
||||
Remote control of the vehicle.
|
||||
Send a _command_ to the vehicle and the _state_ is reporting the execution progress.
|
||||
Only one command can be executed each time.
|
||||
Parallel execution isn't supported.
|
||||
|
||||
- Channel Group ID is **remote**
|
||||
- Available for all commands mentioned in _Services Activated_. See [Vehicle Properties](#properties) for further details
|
||||
- Read/Write access
|
||||
|
||||
| Channel ID | Type | Access |
|
||||
|---------------------|---------|--------|
|
||||
| command | String | Write |
|
||||
| state | String | Read |
|
||||
|
||||
The channel _command_ provides options
|
||||
|
||||
- _light-flash_
|
||||
- _vehicle-finder_
|
||||
- _door-lock_
|
||||
- _door-unlock_
|
||||
- _horn-blow_
|
||||
- _climate-now-start_
|
||||
- _climate-now-stop_
|
||||
- _start-charging_
|
||||
- _stop-charging_
|
||||
|
||||
The channel _state_ shows the progress of the command execution in the following order
|
||||
|
||||
1. _initiated_
|
||||
1. _pending_
|
||||
1. _delivered_
|
||||
1. _executed_
|
||||
|
||||
#### Charge Profile
|
||||
|
||||
Charging options with date and time for preferred time windows and charging modes.
|
||||
|
||||
- Channel Group ID is **profile**
|
||||
- Available for electric and hybrid vehicles
|
||||
- Read access for UI.
|
||||
- There are 4 timers _T1, T2, T3 and T4_ available. Replace _X_ with number 1,2 or 3 to target the correct timer
|
||||
|
||||
| Channel ID | Type |
|
||||
|---------------------------|----------|
|
||||
| mode | String |
|
||||
| prefs | String |
|
||||
| control | String |
|
||||
| target | String |
|
||||
| limit | Switch |
|
||||
| window-start | DateTime |
|
||||
| window-end | DateTime |
|
||||
| climate | Switch |
|
||||
| timer_X_-enabled | Switch |
|
||||
| timer_X_-departure | DateTime |
|
||||
| timer_X_-day-mon | Switch |
|
||||
| timer_X_-day-tue | Switch |
|
||||
| timer_X_-day-wed | Switch |
|
||||
| timer_X_-day-thu | Switch |
|
||||
| timer_X_-day-fri | Switch |
|
||||
| timer_X_-day-sat | Switch |
|
||||
| timer_X_-day-sun | Switch |
|
||||
|
||||
The channel _profile-mode_ supports
|
||||
|
||||
- _immediateCharging_
|
||||
- _delayedCharging_
|
||||
|
||||
The channel _profile-prefs_ supports
|
||||
|
||||
- _noPreSelection_
|
||||
- _chargingWindow_
|
||||
|
||||
#### Charge Statistics
|
||||
|
||||
Shows charge statistics of the current month
|
||||
|
||||
- Channel Group ID is **statistic**
|
||||
- Available for electric and hybrid vehicles
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|-------------------------|----------------|-------------------------|
|
||||
| title | String | Title of the statistics |
|
||||
| energy | Number:Energy | Consumed energy |
|
||||
| sessions | Number | Number of sessions |
|
||||
|
||||
#### Charge Sessions
|
||||
|
||||
Group for past charging sessions.
|
||||
If more than one message is active the channel _name_ contains all active messages as options.
|
||||
|
||||
- Channel Group ID is **session**
|
||||
- Available for electric and hybrid vehicles
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|--------------|----------|-------------------------|
|
||||
| title | String | Title of the session |
|
||||
| subtitle | String | Subtitle of the session |
|
||||
| energy | String | Consumed energy |
|
||||
| issue | String | If an issue occurred |
|
||||
| status | String | Status of the session |
|
||||
|
||||
#### Tire Pressure
|
||||
|
||||
Current and target tire pressure values
|
||||
|
||||
- Channel Group ID is **tires**
|
||||
- Available for all vehicles if corresponding sensors are built-in
|
||||
- Read-only values
|
||||
|
||||
| Channel ID | Type | Description |
|
||||
|-------------------------|------------------|------------------------------|
|
||||
| fl-current | Number:Pressure | Current pressure front left |
|
||||
| fl-target | Number:Pressure | Target pressure front left |
|
||||
| fr-current | Number:Pressure | Current pressure front right |
|
||||
| fr-target | Number:Pressure | Target pressure front right |
|
||||
| rl-current | Number:Pressure | Current pressure rear left |
|
||||
| rl-target | Number:Pressure | Target pressure rear left |
|
||||
| rr-current | Number:Pressure | Current pressure rear right |
|
||||
| rr-target | Number:Pressure | Target pressure rear right |
|
||||
|
||||
#### Image
|
||||
|
||||
Image representation of the vehicle.
|
||||
|
||||
- Channel Group ID is **image**
|
||||
- Available for all vehicles
|
||||
- Read/Write access
|
||||
|
||||
| Channel ID | Type | Access | Description |
|
||||
|---------------------|--------|----------|---------------------------|
|
||||
| png | Image | Read | The image as png |
|
||||
| view | String | Write | The view port of the car |
|
||||
|
||||
Possible view ports:
|
||||
|
||||
- _VehicleStatus_ Front Left Side View
|
||||
- _FrontView_ Front View
|
||||
- _FrontLeft_ Front Left Side View
|
||||
- _FrontRight_ Front Right Side View
|
||||
- _RearView_ Rear View
|
||||
|
||||
## Further Descriptions
|
||||
|
||||
### Dynamic Data
|
||||
|
||||
<img align="right" src="doc/SessionOptions.png" alt="Session options example" width="400" height="250"/>
|
||||
|
||||
There are 3 occurrences of dynamic data delivered
|
||||
|
||||
- Upcoming Services delivered in group [Services](#services)
|
||||
- Check Control Messages delivered in group [Check Control](#check-control)
|
||||
- Charging Session data delivered in group [Charge Sessions](#charge-sessions)
|
||||
|
||||
The channel id _name_ shows the first element as default.
|
||||
All other possibilities are attached as options.
|
||||
The picture on the right shows the _Session Title_ item and 3 possible options.
|
||||
Select the desired service and the corresponding Charge Session with _Energy Charged_, _Session Status_ and
|
||||
_Session Issues_ will be shown.
|
||||
|
||||
### TroubleShooting
|
||||
|
||||
BMW has a high range of vehicles supported by their API.
|
||||
In case of any issues with this binding help to resolve it!
|
||||
Please perform the following steps:
|
||||
|
||||
- Can you log into MyBMW App with your credentials?
|
||||
- Is the vehicle listed in your account?
|
||||
- Is the [MyBMW Brige](#bridge) status _Online_?
|
||||
|
||||
If these preconditions are fulfilled proceed with the fingerprint generation.
|
||||
|
||||
#### Generate Debug Fingerprint
|
||||
|
||||
Login to the openHAB console and use the `mybmw fingerprint` command.
|
||||
|
||||
Fingerprint information on your account and vehicle(s) will show in the console and can be copiedfrom there.
|
||||
A zip file with fingerprint information for your vehicle(s) will also be generated and put into the `mybmw` folder in the userdata folder.
|
||||
This fingerprint information is valuable for the developers to better support your vehicle.
|
||||
|
||||
You can restrict the accounts and vehicles for the fingerprint generation.
|
||||
Full syntax is available through the `mybmw help` console command.
|
||||
|
||||
Personal data is eliminated from fingerprints so it should be possible to share them in public.
|
||||
Data like
|
||||
|
||||
- Vehicle Identification Number (VIN)
|
||||
- Location data
|
||||
|
||||
are anonymized in the JSON response and URL's.
|
||||
|
||||
After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint!
|
||||
|
||||
Your feedback is highly appreciated!
|
||||
|
||||
#### Debug Logging
|
||||
|
||||
You can [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) to get more information on the behaviour of the binding.
|
||||
The package.subpackage in this case would be "org.openhab.binding.mybmw".
|
||||
|
||||
As with fingerprint data, personal data is eliminated from logs.
|
||||
|
||||
### Range vs Range Radius
|
||||
|
||||
<img align="right" src="doc/range-radius.png" alt="Range radius example" width="400" height="350"/>
|
||||
|
||||
You will observe differences in the vehicle range and range radius values.
|
||||
While range is indicating the possible distance to be driven on roads the range radius indicates the reachable range on the map.
|
||||
|
||||
The right picture shows the distance between Kassel and Frankfurt in Germany.
|
||||
While the air-line distance is 145 kilometers the route distance is 192 kilometers.
|
||||
So range value is the normal remaining range while the range radius values can be used e.g. on [Mapview](https://www.openhab.org/docs/ui/sitemaps.html#element-type-mapview) to indicate the reachable range on map.
|
||||
Please note this is just an indicator of the effective range.
|
||||
Especially for electric vehicles it depends on many factors like driving style and usage of electric consumers.
|
||||
|
||||
## Full Example
|
||||
|
||||
The example is based on a BMW i3 with range extender (REX).
|
||||
Exchange configuration parameters in the Things section
|
||||
|
||||
- 4711 - any id you want
|
||||
- YOUR_USERNAME - with your MyBMW login username
|
||||
- YOUR_PASSWORD - with your MyBMW password credentials
|
||||
- VEHICLE_VIN - the vehicle identification number
|
||||
|
||||
In addition search for all occurrences of _i3_ and replace it with your Vehicle Identification like _x3_ or _535d_ and you're ready to go!
|
||||
|
||||
### Things File
|
||||
|
||||
```java
|
||||
Bridge mybmw:account:4711 "MyBMW Account" [userName="YOUR_USERNAME",password="YOUR_PASSWORD",region="ROW"] {
|
||||
Thing bev_rex i3 "BMW i3 94h REX" [ vin="VEHICLE_VIN",refreshInterval=5,vehicleBrand="BMW"]
|
||||
}
|
||||
```
|
||||
|
||||
### Items File
|
||||
|
||||
```java
|
||||
Number:Length i3Mileage "Odometer [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:range#mileage" }
|
||||
Number:Length i3Range "Range [%d %unit%]" <motion> (i3) {channel="mybmw:bev_rex:4711:i3:range#hybrid"}
|
||||
Number:Length i3RangeElectric "Electric Range [%d %unit%]" <motion> (i3,long) {channel="mybmw:bev_rex:4711:i3:range#electric"}
|
||||
Number:Length i3RangeFuel "Fuel Range [%d %unit%]" <motion> (i3) {channel="mybmw:bev_rex:4711:i3:range#fuel"}
|
||||
Number:Dimensionless i3BatterySoc "Battery Charge [%.1f %%]" <battery> (i3,long) {channel="mybmw:bev_rex:4711:i3:range#soc"}
|
||||
Number:Volume i3Fuel "Fuel [%.1f %unit%]" <oil> (i3) {channel="mybmw:bev_rex:4711:i3:range#remaining-fuel"}
|
||||
Number:Length i3RadiusElectric "Electric Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-electric" }
|
||||
Number:Length i3RadiusFuel "Fuel Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-fuel" }
|
||||
Number:Length i3RadiusHybrid "Hybrid Radius [%d %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:range#radius-hybrid" }
|
||||
|
||||
String i3DoorStatus "Door Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#doors" }
|
||||
String i3WindowStatus "Window Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#windows" }
|
||||
String i3LockStatus "Lock Status [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:status#lock" }
|
||||
DateTime i3NextServiceDate "Next Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:status#service-date" }
|
||||
String i3NextServiceMileage "Next Service Mileage [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:status#service-mileage" }
|
||||
String i3CheckControl "Check Control [%s]" <error> (i3) {channel="mybmw:bev_rex:4711:i3:status#check-control" }
|
||||
String i3PlugConnection "Plug [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#plug-connection" }
|
||||
String i3ChargingStatus "[%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#charge" }
|
||||
String i3ChargingInfo "[%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:status#charge-info" }
|
||||
DateTime i3LastUpdate "Update [%1$tA, %1$td.%1$tm. %1$tH:%1$tM]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:status#last-update"}
|
||||
|
||||
Location i3Location "Location [%s]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:location#gps" }
|
||||
Number:Angle i3Heading "Heading [%.1f %unit%]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:location#heading" }
|
||||
|
||||
String i3RemoteCommand "Command [%s]" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:remote#command" }
|
||||
String i3RemoteState "Remote Execution State [%s]" <status> (i3) {channel="mybmw:bev_rex:4711:i3:remote#state" }
|
||||
|
||||
String i3DriverDoor "Driver Door [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#driver-front" }
|
||||
String i3DriverDoorRear "Driver Door Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#driver-rear" }
|
||||
String i3PassengerDoor "Passenger Door [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#passenger-front" }
|
||||
String i3PassengerDoorRear "Passenger Door Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#passenger-rear" }
|
||||
String i3Hood "Hood [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#hood" }
|
||||
String i3Trunk "Trunk [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#trunk" }
|
||||
String i3DriverWindow "Driver Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-driver-front" }
|
||||
String i3DriverWindowRear "Driver Window Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-driver-rear" }
|
||||
String i3PassengerWindow "Passenger Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-passenger-front" }
|
||||
String i3PassengerWindowRear "Passenger Window Rear [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-passenger-rear" }
|
||||
String i3RearWindow "Rear Window [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#win-rear" }
|
||||
String i3Sunroof "Sunroof [%s]" <lock> (i3) {channel="mybmw:bev_rex:4711:i3:doors#sunroof" }
|
||||
|
||||
String i3ServiceName "Service Name [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:service#name" }
|
||||
String i3ServiceDetails "Service Details [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:service#details" }
|
||||
Number:Length i3ServiceMileage "Service Mileage [%d %unit%]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:service#mileage" }
|
||||
DateTime i3ServiceDate "Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:service#date" }
|
||||
|
||||
String i3CCName "CheckControl Name [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:check#name" }
|
||||
String i3CCDetails "CheckControl Details [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:check#details" }
|
||||
String i3CCSeverity "CheckControl Severity [%s]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:check#severity" }
|
||||
|
||||
Switch i3ChargeProfileClimate "Charge Profile Climatization" <temperature> (i3) {channel="mybmw:bev_rex:4711:i3:profile#climate" }
|
||||
String i3ChargeProfileMode "Charge Profile Mode [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#mode" }
|
||||
String i3ChargeProfilePrefs "Charge Profile Preference [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#prefs" }
|
||||
String i3ChargeProfileCtrl "Charge Profile Control [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#control" }
|
||||
Number i3ChargeProfileTarget "Charge Profile SoC Target [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#target" }
|
||||
Switch i3ChargeProfileLimit "Charge Profile limited" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:profile#limit" }
|
||||
DateTime i3ChargeWindowStart "Charge Window Start [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#window-start" }
|
||||
DateTime i3ChargeWindowEnd "Charge Window End [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#window-end" }
|
||||
DateTime i3Timer1Departure "Timer 1 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-departure" }
|
||||
String i3Timer1Days "Timer 1 Days [%s]" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-days" }
|
||||
Switch i3Timer1DayMon "Timer 1 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-mon" }
|
||||
Switch i3Timer1DayTue "Timer 1 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-tue" }
|
||||
Switch i3Timer1DayWed "Timer 1 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-wed" }
|
||||
Switch i3Timer1DayThu "Timer 1 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-thu" }
|
||||
Switch i3Timer1DayFri "Timer 1 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-fri" }
|
||||
Switch i3Timer1DaySat "Timer 1 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-sat" }
|
||||
Switch i3Timer1DaySun "Timer 1 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-day-sun" }
|
||||
Switch i3Timer1Enabled "Timer 1 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer1-enabled" }
|
||||
DateTime i3Timer2Departure "Timer 2 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-departure" }
|
||||
Switch i3Timer2DayMon "Timer 2 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-mon" }
|
||||
Switch i3Timer2DayTue "Timer 2 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-tue" }
|
||||
Switch i3Timer2DayWed "Timer 2 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-wed" }
|
||||
Switch i3Timer2DayThu "Timer 2 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-thu" }
|
||||
Switch i3Timer2DayFri "Timer 2 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-fri" }
|
||||
Switch i3Timer2DaySat "Timer 2 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-sat" }
|
||||
Switch i3Timer2DaySun "Timer 2 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-day-sun" }
|
||||
Switch i3Timer2Enabled "Timer 2 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer2-enabled" }
|
||||
DateTime i3Timer3Departure "Timer 3 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-departure" }
|
||||
Switch i3Timer3DayMon "Timer 3 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-mon" }
|
||||
Switch i3Timer3DayTue "Timer 3 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-tue" }
|
||||
Switch i3Timer3DayWed "Timer 3 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-wed" }
|
||||
Switch i3Timer3DayThu "Timer 3 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-thu" }
|
||||
Switch i3Timer3DayFri "Timer 3 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-fri" }
|
||||
Switch i3Timer3DaySat "Timer 3 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-sat" }
|
||||
Switch i3Timer3DaySun "Timer 3 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-day-sun" }
|
||||
Switch i3Timer3Enabled "Timer 3 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer3-enabled" }
|
||||
DateTime i3Timer4Departure "Timer 4 Departure [%1$tH:%1$tM]" <time> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-departure" }
|
||||
Switch i3Timer4DayMon "Timer 4 Monday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-mon" }
|
||||
Switch i3Timer4DayTue "Timer 4 Tuesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-tue" }
|
||||
Switch i3Timer4DayWed "Timer 4 Wednesday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-wed" }
|
||||
Switch i3Timer4DayThu "Timer 4 Thursday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-thu" }
|
||||
Switch i3Timer4DayFri "Timer 4 Friday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-fri" }
|
||||
Switch i3Timer4DaySat "Timer 4 Saturday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-sat" }
|
||||
Switch i3Timer4DaySun "Timer 4 Sunday" <calendar> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-day-sun" }
|
||||
Switch i3Timer4Enabled "Timer 4 Enabled" <switch> (i3) {channel="mybmw:bev_rex:4711:i3:profile#timer4-enabled" }
|
||||
|
||||
String i3StatisticsTitle "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#title" }
|
||||
Number:Energy i3StatisticsEnergy "Charged [%d %unit%]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#energy" }
|
||||
Number i3StatisticsSessions "Sessions [%d]" <line> (i3) {channel="mybmw:bev_rex:4711:i3:statistic#sessions" }
|
||||
|
||||
String i3SessionTitle "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#title" }
|
||||
String i3SessionDetails "[%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#subtitle" }
|
||||
String i3SessionCharged "Energy Charged [%s]" <energy> (i3) {channel="mybmw:bev_rex:4711:i3:session#energy" }
|
||||
String i3SessionProblems "Problems [%s]" <error> (i3) {channel="mybmw:bev_rex:4711:i3:session#issue" }
|
||||
String i3SessionStatus "Session status [%s]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:session#status" }
|
||||
|
||||
Number:Pressure i3TireFLCurrent "Tire Front Left [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fl-current" }
|
||||
Number:Pressure i3TireFLTarget "Tire Front Left Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fl-target" }
|
||||
Number:Pressure i3TireFRCurrent "Tire Front Right [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fr-current" }
|
||||
Number:Pressure i3TireFRTarget "Tire Front Right Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#fr-target" }
|
||||
Number:Pressure i3TireRLCurrent "Tire Rear Left [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rl-current" }
|
||||
Number:Pressure i3TireRLTarget "Tire Rear Left Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rl-target" }
|
||||
Number:Pressure i3TireRRCurrent "Tire Rear Right [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rr-current" }
|
||||
Number:Pressure i3TireRRTarget "Tire Rear Right Target [%.1f %unit%]" <text> (i3) {channel="mybmw:bev_rex:4711:i3:tires#rr-target" }
|
||||
|
||||
Image i3Image "Image" (i3) {channel="mybmw:bev_rex:4711:i3:image#png" }
|
||||
String i3ImageViewport "Image Viewport [%s]" <zoom> (i3) {channel="mybmw:bev_rex:4711:i3:image#view" }
|
||||
```
|
||||
|
||||
### Sitemap File
|
||||
|
||||
```perl
|
||||
sitemap BMW label="BMW" {
|
||||
Frame label="BMW i3" {
|
||||
Image item=i3Image
|
||||
|
||||
}
|
||||
Frame label="Status" {
|
||||
Text item=i3DoorStatus
|
||||
Text item=i3WindowStatus
|
||||
Text item=i3LockStatus
|
||||
Text item=i3NextServiceDate
|
||||
Text item=i3NextServiceMileage
|
||||
Text item=i3CheckControl
|
||||
Text item=i3ChargingStatus
|
||||
Text item=i3LastUpdate
|
||||
}
|
||||
Frame label="Range" {
|
||||
Text item=i3Mileage
|
||||
Text item=i3Range
|
||||
Text item=i3RangeElectric
|
||||
Text item=i3RangeFuel
|
||||
Text item=i3BatterySoc
|
||||
Text item=i3Fuel
|
||||
Text item=i3RadiusElectric
|
||||
Text item=i3RadiusHybrid
|
||||
}
|
||||
Frame label="Remote Services" {
|
||||
Selection item=i3RemoteCommand
|
||||
Text item=i3RemoteState
|
||||
}
|
||||
Frame label="Services" {
|
||||
Selection item=i3ServiceName
|
||||
Text item=i3ServiceDetails
|
||||
Text item=i3ServiceMileage
|
||||
Text item=i3ServiceDate
|
||||
}
|
||||
Frame label="CheckControl" {
|
||||
Selection item=i3CCName
|
||||
Text item=i3CCDetails
|
||||
Text item=i3CCSeverity
|
||||
}
|
||||
Frame label="Door Details" {
|
||||
Text item=i3DriverDoor visibility=[i3DriverDoor!="INVALID"]
|
||||
Text item=i3DriverDoorRear visibility=[i3DriverDoorRear!="INVALID"]
|
||||
Text item=i3PassengerDoor visibility=[i3PassengerDoor!="INVALID"]
|
||||
Text item=i3PassengerDoorRear visibility=[i3PassengerDoorRear!="INVALID"]
|
||||
Text item=i3Hood visibility=[i3Hood!="INVALID"]
|
||||
Text item=i3Trunk visibility=[i3Trunk!="INVALID"]
|
||||
Text item=i3DriverWindow visibility=[i3DriverWindow!="INVALID"]
|
||||
Text item=i3DriverWindowRear visibility=[i3DriverWindowRear!="INVALID"]
|
||||
Text item=i3PassengerWindow visibility=[i3PassengerWindow!="INVALID"]
|
||||
Text item=i3PassengerWindowRear visibility=[i3PassengerWindowRear!="INVALID"]
|
||||
Text item=i3RearWindow visibility=[i3RearWindow!="INVALID"]
|
||||
Text item=i3Sunroof visibility=[i3Sunroof!="INVALID"]
|
||||
}
|
||||
Frame label="Location" {
|
||||
Text item=i3Location
|
||||
Text item=i3Heading
|
||||
}
|
||||
Frame label="Charge Profile" {
|
||||
Switch item=i3ChargeProfileClimate
|
||||
Selection item=i3ChargeProfileMode
|
||||
Text item=i3ChargeWindowStart
|
||||
Text item=i3ChargeWindowEnd
|
||||
Text item=i3Timer1Departure
|
||||
Switch item=i3Timer1DayMon
|
||||
Switch item=i3Timer1DayTue
|
||||
Switch item=i3Timer1DayWed
|
||||
Switch item=i3Timer1DayThu
|
||||
Switch item=i3Timer1DayFri
|
||||
Switch item=i3Timer1DaySat
|
||||
Switch item=i3Timer1DaySun
|
||||
Switch item=i3Timer1Enabled
|
||||
Text item=i3Timer2Departure
|
||||
Switch item=i3Timer2DayMon
|
||||
Switch item=i3Timer2DayTue
|
||||
Switch item=i3Timer2DayWed
|
||||
Switch item=i3Timer2DayThu
|
||||
Switch item=i3Timer2DayFri
|
||||
Switch item=i3Timer2DaySat
|
||||
Switch item=i3Timer2DaySun
|
||||
Switch item=i3Timer2Enabled
|
||||
Text item=i3Timer3Departure
|
||||
Switch item=i3Timer3DayMon
|
||||
Switch item=i3Timer3DayTue
|
||||
Switch item=i3Timer3DayWed
|
||||
Switch item=i3Timer3DayThu
|
||||
Switch item=i3Timer3DayFri
|
||||
Switch item=i3Timer3DaySat
|
||||
Switch item=i3Timer3DaySun
|
||||
Switch item=i3Timer3Enabled
|
||||
Text item=i3Timer4Departure
|
||||
Switch item=i3Timer4DayMon
|
||||
Switch item=i3Timer4DayTue
|
||||
Switch item=i3Timer4DayWed
|
||||
Switch item=i3Timer4DayThu
|
||||
Switch item=i3Timer4DayFri
|
||||
Switch item=i3Timer4DaySat
|
||||
Switch item=i3Timer4DaySun
|
||||
Switch item=i3Timer4Enabled
|
||||
}
|
||||
Frame label="Charge Statistics" {
|
||||
Text item=i3StatisticsTitle
|
||||
Text item=i3StatisticsEnergy
|
||||
Text item=i3StatisticsSessions
|
||||
}
|
||||
|
||||
Frame label="Charge Sessions" {
|
||||
Selection item=i3SessionTitle
|
||||
Text item=i3SessionDetails
|
||||
Text item=i3SessionCharged
|
||||
Text item=i3SessionProblems
|
||||
Text item=i3SessionStatus
|
||||
}
|
||||
Frame label="Tires" {
|
||||
Text item=i3TireFLCurrent
|
||||
Text item=i3TireFLTarget
|
||||
Text item=i3TireFRCurrent
|
||||
Text item=i3TireFRTarget
|
||||
Text item=i3TireRLCurrent
|
||||
Text item=i3TireRLTarget
|
||||
Text item=i3TireRRCurrent
|
||||
Text item=i3TireRRTarget
|
||||
}
|
||||
Frame label="Image Properties" {
|
||||
Selection item=i3ImageViewport
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
This work is based on the great work of the project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
|
||||
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 460 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 337 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 740 KiB |
|
Before Width: | Height: | Size: 97 KiB |
@@ -1,182 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.mybmw</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: MyBMW Binding</name>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>test-coverage</id>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<!-- must be on the classpath -->
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>org.jacoco.agent</artifactId>
|
||||
<version>0.8.14</version>
|
||||
<classifier>runtime</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<jacoco-agent.destfile>target/jacoco.exec</jacoco-agent.destfile>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.14</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default-instrument</id>
|
||||
<goals>
|
||||
<goal>instrument</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-restore-instrumented-classes</id>
|
||||
<goals>
|
||||
<goal>restore-instrumented-classes</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-report</id>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<phase>test</phase>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>default-check</id>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<rule>
|
||||
<element>BUNDLE</element>
|
||||
<limits>
|
||||
<limit>
|
||||
<counter>INSTRUCTION</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.20</minimum>
|
||||
</limit>
|
||||
<limit>
|
||||
<counter>BRANCH</counter>
|
||||
<value>COVEREDRATIO</value>
|
||||
<minimum>0.20</minimum>
|
||||
</limit>
|
||||
</limits>
|
||||
</rule>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!--
|
||||
If you activate this profile, the MyBmwProxyIT is executed which means real
|
||||
backend requests. The test is only successful if you provide CONNECTED_USER and
|
||||
CONNECTED_PASSWORD as environment variable of the Maven command.
|
||||
-->
|
||||
<id>integration-tests</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.5.5</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<!--
|
||||
This profile generates a jar file <regular-jar-file-name>-testenv.jar in the target folder. This
|
||||
testenv jar contains the regular classes and in addition all responses from
|
||||
src/test/resources. If you copy this jar file to your addons folder, you can simulate all
|
||||
accounts which are available as fingerprints in the responses folder. This can be done like
|
||||
this:
|
||||
1. start openhab with the environment variable "ENVIRONMENT=test"
|
||||
|
||||
2. configure the connected account with username "testuser"
|
||||
|
||||
3. configure as connected password the folder which you want to test, e.g. "BEV", "BEV2", "PHEV", "ICE", "ICE2",
|
||||
"MILD_HYBRID"
|
||||
|
||||
after that you should get the vehicles loaded properly so you can check if the channels are populated with data properly.
|
||||
-->
|
||||
<id>test-jar</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<!-- here the phase you need -->
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<outputDirectory>${basedir}/target/classes</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/test/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<configuration>
|
||||
<classifier>testenv</classifier>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.mybmw-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${project.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-mybmw" description="MyBMW Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mybmw/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - renamed and added hcaptchastring
|
||||
* @author Mark Herwege - added authorisation servlet
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWBridgeConfiguration {
|
||||
|
||||
/**
|
||||
* Depending on the location the correct server needs to be called
|
||||
*/
|
||||
private String region = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* MyBMW App Username
|
||||
*/
|
||||
private String userName = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* MyBMW App Password
|
||||
*/
|
||||
private String password = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Preferred Locale language
|
||||
*/
|
||||
private String language = Constants.LANGUAGE_AUTODETECT;
|
||||
|
||||
/**
|
||||
* the hCaptcha string
|
||||
*/
|
||||
private String hcaptchatoken = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* the callback IP address for the authorisation servlet
|
||||
*/
|
||||
private String callbackIP = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* the callback port for the authorisation servlet
|
||||
*/
|
||||
private int callbackPort = 8090;
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public void setRegion(String region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getHCaptchaToken() {
|
||||
return hcaptchatoken;
|
||||
}
|
||||
|
||||
public void setHCaptchaToken(String hcaptchatoken) {
|
||||
this.hcaptchatoken = hcaptchatoken;
|
||||
}
|
||||
|
||||
public String getCallbackIP() {
|
||||
return Constants.EMPTY.equals(callbackIP) ? "" : callbackIP;
|
||||
}
|
||||
|
||||
public void setCallbackIP(String callbackIP) {
|
||||
this.callbackIP = callbackIP;
|
||||
}
|
||||
|
||||
public int getCallbackPort() {
|
||||
return callbackPort;
|
||||
}
|
||||
|
||||
public void setCallbackPort(int callbackPort) {
|
||||
this.callbackPort = callbackPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MyBMWBridgeConfiguration [region=" + region + ", userName=" + userName + ", password=" + password
|
||||
+ ", language=" + language + ", hcaptchatoken=" + hcaptchatoken + ", callbackAddress=" + callbackIP
|
||||
+ ":" + callbackPort + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - updated enum values
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MyBMWConstants {
|
||||
|
||||
static final String BINDING_ID = "mybmw";
|
||||
|
||||
static final String VIN = "vin";
|
||||
|
||||
static final String REFRESH_INTERVAL = "refreshInterval";
|
||||
|
||||
static final String VEHICLE_BRAND = "vehicleBrand";
|
||||
|
||||
static final String REMOTE_SERVICES_DISABLED = "remoteServicesDisabled";
|
||||
|
||||
static final String REMOTE_SERVICES_ENABLED = "remoteServicesEnabled";
|
||||
|
||||
static final String SERVICES_DISABLED = "servicesDisabled";
|
||||
|
||||
static final String SERVICES_ENABLED = "servicesEnabled";
|
||||
|
||||
static final String SERVICES_UNSUPPORTED = "servicesUnsupported";
|
||||
|
||||
static final String SERVICES_SUPPORTED = "servicesSupported";
|
||||
|
||||
static final String VEHICLE_BODYTYPE = "vehicleBodytype";
|
||||
|
||||
static final String VEHICLE_CONSTRUCTION_YEAR = "vehicleConstructionYear";
|
||||
|
||||
static final String VEHICLE_DRIVE_TRAIN = "vehicleDriveTrain";
|
||||
|
||||
static final String VEHICLE_MODEL = "vehicleModel";
|
||||
|
||||
static final int DEFAULT_IMAGE_SIZE_PX = 1024;
|
||||
|
||||
static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 60;
|
||||
|
||||
// Captcha servlet constants
|
||||
static final String LOCAL_OPENHAB_BASE_PATH = "/" + BINDING_ID + "/";
|
||||
static final String CAPTCHA_URL_ROOT = "captcha/";
|
||||
static final String NORTH_AMERICA = "NORTH_AMERICA";
|
||||
static final String ROW = "ROW";
|
||||
static final Map<String, String> CAPTCHA_HTML = Map.of(NORTH_AMERICA, CAPTCHA_URL_ROOT + "north_america_form.html",
|
||||
ROW, CAPTCHA_URL_ROOT + "rest_of_world_form.html");
|
||||
|
||||
// Thing status detail messages
|
||||
static final String STATUS_AUTH_NEEDED = "@text/mybmw.status.authorization-needed";
|
||||
static final String STATUS_USER_DETAILS_MISSING = "@text/mybmw.status.user-details-missing";
|
||||
static final String STATUS_REGION_MISSING = "@text/mybmw.status.region-missing";
|
||||
static final String STATUS_IP_MISSING = "@text/mybmw.status.ip-missing";
|
||||
static final String STATUS_VEHICLE_RETRIEVAL_ERROR = "@text/mybmw.status.vehicle-retrieval-error";
|
||||
static final String STATUS_NETWORK_ERROR = "@text/mybmw.status.network-error";
|
||||
static final String STATUS_QUOTA_ERROR = "@text/mybmw.status.quota-error";
|
||||
|
||||
// See constants from bimmer-connected
|
||||
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
|
||||
enum VehicleType {
|
||||
CONVENTIONAL("conv"),
|
||||
PLUGIN_HYBRID("phev"),
|
||||
MILD_HYBRID("hybrid"),
|
||||
ELECTRIC_REX("bev_rex"),
|
||||
ELECTRIC("bev"),
|
||||
UNKNOWN("unknown");
|
||||
|
||||
private final String type;
|
||||
|
||||
VehicleType(String s) {
|
||||
type = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
enum ChargingMode {
|
||||
IMMEDIATE_CHARGING,
|
||||
DELAYED_CHARGING
|
||||
}
|
||||
|
||||
enum ChargingPreference {
|
||||
NO_PRESELECTION,
|
||||
CHARGING_WINDOW
|
||||
}
|
||||
|
||||
static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
|
||||
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||
static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
|
||||
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID, VehicleType.CONVENTIONAL.toString());
|
||||
static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID, VehicleType.PLUGIN_HYBRID.toString());
|
||||
static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC_REX.toString());
|
||||
static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
|
||||
static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT, THING_TYPE_CONV,
|
||||
THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
|
||||
|
||||
// Thing Group definitions
|
||||
static final String CHANNEL_GROUP_UPDATE = "update";
|
||||
static final String CHANNEL_GROUP_STATUS = "status";
|
||||
static final String CHANNEL_GROUP_SERVICE = "service";
|
||||
static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
|
||||
static final String CHANNEL_GROUP_DOORS = "doors";
|
||||
static final String CHANNEL_GROUP_RANGE = "range";
|
||||
static final String CHANNEL_GROUP_LOCATION = "location";
|
||||
static final String CHANNEL_GROUP_REMOTE = "remote";
|
||||
static final String CHANNEL_GROUP_CHARGE_PROFILE = "profile";
|
||||
static final String CHANNEL_GROUP_CHARGE_STATISTICS = "statistic";
|
||||
static final String CHANNEL_GROUP_CHARGE_SESSION = "session";
|
||||
static final String CHANNEL_GROUP_TIRES = "tires";
|
||||
static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
|
||||
|
||||
// types of updates
|
||||
static final String STATE_UPDATE = "state-update";
|
||||
static final String CHARGING_UPDATE = "charging-update";
|
||||
static final String IMAGE_UPDATE = "image-update";
|
||||
|
||||
// Charge Statistics & Sessions
|
||||
static final String SESSIONS = "sessions";
|
||||
static final String ENERGY = "energy";
|
||||
static final String TITLE = "title";
|
||||
static final String SUBTITLE = "subtitle";
|
||||
static final String ISSUE = "issue";
|
||||
static final String STATUS = "status";
|
||||
|
||||
// Generic Constants for several groups
|
||||
static final String NAME = "name";
|
||||
static final String DETAILS = "details";
|
||||
static final String SEVERITY = "severity";
|
||||
static final String DATE = "date";
|
||||
static final String MILEAGE = "mileage";
|
||||
static final String GPS = "gps";
|
||||
static final String HEADING = "heading";
|
||||
static final String ADDRESS = "address";
|
||||
static final String HOME_DISTANCE = "home-distance";
|
||||
|
||||
// Status
|
||||
static final String DOORS = "doors";
|
||||
static final String WINDOWS = "windows";
|
||||
static final String LOCK = "lock";
|
||||
static final String SERVICE_DATE = "service-date";
|
||||
static final String SERVICE_MILEAGE = "service-mileage";
|
||||
static final String CHECK_CONTROL = "check-control";
|
||||
static final String PLUG_CONNECTION = "plug-connection";
|
||||
static final String CHARGE_STATUS = "charge";
|
||||
static final String CHARGE_REMAINING = "charge-remaining";
|
||||
static final String LAST_UPDATE = "last-update";
|
||||
static final String LAST_FETCHED = "last-fetched";
|
||||
static final String RAW = "raw";
|
||||
|
||||
// Door Details
|
||||
static final String DOOR_DRIVER_FRONT = "driver-front";
|
||||
static final String DOOR_DRIVER_REAR = "driver-rear";
|
||||
static final String DOOR_PASSENGER_FRONT = "passenger-front";
|
||||
static final String DOOR_PASSENGER_REAR = "passenger-rear";
|
||||
static final String HOOD = "hood";
|
||||
static final String TRUNK = "trunk";
|
||||
static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
|
||||
static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
|
||||
static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
|
||||
static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
|
||||
static final String WINDOW_REAR = "win-rear";
|
||||
static final String SUNROOF = "sunroof";
|
||||
|
||||
// Charge Profile
|
||||
static final String CHARGE_PROFILE_CLIMATE = "climate";
|
||||
static final String CHARGE_PROFILE_MODE = "mode";
|
||||
static final String CHARGE_PROFILE_PREFERENCE = "prefs";
|
||||
static final String CHARGE_PROFILE_CONTROL = "control";
|
||||
static final String CHARGE_PROFILE_TARGET = "target";
|
||||
static final String CHARGE_PROFILE_LIMIT = "limit";
|
||||
static final String CHARGE_WINDOW_START = "window-start";
|
||||
static final String CHARGE_WINDOW_END = "window-end";
|
||||
static final String CHARGE_TIMER1 = "timer1";
|
||||
static final String CHARGE_TIMER2 = "timer2";
|
||||
static final String CHARGE_TIMER3 = "timer3";
|
||||
static final String CHARGE_TIMER4 = "timer4";
|
||||
static final String CHARGE_DEPARTURE = "-departure";
|
||||
static final String CHARGE_ENABLED = "-enabled";
|
||||
static final String CHARGE_DAY_MON = "-day-mon";
|
||||
static final String CHARGE_DAY_TUE = "-day-tue";
|
||||
static final String CHARGE_DAY_WED = "-day-wed";
|
||||
static final String CHARGE_DAY_THU = "-day-thu";
|
||||
static final String CHARGE_DAY_FRI = "-day-fri";
|
||||
static final String CHARGE_DAY_SAT = "-day-sat";
|
||||
static final String CHARGE_DAY_SUN = "-day-sun";
|
||||
|
||||
// Range
|
||||
static final String RANGE_ELECTRIC = "electric";
|
||||
static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
|
||||
static final String RANGE_FUEL = "fuel";
|
||||
static final String RANGE_RADIUS_FUEL = "radius-fuel";
|
||||
static final String RANGE_HYBRID = "hybrid";
|
||||
static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
|
||||
static final String REMAINING_FUEL = "remaining-fuel";
|
||||
static final String ESTIMATED_FUEL_L_100KM = "estimated-fuel-l-100km";
|
||||
static final String ESTIMATED_FUEL_MPG = "estimated-fuel-mpg";
|
||||
static final String SOC = "soc";
|
||||
|
||||
// Image
|
||||
static final String IMAGE_FORMAT = "png";
|
||||
static final String IMAGE_VIEWPORT = "view";
|
||||
|
||||
// Remote Services
|
||||
static final String REMOTE_SERVICE_LIGHT_FLASH = "light-flash";
|
||||
static final String REMOTE_SERVICE_VEHICLE_FINDER = "vehicle-finder";
|
||||
static final String REMOTE_SERVICE_DOOR_LOCK = "door-lock";
|
||||
static final String REMOTE_SERVICE_DOOR_UNLOCK = "door-unlock";
|
||||
static final String REMOTE_SERVICE_HORN = "horn-blow";
|
||||
static final String REMOTE_SERVICE_AIR_CONDITIONING_START = "climate-now-start";
|
||||
static final String REMOTE_SERVICE_AIR_CONDITIONING_STOP = "climate-now-stop";
|
||||
static final String REMOTE_SERVICE_START_CHARGING = "start-charging";
|
||||
static final String REMOTE_SERVICE_STOP_CHARGING = "stop-charging";
|
||||
|
||||
static final String REMOTE_SERVICE_COMMAND = "command";
|
||||
static final String REMOTE_STATE = "state";
|
||||
|
||||
// TIRES
|
||||
static final String FRONT_LEFT_CURRENT = "fl-current";
|
||||
static final String FRONT_LEFT_TARGET = "fl-target";
|
||||
static final String FRONT_RIGHT_CURRENT = "fr-current";
|
||||
static final String FRONT_RIGHT_TARGET = "fr-target";
|
||||
static final String REAR_LEFT_CURRENT = "rl-current";
|
||||
static final String REAR_LEFT_TARGET = "rl-target";
|
||||
static final String REAR_RIGHT_CURRENT = "rr-current";
|
||||
static final String REAR_RIGHT_TARGET = "rr-target";
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
|
||||
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.osgi.service.http.HttpService;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - changed localeProvider handling
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
|
||||
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final HttpClientFactory httpClientFactory;
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private final NetworkAddressService networkAddressService;
|
||||
private final HttpService httpService;
|
||||
private final LocationProvider locationProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
|
||||
@Activate
|
||||
public MyBMWHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference OAuthFactory oAuthFactory,
|
||||
final @Reference MyBMWCommandOptionProvider commandOptionProvider,
|
||||
final @Reference NetworkAddressService networkAddressService, final @Reference HttpService httpService,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference LocationProvider locationProvider,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.httpClientFactory = httpClientFactory;
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.commandOptionProvider = commandOptionProvider;
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.httpService = httpService;
|
||||
this.locationProvider = locationProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_SET.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(thingTypeUID)) {
|
||||
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, oAuthFactory, httpService,
|
||||
networkAddressService, localeProvider);
|
||||
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
|
||||
return new VehicleHandler(thing, commandOptionProvider, locationProvider, timeZoneProvider,
|
||||
thingTypeUID.getId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWVehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - renaming and refactoring to Java Beans
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWVehicleConfiguration {
|
||||
/**
|
||||
* Vehicle Identification Number (VIN)
|
||||
*/
|
||||
private String vin = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Vehicle brand
|
||||
* - bmw
|
||||
* - bmw_i
|
||||
* - mini
|
||||
*/
|
||||
private String vehicleBrand = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Data refresh rate in minutes
|
||||
*/
|
||||
private int refreshInterval = MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
|
||||
|
||||
/**
|
||||
* @return the vin
|
||||
*/
|
||||
public String getVin() {
|
||||
return vin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vin the vin to set
|
||||
*/
|
||||
public void setVin(String vin) {
|
||||
this.vin = vin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vehicleBrand
|
||||
*/
|
||||
public String getVehicleBrand() {
|
||||
return vehicleBrand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param vehicleBrand the vehicleBrand to set
|
||||
*/
|
||||
public void setVehicleBrand(String vehicleBrand) {
|
||||
this.vehicleBrand = vehicleBrand;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the refreshInterval
|
||||
*/
|
||||
public int getRefreshInterval() {
|
||||
return refreshInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param refreshInterval the refreshInterval to set
|
||||
*/
|
||||
public void setRefreshInterval(int refreshInterval) {
|
||||
this.refreshInterval = refreshInterval;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MyBMWVehicleConfiguration [vin=" + vin + ", vehicleBrand=" + vehicleBrand + ", refreshInterval="
|
||||
+ refreshInterval + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.console;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.ResponseContentAnonymizer;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.core.io.console.Console;
|
||||
import org.openhab.core.io.console.ConsoleCommandCompleter;
|
||||
import org.openhab.core.io.console.StringsCompleter;
|
||||
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
|
||||
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWCommandExtension} is responsible for handling console commands
|
||||
*
|
||||
* @author Mark Herwege - Initial contribution
|
||||
* @author Martin Grassl - improved exception handling
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
@Component(service = ConsoleCommandExtension.class)
|
||||
public class MyBMWCommandExtension extends AbstractConsoleCommandExtension implements ConsoleCommandCompleter {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private static final String FINGERPRINT_ROOT_PATH = System.getProperty("user.home") + File.separator + BINDING_ID;
|
||||
|
||||
private static final String FINGERPRINT = "fingerprint";
|
||||
private static final StringsCompleter CMD_COMPLETER = new StringsCompleter(List.of(FINGERPRINT), false);
|
||||
|
||||
private final ThingRegistry thingRegistry;
|
||||
|
||||
@Activate
|
||||
public MyBMWCommandExtension(final @Reference ThingRegistry thingRegistry) {
|
||||
super("mybmw", "Interact with the MyBMW binding");
|
||||
this.thingRegistry = thingRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String[] args, Console console) {
|
||||
if ((args.length < 1) || (args.length > 3)) {
|
||||
console.println("Invalid number of arguments");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
|
||||
List<MyBMWBridgeHandler> bridgeHandlers = thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
|
||||
.map(b -> ((MyBMWBridgeHandler) b.getHandler())).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (bridgeHandlers.isEmpty()) {
|
||||
console.println("No account bridges configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FINGERPRINT.equalsIgnoreCase(args[0])) {
|
||||
console.println("Unsupported command '" + args[0] + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
|
||||
List<MyBMWBridgeHandler> handlers;
|
||||
if (args.length > 1) {
|
||||
handlers = bridgeHandlers.stream()
|
||||
.filter(b -> args[1].equalsIgnoreCase(b.getThing().getConfiguration().get("userName").toString()))
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
if (handlers.isEmpty()) {
|
||||
console.println("No myBMW account bridge for user '" + args[1] + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
handlers = bridgeHandlers;
|
||||
}
|
||||
|
||||
String basePath = FINGERPRINT_ROOT_PATH + File.separator
|
||||
+ LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE);
|
||||
String path = nextPath(basePath, null);
|
||||
|
||||
console.println("# Start fingerprint");
|
||||
int accountNdx = 0;
|
||||
for (MyBMWBridgeHandler handler : handlers) {
|
||||
accountNdx++;
|
||||
console.println("### Account " + String.valueOf(accountNdx));
|
||||
if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
|
||||
console.println("MyBMW bridge for account not online, cannot create fingerprint");
|
||||
} else {
|
||||
String accountPath = path + File.separator + "Account-" + String.valueOf(accountNdx);
|
||||
handler.getMyBmwProxy().ifPresentOrElse(prox -> {
|
||||
// get list of vehicles
|
||||
List<VehicleBase> vehicles = null;
|
||||
try {
|
||||
vehicles = prox.requestVehiclesBase();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
console.println("###### Vehicles base for brand " + brand);
|
||||
printAndSave(console, accountPath, "VehicleBase_" + brand,
|
||||
prox.requestVehiclesBaseJson(brand));
|
||||
}
|
||||
|
||||
if (args.length == 3) {
|
||||
Optional<VehicleBase> vehicleOptional = vehicles.stream()
|
||||
.filter(v -> v.getVin().equalsIgnoreCase(args[2])).findAny();
|
||||
if (vehicleOptional.isEmpty()) {
|
||||
console.println("'" + args[2] + "' is not a valid vin on the account bridge with id '"
|
||||
+ handler.getThing().getUID().getId() + "'");
|
||||
printUsage(console);
|
||||
return;
|
||||
}
|
||||
vehicles = List.of(vehicleOptional.get());
|
||||
}
|
||||
|
||||
int vinNdx = 0;
|
||||
for (VehicleBase vehicleBase : vehicles) {
|
||||
vinNdx++;
|
||||
String vinPath = accountPath + File.separator + "Vin-" + String.valueOf(vinNdx);
|
||||
console.println("###### Vehicle " + String.valueOf(vinNdx));
|
||||
|
||||
// get state
|
||||
console.println("######## Vehicle state");
|
||||
printAndSave(console, vinPath, "VehicleState", prox.requestVehicleStateJson(
|
||||
vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
// get charge statistics -> only successful for electric vehicles
|
||||
console.println("######### Vehicle charging statistics");
|
||||
printAndSave(console, vinPath, "VehicleChargingStatistics",
|
||||
prox.requestChargeStatisticsJson(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
// get charge sessions -> only successful for electric vehicles
|
||||
console.println("######### Vehicle charging sessions");
|
||||
printAndSave(console, vinPath, "VehicleChargingSessions", prox.requestChargeSessionsJson(
|
||||
vehicleBase.getVin(), vehicleBase.getAttributes().getBrand()));
|
||||
|
||||
console.println("###### End vehicle " + String.valueOf(vinNdx));
|
||||
}
|
||||
} catch (NetworkException e) {
|
||||
console.println("Fingerprint failed, network exception: " + e.getReason());
|
||||
}
|
||||
}, () -> {
|
||||
console.println("MyBMW bridge with id '" + handler.getThing().getUID().getId()
|
||||
+ "', communication not started, cannot retrieve fingerprint");
|
||||
});
|
||||
}
|
||||
console.println("### End account " + String.valueOf(accountNdx));
|
||||
}
|
||||
|
||||
try {
|
||||
String zipfile = nextPath(basePath, "zip");
|
||||
zipDirectory(Paths.get(path), Paths.get(zipfile));
|
||||
deleteDirectory(path);
|
||||
console.println("### Fingerprint has been written to zipfile: " + zipfile);
|
||||
} catch (IOException e) {
|
||||
console.println("Exception zipping fingerprint: " + e.getMessage());
|
||||
console.println("### Fingerprint has been written to files in directory: " + path);
|
||||
}
|
||||
|
||||
console.println("# End fingerprint");
|
||||
}
|
||||
|
||||
private void printAndSave(Console console, String path, String filename, String content) throws NetworkException {
|
||||
String json = prettyJson(ResponseContentAnonymizer.anonymizeResponseContent(content));
|
||||
console.println(json);
|
||||
try {
|
||||
writeJsonToFile(path, filename, json);
|
||||
} catch (IOException e) {
|
||||
console.println("Exception writing to file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String nextPath(String pathString, @Nullable String extension) {
|
||||
String path = pathString + ((extension != null) ? ("." + extension) : "");
|
||||
int pathNdx = 1;
|
||||
while (Files.exists(Paths.get(path))) {
|
||||
path = pathString + "_" + String.valueOf(pathNdx) + ((extension != null) ? ("." + extension) : "");
|
||||
pathNdx++;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String prettyJson(String json) {
|
||||
try {
|
||||
return GSON.toJson(JsonParser.parseString(json));
|
||||
} catch (JsonSyntaxException e) {
|
||||
// Keep the unformatted json if there is a syntax exception
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeJsonToFile(String pathString, String filename, String json) throws IOException {
|
||||
try {
|
||||
JsonElement element = JsonParser.parseString(json);
|
||||
if (element.isJsonNull() || (element.isJsonArray() && ((JsonArray) element).size() == 0)) {
|
||||
// Don't write a file if empty
|
||||
return;
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
// Just continue and write the file with non-valid json anyway
|
||||
}
|
||||
|
||||
String path = nextPath(pathString + File.separator + filename, "json");
|
||||
|
||||
// ensure full path exists
|
||||
File file = new File(path);
|
||||
Objects.requireNonNull(file.getParentFile()).mkdirs();
|
||||
|
||||
final byte[] contents = json.getBytes(StandardCharsets.UTF_8);
|
||||
Files.write(file.toPath(), contents);
|
||||
}
|
||||
|
||||
// Stackoverflow:
|
||||
// https://stackoverflow.com/questions/57997257/how-can-i-zip-a-complete-directory-with-all-subfolders-in-java
|
||||
private void zipDirectory(Path sourceDirectoryPath, Path zipPath) throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
|
||||
ZipOutputStream zos = new ZipOutputStream(fos)) {
|
||||
Files.walkFileTree(sourceDirectoryPath, new SimpleFileVisitor<@Nullable Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(@Nullable Path file, @Nullable BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
zos.putNextEntry(new ZipEntry(sourceDirectoryPath.relativize(file).toString()));
|
||||
Files.copy(file, zos);
|
||||
zos.closeEntry();
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteDirectory(String path) throws IOException {
|
||||
Files.walk(Paths.get(path)).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getUsages() {
|
||||
return Arrays.asList(
|
||||
new String[] { buildCommandUsage(FINGERPRINT, "generate fingerprint for all vehicles on all accounts"),
|
||||
buildCommandUsage(FINGERPRINT + " <userName>", "generate fingerprint for vehicles on account"),
|
||||
buildCommandUsage(FINGERPRINT + " <userName> <vin>",
|
||||
"generate fingerprint for vehicle with vin on account") });
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ConsoleCommandCompleter getCompleter() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean complete(String[] args, int cursorArgumentIndex, int cursorPosition, List<String> candidates) {
|
||||
try {
|
||||
if (cursorArgumentIndex <= 0) {
|
||||
return CMD_COMPLETER.complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 1) {
|
||||
return new StringsCompleter(
|
||||
thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID()))
|
||||
.map(t -> t.getConfiguration().get("userName").toString()).collect(Collectors.toList()),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
} else if (cursorArgumentIndex == 2) {
|
||||
MyBMWBridgeHandler handler = (MyBMWBridgeHandler) thingRegistry.stream()
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID())
|
||||
&& args[1].equals(t.getConfiguration().get("userName")))
|
||||
.map(t -> t.getHandler()).findAny().get();
|
||||
List<VehicleBase> vehicles = handler.getMyBmwProxy().get().requestVehiclesBase();
|
||||
return new StringsCompleter(
|
||||
vehicles.stream().map(v -> v.getVin()).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
}
|
||||
} catch (NoSuchElementException | NetworkException e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.discovery;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleAttributes;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleCapabilities;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.ServiceScope;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleDiscovery} requests data from BMW API and is identifying
|
||||
* the Vehicles after response
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
@Component(scope = ServiceScope.PROTOTYPE, service = VehicleDiscovery.class)
|
||||
@NonNullByDefault
|
||||
public class VehicleDiscovery extends AbstractThingHandlerDiscoveryService<MyBMWBridgeHandler> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
|
||||
|
||||
private static final int DISCOVERY_TIMEOUT = 10;
|
||||
|
||||
private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
|
||||
private @NonNullByDefault({}) ThingUID bridgeUid;
|
||||
|
||||
public VehicleDiscovery() {
|
||||
super(MyBMWBridgeHandler.class, MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for tests only.
|
||||
*
|
||||
* @param scheduler the {@link ScheduledExecutorService} to use during testing.
|
||||
*/
|
||||
VehicleDiscovery(ScheduledExecutorService scheduler) {
|
||||
super(scheduler, MyBMWBridgeHandler.class, MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false, null,
|
||||
null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
thingHandler.setVehicleDiscovery(this);
|
||||
bridgeUid = thingHandler.getThing().getUID();
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.trace("VehicleDiscovery.startScan");
|
||||
discoverVehicles();
|
||||
}
|
||||
|
||||
public void discoverVehicles() {
|
||||
logger.trace("VehicleDiscovery.discoverVehicles");
|
||||
|
||||
myBMWProxy = thingHandler.getMyBmwProxy();
|
||||
|
||||
try {
|
||||
Optional<List<Vehicle>> vehicleList = myBMWProxy.map(prox -> {
|
||||
try {
|
||||
return prox.requestVehicles();
|
||||
} catch (NetworkException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
});
|
||||
vehicleList.ifPresentOrElse(vehicles -> {
|
||||
if (!vehicles.isEmpty()) {
|
||||
thingHandler.vehicleDiscoverySuccess();
|
||||
processVehicles(vehicles);
|
||||
} else {
|
||||
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
|
||||
}
|
||||
}, () -> thingHandler.vehicleDiscoveryError(Constants.EMPTY));
|
||||
} catch (IllegalStateException ex) {
|
||||
NetworkException ne = (NetworkException) ex.getCause();
|
||||
if (ne != null && (ne.getStatus() == 403 || ne.getStatus() == 429)) {
|
||||
thingHandler.vehicleQuotaDiscoveryError(myBMWProxy.get().getNextQuota());
|
||||
} else {
|
||||
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
|
||||
*
|
||||
* it iterates through the list of existing things and checks if the vehicles found via the API
|
||||
* call are already known to OH. If not, it creates a new thing and puts it into the inbox
|
||||
*
|
||||
* @param vehicleList
|
||||
*/
|
||||
private void processVehicles(List<Vehicle> vehicleList) {
|
||||
logger.trace("VehicleDiscovery.processVehicles");
|
||||
|
||||
vehicleList.forEach(vehicle -> {
|
||||
// the DriveTrain field in the delivered json is defining the Vehicle Type
|
||||
String vehicleType = VehicleStatusUtils
|
||||
.vehicleType(vehicle.getVehicleBase().getAttributes().getDriveTrain(),
|
||||
vehicle.getVehicleBase().getAttributes().getModel())
|
||||
.toString();
|
||||
MyBMWConstants.SUPPORTED_THING_SET.forEach(entry -> {
|
||||
if (entry.getId().equals(vehicleType)) {
|
||||
ThingUID uid = new ThingUID(entry, vehicle.getVehicleBase().getVin(), bridgeUid.getId());
|
||||
|
||||
Map<String, String> properties = generateProperties(vehicle);
|
||||
|
||||
boolean thingFound = false;
|
||||
// Update Properties for already created Things
|
||||
List<Thing> vehicleThings = thingHandler.getThing().getThings();
|
||||
for (Thing vehicleThing : vehicleThings) {
|
||||
Configuration configuration = vehicleThing.getConfiguration();
|
||||
|
||||
if (configuration.containsKey(MyBMWConstants.VIN)) {
|
||||
String thingVIN = configuration.get(MyBMWConstants.VIN).toString();
|
||||
if (vehicle.getVehicleBase().getVin().equals(thingVIN)) {
|
||||
vehicleThing.setProperties(properties);
|
||||
thingFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the vehicle found is not yet known to OH, so put it into the inbox
|
||||
if (!thingFound) {
|
||||
// Properties needed for functional Thing
|
||||
VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
|
||||
Map<String, Object> convertedProperties = new HashMap<>(properties);
|
||||
convertedProperties.put(MyBMWConstants.VIN, vehicle.getVehicleBase().getVin());
|
||||
convertedProperties.put(MyBMWConstants.VEHICLE_BRAND, vehicleAttributes.getBrand());
|
||||
convertedProperties.put(MyBMWConstants.REFRESH_INTERVAL,
|
||||
Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
|
||||
|
||||
String vehicleLabel = vehicleAttributes.getBrand() + " " + vehicleAttributes.getModel();
|
||||
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUid)
|
||||
.withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
|
||||
.withProperties(convertedProperties).build());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, String> generateProperties(Vehicle vehicle) {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
// Vehicle Properties
|
||||
VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
|
||||
properties.put(MyBMWConstants.VEHICLE_MODEL, vehicleAttributes.getModel());
|
||||
properties.put(MyBMWConstants.VEHICLE_DRIVE_TRAIN, vehicleAttributes.getDriveTrain());
|
||||
properties.put(MyBMWConstants.VEHICLE_CONSTRUCTION_YEAR, Integer.toString(vehicleAttributes.getYear()));
|
||||
properties.put(MyBMWConstants.VEHICLE_BODYTYPE, vehicleAttributes.getBodyType());
|
||||
|
||||
VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
|
||||
|
||||
properties.put(MyBMWConstants.SERVICES_SUPPORTED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, true));
|
||||
properties.put(MyBMWConstants.SERVICES_UNSUPPORTED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, false));
|
||||
properties.put(MyBMWConstants.SERVICES_ENABLED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, true));
|
||||
properties.put(MyBMWConstants.SERVICES_DISABLED,
|
||||
vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, false));
|
||||
|
||||
// For RemoteServices we need to do it step-by-step
|
||||
StringBuffer remoteServicesEnabled = new StringBuffer();
|
||||
StringBuffer remoteServicesDisabled = new StringBuffer();
|
||||
if (vehicleCapabilities.isLock()) {
|
||||
remoteServicesEnabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isUnlock()) {
|
||||
remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isLights()) {
|
||||
remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isHorn()) {
|
||||
remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isVehicleFinder()) {
|
||||
remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
if (vehicleCapabilities.isVehicleFinder()) {
|
||||
remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
|
||||
} else {
|
||||
remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
|
||||
}
|
||||
properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
|
||||
properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.auth;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* The {@link AuthResponse} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted from myBmwProxy
|
||||
*/
|
||||
public class AuthResponse {
|
||||
@SerializedName("access_token")
|
||||
public String accessToken = Constants.EMPTY;
|
||||
|
||||
@SerializedName("refresh_token")
|
||||
public String refreshToken = Constants.EMPTY;
|
||||
|
||||
@SerializedName("token_type")
|
||||
public String tokenType = Constants.EMPTY;
|
||||
|
||||
@SerializedName("gcid")
|
||||
public String gcid = Constants.EMPTY;
|
||||
|
||||
@SerializedName("expires_in")
|
||||
public int expiresIn = -1;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthResponse [accessToken=" + accessToken + ", refreshToken=" + refreshToken + ", tokenType="
|
||||
+ tokenType + ", gcid=" + gcid + ", expiresIn=" + expiresIn + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.auth;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link OAuthSettingsQueryResponse} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - add toString for debugging
|
||||
*/
|
||||
public class OAuthSettingsQueryResponse {
|
||||
public String clientName;// ": "mybmwapp",
|
||||
public String clientSecret;// ": "c0e3393d-70a2-4f6f-9d3c-8530af64d552",
|
||||
public String clientId;// ": "31c357a0-7a1d-4590-aa99-33b97244d048",
|
||||
public String gcdmBaseUrl;// ": "https://customer.bmwgroup.com",
|
||||
public String returnUrl;// ": "com.bmw.connected://oauth",
|
||||
public String brand;// ": "bmw",
|
||||
public String language;// ": "en",
|
||||
public String country;// ": "US",
|
||||
public String authorizationEndpoint;// ": "https://customer.bmwgroup.com/oneid/login",
|
||||
public String tokenEndpoint;// ": "https://customer.bmwgroup.com/gcdm/oauth/token",
|
||||
private List<String> scopes;// ;": [
|
||||
// "openid",
|
||||
// "profile",
|
||||
// "email",
|
||||
// "offline_access",
|
||||
// "smacc",
|
||||
// "vehicle_data",
|
||||
// "perseus",
|
||||
// "dlm",
|
||||
// "svds",
|
||||
// "cesim",
|
||||
// "vsapi",
|
||||
// "remote_services",
|
||||
// "fupo",
|
||||
// "authenticate_user"
|
||||
// ],
|
||||
public List<String> promptValues; // ": ["login"]
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
public String scopes() {
|
||||
return String.join(Constants.SPACE, scopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AuthQueryResponse [clientName=" + clientName + ", clientSecret=" + clientSecret + ", clientId="
|
||||
+ clientId + ", gcdmBaseUrl=" + gcdmBaseUrl + ", returnUrl=" + returnUrl + ", brand=" + brand
|
||||
+ ", language=" + language + ", country=" + country + ", authorizationEndpoint=" + authorizationEndpoint
|
||||
+ ", tokenEndpoint=" + tokenEndpoint + ", scopes=" + scopes + ", promptValues=" + promptValues + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargingProfile} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingProfile {
|
||||
private ChargingWindow reductionOfChargeCurrent = new ChargingWindow();
|
||||
private String chargingMode = "";// ": "immediateCharging",
|
||||
private String chargingPreference = "";// ": "chargingWindow",
|
||||
private String chargingControlType = "";// ": "weeklyPlanner",
|
||||
private List<Timer> departureTimes = new ArrayList<>();
|
||||
private boolean climatisationOn = false;// ": false,
|
||||
private ChargingSettings chargingSettings = new ChargingSettings();
|
||||
|
||||
public Timer getTimerId(int id) {
|
||||
if (departureTimes != null) {
|
||||
for (Timer t : departureTimes) {
|
||||
if (t.id == id) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Timer();
|
||||
}
|
||||
|
||||
public ChargingWindow getReductionOfChargeCurrent() {
|
||||
return reductionOfChargeCurrent;
|
||||
}
|
||||
|
||||
public String getChargingMode() {
|
||||
return chargingMode;
|
||||
}
|
||||
|
||||
public String getChargingPreference() {
|
||||
return chargingPreference;
|
||||
}
|
||||
|
||||
public String getChargingControlType() {
|
||||
return chargingControlType;
|
||||
}
|
||||
|
||||
public List<Timer> getDepartureTimes() {
|
||||
return departureTimes;
|
||||
}
|
||||
|
||||
public boolean isClimatisationOn() {
|
||||
return climatisationOn;
|
||||
}
|
||||
|
||||
public ChargingSettings getChargingSettings() {
|
||||
return chargingSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingProfile [reductionOfChargeCurrent=" + reductionOfChargeCurrent + ", chargingMode="
|
||||
+ chargingMode + ", chargingPreference=" + chargingPreference + ", chargingControlType="
|
||||
+ chargingControlType + ", departureTimes=" + departureTimes + ", climatisationOn=" + climatisationOn
|
||||
+ ", chargingSettings=" + chargingSettings + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSession} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingSession {
|
||||
private String id;// ": "2021-12-26T16:57:20Z_128fa4af",
|
||||
private String title;// ": "Gestern 17:57",
|
||||
private String subtitle;// ": "Uferstraße 4B • 7h 45min • -- EUR",
|
||||
private String energyCharged;// ": "~ 31 kWh",
|
||||
private String sessionStatus;// ": "FINISHED",
|
||||
private String issues;// ": "2 Probleme",
|
||||
private String isPublic;// ": false
|
||||
|
||||
/**
|
||||
* @return the id
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the title
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title the title to set
|
||||
*/
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the subtitle
|
||||
*/
|
||||
public String getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the energyCharged
|
||||
*/
|
||||
public String getEnergyCharged() {
|
||||
return energyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sessionStatus
|
||||
*/
|
||||
public String getSessionStatus() {
|
||||
return sessionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the issues
|
||||
*/
|
||||
public String getIssues() {
|
||||
return issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isPublic
|
||||
*/
|
||||
public String getIsPublic() {
|
||||
return isPublic;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSession [id=" + id + ", title=" + title + ", subtitle=" + subtitle + ", energyCharged="
|
||||
+ energyCharged + ", sessionStatus=" + sessionStatus + ", issues=" + issues + ", isPublic=" + isPublic
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSessions} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingSessions {
|
||||
private String total;// ": "~ 218 kWh",
|
||||
private String numberOfSessions;// ": "17",
|
||||
private String chargingListState;// ": "HAS_SESSIONS",
|
||||
private List<ChargingSession> sessions;
|
||||
|
||||
/**
|
||||
* @return the total
|
||||
*/
|
||||
public String getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfSessions
|
||||
*/
|
||||
public String getNumberOfSessions() {
|
||||
return numberOfSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingListState
|
||||
*/
|
||||
public String getChargingListState() {
|
||||
return chargingListState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sessions
|
||||
*/
|
||||
public List<ChargingSession> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSessions [total=" + total + ", numberOfSessions=" + numberOfSessions + ", chargingListState="
|
||||
+ chargingListState + ", sessions=" + sessions + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSessionsContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingSessionsContainer {
|
||||
public Object paginationInfo;
|
||||
public ChargingSessions chargingSessions;
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingSettings} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingSettings {
|
||||
private int acCurrentLimit = -1; // 32,
|
||||
private String hospitality = ""; // HOSP_INACTIVE,
|
||||
private String idcc = ""; // AUTOMATIC_INTELLIGENT,
|
||||
private boolean isAcCurrentLimitActive = false; // false,
|
||||
private int targetSoc = -1; // 80
|
||||
|
||||
public int getAcCurrentLimit() {
|
||||
return acCurrentLimit;
|
||||
}
|
||||
|
||||
public void setAcCurrentLimit(int acCurrentLimit) {
|
||||
this.acCurrentLimit = acCurrentLimit;
|
||||
}
|
||||
|
||||
public String getHospitality() {
|
||||
return hospitality;
|
||||
}
|
||||
|
||||
public void setHospitality(String hospitality) {
|
||||
this.hospitality = hospitality;
|
||||
}
|
||||
|
||||
public String getIdcc() {
|
||||
return idcc;
|
||||
}
|
||||
|
||||
public void setIdcc(String idcc) {
|
||||
this.idcc = idcc;
|
||||
}
|
||||
|
||||
public boolean isAcCurrentLimitActive() {
|
||||
return isAcCurrentLimitActive;
|
||||
}
|
||||
|
||||
public void setAcCurrentLimitActive(boolean isAcCurrentLimitActive) {
|
||||
this.isAcCurrentLimitActive = isAcCurrentLimitActive;
|
||||
}
|
||||
|
||||
public int getTargetSoc() {
|
||||
return targetSoc;
|
||||
}
|
||||
|
||||
public void setTargetSoc(int targetSoc) {
|
||||
this.targetSoc = targetSoc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingSettings [acCurrentLimit=" + acCurrentLimit + ", hospitality=" + hospitality + ", idcc=" + idcc
|
||||
+ ", isAcCurrentLimitActive=" + isAcCurrentLimitActive + ", targetSoc=" + targetSoc + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingStatistics} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
public class ChargingStatistics {
|
||||
private int totalEnergyCharged;// ": 173,
|
||||
private String totalEnergyChargedSemantics;// ": "Insgesamt circa 173 Kilowattstunden geladen",
|
||||
private String symbol;// ": "~",
|
||||
private int numberOfChargingSessions;// ": 13,
|
||||
private String numberOfChargingSessionsSemantics;// ": "13 Ladevorgänge"
|
||||
|
||||
/**
|
||||
* @return the totalEnergyCharged
|
||||
*/
|
||||
public int getTotalEnergyCharged() {
|
||||
return totalEnergyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalEnergyCharged the totalEnergyCharged to set
|
||||
*/
|
||||
public void setTotalEnergyCharged(int totalEnergyCharged) {
|
||||
this.totalEnergyCharged = totalEnergyCharged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the totalEnergyChargedSemantics
|
||||
*/
|
||||
public String getTotalEnergyChargedSemantics() {
|
||||
return totalEnergyChargedSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param totalEnergyChargedSemantics the totalEnergyChargedSemantics to set
|
||||
*/
|
||||
public void setTotalEnergyChargedSemantics(String totalEnergyChargedSemantics) {
|
||||
this.totalEnergyChargedSemantics = totalEnergyChargedSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the symbol
|
||||
*/
|
||||
public String getSymbol() {
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param symbol the symbol to set
|
||||
*/
|
||||
public void setSymbol(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfChargingSessions
|
||||
*/
|
||||
public int getNumberOfChargingSessions() {
|
||||
return numberOfChargingSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numberOfChargingSessions the numberOfChargingSessions to set
|
||||
*/
|
||||
public void setNumberOfChargingSessions(int numberOfChargingSessions) {
|
||||
this.numberOfChargingSessions = numberOfChargingSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the numberOfChargingSessionsSemantics
|
||||
*/
|
||||
public String getNumberOfChargingSessionsSemantics() {
|
||||
return numberOfChargingSessionsSemantics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numberOfChargingSessionsSemantics the numberOfChargingSessionsSemantics to set
|
||||
*/
|
||||
public void setNumberOfChargingSessionsSemantics(String numberOfChargingSessionsSemantics) {
|
||||
this.numberOfChargingSessionsSemantics = numberOfChargingSessionsSemantics;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingStatistics [totalEnergyCharged=" + totalEnergyCharged + ", totalEnergyChargedSemantics="
|
||||
+ totalEnergyChargedSemantics + ", symbol=" + symbol + ", numberOfChargingSessions="
|
||||
+ numberOfChargingSessions + ", numberOfChargingSessionsSemantics=" + numberOfChargingSessionsSemantics
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingStatisticsContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ChargingStatisticsContainer {
|
||||
private String description;// ": "Dezember 2021",
|
||||
private String optStateType;// ": "OPT_IN_WITH_SESSIONS",
|
||||
private ChargingStatistics statistics;// ": {
|
||||
|
||||
/**
|
||||
* @return the description
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description the description to set
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the optStateType
|
||||
*/
|
||||
public String getOptStateType() {
|
||||
return optStateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param optStateType the optStateType to set
|
||||
*/
|
||||
public void setOptStateType(String optStateType) {
|
||||
this.optStateType = optStateType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the statistics
|
||||
*/
|
||||
public ChargingStatistics getStatistics() {
|
||||
return statistics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param statistics the statistics to set
|
||||
*/
|
||||
public void setStatistics(ChargingStatistics statistics) {
|
||||
this.statistics = statistics;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingStatisticsContainer [description=" + description + ", optStateType=" + optStateType
|
||||
+ ", statistics=" + statistics + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link ChargingWindow} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ChargingWindow {
|
||||
private Time start = new Time();
|
||||
private Time end = new Time();
|
||||
|
||||
public Time getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(Time start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Time getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(Time end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChargingWindow [start=" + start + ", end=" + end + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class RemoteChargingCommands {
|
||||
private List<String> chargingControl = new ArrayList<>();
|
||||
private List<String> flapControl = new ArrayList<>();
|
||||
private List<String> plugControl = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @return the chargingControl
|
||||
*/
|
||||
public List<String> getChargingControl() {
|
||||
return chargingControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingControl the chargingControl to set
|
||||
*/
|
||||
public void setChargingControl(List<String> chargingControl) {
|
||||
this.chargingControl = chargingControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the flapControl
|
||||
*/
|
||||
public List<String> getFlapControl() {
|
||||
return flapControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param flapControl the flapControl to set
|
||||
*/
|
||||
public void setFlapControl(List<String> flapControl) {
|
||||
this.flapControl = flapControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the plugControl
|
||||
*/
|
||||
public List<String> getPlugControl() {
|
||||
return plugControl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param plugControl the plugControl to set
|
||||
*/
|
||||
public void setPlugControl(List<String> plugControl) {
|
||||
this.plugControl = plugControl;
|
||||
}
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoteChargingCommands [chargingControl=" + chargingControl + ", flapControl=" + flapControl
|
||||
+ ", plugControl=" + plugControl + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
/**
|
||||
* The {@link Time} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Time {
|
||||
private int hour = -1;// ": 11,
|
||||
private int minute = -1;// ": 0
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Time [hour=" + hour + ", minute=" + minute + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.charge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link Timer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
*/
|
||||
public class Timer {
|
||||
public int id = -1;// ": 1,
|
||||
public String action;// ": "deactivate",
|
||||
public Time timeStamp;
|
||||
public List<String> timerWeekDays;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id + Constants.COLON + action + Constants.COLON + timeStamp + Constants.COLON + timerWeekDays;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.remote;
|
||||
|
||||
/**
|
||||
* The {@link ExecutionError} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
public class ExecutionError {
|
||||
public String title;// ": "Etwas ist schiefgelaufen",
|
||||
public String description;// ": "Die folgenden Einschränkungen verbieten die Ausführung von Remote Services: Aus
|
||||
// Sicherheitsgründen sind Remote Services nicht verfügbar, wenn die Fahrbereitschaft
|
||||
// eingeschaltet ist. Remote Services können nur mit einem ausreichenden Ladezustand
|
||||
// durchgeführt werden. Die Remote Services „Verriegeln" und „Entriegeln" können nur
|
||||
// ausgeführt werden, wenn die Fahrertür geschlossen und der Türstatus bekannt ist.",
|
||||
public String presentationType;// ": "PAGE",
|
||||
public int iconId;// ": 60217,
|
||||
public boolean isRetriable;// ": true,
|
||||
public String errorDetails;// ": "NACK"
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.remote;
|
||||
|
||||
/**
|
||||
* The {@link ExecutionStatusContainer} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class ExecutionStatusContainer {
|
||||
private String eventId = "";
|
||||
private String creationTime = "";
|
||||
private String eventStatus = "";
|
||||
private ExecutionError errorDetails = null;
|
||||
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
public String getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public void setCreationTime(String creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public String getEventStatus() {
|
||||
return eventStatus;
|
||||
}
|
||||
|
||||
public void setEventStatus(String eventStatus) {
|
||||
this.eventStatus = eventStatus;
|
||||
}
|
||||
|
||||
public ExecutionError getErrorDetails() {
|
||||
return errorDetails;
|
||||
}
|
||||
|
||||
public void setErrorDetails(ExecutionError errorDetails) {
|
||||
this.errorDetails = errorDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExecutionStatusContainer [eventId=" + eventId + ", creationTime=" + creationTime + ", eventStatus="
|
||||
+ eventStatus + ", errorDetails=" + errorDetails + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Address} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Address {
|
||||
private String formatted = "";
|
||||
|
||||
public String getFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Address [formatted=" + formatted + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class CheckControlMessage {
|
||||
private String type = ""; // TIRE_PRESSURE,
|
||||
private String severity = ""; // LOW
|
||||
private int id = -1; // 955,
|
||||
private String description = ""; // Tire pressure notification: You can continue driving. Check tire pressure when
|
||||
// the tires are cold and adjust if necessary. Perform reset after adjustment. See
|
||||
// Owner's Manual for further information.
|
||||
private String name = ""; // Tire pressure notification
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public void setSeverity(String severity) {
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckControlMessage [type=" + type + ", severity=" + severity + ", id=" + id + ", description="
|
||||
+ description + ", name=" + name + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class ClimateControlState {
|
||||
private String activity = ""; // INACTIVE
|
||||
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
public void setActivity(String activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClimateControlState [activity=" + activity + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class ClimateTimer {
|
||||
private boolean isWeeklyTimer = false; // true,
|
||||
private String timerAction = ""; // DEACTIVATE,
|
||||
private List<String> timerWeekDays = new ArrayList<>(); // [ MONDAY ]
|
||||
private DepartureTime departureTime = new DepartureTime();
|
||||
|
||||
public boolean isWeeklyTimer() {
|
||||
return isWeeklyTimer;
|
||||
}
|
||||
|
||||
public void setWeeklyTimer(boolean isWeeklyTimer) {
|
||||
this.isWeeklyTimer = isWeeklyTimer;
|
||||
}
|
||||
|
||||
public String getTimerAction() {
|
||||
return timerAction;
|
||||
}
|
||||
|
||||
public void setTimerAction(String timerAction) {
|
||||
this.timerAction = timerAction;
|
||||
}
|
||||
|
||||
public List<String> getTimerWeekDays() {
|
||||
return timerWeekDays;
|
||||
}
|
||||
|
||||
public void setTimerWeekDays(List<String> timerWeekDays) {
|
||||
this.timerWeekDays = timerWeekDays;
|
||||
}
|
||||
|
||||
public DepartureTime getDepartureTime() {
|
||||
return departureTime;
|
||||
}
|
||||
|
||||
public void setDepartureTime(DepartureTime departureTime) {
|
||||
this.departureTime = departureTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ClimateTimer [isWeeklyTimer=" + isWeeklyTimer + ", timerAction=" + timerAction + ", timerWeekDays="
|
||||
+ timerWeekDays + ", departureTime=" + departureTime + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class CombustionFuelLevel {
|
||||
private int remainingFuelPercent = -1; // 65,
|
||||
private int remainingFuelLiters = -1; // 34,
|
||||
private int range = -1; // 435
|
||||
|
||||
public int getRemainingFuelPercent() {
|
||||
return remainingFuelPercent;
|
||||
}
|
||||
|
||||
public void setRemainingFuelPercent(int remainingFuelPercent) {
|
||||
this.remainingFuelPercent = remainingFuelPercent;
|
||||
}
|
||||
|
||||
public int getRemainingFuelLiters() {
|
||||
return remainingFuelLiters;
|
||||
}
|
||||
|
||||
public void setRemainingFuelLiters(int remainingFuelLiters) {
|
||||
this.remainingFuelLiters = remainingFuelLiters;
|
||||
}
|
||||
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
public void setRange(int range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CombustionFuelLevel [remainingFuelPercent=" + remainingFuelPercent + ", remainingFuelLiters="
|
||||
+ remainingFuelLiters + ", range=" + range + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Coordinates} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class Coordinates {
|
||||
private double latitude = -1.0;
|
||||
private double longitude = -1.0;
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public void setLatitude(double latitude) {
|
||||
this.latitude = latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
public void setLongitude(double longitude) {
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Coordinates [latitude=" + latitude + ", longitude=" + longitude + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DepartureTime {
|
||||
private int hour = -1; // 7,
|
||||
private int minute = -1; // 0
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public void setHour(int hour) {
|
||||
this.hour = hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
public void setMinute(int minute) {
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DepartureTime [hour=" + hour + ", minute=" + minute + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DigitalKey {
|
||||
private String bookedServicePackage = ""; // NONE,
|
||||
private String readerGraphics = "";
|
||||
private String state = ""; // NOT_AVAILABLE
|
||||
|
||||
public String getBookedServicePackage() {
|
||||
return bookedServicePackage;
|
||||
}
|
||||
|
||||
public void setBookedServicePackage(String bookedServicePackage) {
|
||||
this.bookedServicePackage = bookedServicePackage;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getReaderGraphics() {
|
||||
return readerGraphics;
|
||||
}
|
||||
|
||||
public void setReaderGraphics(String readerGraphics) {
|
||||
this.readerGraphics = readerGraphics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DigitalKey [bookedServicePackage=" + bookedServicePackage + ", readerGraphics=" + readerGraphics
|
||||
+ ", state=" + state + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class DriverPreferences {
|
||||
private String lscPrivacyMode = ""; // OFF
|
||||
|
||||
public String getLscPrivacyMode() {
|
||||
return lscPrivacyMode;
|
||||
}
|
||||
|
||||
public void setLscPrivacyMode(String lscPrivacyMode) {
|
||||
this.lscPrivacyMode = lscPrivacyMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DriverPreferences [lscPrivacyMode=" + lscPrivacyMode + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - refactoring, V2 API charging
|
||||
*/
|
||||
public class ElectricChargingState {
|
||||
private String chargingConnectionType = ""; // UNKNOWN,
|
||||
private String chargingStatus = ""; // FINISHED_FULLY_CHARGED,
|
||||
private boolean isChargerConnected = false; // true,
|
||||
private int chargingTarget = -1; // 80,
|
||||
private int chargingLevelPercent = -1; // 80,
|
||||
private int remainingChargingMinutes = -1; // 178
|
||||
private int range = -1; // 286
|
||||
|
||||
/**
|
||||
* @return the chargingConnectionType
|
||||
*/
|
||||
public String getChargingConnectionType() {
|
||||
return chargingConnectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingConnectionType the chargingConnectionType to set
|
||||
*/
|
||||
public void setChargingConnectionType(String chargingConnectionType) {
|
||||
this.chargingConnectionType = chargingConnectionType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingStatus
|
||||
*/
|
||||
public String getChargingStatus() {
|
||||
return chargingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingStatus the chargingStatus to set
|
||||
*/
|
||||
public void setChargingStatus(String chargingStatus) {
|
||||
this.chargingStatus = chargingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isChargerConnected
|
||||
*/
|
||||
public boolean isChargerConnected() {
|
||||
return isChargerConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isChargerConnected the isChargerConnected to set
|
||||
*/
|
||||
public void setChargerConnected(boolean isChargerConnected) {
|
||||
this.isChargerConnected = isChargerConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingTarget
|
||||
*/
|
||||
public int getChargingTarget() {
|
||||
return chargingTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingTarget the chargingTarget to set
|
||||
*/
|
||||
public void setChargingTarget(int chargingTarget) {
|
||||
this.chargingTarget = chargingTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingLevelPercent
|
||||
*/
|
||||
public int getChargingLevelPercent() {
|
||||
return chargingLevelPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param chargingLevelPercent the chargingLevelPercent to set
|
||||
*/
|
||||
public void setChargingLevelPercent(int chargingLevelPercent) {
|
||||
this.chargingLevelPercent = chargingLevelPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remainingChargingMinutes
|
||||
*/
|
||||
public int getRemainingChargingMinutes() {
|
||||
return remainingChargingMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remainingChargingMinutes the remainingChargingMinutes to set
|
||||
*/
|
||||
public void setRemainingChargingMinutes(int remainingChargingMinutes) {
|
||||
this.remainingChargingMinutes = remainingChargingMinutes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param range the range to set
|
||||
*/
|
||||
public void setRange(int range) {
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ElectricChargingState [chargingConnectionType=" + chargingConnectionType + ", chargingStatus="
|
||||
+ chargingStatus + ", isChargerConnected=" + isChargerConnected + ", chargingTarget=" + chargingTarget
|
||||
+ ", chargingLevelPercent=" + chargingLevelPercent + ", remainingChargingMinutes="
|
||||
+ remainingChargingMinutes + ", range=" + range + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class RequiredService {
|
||||
private String dateTime = ""; // 2024-06-01T00:00:00.000Z,
|
||||
private int mileage = -1; // 29000,
|
||||
private String type = ""; // OIL,
|
||||
private String status = ""; // OK,
|
||||
private String description = ""; // Next service due after the specified distance or date.
|
||||
|
||||
public String getDateTime() {
|
||||
return dateTime;
|
||||
}
|
||||
|
||||
public void setDateTime(String dateTime) {
|
||||
this.dateTime = dateTime;
|
||||
}
|
||||
|
||||
public int getMileage() {
|
||||
return mileage;
|
||||
}
|
||||
|
||||
public void setMileage(int mileage) {
|
||||
this.mileage = mileage;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RequiredService [dateTime=" + dateTime + ", mileage=" + mileage + ", type=" + type + ", status="
|
||||
+ status + ", description=" + description + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link Vehicle} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored for v2 API
|
||||
*/
|
||||
public class Vehicle {
|
||||
private VehicleBase vehicleBase = new VehicleBase();
|
||||
private VehicleStateContainer vehicleState = new VehicleStateContainer();
|
||||
|
||||
public VehicleBase getVehicleBase() {
|
||||
return vehicleBase;
|
||||
}
|
||||
|
||||
public void setVehicleBase(VehicleBase vehicleBase) {
|
||||
this.vehicleBase = vehicleBase;
|
||||
}
|
||||
|
||||
public VehicleStateContainer getVehicleState() {
|
||||
return vehicleState;
|
||||
}
|
||||
|
||||
public void setVehicleState(VehicleStateContainer vehicleState) {
|
||||
this.vehicleState = vehicleState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vehicle [vehicleBase=" + vehicleBase + ", vehicleState=" + vehicleState + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - fix brand BMW_I
|
||||
*/
|
||||
public class VehicleAttributes {
|
||||
private String lastFetched = ""; // "2022-12-21T17:30:40.363Z"
|
||||
private String model = "";// ": "i3 94 (+ REX)",
|
||||
private int year = -1;// ": 2017,
|
||||
private long color = -1;// ": 4284572001,
|
||||
private String brand = "";// ": "BMW",
|
||||
private String driveTrain = "";// ": "ELECTRIC",
|
||||
private String headUnitType = "";// ": "ID5",
|
||||
private String headUnitRaw = "";// ": "ID5",
|
||||
private String hmiVersion = "";// ": "ID4",
|
||||
// softwareVersionCurrent - needed?
|
||||
// softwareVersionExFactory - needed?
|
||||
private String telematicsUnit = "";// ": "TCB1",
|
||||
private String bodyType = "";// ": "I01",
|
||||
private String countryOfOrigin = ""; // "DE"
|
||||
// driverGuideInfo - needed?
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(int year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public long getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(long color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
if (BimmerConstants.BRAND_BMWI.equals(brand.toLowerCase())) {
|
||||
return BimmerConstants.BRAND_BMW;
|
||||
} else {
|
||||
return brand.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
public void setBrand(String brand) {
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getDriveTrain() {
|
||||
return driveTrain;
|
||||
}
|
||||
|
||||
public void setDriveTrain(String driveTrain) {
|
||||
this.driveTrain = driveTrain;
|
||||
}
|
||||
|
||||
public String getHeadUnitType() {
|
||||
return headUnitType;
|
||||
}
|
||||
|
||||
public void setHeadUnitType(String headUnitType) {
|
||||
this.headUnitType = headUnitType;
|
||||
}
|
||||
|
||||
public String getHeadUnitRaw() {
|
||||
return headUnitRaw;
|
||||
}
|
||||
|
||||
public void setHeadUnitRaw(String headUnitRaw) {
|
||||
this.headUnitRaw = headUnitRaw;
|
||||
}
|
||||
|
||||
public String getHmiVersion() {
|
||||
return hmiVersion;
|
||||
}
|
||||
|
||||
public void setHmiVersion(String hmiVersion) {
|
||||
this.hmiVersion = hmiVersion;
|
||||
}
|
||||
|
||||
public String getTelematicsUnit() {
|
||||
return telematicsUnit;
|
||||
}
|
||||
|
||||
public void setTelematicsUnit(String telematicsUnit) {
|
||||
this.telematicsUnit = telematicsUnit;
|
||||
}
|
||||
|
||||
public String getBodyType() {
|
||||
return bodyType;
|
||||
}
|
||||
|
||||
public void setBodyType(String bodyType) {
|
||||
this.bodyType = bodyType;
|
||||
}
|
||||
|
||||
public String getCountryOfOrigin() {
|
||||
return countryOfOrigin;
|
||||
}
|
||||
|
||||
public void setCountryOfOrigin(String countryOfOrigin) {
|
||||
this.countryOfOrigin = countryOfOrigin;
|
||||
}
|
||||
|
||||
public String getLastFetched() {
|
||||
return lastFetched;
|
||||
}
|
||||
|
||||
public void setLastFetched(String lastFetched) {
|
||||
this.lastFetched = lastFetched;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleAttributes [lastFetched=" + lastFetched + ", model=" + model + ", year=" + year + ", color="
|
||||
+ color + ", brand=" + brand + ", driveTrain=" + driveTrain + ", headUnitType=" + headUnitType
|
||||
+ ", headUnitRaw=" + headUnitRaw + ", hmiVersion=" + hmiVersion + ", telematicsUnit=" + telematicsUnit
|
||||
+ ", bodyType=" + bodyType + ", countryOfOrigin=" + countryOfOrigin + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link VehicleBase} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class VehicleBase {
|
||||
private String vin = "";// ": "WBY1Z81040V905639",
|
||||
// mappingInfo - needed?
|
||||
// appVehicleType - needed?
|
||||
private VehicleAttributes attributes = new VehicleAttributes();
|
||||
|
||||
public String getVin() {
|
||||
return vin;
|
||||
}
|
||||
|
||||
public void setVin(String vin) {
|
||||
this.vin = vin;
|
||||
}
|
||||
|
||||
public VehicleAttributes getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(VehicleAttributes attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleBase [vin=" + vin + ", attributes=" + attributes + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.RemoteChargingCommands;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleCapabilities} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
|
||||
public class VehicleCapabilities {
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleCapabilities.class);
|
||||
|
||||
private static final String PREFIX_IS = "is";
|
||||
public static final String SUPPORTED_SUFFIX = "Supported";
|
||||
public static final String ENABLED_SUFFIX = "Enabled";
|
||||
|
||||
private boolean checkSustainabilityDPP = false;
|
||||
private boolean climateNow = false;
|
||||
private boolean horn = false;
|
||||
private boolean isBmwChargingSupported = false;
|
||||
private boolean isCarSharingSupported = false;
|
||||
private boolean isChargeNowForBusinessSupported = false;
|
||||
private boolean isChargingHistorySupported = false;
|
||||
private boolean isChargingHospitalityEnabled = false;
|
||||
private boolean isChargingLoudnessEnabled = false;
|
||||
private boolean isChargingPlanSupported = false;
|
||||
private boolean isChargingPowerLimitEnabled = false;
|
||||
private boolean isChargingSettingsEnabled = false;
|
||||
private boolean isChargingTargetSocEnabled = false;
|
||||
private boolean isClimateTimerSupported = false;
|
||||
private boolean isClimateTimerWeeklyActive = false;
|
||||
private boolean isCustomerEsimSupported = false;
|
||||
private boolean isDataPrivacyEnabled = false;
|
||||
private boolean isDCSContractManagementSupported = false;
|
||||
private boolean isEasyChargeEnabled = false;
|
||||
private boolean isEvGoChargingSupported = false;
|
||||
private boolean isMiniChargingSupported = false;
|
||||
private boolean isNonLscFeatureEnabled = false;
|
||||
private boolean isRemoteEngineStartSupported = false;
|
||||
private boolean isRemoteHistoryDeletionSupported = false;
|
||||
private boolean isRemoteHistorySupported = false;
|
||||
private boolean isRemoteParkingSupported = false;
|
||||
private boolean isRemoteServicesActivationRequired = false;
|
||||
private boolean isRemoteServicesBookingRequired = false;
|
||||
private boolean isScanAndChargeSupported = false;
|
||||
private boolean isSustainabilityAccumulatedViewEnabled = false;
|
||||
private boolean isSustainabilitySupported = false;
|
||||
private boolean isWifiHotspotServiceSupported = false;
|
||||
private boolean lights = false;
|
||||
private boolean lock = false;
|
||||
private boolean remote360 = false;
|
||||
private RemoteChargingCommands remoteChargingCommands = new RemoteChargingCommands();
|
||||
private boolean remoteSoftwareUpgrade = false;
|
||||
private boolean sendPoi = false;
|
||||
private boolean speechThirdPartyAlexa = false;
|
||||
private boolean speechThirdPartyAlexaSDK = false;
|
||||
private boolean unlock = false;
|
||||
private boolean vehicleFinder = false;
|
||||
private DigitalKey digitalKey = new DigitalKey();
|
||||
private String a4aType = ""; // NOT_SUPPORTED,
|
||||
private String climateFunction = ""; // VENTILATION,
|
||||
private String climateTimerTrigger = ""; // DEPARTURE_TIMER,
|
||||
private String lastStateCallState = ""; // ACTIVATED,
|
||||
private String vehicleStateSource = ""; // LAST_STATE_CALL,
|
||||
|
||||
/**
|
||||
* @return the climateNow
|
||||
*/
|
||||
public boolean isClimateNow() {
|
||||
return climateNow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the horn
|
||||
*/
|
||||
public boolean isHorn() {
|
||||
return horn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lights
|
||||
*/
|
||||
public boolean isLights() {
|
||||
return lights;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lock
|
||||
*/
|
||||
public boolean isLock() {
|
||||
return lock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the remote360
|
||||
*/
|
||||
public boolean isRemote360() {
|
||||
return remote360;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sendPoi
|
||||
*/
|
||||
public boolean isSendPoi() {
|
||||
return sendPoi;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unlock
|
||||
*/
|
||||
public boolean isUnlock() {
|
||||
return unlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the vehicleFinder
|
||||
*/
|
||||
public boolean isVehicleFinder() {
|
||||
return vehicleFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the digitalKey
|
||||
*/
|
||||
public DigitalKey getDigitalKey() {
|
||||
return digitalKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of capabilities filtered by the provided suffix and the enabled requirement
|
||||
*
|
||||
* @param suffix the suffix of the capability
|
||||
* @param enabled if it should return only enabled or disabled capabilities
|
||||
* @return the list of capabilities as single string
|
||||
*/
|
||||
public String getCapabilitiesAsString(String suffix, boolean enabled) {
|
||||
StringBuffer capabilitiesAsString = new StringBuffer();
|
||||
List<String> capabilitiesAsStringList = getCapabilitiesAsStringList(suffix, enabled);
|
||||
|
||||
for (String capEntry : capabilitiesAsStringList) {
|
||||
// remove "is" prefix and provided suffix
|
||||
String cut = capEntry.substring(2);
|
||||
if (cut.endsWith(suffix)) {
|
||||
if (capabilitiesAsString.length() > 0) {
|
||||
capabilitiesAsString.append(Constants.SEMICOLON);
|
||||
}
|
||||
capabilitiesAsString.append(cut.substring(0, cut.length() - suffix.length()));
|
||||
}
|
||||
}
|
||||
return capabilitiesAsString.toString();
|
||||
}
|
||||
|
||||
private List<String> getCapabilitiesAsStringList(String suffix, boolean compare) {
|
||||
List<String> l = new ArrayList<>();
|
||||
|
||||
Arrays.asList(VehicleCapabilities.class.getDeclaredFields()).stream()
|
||||
.filter(field -> field.getName().startsWith(PREFIX_IS) && field.getName().endsWith(suffix))
|
||||
.forEach(field -> {
|
||||
try {
|
||||
boolean value = field.getBoolean(this);
|
||||
if (compare == value) {
|
||||
l.add(field.getName());
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
logger.trace("field {} not usable: ", field.getName());
|
||||
}
|
||||
});
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleCapabilities [checkSustainabilityDPP=" + checkSustainabilityDPP + ", climateNow=" + climateNow
|
||||
+ ", horn=" + horn + ", isBmwChargingSupported=" + isBmwChargingSupported + ", isCarSharingSupported="
|
||||
+ isCarSharingSupported + ", isChargeNowForBusinessSupported=" + isChargeNowForBusinessSupported
|
||||
+ ", isChargingHistorySupported=" + isChargingHistorySupported + ", isChargingHospitalityEnabled="
|
||||
+ isChargingHospitalityEnabled + ", isChargingLoudnessEnabled=" + isChargingLoudnessEnabled
|
||||
+ ", isChargingPlanSupported=" + isChargingPlanSupported + ", isChargingPowerLimitEnabled="
|
||||
+ isChargingPowerLimitEnabled + ", isChargingSettingsEnabled=" + isChargingSettingsEnabled
|
||||
+ ", isChargingTargetSocEnabled=" + isChargingTargetSocEnabled + ", isClimateTimerSupported="
|
||||
+ isClimateTimerSupported + ", isClimateTimerWeeklyActive=" + isClimateTimerWeeklyActive
|
||||
+ ", isCustomerEsimSupported=" + isCustomerEsimSupported + ", isDataPrivacyEnabled="
|
||||
+ isDataPrivacyEnabled + ", isDCSContractManagementSupported=" + isDCSContractManagementSupported
|
||||
+ ", isEasyChargeEnabled=" + isEasyChargeEnabled + ", isEvGoChargingSupported="
|
||||
+ isEvGoChargingSupported + ", isMiniChargingSupported=" + isMiniChargingSupported
|
||||
+ ", isNonLscFeatureEnabled=" + isNonLscFeatureEnabled + ", isRemoteEngineStartSupported="
|
||||
+ isRemoteEngineStartSupported + ", isRemoteHistoryDeletionSupported="
|
||||
+ isRemoteHistoryDeletionSupported + ", isRemoteHistorySupported=" + isRemoteHistorySupported
|
||||
+ ", isRemoteParkingSupported=" + isRemoteParkingSupported + ", isRemoteServicesActivationRequired="
|
||||
+ isRemoteServicesActivationRequired + ", isRemoteServicesBookingRequired="
|
||||
+ isRemoteServicesBookingRequired + ", isScanAndChargeSupported=" + isScanAndChargeSupported
|
||||
+ ", isSustainabilityAccumulatedViewEnabled=" + isSustainabilityAccumulatedViewEnabled
|
||||
+ ", isSustainabilitySupported=" + isSustainabilitySupported + ", isWifiHotspotServiceSupported="
|
||||
+ isWifiHotspotServiceSupported + ", lights=" + lights + ", lock=" + lock + ", remote360=" + remote360
|
||||
+ ", remoteChargingCommands=" + remoteChargingCommands + ", remoteSoftwareUpgrade="
|
||||
+ remoteSoftwareUpgrade + ", sendPoi=" + sendPoi + ", speechThirdPartyAlexa=" + speechThirdPartyAlexa
|
||||
+ ", speechThirdPartyAlexaSDK=" + speechThirdPartyAlexaSDK + ", unlock=" + unlock + ", vehicleFinder="
|
||||
+ vehicleFinder + ", digitalKey=" + digitalKey + ", a4aType=" + a4aType + ", climateFunction="
|
||||
+ climateFunction + ", climateTimerTrigger=" + climateTimerTrigger + ", lastStateCallState="
|
||||
+ lastStateCallState + ", vehicleStateSource=" + vehicleStateSource + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleDoorsState {
|
||||
private String combinedSecurityState = ""; // SECURED,
|
||||
private String leftFront = ""; // CLOSED
|
||||
private String leftRear = ""; // CLOSED
|
||||
private String rightFront = ""; // CLOSED
|
||||
private String rightRear = ""; // CLOSED
|
||||
private String combinedState = ""; // CLOSED
|
||||
private String hood = ""; // CLOSED
|
||||
private String trunk = ""; // CLOSED
|
||||
|
||||
public String getCombinedSecurityState() {
|
||||
return combinedSecurityState;
|
||||
}
|
||||
|
||||
public void setCombinedSecurityState(String combinedSecurityState) {
|
||||
this.combinedSecurityState = combinedSecurityState;
|
||||
}
|
||||
|
||||
public String getLeftFront() {
|
||||
return leftFront;
|
||||
}
|
||||
|
||||
public void setLeftFront(String leftFront) {
|
||||
this.leftFront = leftFront;
|
||||
}
|
||||
|
||||
public String getLeftRear() {
|
||||
return leftRear;
|
||||
}
|
||||
|
||||
public void setLeftRear(String leftRear) {
|
||||
this.leftRear = leftRear;
|
||||
}
|
||||
|
||||
public String getRightFront() {
|
||||
return rightFront;
|
||||
}
|
||||
|
||||
public void setRightFront(String rightFront) {
|
||||
this.rightFront = rightFront;
|
||||
}
|
||||
|
||||
public String getRightRear() {
|
||||
return rightRear;
|
||||
}
|
||||
|
||||
public void setRightRear(String rightRear) {
|
||||
this.rightRear = rightRear;
|
||||
}
|
||||
|
||||
public String getCombinedState() {
|
||||
return combinedState;
|
||||
}
|
||||
|
||||
public void setCombinedState(String combinedState) {
|
||||
this.combinedState = combinedState;
|
||||
}
|
||||
|
||||
public String getHood() {
|
||||
return hood;
|
||||
}
|
||||
|
||||
public void setHood(String hood) {
|
||||
this.hood = hood;
|
||||
}
|
||||
|
||||
public String getTrunk() {
|
||||
return trunk;
|
||||
}
|
||||
|
||||
public void setTrunk(String trunk) {
|
||||
this.trunk = trunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleDoorsState [combinedSecurityState=" + combinedSecurityState + ", leftFront=" + leftFront
|
||||
+ ", leftRear=" + leftRear + ", rightFront=" + rightFront + ", rightRear=" + rightRear
|
||||
+ ", combinedState=" + combinedState + ", hood=" + hood + ", trunk=" + trunk + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
* The {@link VehicleLocation} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored to Java Bean
|
||||
*/
|
||||
public class VehicleLocation {
|
||||
private Coordinates coordinates = new Coordinates();
|
||||
private Address address = new Address();
|
||||
private int heading = -1;
|
||||
|
||||
public Coordinates getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public void setCoordinates(Coordinates coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(Address address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public int getHeading() {
|
||||
return heading;
|
||||
}
|
||||
|
||||
public void setHeading(int heading) {
|
||||
this.heading = heading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleLocation [coordinates=" + coordinates + ", address=" + address + ", heading=" + heading + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleRoofState {
|
||||
private String roofState = ""; // CLOSED,
|
||||
private String roofStateType = ""; // SUN_ROOF
|
||||
|
||||
public String getRoofState() {
|
||||
return roofState;
|
||||
}
|
||||
|
||||
public void setRoofState(String roofState) {
|
||||
this.roofState = roofState;
|
||||
}
|
||||
|
||||
public String getRoofStateType() {
|
||||
return roofStateType;
|
||||
}
|
||||
|
||||
public void setRoofStateType(String roofStateType) {
|
||||
this.roofStateType = roofStateType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleRoofState [roofState=" + roofState + ", roofStateType=" + roofStateType + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleState {
|
||||
|
||||
public static final String CHECK_CONTROL_OVERALL_MESSAGE_OK = "No Issues";
|
||||
|
||||
private boolean isLeftSteering = false;
|
||||
private String lastFetched = ""; // 2022-12-21T17:31:26.560Z,
|
||||
private String lastUpdatedAt = ""; // 2022-12-21T15:41:23Z,
|
||||
private boolean isLscSupported = false; // true,
|
||||
private int range = -1; // 435,
|
||||
private VehicleDoorsState doorsState = new VehicleDoorsState();
|
||||
private VehicleWindowsState windowsState = new VehicleWindowsState();
|
||||
private VehicleRoofState roofState = new VehicleRoofState();
|
||||
private VehicleTireStates tireState = new VehicleTireStates();
|
||||
|
||||
private VehicleLocation location = new VehicleLocation();
|
||||
private int currentMileage = -1;
|
||||
private ClimateControlState climateControlState = new ClimateControlState();
|
||||
private List<RequiredService> requiredServices = new ArrayList<>();
|
||||
private List<CheckControlMessage> checkControlMessages = new ArrayList<>();
|
||||
private CombustionFuelLevel combustionFuelLevel = new CombustionFuelLevel();
|
||||
private DriverPreferences driverPreferences = new DriverPreferences();
|
||||
private ElectricChargingState electricChargingState = new ElectricChargingState();
|
||||
private boolean isDeepSleepModeActive = false; // false
|
||||
private List<ClimateTimer> climateTimers = new ArrayList<>();
|
||||
private ChargingProfile chargingProfile = new ChargingProfile();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return the isLeftSteering
|
||||
*/
|
||||
public boolean isLeftSteering() {
|
||||
return isLeftSteering;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastFetched
|
||||
*/
|
||||
public String getLastFetched() {
|
||||
return lastFetched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the lastUpdatedAt
|
||||
*/
|
||||
public String getLastUpdatedAt() {
|
||||
return lastUpdatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isLscSupported
|
||||
*/
|
||||
public boolean isLscSupported() {
|
||||
return isLscSupported;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the range
|
||||
*/
|
||||
public int getRange() {
|
||||
return range;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the doorsState
|
||||
*/
|
||||
public VehicleDoorsState getDoorsState() {
|
||||
return doorsState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the windowsState
|
||||
*/
|
||||
public VehicleWindowsState getWindowsState() {
|
||||
return windowsState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the roofState
|
||||
*/
|
||||
public VehicleRoofState getRoofState() {
|
||||
return roofState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tireState
|
||||
*/
|
||||
public VehicleTireStates getTireState() {
|
||||
return tireState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the location
|
||||
*/
|
||||
public VehicleLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the currentMileage
|
||||
*/
|
||||
public int getCurrentMileage() {
|
||||
return currentMileage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the climateControlState
|
||||
*/
|
||||
public ClimateControlState getClimateControlState() {
|
||||
return climateControlState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the requiredServices
|
||||
*/
|
||||
public List<RequiredService> getRequiredServices() {
|
||||
return requiredServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the checkControlMessages
|
||||
*/
|
||||
public List<CheckControlMessage> getCheckControlMessages() {
|
||||
return checkControlMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the combustionFuelLevel
|
||||
*/
|
||||
public CombustionFuelLevel getCombustionFuelLevel() {
|
||||
return combustionFuelLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the driverPreferences
|
||||
*/
|
||||
public DriverPreferences getDriverPreferences() {
|
||||
return driverPreferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the electricChargingState
|
||||
*/
|
||||
public ElectricChargingState getElectricChargingState() {
|
||||
return electricChargingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isDeepSleepModeActive
|
||||
*/
|
||||
public boolean isDeepSleepModeActive() {
|
||||
return isDeepSleepModeActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the climateTimers
|
||||
*/
|
||||
public List<ClimateTimer> getClimateTimers() {
|
||||
return climateTimers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the chargingProfile
|
||||
*/
|
||||
public ChargingProfile getChargingProfile() {
|
||||
return chargingProfile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleState [isLeftSteering=" + isLeftSteering + ", lastFetched=" + lastFetched + ", lastUpdatedAt="
|
||||
+ lastUpdatedAt + ", isLscSupported=" + isLscSupported + ", range=" + range + ", doorsState="
|
||||
+ doorsState + ", windowsState=" + windowsState + ", roofState=" + roofState + ", tireState="
|
||||
+ tireState + ", location=" + location + ", currentMileage=" + currentMileage + ", climateControlState="
|
||||
+ climateControlState + ", requiredServices=" + requiredServices + ", checkControlMessages="
|
||||
+ checkControlMessages + ", combustionFuelLevel=" + combustionFuelLevel + ", driverPreferences="
|
||||
+ driverPreferences + ", electricChargingState=" + electricChargingState + ", isDeepSleepModeActive="
|
||||
+ isDeepSleepModeActive + ", climateTimers=" + climateTimers + ", chargingProfile=" + chargingProfile
|
||||
+ "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* helper methods
|
||||
*/
|
||||
public String getOverallCheckControlStatus() {
|
||||
StringBuilder overallMessage = new StringBuilder();
|
||||
|
||||
for (CheckControlMessage checkControlMessage : checkControlMessages) {
|
||||
if (checkControlMessage.getId() > 0) {
|
||||
overallMessage.append(checkControlMessage.getName() + "; ");
|
||||
}
|
||||
}
|
||||
|
||||
String overallMessageString = overallMessage.toString();
|
||||
|
||||
if (overallMessageString.isEmpty()) {
|
||||
overallMessageString = CHECK_CONTROL_OVERALL_MESSAGE_OK;
|
||||
}
|
||||
|
||||
return overallMessageString;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleStateContainer {
|
||||
private VehicleState state = new VehicleState();
|
||||
private VehicleCapabilities capabilities = new VehicleCapabilities();
|
||||
|
||||
private String rawStateJson = "";
|
||||
|
||||
public VehicleState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(VehicleState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public VehicleCapabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
public void setCapabilities(VehicleCapabilities capabilities) {
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleState [state=" + state + ", capabilities=" + capabilities + "]";
|
||||
}
|
||||
|
||||
public String getRawStateJson() {
|
||||
return rawStateJson;
|
||||
}
|
||||
|
||||
public void setRawStateJson(String rawStateJson) {
|
||||
this.rawStateJson = rawStateJson;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireState {
|
||||
private VehicleTireStateDetails details = new VehicleTireStateDetails();
|
||||
private VehicleTireStateStatus status = new VehicleTireStateStatus();
|
||||
|
||||
public VehicleTireStateDetails getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public void setDetails(VehicleTireStateDetails details) {
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public VehicleTireStateStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(VehicleTireStateStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireState [details=" + details + ", status=" + status + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateDetails {
|
||||
private String dimension = ""; // 225/45 R18 95V XL,
|
||||
private String treadDesign = ""; // Winter Contact TS 860 S SSR,
|
||||
private String manufacturer = ""; // Continental,
|
||||
private int manufacturingWeek = -1; // 5299,
|
||||
private boolean isOptimizedForOemBmw = false; // true,
|
||||
private String partNumber = ""; // 2471558,
|
||||
private VehicleTireStateDetailsClassification speedClassification; //
|
||||
private String mountingDate = ""; // 2022-10-06T00:00:00.000Z,
|
||||
private int season = -1; // 4,
|
||||
private boolean identificationInProgress = false; // false
|
||||
|
||||
public String getDimension() {
|
||||
return dimension;
|
||||
}
|
||||
|
||||
public void setDimension(String dimension) {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public String getTreadDesign() {
|
||||
return treadDesign;
|
||||
}
|
||||
|
||||
public void setTreadDesign(String treadDesign) {
|
||||
this.treadDesign = treadDesign;
|
||||
}
|
||||
|
||||
public String getManufacturer() {
|
||||
return manufacturer;
|
||||
}
|
||||
|
||||
public void setManufacturer(String manufacturer) {
|
||||
this.manufacturer = manufacturer;
|
||||
}
|
||||
|
||||
public int getManufacturingWeek() {
|
||||
return manufacturingWeek;
|
||||
}
|
||||
|
||||
public void setManufacturingWeek(int manufacturingWeek) {
|
||||
this.manufacturingWeek = manufacturingWeek;
|
||||
}
|
||||
|
||||
public boolean isOptimizedForOemBmw() {
|
||||
return isOptimizedForOemBmw;
|
||||
}
|
||||
|
||||
public void setOptimizedForOemBmw(boolean isOptimizedForOemBmw) {
|
||||
this.isOptimizedForOemBmw = isOptimizedForOemBmw;
|
||||
}
|
||||
|
||||
public String getPartNumber() {
|
||||
return partNumber;
|
||||
}
|
||||
|
||||
public void setPartNumber(String partNumber) {
|
||||
this.partNumber = partNumber;
|
||||
}
|
||||
|
||||
public VehicleTireStateDetailsClassification getSpeedClassification() {
|
||||
return speedClassification;
|
||||
}
|
||||
|
||||
public void setSpeedClassification(VehicleTireStateDetailsClassification speedClassification) {
|
||||
this.speedClassification = speedClassification;
|
||||
}
|
||||
|
||||
public String getMountingDate() {
|
||||
return mountingDate;
|
||||
}
|
||||
|
||||
public void setMountingDate(String mountingDate) {
|
||||
this.mountingDate = mountingDate;
|
||||
}
|
||||
|
||||
public int getSeason() {
|
||||
return season;
|
||||
}
|
||||
|
||||
public void setSeason(int season) {
|
||||
this.season = season;
|
||||
}
|
||||
|
||||
public boolean isIdentificationInProgress() {
|
||||
return identificationInProgress;
|
||||
}
|
||||
|
||||
public void setIdentificationInProgress(boolean identificationInProgress) {
|
||||
this.identificationInProgress = identificationInProgress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateDetails [dimension=" + dimension + ", treadDesign=" + treadDesign + ", manufacturer="
|
||||
+ manufacturer + ", manufacturingWeek=" + manufacturingWeek + ", isOptimizedForOemBmw="
|
||||
+ isOptimizedForOemBmw + ", partNumber=" + partNumber + ", speedClassification=" + speedClassification
|
||||
+ ", mountingDate=" + mountingDate + ", season=" + season + ", identificationInProgress="
|
||||
+ identificationInProgress + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateDetailsClassification {
|
||||
private int speedRating = -1; // 240,
|
||||
private boolean atLeast = false; // false
|
||||
|
||||
public int getSpeedRating() {
|
||||
return speedRating;
|
||||
}
|
||||
|
||||
public void setSpeedRating(int speedRating) {
|
||||
this.speedRating = speedRating;
|
||||
}
|
||||
|
||||
public boolean isAtLeast() {
|
||||
return atLeast;
|
||||
}
|
||||
|
||||
public void setAtLeast(boolean atLeast) {
|
||||
this.atLeast = atLeast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateDetailsClassification [speedRating=" + speedRating + ", atLeast=" + atLeast + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from API response
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStateStatus {
|
||||
private int currentPressure = -1; // 280,
|
||||
private int targetPressure = -1; // 290
|
||||
|
||||
public int getCurrentPressure() {
|
||||
return currentPressure;
|
||||
}
|
||||
|
||||
public void setCurrentPressure(int currentPressure) {
|
||||
this.currentPressure = currentPressure;
|
||||
}
|
||||
|
||||
public int getTargetPressure() {
|
||||
return targetPressure;
|
||||
}
|
||||
|
||||
public void setTargetPressure(int targetPressure) {
|
||||
this.targetPressure = targetPressure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStateStatus [currentPressure=" + currentPressure + ", targetPressure=" + targetPressure
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleTireStates {
|
||||
private VehicleTireState frontLeft = new VehicleTireState();
|
||||
private VehicleTireState frontRight = new VehicleTireState();
|
||||
private VehicleTireState rearLeft = new VehicleTireState();
|
||||
private VehicleTireState rearRight = new VehicleTireState();
|
||||
|
||||
public VehicleTireState getFrontLeft() {
|
||||
return frontLeft;
|
||||
}
|
||||
|
||||
public void setFrontLeft(VehicleTireState frontLeft) {
|
||||
this.frontLeft = frontLeft;
|
||||
}
|
||||
|
||||
public VehicleTireState getFrontRight() {
|
||||
return frontRight;
|
||||
}
|
||||
|
||||
public void setFrontRight(VehicleTireState frontRight) {
|
||||
this.frontRight = frontRight;
|
||||
}
|
||||
|
||||
public VehicleTireState getRearLeft() {
|
||||
return rearLeft;
|
||||
}
|
||||
|
||||
public void setRearLeft(VehicleTireState rearLeft) {
|
||||
this.rearLeft = rearLeft;
|
||||
}
|
||||
|
||||
public VehicleTireState getRearRight() {
|
||||
return rearRight;
|
||||
}
|
||||
|
||||
public void setRearRight(VehicleTireState rearRight) {
|
||||
this.rearRight = rearRight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleTireStates [frontLeft=" + frontLeft + ", frontRight=" + frontRight + ", rearLeft=" + rearLeft
|
||||
+ ", rearRight=" + rearRight + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.dto.vehicle;
|
||||
|
||||
/**
|
||||
*
|
||||
* derived from the API responses
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
public class VehicleWindowsState {
|
||||
private String leftFront = ""; // CLOSED,
|
||||
private String leftRear = ""; // CLOSED,
|
||||
private String rightFront = ""; // CLOSED,
|
||||
private String rightRear = ""; // CLOSED,
|
||||
private String rear = ""; // CLOSED,
|
||||
private String combinedState = ""; // CLOSED
|
||||
|
||||
public String getLeftFront() {
|
||||
return leftFront;
|
||||
}
|
||||
|
||||
public void setLeftFront(String leftFront) {
|
||||
this.leftFront = leftFront;
|
||||
}
|
||||
|
||||
public String getLeftRear() {
|
||||
return leftRear;
|
||||
}
|
||||
|
||||
public void setLeftRear(String leftRear) {
|
||||
this.leftRear = leftRear;
|
||||
}
|
||||
|
||||
public String getRightFront() {
|
||||
return rightFront;
|
||||
}
|
||||
|
||||
public void setRightFront(String rightFront) {
|
||||
this.rightFront = rightFront;
|
||||
}
|
||||
|
||||
public String getRightRear() {
|
||||
return rightRear;
|
||||
}
|
||||
|
||||
public void setRightRear(String rightRear) {
|
||||
this.rightRear = rightRear;
|
||||
}
|
||||
|
||||
public String getRear() {
|
||||
return rear;
|
||||
}
|
||||
|
||||
public void setRear(String rear) {
|
||||
this.rear = rear;
|
||||
}
|
||||
|
||||
public String getCombinedState() {
|
||||
return combinedState;
|
||||
}
|
||||
|
||||
public void setCombinedState(String combinedState) {
|
||||
this.combinedState = combinedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VehicleWindowsState [leftFront=" + leftFront + ", leftRear=" + leftRear + ", rightFront=" + rightFront
|
||||
+ ", rightRear=" + rightRear + ", rear=" + rear + ", combinedState=" + combinedState + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||
import org.openhab.binding.mybmw.internal.discovery.VehicleDiscovery;
|
||||
import org.openhab.binding.mybmw.internal.handler.auth.MyBMWAuthServlet;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWFileProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWHttpProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.net.NetUtil;
|
||||
import org.openhab.core.net.NetworkAddressService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWBridgeHandler} is responsible for handling commands, which
|
||||
* are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactored, all discovery functionality moved to VehicleDiscovery
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private static final String ENVIRONMENT = "ENVIRONMENT";
|
||||
private static final String TEST = "test";
|
||||
private static final String TESTUSER = "testuser";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWBridgeHandler.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final HttpService httpService;
|
||||
private final NetworkAddressService networkAddressService;
|
||||
private Optional<MyBMWProxy> myBmwProxy = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
|
||||
private Optional<VehicleDiscovery> vehicleDiscovery = Optional.empty();
|
||||
private LocaleProvider localeProvider;
|
||||
|
||||
private CompletableFuture<Boolean> isInitialized = new CompletableFuture<>();
|
||||
|
||||
private Optional<MyBMWAuthServlet> authServlet = Optional.empty();
|
||||
private boolean tokenInitError = false;
|
||||
|
||||
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory, OAuthFactory oAuthFactory,
|
||||
HttpService httpService, NetworkAddressService networkAddressService, LocaleProvider localeProvider) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.httpService = httpService;
|
||||
this.networkAddressService = networkAddressService;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public void setVehicleDiscovery(VehicleDiscovery vehicleDiscovery) {
|
||||
logger.trace("MyBMWBridgeHandler.setVehicleDiscovery");
|
||||
this.vehicleDiscovery = Optional.of(vehicleDiscovery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// no commands available
|
||||
logger.trace("MyBMWBridgeHandler.handleCommand");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
isInitialized = new CompletableFuture<>();
|
||||
tokenInitError = false;
|
||||
|
||||
logger.trace("MyBMWBridgeHandler.initialize");
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
MyBMWBridgeConfiguration localBridgeConfiguration = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
|
||||
if (Constants.EMPTY.equals(localBridgeConfiguration.getUserName())
|
||||
|| Constants.EMPTY.equals(localBridgeConfiguration.getPassword())) {
|
||||
logger.warn("username or password no set");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
MyBMWConstants.STATUS_USER_DETAILS_MISSING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Constants.EMPTY.equals(localBridgeConfiguration.getRegion())) {
|
||||
logger.warn("region not set");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
MyBMWConstants.STATUS_REGION_MISSING);
|
||||
return;
|
||||
}
|
||||
|
||||
Configuration config = super.editConfiguration();
|
||||
if (Constants.LANGUAGE_AUTODETECT.equals(localBridgeConfiguration.getLanguage())) {
|
||||
config.put("language", localeProvider.getLocale().getLanguage().toLowerCase());
|
||||
}
|
||||
String ipConfig = localBridgeConfiguration.getCallbackIP();
|
||||
if (Constants.EMPTY.equals(ipConfig) || NetUtil.getAllInterfaceAddresses().stream()
|
||||
.map(cidr -> cidr.getAddress().getHostAddress()).noneMatch(a -> ipConfig.equals(a))) {
|
||||
String ip = networkAddressService.getPrimaryIpv4HostAddress();
|
||||
if (ip != null) {
|
||||
config.put("callbackIP", ipConfig);
|
||||
} else {
|
||||
logger.warn("the callback IP address could not be retrieved");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
MyBMWConstants.STATUS_IP_MISSING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Update the central configuration and get the updates configuration back
|
||||
super.updateConfiguration(config);
|
||||
localBridgeConfiguration = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
|
||||
// there is no risk in this functionality as several steps have to happen to get the file proxy working:
|
||||
// 1. environment variable ENVIRONMENT has to be available
|
||||
// 2. username of the myBMW account must be set to "testuser" which is anyhow no valid username
|
||||
// 3. the jar file must contain the fingerprints which will only happen if it has been built with the
|
||||
// test-jar profile
|
||||
String environment = System.getenv(ENVIRONMENT);
|
||||
|
||||
if (environment == null) {
|
||||
environment = "";
|
||||
}
|
||||
|
||||
createMyBmwProxy(localBridgeConfiguration, environment);
|
||||
initializerJob = Optional.of(scheduler.schedule(this::discoverVehicles, 2, TimeUnit.SECONDS));
|
||||
isInitialized.complete(true);
|
||||
}
|
||||
|
||||
private void createMyBmwProxy(MyBMWBridgeConfiguration config, String environment) {
|
||||
if (!myBmwProxy.isPresent()) {
|
||||
if (!(TEST.equals(environment) && TESTUSER.equals(config.getUserName()))) {
|
||||
myBmwProxy = Optional.of(new MyBMWHttpProxy(this, httpClient, oAuthFactory, config));
|
||||
} else {
|
||||
myBmwProxy = Optional.of(new MyBMWFileProxy(httpClient, config));
|
||||
}
|
||||
logger.trace("MyBMWBridgeHandler proxy set");
|
||||
} else {
|
||||
myBmwProxy.get().setBridgeConfiguration(config);
|
||||
logger.trace("MyBMWBridgeHandler update proxy with bridge configuration");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.trace("MyBMWBridgeHandler.dispose");
|
||||
initializerJob.ifPresent(job -> job.cancel(true));
|
||||
authServlet.ifPresent(servlet -> servlet.dispose());
|
||||
authServlet = Optional.empty();
|
||||
isInitialized.cancel(true);
|
||||
}
|
||||
|
||||
public void vehicleDiscoveryError(String message) {
|
||||
logger.trace("MyBMWBridgeHandler.vehicleDiscoveryError");
|
||||
if (!tokenInitError) {
|
||||
String errorMessage = message.isEmpty() ? MyBMWConstants.STATUS_VEHICLE_RETRIEVAL_ERROR : message;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public void vehicleQuotaDiscoveryError(Instant nextQuota) {
|
||||
logger.trace("MyBMWBridgeHandler.vehicleQuotaDiscoveryError");
|
||||
if (!tokenInitError) {
|
||||
String timeString = DateTimeFormatter.ofPattern("HH:mm:ss")
|
||||
.format(LocalDateTime.ofInstant(nextQuota, ZoneId.systemDefault()));
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
(MyBMWConstants.STATUS_QUOTA_ERROR + " [%s]").formatted(timeString));
|
||||
}
|
||||
}
|
||||
|
||||
public void vehicleDiscoverySuccess() {
|
||||
logger.trace("MyBMWBridgeHandler.vehicleDiscoverySuccess");
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
private void discoverVehicles() {
|
||||
logger.trace("MyBMWBridgeHandler.requestVehicles");
|
||||
|
||||
vehicleDiscovery.ifPresent(discovery -> discovery.discoverVehicles());
|
||||
}
|
||||
|
||||
public void tokenInitError() {
|
||||
Configuration config = super.editConfiguration();
|
||||
config.remove("hcaptchatoken");
|
||||
super.updateConfiguration(config);
|
||||
|
||||
authServlet.ifPresent(servlet -> servlet.dispose());
|
||||
MyBMWAuthServlet servlet = new MyBMWAuthServlet(this, getConfigAs(MyBMWBridgeConfiguration.class).getRegion(),
|
||||
httpService);
|
||||
servlet.startListening();
|
||||
this.authServlet = Optional.of(servlet);
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
(MyBMWConstants.STATUS_AUTH_NEEDED + " [ \"http(s)://<YOUROPENHAB>:<YOURPORT>%s\" ]")
|
||||
.formatted(servlet.getPath()));
|
||||
tokenInitError = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
logger.trace("MyBMWBridgeHandler.getServices");
|
||||
return List.of(VehicleDiscovery.class);
|
||||
}
|
||||
|
||||
public Optional<MyBMWProxy> getMyBmwProxy() {
|
||||
// wait for initialization to complete
|
||||
try {
|
||||
isInitialized.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
logger.debug("exception waiting for bridge initialization: {}", e.toString());
|
||||
}
|
||||
return myBmwProxy;
|
||||
}
|
||||
|
||||
public void setHCaptchaToken(String hCaptchaToken) {
|
||||
Configuration config = super.editConfiguration();
|
||||
config.put("hcaptchatoken", hCaptchaToken);
|
||||
super.updateConfiguration(config);
|
||||
|
||||
if (!hCaptchaToken.isEmpty()) {
|
||||
initializerJob.ifPresent(job -> job.cancel(true));
|
||||
authServlet.ifPresent(servlet -> servlet.dispose());
|
||||
authServlet = Optional.empty();
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of command options while leaving other state description fields as original.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicCommandDescriptionProvider.class, MyBMWCommandOptionProvider.class })
|
||||
public class MyBMWCommandOptionProvider extends BaseDynamicCommandDescriptionProvider {
|
||||
@Activate
|
||||
public MyBMWCommandOptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.ExecutionState;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link RemoteServiceExecutor} handles executions of remote services
|
||||
* towards your Vehicle
|
||||
*
|
||||
* @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - rename and refactor for v2
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteServiceExecutor {
|
||||
private final Logger logger = LoggerFactory.getLogger(RemoteServiceExecutor.class);
|
||||
|
||||
private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
|
||||
private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
|
||||
|
||||
private final MyBMWProxy proxy;
|
||||
private final VehicleHandler handler;
|
||||
|
||||
private int counter = 0;
|
||||
private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
|
||||
private Optional<String> serviceExecuting = Optional.empty();
|
||||
private Optional<String> executingEventId = Optional.empty();
|
||||
|
||||
public RemoteServiceExecutor(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
|
||||
handler = vehicleHandler;
|
||||
proxy = myBmwProxy;
|
||||
}
|
||||
|
||||
public boolean execute(RemoteService service) {
|
||||
synchronized (this) {
|
||||
if (serviceExecuting.isPresent()) {
|
||||
logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
|
||||
// only one service executing
|
||||
return false;
|
||||
}
|
||||
serviceExecuting = Optional.of(service.getId());
|
||||
}
|
||||
try {
|
||||
ExecutionStatusContainer executionStatus = proxy.executeRemoteServiceCall(
|
||||
handler.getVehicleConfiguration().get().getVin(),
|
||||
handler.getVehicleConfiguration().get().getVehicleBrand(), service);
|
||||
handleRemoteExecution(executionStatus);
|
||||
} catch (NetworkException e) {
|
||||
handleRemoteServiceException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void getState() {
|
||||
synchronized (this) {
|
||||
serviceExecuting.ifPresentOrElse(service -> {
|
||||
if (counter >= GIVEUP_COUNTER) {
|
||||
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.TIMEOUT.name().toLowerCase());
|
||||
reset();
|
||||
// immediately refresh data
|
||||
handler.updateData();
|
||||
} else {
|
||||
counter++;
|
||||
try {
|
||||
ExecutionStatusContainer executionStatusContainer = proxy.executeRemoteServiceStatusCall(
|
||||
handler.getVehicleConfiguration().get().getVehicleBrand(), executingEventId.get());
|
||||
handleRemoteExecution(executionStatusContainer);
|
||||
} catch (NetworkException e) {
|
||||
handleRemoteServiceException(e);
|
||||
}
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("No Service executed to get state");
|
||||
});
|
||||
stateJob = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoteServiceException(NetworkException e) {
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(e.getStatus()));
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRemoteExecution(ExecutionStatusContainer executionStatusContainer) {
|
||||
if (!executionStatusContainer.getEventId().isEmpty()) {
|
||||
// service initiated - store event id for further MyBMW updates
|
||||
executingEventId = Optional.of(executionStatusContainer.getEventId());
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.INITIATED.name().toLowerCase());
|
||||
} else if (!executionStatusContainer.getEventStatus().isEmpty()) {
|
||||
// service status updated
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
executionStatusContainer.getEventStatus().toLowerCase());
|
||||
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
|
||||
|| ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
|
||||
// refresh loop ends - update of status handled in the normal refreshInterval.
|
||||
// Earlier update doesn't show better results!
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// schedule even if no result is present until retries exceeded
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(job -> {
|
||||
if (!job.isDone()) {
|
||||
job.cancel(true);
|
||||
}
|
||||
});
|
||||
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
serviceExecuting = Optional.empty();
|
||||
executingEventId = Optional.empty();
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
synchronized (this) {
|
||||
stateJob.ifPresent(action -> {
|
||||
if (!action.isDone()) {
|
||||
action.cancel(true);
|
||||
}
|
||||
stateJob = Optional.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,949 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Length;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWVehicleConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSession;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.CheckControlMessage;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleDoorsState;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleLocation;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleRoofState;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleState;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleTireStates;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleWindowsState;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils.TimedChannel;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
|
||||
import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
|
||||
import org.openhab.core.i18n.LocationProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleHandler} handles responses from BMW API
|
||||
*
|
||||
* the introduction of channelToBeUpdated is ugly, but if there is a refresh of one channel, always all channels were
|
||||
* updated
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send charge profile
|
||||
* @author Martin Grassl - refactoring, merge with VehicleChannelHandler
|
||||
* @author Mark Herwege - refactoring, V2 API charging
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
|
||||
|
||||
private boolean hasFuel = false;
|
||||
private boolean isElectric = false;
|
||||
private boolean isHybrid = false;
|
||||
|
||||
// List Interfaces
|
||||
private volatile List<RequiredService> serviceList = List.of();
|
||||
private volatile String selectedService = Constants.UNDEF;
|
||||
private volatile List<CheckControlMessage> checkControlList = List.of();
|
||||
private volatile String selectedCC = Constants.UNDEF;
|
||||
private volatile List<ChargingSession> sessionList = List.of();
|
||||
private volatile String selectedSession = Constants.UNDEF;
|
||||
|
||||
private MyBMWCommandOptionProvider commandOptionProvider;
|
||||
private LocationProvider locationProvider;
|
||||
private TimeZoneProvider timeZoneProvider;
|
||||
|
||||
// Data Caches
|
||||
private Optional<VehicleStateContainer> vehicleStatusCache = Optional.empty();
|
||||
private Optional<byte[]> imageCache = Optional.empty();
|
||||
|
||||
private Optional<MyBMWProxy> proxy = Optional.empty();
|
||||
private Optional<RemoteServiceExecutor> remote = Optional.empty();
|
||||
private Optional<MyBMWVehicleConfiguration> vehicleConfiguration = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
|
||||
private Optional<ScheduledFuture<?>> editTimeout = Optional.empty();
|
||||
|
||||
private ImageProperties imageProperties = new ImageProperties();
|
||||
|
||||
private ThingStatus currentStatus = ThingStatus.UNKNOWN;
|
||||
|
||||
public VehicleHandler(Thing thing, MyBMWCommandOptionProvider commandOptionProvider,
|
||||
LocationProvider locationProvider, TimeZoneProvider timeZoneProvider, String driveTrain) {
|
||||
super(thing);
|
||||
logger.trace("VehicleHandler.constructor {}, {}", thing.getUID(), driveTrain);
|
||||
this.commandOptionProvider = commandOptionProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.locationProvider = locationProvider;
|
||||
if (locationProvider.getLocation() == null) {
|
||||
logger.debug("Home location not available");
|
||||
}
|
||||
|
||||
hasFuel = driveTrain.equals(VehicleType.CONVENTIONAL.toString())
|
||||
|| driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
|
||||
|| driveTrain.equals(VehicleType.MILD_HYBRID.toString());
|
||||
isElectric = driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
|
||||
|| driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
|
||||
|| driveTrain.equals(VehicleType.ELECTRIC.toString());
|
||||
isHybrid = hasFuel && isElectric;
|
||||
|
||||
setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
|
||||
}
|
||||
|
||||
private void setOptions(final String group, final String id, List<CommandOption> options) {
|
||||
commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.trace("VehicleHandler.initialize");
|
||||
currentStatus = ThingStatus.UNKNOWN;
|
||||
updateStatus(currentStatus);
|
||||
vehicleConfiguration = Optional.of(getConfigAs(MyBMWVehicleConfiguration.class));
|
||||
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if (handler != null) {
|
||||
// Can be long running if we have to wait for the bridge to be initialized
|
||||
scheduler.submit(() -> {
|
||||
proxy = ((MyBMWBridgeHandler) handler).getMyBmwProxy();
|
||||
remote = Optional.of(new RemoteServiceExecutor(this, proxy.get()));
|
||||
|
||||
imageProperties = new ImageProperties();
|
||||
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT,
|
||||
Converter.toTitleCase(imageProperties.viewport), null);
|
||||
|
||||
startSchedule(vehicleConfiguration.get().getRefreshInterval());
|
||||
});
|
||||
} else {
|
||||
logger.debug("Bridge Handler null");
|
||||
}
|
||||
} else {
|
||||
logger.debug("Bridge null");
|
||||
}
|
||||
}
|
||||
|
||||
private void startSchedule(int interval) {
|
||||
// start update schedule only if the refreshInterval is not 0
|
||||
if (interval > 0) {
|
||||
logger.info("VehicleHandler.startSchedule with interval {}min", interval);
|
||||
refreshJob.ifPresentOrElse(job -> {
|
||||
if (job.isCancelled()) {
|
||||
refreshJob = Optional
|
||||
.of(scheduler.scheduleWithFixedDelay(this::updateData, 0, interval, TimeUnit.MINUTES));
|
||||
} // else - scheduler is already running!
|
||||
}, () -> {
|
||||
refreshJob = Optional
|
||||
.of(scheduler.scheduleWithFixedDelay(this::updateData, 0, interval, TimeUnit.MINUTES));
|
||||
});
|
||||
} else {
|
||||
logger.info("VehicleHandler initialize: don't start schedule as interval is 0");
|
||||
updateData();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.trace("VehicleHandler.dispose");
|
||||
refreshJob.ifPresent(job -> job.cancel(true));
|
||||
editTimeout.ifPresent(job -> job.cancel(true));
|
||||
remote.ifPresent(RemoteServiceExecutor::cancel);
|
||||
}
|
||||
|
||||
/**
|
||||
* update all data
|
||||
*/
|
||||
void updateData() {
|
||||
logger.trace("VehicleHandler.updateData");
|
||||
updateVehicleStatus();
|
||||
if (isElectric) {
|
||||
updateCharging();
|
||||
}
|
||||
updateImage();
|
||||
}
|
||||
|
||||
private void updateVehicleStatus() {
|
||||
proxy.ifPresentOrElse(prox -> {
|
||||
vehicleConfiguration.ifPresentOrElse(config -> {
|
||||
try {
|
||||
VehicleStateContainer vehicleState = prox.requestVehicleState(config.getVin(),
|
||||
config.getVehicleBrand());
|
||||
triggerVehicleStatusUpdate(vehicleState, null);
|
||||
currentStatus = ThingStatus.ONLINE;
|
||||
updateStatus(currentStatus);
|
||||
} catch (NetworkException e) {
|
||||
logger.debug("{}", e.toString());
|
||||
currentStatus = ThingStatus.OFFLINE;
|
||||
updateStatus(currentStatus, ThingStatusDetail.COMMUNICATION_ERROR, "Vehicle State Update failed");
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("MyBMW Vehicle Configuration isn't present");
|
||||
});
|
||||
}, () -> {
|
||||
logger.warn("MyBMWProxy isn't present");
|
||||
});
|
||||
}
|
||||
|
||||
private void updateCharging() {
|
||||
proxy.ifPresentOrElse(prox -> {
|
||||
vehicleConfiguration.ifPresentOrElse(config -> {
|
||||
if (isElectric && ThingStatus.ONLINE.equals(currentStatus)) {
|
||||
try {
|
||||
updateChargingStatistics(
|
||||
prox.requestChargeStatistics(config.getVin(), config.getVehicleBrand()), null);
|
||||
updateChargingSessions(prox.requestChargeSessions(config.getVin(), config.getVehicleBrand()),
|
||||
null);
|
||||
} catch (NetworkException e) {
|
||||
logger.debug("{}", e.toString());
|
||||
}
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("MyBMW Vehicle Configuration isn't present");
|
||||
});
|
||||
}, () -> {
|
||||
logger.warn("MyBMWProxy isn't present");
|
||||
});
|
||||
}
|
||||
|
||||
private void updateImage() {
|
||||
proxy.ifPresentOrElse(prox -> {
|
||||
vehicleConfiguration.ifPresentOrElse(config -> {
|
||||
if (!imageCache.isPresent() && !imageProperties.failLimitReached()
|
||||
&& ThingStatus.ONLINE.equals(currentStatus)) {
|
||||
try {
|
||||
updateImage(prox.requestImage(config.getVin(), config.getVehicleBrand(), imageProperties));
|
||||
} catch (NetworkException e) {
|
||||
logger.debug("{}", e.toString());
|
||||
}
|
||||
}
|
||||
}, () -> {
|
||||
logger.warn("MyBMW Vehicle Configuration isn't present");
|
||||
});
|
||||
}, () -> {
|
||||
logger.warn("MyBMWProxy isn't present");
|
||||
});
|
||||
}
|
||||
|
||||
private void triggerVehicleStatusUpdate(VehicleStateContainer vehicleState, @Nullable String channelToBeUpdated) {
|
||||
logger.trace("VehicleHandler.triggerVehicleStatusUpdate for {}", channelToBeUpdated);
|
||||
if (vehicleConfiguration.isPresent()) {
|
||||
vehicleStatusCache = Optional.of(vehicleState);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, RAW, vehicleState.getRawStateJson(), channelToBeUpdated);
|
||||
|
||||
updateVehicleStatus(vehicleState.getState(), channelToBeUpdated);
|
||||
if (isElectric) {
|
||||
updateChargingProfile(vehicleState.getState().getChargingProfile(), channelToBeUpdated);
|
||||
}
|
||||
} else {
|
||||
logger.debug("configuration not present");
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRemoteExecutionStatus(@Nullable String service, String status) {
|
||||
updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
|
||||
(service == null ? "-" : service) + Constants.SPACE + status.toLowerCase(), null);
|
||||
}
|
||||
|
||||
public Optional<MyBMWVehicleConfiguration> getVehicleConfiguration() {
|
||||
logger.trace("VehicleHandler.getVehicleConfiguration");
|
||||
return vehicleConfiguration;
|
||||
}
|
||||
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
logger.trace("VehicleHandler.getScheduler");
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private void updateChannel(final String group, final String id, final String state,
|
||||
@Nullable final String channelToBeUpdated) {
|
||||
updateChannel(group, id, StringType.valueOf(state), channelToBeUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
* this method sets the state for a single channel. if a channelToBeUpdated is provided, the update will only take
|
||||
* place for that single channel.
|
||||
*/
|
||||
private void updateChannel(final String group, final String id, final State state,
|
||||
@Nullable final String channelToBeUpdated) {
|
||||
if (channelToBeUpdated == null || id.equals(channelToBeUpdated)) {
|
||||
if (!"png".equals(id)) {
|
||||
logger.trace("updating channel {}, {}, {}", group, id, state.toFullString());
|
||||
} else {
|
||||
logger.trace("updating channel {}, {}, {}", group, id, "not printed");
|
||||
}
|
||||
|
||||
updateState(new ChannelUID(thing.getUID(), group, id), state);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChargingStatistics(ChargingStatisticsContainer chargingStatisticsContainer,
|
||||
@Nullable String channelToBeUpdated) {
|
||||
if (!"".equals(chargingStatisticsContainer.getDescription())) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, chargingStatisticsContainer.getDescription(),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY, QuantityType
|
||||
.valueOf(chargingStatisticsContainer.getStatistics().getTotalEnergyCharged(), Units.KILOWATT_HOUR),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
|
||||
DecimalType.valueOf(Integer
|
||||
.toString(chargingStatisticsContainer.getStatistics().getNumberOfChargingSessions())),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the channels with the current state of the vehicle
|
||||
*
|
||||
* @param vehicleStateState
|
||||
*/
|
||||
private void updateVehicleStatus(VehicleState vehicleStateState, @Nullable String channelToBeUpdated) {
|
||||
boolean isLeftSteering = vehicleStateState.isLeftSteering();
|
||||
|
||||
updateVehicleOverallStatus(vehicleStateState, channelToBeUpdated);
|
||||
updateRange(vehicleStateState, channelToBeUpdated);
|
||||
updateDoors(vehicleStateState.getDoorsState(), isLeftSteering, channelToBeUpdated);
|
||||
updateWindows(vehicleStateState.getWindowsState(), isLeftSteering, channelToBeUpdated);
|
||||
updateRoof(vehicleStateState.getRoofState(), channelToBeUpdated);
|
||||
updatePosition(vehicleStateState.getLocation(), channelToBeUpdated);
|
||||
updateServices(vehicleStateState.getRequiredServices(), channelToBeUpdated);
|
||||
updateCheckControls(vehicleStateState.getCheckControlMessages(), channelToBeUpdated);
|
||||
updateTires(vehicleStateState.getTireState(), channelToBeUpdated);
|
||||
}
|
||||
|
||||
private void updateTires(@Nullable VehicleTireStates vehicleTireStates, @Nullable String channelToBeUpdated) {
|
||||
if (vehicleTireStates == null) {
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
|
||||
calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getCurrentPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
|
||||
calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getTargetPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
|
||||
calculatePressure(vehicleTireStates.getFrontRight().getStatus().getCurrentPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
|
||||
calculatePressure(vehicleTireStates.getFrontRight().getStatus().getTargetPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
|
||||
calculatePressure(vehicleTireStates.getRearLeft().getStatus().getCurrentPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
|
||||
calculatePressure(vehicleTireStates.getRearLeft().getStatus().getTargetPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
|
||||
calculatePressure(vehicleTireStates.getRearRight().getStatus().getCurrentPressure()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
|
||||
calculatePressure(vehicleTireStates.getRearRight().getStatus().getTargetPressure()),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if the pressure is undef it is < 0
|
||||
*
|
||||
* @param pressure
|
||||
* @return
|
||||
*/
|
||||
private State calculatePressure(int pressure) {
|
||||
if (pressure > 0) {
|
||||
return QuantityType.valueOf(pressure / 100.0, Units.BAR);
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVehicleOverallStatus(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
|
||||
updateChannel(CHANNEL_GROUP_STATUS, LOCK,
|
||||
Converter.toTitleCase(vehicleState.getDoorsState().getCombinedSecurityState()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
|
||||
VehicleStatusUtils.getNextServiceDate(vehicleState.getRequiredServices()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
|
||||
VehicleStatusUtils.getNextServiceMileage(vehicleState.getRequiredServices()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
|
||||
Converter.toTitleCase(vehicleState.getOverallCheckControlStatus()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
|
||||
Converter.zonedToLocalDateTime(vehicleState.getLastUpdatedAt(), timeZoneProvider.getTimeZone()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, LAST_FETCHED,
|
||||
Converter.zonedToLocalDateTime(vehicleState.getLastFetched(), timeZoneProvider.getTimeZone()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, DOORS,
|
||||
Converter.toTitleCase(vehicleState.getDoorsState().getCombinedState()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, WINDOWS,
|
||||
Converter.toTitleCase(vehicleState.getWindowsState().getCombinedState()), channelToBeUpdated);
|
||||
|
||||
if (isElectric) {
|
||||
updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
|
||||
Converter.getConnectionState(vehicleState.getElectricChargingState().isChargerConnected()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
|
||||
Converter.toTitleCase(vehicleState.getElectricChargingState().getChargingStatus()),
|
||||
channelToBeUpdated);
|
||||
|
||||
int remainingTime = vehicleState.getElectricChargingState().getRemainingChargingMinutes();
|
||||
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING,
|
||||
remainingTime >= 0 ? QuantityType.valueOf(remainingTime, Units.MINUTE) : UnDefType.UNDEF,
|
||||
channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRange(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
|
||||
// get the right unit
|
||||
Unit<Length> lengthUnit = Constants.KILOMETRE_UNIT;
|
||||
|
||||
if (isElectric) {
|
||||
int rangeElectric = vehicleState.getElectricChargingState().getRange();
|
||||
QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
|
||||
QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
|
||||
lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius, channelToBeUpdated);
|
||||
}
|
||||
|
||||
if (hasFuel && !isHybrid) {
|
||||
int rangeFuel = vehicleState.getCombustionFuelLevel().getRange();
|
||||
QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
|
||||
QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
|
||||
}
|
||||
|
||||
if (isHybrid) {
|
||||
int rangeCombined = vehicleState.getRange();
|
||||
|
||||
// there is a bug/feature in the API that the fuel range is the same like the combined range, hence in case
|
||||
// of hybrid the fuel range has to be subtracted by the electric range
|
||||
int rangeFuel = vehicleState.getCombustionFuelLevel().getRange()
|
||||
- vehicleState.getElectricChargingState().getRange();
|
||||
|
||||
QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
|
||||
QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
|
||||
lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius, channelToBeUpdated);
|
||||
|
||||
QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
|
||||
QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
|
||||
}
|
||||
|
||||
if (vehicleState.getCurrentMileage() == Constants.INT_UNDEF) {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
|
||||
QuantityType.valueOf(vehicleState.getCurrentMileage(), lengthUnit), channelToBeUpdated);
|
||||
}
|
||||
if (isElectric) {
|
||||
updateChannel(
|
||||
CHANNEL_GROUP_RANGE, SOC, QuantityType
|
||||
.valueOf(vehicleState.getElectricChargingState().getChargingLevelPercent(), Units.PERCENT),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
if (hasFuel) {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
|
||||
QuantityType.valueOf(vehicleState.getCombustionFuelLevel().getRemainingFuelLiters(), Units.LITRE),
|
||||
channelToBeUpdated);
|
||||
|
||||
if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
|
||||
&& vehicleState.getCombustionFuelLevel().getRange() > 1) {
|
||||
double estimatedFuelConsumption = vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() * 1.0
|
||||
/ vehicleState.getCombustionFuelLevel().getRange() * 100.0;
|
||||
updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM,
|
||||
DecimalType.valueOf(estimatedFuelConsumption + ""), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG,
|
||||
DecimalType.valueOf((235.214583 / estimatedFuelConsumption) + ""), channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG, UnDefType.UNDEF, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCheckControls(List<CheckControlMessage> checkControlMessages,
|
||||
@Nullable String channelToBeUpdated) {
|
||||
if (checkControlMessages.isEmpty()) {
|
||||
// No Check Control available - show not active
|
||||
CheckControlMessage checkControlMessage = new CheckControlMessage();
|
||||
checkControlMessage.setName(Constants.NO_ENTRIES);
|
||||
checkControlMessage.setDescription(Constants.NO_ENTRIES);
|
||||
checkControlMessage.setSeverity(Constants.NO_ENTRIES);
|
||||
checkControlMessage.setType(Constants.NO_ENTRIES);
|
||||
checkControlMessage.setId(-1);
|
||||
checkControlMessages.add(checkControlMessage);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
checkControlList = checkControlMessages;
|
||||
List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (CheckControlMessage checkControlMessage : checkControlList) {
|
||||
ccmDescriptionOptions.add(
|
||||
new CommandOption(Integer.toString(index), Converter.toTitleCase(checkControlMessage.getType())));
|
||||
if (selectedCC.equals(checkControlMessage.getType())) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectCheckControl(0, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectCheckControl(int index, @Nullable String channelToBeUpdated) {
|
||||
if (index >= 0 && index < checkControlList.size()) {
|
||||
CheckControlMessage checkControlMessage = checkControlList.get(index);
|
||||
selectedCC = checkControlMessage.getType();
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, Converter.toTitleCase(checkControlMessage.getType()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS,
|
||||
StringType.valueOf(checkControlMessage.getDescription()), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY,
|
||||
Converter.toTitleCase(checkControlMessage.getSeverity()), channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateServices(List<RequiredService> requiredServiceList, @Nullable String channelToBeUpdated) {
|
||||
// if list is empty add "undefined" element
|
||||
if (requiredServiceList.isEmpty()) {
|
||||
RequiredService requiredService = new RequiredService();
|
||||
requiredService.setType(Constants.NO_ENTRIES);
|
||||
requiredService.setDescription(Constants.NO_ENTRIES);
|
||||
requiredServiceList.add(requiredService);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
serviceList = requiredServiceList;
|
||||
List<CommandOption> serviceNameOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (RequiredService requiredService : requiredServiceList) {
|
||||
// create StateOption with "value = list index" and "label = human readable
|
||||
// string"
|
||||
serviceNameOptions
|
||||
.add(new CommandOption(Integer.toString(index), Converter.toTitleCase(requiredService.getType())));
|
||||
if (selectedService.equals(requiredService.getType())) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectService(0, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectService(int index, @Nullable String channelToBeUpdated) {
|
||||
if (index >= 0 && index < serviceList.size()) {
|
||||
RequiredService serviceEntry = serviceList.get(index);
|
||||
selectedService = serviceEntry.getType();
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, NAME, Converter.toTitleCase(serviceEntry.getType()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, DETAILS, StringType.valueOf(serviceEntry.getDescription()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, DATE,
|
||||
Converter.zonedToLocalDateTime(serviceEntry.getDateTime(), timeZoneProvider.getTimeZone()),
|
||||
channelToBeUpdated);
|
||||
|
||||
if (serviceEntry.getMileage() > 0) {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
|
||||
QuantityType.valueOf(serviceEntry.getMileage(), Constants.KILOMETRE_UNIT), channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChargingSessions(ChargingSessionsContainer chargeSessionsContainer,
|
||||
@Nullable String channelToBeUpdated) {
|
||||
List<ChargingSession> chargeSessions = new ArrayList<>();
|
||||
|
||||
if (chargeSessionsContainer.chargingSessions != null
|
||||
&& chargeSessionsContainer.chargingSessions.getSessions() != null
|
||||
&& !chargeSessionsContainer.chargingSessions.getSessions().isEmpty()) {
|
||||
chargeSessions.addAll(chargeSessionsContainer.chargingSessions.getSessions());
|
||||
} else {
|
||||
// if list is empty add "undefined" element
|
||||
ChargingSession cs = new ChargingSession();
|
||||
cs.setTitle(Constants.NO_ENTRIES);
|
||||
chargeSessions.add(cs);
|
||||
}
|
||||
|
||||
// add all elements to options
|
||||
sessionList = chargeSessions;
|
||||
List<CommandOption> sessionNameOptions = new ArrayList<>();
|
||||
boolean isSelectedElementIn = false;
|
||||
int index = 0;
|
||||
for (ChargingSession session : sessionList) {
|
||||
// create StateOption with "value = list index" and "label = human readable
|
||||
// string"
|
||||
sessionNameOptions.add(new CommandOption(Integer.toString(index), session.getTitle()));
|
||||
if (selectedSession.equals(session.getTitle())) {
|
||||
isSelectedElementIn = true;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
|
||||
|
||||
// if current selected item isn't anymore in the list select first entry
|
||||
if (!isSelectedElementIn) {
|
||||
selectSession(0, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectSession(int index, @Nullable String channelToBeUpdated) {
|
||||
if (index >= 0 && index < sessionList.size()) {
|
||||
ChargingSession sessionEntry = sessionList.get(index);
|
||||
selectedSession = sessionEntry.getTitle();
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.getTitle()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.getSubtitle()),
|
||||
channelToBeUpdated);
|
||||
if (sessionEntry.getEnergyCharged() != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.getEnergyCharged()),
|
||||
channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
if (sessionEntry.getIssues() != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.getIssues()),
|
||||
channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.getSessionStatus()),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChargingProfile(ChargingProfile cp, @Nullable String channelToBeUpdated) {
|
||||
ChargingProfileWrapper cpw = new ChargingProfileWrapper(cp);
|
||||
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()),
|
||||
channelToBeUpdated);
|
||||
ChargingSettings cs = cpw.getChargingSettings();
|
||||
if (cs != null) {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
|
||||
QuantityType.valueOf(cs.getTargetSoc(), Units.PERCENT), channelToBeUpdated);
|
||||
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
|
||||
OnOffType.from(cs.isAcCurrentLimitActive()), channelToBeUpdated);
|
||||
}
|
||||
final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
|
||||
climate == null ? UnDefType.UNDEF : OnOffType.from(climate), channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.WINDOWSTART, channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.WINDOWEND, channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.TIMER1, channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.TIMER2, channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.TIMER3, channelToBeUpdated);
|
||||
updateTimedState(cpw, ProfileKey.TIMER4, channelToBeUpdated);
|
||||
}
|
||||
|
||||
private void updateTimedState(ChargingProfileWrapper profile, ProfileKey key, @Nullable String channelToBeUpdated) {
|
||||
final TimedChannel timed = ChargingProfileUtils.getTimedChannel(key);
|
||||
if (timed != null) {
|
||||
final LocalTime time = profile.getTime(key);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
|
||||
time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
|
||||
: new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())),
|
||||
channelToBeUpdated);
|
||||
if (timed.timer != null) {
|
||||
final Boolean enabled = profile.isEnabled(key);
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
|
||||
enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled), channelToBeUpdated);
|
||||
if (timed.hasDays) {
|
||||
final Set<DayOfWeek> days = profile.getDays(key);
|
||||
EnumSet.allOf(DayOfWeek.class).forEach(day -> {
|
||||
updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
|
||||
timed.timer + ChargingProfileUtils.getDaysChannel(day),
|
||||
days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)),
|
||||
channelToBeUpdated);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDoors(VehicleDoorsState vehicleDoorsState, boolean isLeftSteering,
|
||||
@Nullable String channelToBeUpdated) {
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleDoorsState.getLeftFront() : vehicleDoorsState.getRightFront())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleDoorsState.getLeftRear() : vehicleDoorsState.getRightRear())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleDoorsState.getRightFront() : vehicleDoorsState.getLeftFront())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleDoorsState.getRightRear() : vehicleDoorsState.getLeftRear())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, TRUNK,
|
||||
StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getTrunk())), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getHood())),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
|
||||
private void updateWindows(VehicleWindowsState vehicleWindowState, boolean isLeftSteering,
|
||||
@Nullable String channelToBeUpdated) {
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleWindowState.getLeftFront() : vehicleWindowState.getRightFront())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleWindowState.getLeftRear() : vehicleWindowState.getRightRear())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleWindowState.getRightFront() : vehicleWindowState.getLeftFront())),
|
||||
channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
|
||||
StringType.valueOf(Converter.toTitleCase(
|
||||
isLeftSteering ? vehicleWindowState.getRightRear() : vehicleWindowState.getLeftRear())),
|
||||
channelToBeUpdated);
|
||||
}
|
||||
|
||||
private void updateRoof(VehicleRoofState vehicleRoofState, @Nullable String channelToBeUpdated) {
|
||||
updateChannel(CHANNEL_GROUP_DOORS, SUNROOF,
|
||||
StringType.valueOf(Converter.toTitleCase(vehicleRoofState.getRoofState())), channelToBeUpdated);
|
||||
}
|
||||
|
||||
private void updatePosition(VehicleLocation location, @Nullable String channelToBeUpdated) {
|
||||
if (location.getCoordinates().getLatitude() < 0) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
|
||||
} else {
|
||||
PointType vehicleLocation = PointType.valueOf(Double.toString(location.getCoordinates().getLatitude()) + ","
|
||||
+ Double.toString(location.getCoordinates().getLongitude()));
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation, channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HEADING,
|
||||
QuantityType.valueOf(location.getHeading(), Units.DEGREE_ANGLE), channelToBeUpdated);
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(location.getAddress().getFormatted()),
|
||||
channelToBeUpdated);
|
||||
PointType homeLocation = locationProvider.getLocation();
|
||||
if (homeLocation != null) {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
|
||||
QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE),
|
||||
channelToBeUpdated);
|
||||
} else {
|
||||
updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("VehicleHandler.handleCommand {}, {}, {}", command.toFullString(), channelUID.getAsString(),
|
||||
channelUID.getIdWithoutGroup());
|
||||
String group = channelUID.getGroupId();
|
||||
|
||||
if (group == null) {
|
||||
logger.debug("Cannot handle command {}, no group for channel {}", command.toFullString(),
|
||||
channelUID.getAsString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
// Refresh of Channels with cached values
|
||||
if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
|
||||
imageCache.ifPresent(image -> updateImage(image));
|
||||
} else {
|
||||
vehicleStatusCache.ifPresent(
|
||||
vehicleStatus -> triggerVehicleStatusUpdate(vehicleStatus, channelUID.getIdWithoutGroup()));
|
||||
}
|
||||
} else if (command instanceof StringType) {
|
||||
// Check for Channel Group and corresponding Actions
|
||||
switch (group) {
|
||||
case CHANNEL_GROUP_REMOTE:
|
||||
// Executing Remote Services
|
||||
String serviceCommand = ((StringType) command).toFullString();
|
||||
remote.ifPresent(remot -> {
|
||||
RemoteServiceUtils.getRemoteServiceFromCommand(serviceCommand)
|
||||
.ifPresentOrElse(service -> remot.execute(service), () -> {
|
||||
logger.debug("Remote service execution {} unknown", serviceCommand);
|
||||
});
|
||||
});
|
||||
break;
|
||||
case CHANNEL_GROUP_VEHICLE_IMAGE:
|
||||
// Image Change
|
||||
vehicleConfiguration.ifPresent(config -> {
|
||||
if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
|
||||
String newViewport = command.toString();
|
||||
synchronized (imageProperties) {
|
||||
if (!imageProperties.viewport.equals(newViewport)) {
|
||||
imageProperties = new ImageProperties(newViewport);
|
||||
imageCache = Optional.empty();
|
||||
Optional<byte[]> imageContent = proxy.map(prox -> {
|
||||
try {
|
||||
return prox.requestImage(config.getVin(), config.getVehicleBrand(),
|
||||
imageProperties);
|
||||
} catch (NetworkException e) {
|
||||
logger.debug("{}", e.toString());
|
||||
return "".getBytes();
|
||||
}
|
||||
});
|
||||
imageContent.ifPresent(imageContentData -> updateImage(imageContentData));
|
||||
}
|
||||
}
|
||||
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport),
|
||||
IMAGE_VIEWPORT);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case CHANNEL_GROUP_SERVICE:
|
||||
int serviceIndex = Converter.parseIntegerString(command.toFullString());
|
||||
if (serviceIndex != -1) {
|
||||
selectService(serviceIndex, null);
|
||||
} else {
|
||||
logger.debug("Cannot select Service index {}", command.toFullString());
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GROUP_CHECK_CONTROL:
|
||||
int checkControlIndex = Converter.parseIntegerString(command.toFullString());
|
||||
if (checkControlIndex != -1) {
|
||||
selectCheckControl(checkControlIndex, null);
|
||||
} else {
|
||||
logger.debug("Cannot select CheckControl index {}", command.toFullString());
|
||||
}
|
||||
break;
|
||||
case CHANNEL_GROUP_CHARGE_SESSION:
|
||||
int sessionIndex = Converter.parseIntegerString(command.toFullString());
|
||||
if (sessionIndex != -1) {
|
||||
selectSession(sessionIndex, null);
|
||||
} else {
|
||||
logger.debug("Cannot select Session index {}", command.toFullString());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Cannot handle command {}, channel {} in group {} not a command channel",
|
||||
command.toFullString(), channelUID.getAsString(), group);
|
||||
}
|
||||
} else if (command instanceof OnOffType) {
|
||||
if (CHANNEL_GROUP_UPDATE.equals(group) && OnOffType.ON.equals(command)) {
|
||||
// triggering the update of the respective channel
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case STATE_UPDATE:
|
||||
updateVehicleStatus();
|
||||
break;
|
||||
case CHARGING_UPDATE:
|
||||
updateCharging();
|
||||
break;
|
||||
case IMAGE_UPDATE:
|
||||
updateImage();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateImage(byte[] imageContent) {
|
||||
if (imageContent.length > 0) {
|
||||
imageCache = Optional.of(imageContent);
|
||||
String contentType = HttpUtil.guessContentTypeFromData(imageContent);
|
||||
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(imageContent, contentType),
|
||||
IMAGE_FORMAT);
|
||||
} else {
|
||||
synchronized (imageProperties) {
|
||||
imageProperties.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.auth;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.osgi.service.http.HttpService;
|
||||
import org.osgi.service.http.NamespaceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link MyBMWAuthServlet} provides captcha html pages
|
||||
*
|
||||
* @author Mark Herwege - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWAuthServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 3817341543768551687L;
|
||||
private static final String CONTENT_TYPE = "text/html;charset=UTF-8";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWAuthServlet.class);
|
||||
private final @NonNullByDefault({}) ClassLoader classLoader = MyBMWAuthServlet.class.getClassLoader();
|
||||
private final HttpService httpService;
|
||||
private final MyBMWBridgeHandler bridgeHandler;
|
||||
private String path = "";
|
||||
private String captchaHtml = "";
|
||||
|
||||
public MyBMWAuthServlet(MyBMWBridgeHandler bridgeHandler, String region, HttpService httpService) {
|
||||
this.httpService = httpService;
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.path = MyBMWConstants.LOCAL_OPENHAB_BASE_PATH + bridgeHandler.getThing().getUID().getAsString();
|
||||
String captchaTemplate = MyBMWConstants.CAPTCHA_HTML.get(region);
|
||||
if (captchaTemplate != null) {
|
||||
try (InputStream stream = classLoader.getResourceAsStream(captchaTemplate)) {
|
||||
captchaHtml = stream != null ? new String(stream.readAllBytes(), StandardCharsets.UTF_8) : "";
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("No captcha html found for region " + region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
logger.debug("myBMW auth servlet received GET request {}.", request.getRequestURI());
|
||||
StringBuffer requestUrl = request.getRequestURL();
|
||||
if (requestUrl != null) {
|
||||
response.setContentType(CONTENT_TYPE);
|
||||
response.getWriter().append(captchaHtml);
|
||||
response.getWriter().close();
|
||||
} else {
|
||||
logger.warn("Unexpected: GET requestUrl is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
String line;
|
||||
try (BufferedReader reader = request.getReader()) {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
buffer.append(line);
|
||||
}
|
||||
logger.trace("myBMW auth servlet received POST content: {}", buffer.toString());
|
||||
this.bridgeHandler.setHCaptchaToken(buffer.toString().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public void startListening() {
|
||||
try {
|
||||
httpService.registerServlet(path, this, null, httpService.createDefaultHttpContext());
|
||||
logger.info("Registered myBMW servlet at '{}'", path);
|
||||
} catch (NamespaceException | ServletException e) {
|
||||
logger.warn("Registering servlet failed:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
logger.debug("Stopping myBMW Servlet {}", path);
|
||||
httpService.unregister(path);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.auth;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.*;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.auth.OAuthSettingsQueryResponse;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.RandomUserAgentVersionGenerator;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.openhab.core.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* requests the tokens for MyBMW API authorization
|
||||
*
|
||||
* thanks to bimmer_connected
|
||||
* https://github.com/bimmerconnected/bimmer_connected
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted from myBmwProxy
|
||||
* @author Mark Herwege - refactor to use OAuthFactory
|
||||
* @author Mark Herwege - remove China
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWTokenController {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWTokenController.class);
|
||||
|
||||
private MyBMWBridgeHandler bridgeHandler;
|
||||
private MyBMWBridgeConfiguration bridgeConfiguration;
|
||||
private final HttpClient httpClient;
|
||||
private final OAuthFactory oAuthFactory;
|
||||
|
||||
private String oAuthClientServiceId;
|
||||
private @Nullable OAuthClientService oAuthClientService = null;
|
||||
private AccessTokenResponse tokenResponse = new AccessTokenResponse();
|
||||
private boolean waitingForInitialToken = false;
|
||||
|
||||
private static final String SESSION_ID = UUID.randomUUID().toString();
|
||||
|
||||
public MyBMWTokenController(MyBMWBridgeHandler bridgeHandler, MyBMWBridgeConfiguration configuration,
|
||||
HttpClient httpClient, OAuthFactory oAuthFactory) {
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
this.bridgeConfiguration = configuration;
|
||||
this.httpClient = httpClient;
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.oAuthClientServiceId = bridgeHandler.getThing().getUID().getAsString();
|
||||
|
||||
this.oAuthClientService = oAuthFactory.getOAuthClientService(oAuthClientServiceId);
|
||||
}
|
||||
|
||||
public synchronized void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets new token if old one is expired or invalid. In case of error the token remains. So if token refresh fails
|
||||
* the corresponding requests will also fail and update the Thing status accordingly.
|
||||
*
|
||||
* @return token
|
||||
*/
|
||||
public synchronized AccessTokenResponse getToken() {
|
||||
logger.trace("getToken");
|
||||
|
||||
if (waitingForInitialToken && !bridgeConfiguration.getHCaptchaToken().isBlank()) {
|
||||
// if the hCaptchaToken is available, then a new login is triggered
|
||||
logger.trace("initial login, using captchatoken {}", bridgeConfiguration.getHCaptchaToken());
|
||||
|
||||
boolean tokenCreationSuccess = loginROW();
|
||||
if (tokenCreationSuccess) {
|
||||
waitingForInitialToken = false;
|
||||
logger.trace("get inital token success");
|
||||
} else {
|
||||
logger.warn(
|
||||
"initial Authentication failed, maybe request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!");
|
||||
bridgeHandler.tokenInitError();
|
||||
logger.trace("get inital token failed");
|
||||
}
|
||||
|
||||
// reset the token as it times out
|
||||
bridgeHandler.setHCaptchaToken(Constants.EMPTY);
|
||||
} else if (!waitingForInitialToken && tokenResponse.isExpired(Instant.now(), 5)) {
|
||||
// try to refresh the token
|
||||
boolean tokenUpdateSuccess = refreshTokenROW();
|
||||
logger.trace("update token {}", tokenUpdateSuccess ? "success" : "failed");
|
||||
|
||||
if (!tokenUpdateSuccess) {
|
||||
logger.warn("Updating token failed!");
|
||||
waitingForInitialToken = true;
|
||||
|
||||
if (bridgeConfiguration.getHCaptchaToken().isBlank()) {
|
||||
logger.warn(
|
||||
"initial Authentication failed, request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!");
|
||||
bridgeHandler.tokenInitError();
|
||||
} else {
|
||||
getToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the token was successfully retrieved
|
||||
*/
|
||||
private boolean loginROW() {
|
||||
logger.trace("get initial token");
|
||||
|
||||
try {
|
||||
/**
|
||||
* Step 1) Get OAuth2 settings for further queries
|
||||
*/
|
||||
OAuthSettingsQueryResponse aqr = getOAuthSettings();
|
||||
|
||||
/**
|
||||
* Step 2) Calculate values for oauth base parameters
|
||||
*/
|
||||
String codeVerifier = generateToken(86);
|
||||
String codeChallenge = generateCodeChallenge(codeVerifier);
|
||||
String state = generateToken(22);
|
||||
String nonce = generateToken(22);
|
||||
|
||||
MultiMap<@Nullable String> baseParams = new MultiMap<>();
|
||||
baseParams.put(CLIENT_ID, aqr.clientId);
|
||||
baseParams.put(RESPONSE_TYPE, CODE);
|
||||
baseParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
baseParams.put(STATE, state);
|
||||
baseParams.put(NONCE, nonce);
|
||||
baseParams.put(SCOPE, aqr.scopes());
|
||||
baseParams.put(CODE_CHALLENGE, codeChallenge);
|
||||
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
|
||||
|
||||
/**
|
||||
* Step 3) Authentication with username and password, get authentication code
|
||||
*/
|
||||
String loginUrl = aqr.gcdmBaseUrl + OAUTH_ENDPOINT;
|
||||
Request loginRequest = httpClient.POST(loginUrl);
|
||||
|
||||
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
loginRequest.header(HCAPTCHA_TOKEN, bridgeConfiguration.getHCaptchaToken());
|
||||
|
||||
MultiMap<@Nullable String> loginParams = new MultiMap<>(baseParams);
|
||||
loginParams.put(GRANT_TYPE, AUTHORIZATION_CODE);
|
||||
loginParams.put(USERNAME, bridgeConfiguration.getUserName());
|
||||
loginParams.put(PASSWORD, bridgeConfiguration.getPassword());
|
||||
loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse loginResponse = loginRequest.send();
|
||||
if (loginResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: "
|
||||
+ loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
|
||||
loginResponse);
|
||||
}
|
||||
|
||||
String authCode = getAuthCode(loginResponse.getContentAsString());
|
||||
logger.trace("authentication code: {}", authCode);
|
||||
|
||||
/**
|
||||
* Step 4) With authentication code get code
|
||||
*/
|
||||
Request authRequest = httpClient.POST(loginUrl).followRedirects(false);
|
||||
MultiMap<@Nullable String> authParams = new MultiMap<>(baseParams);
|
||||
authParams.put(AUTHORIZATION, authCode);
|
||||
authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
authRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(authParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse authResponse = authRequest.send();
|
||||
if (authResponse.getStatus() != 302) {
|
||||
throw new HttpResponseException("URL: " + authRequest.getURI() + ", Error: " + authResponse.getStatus()
|
||||
+ ", Message: " + authResponse.getContentAsString(), authResponse);
|
||||
}
|
||||
String code = codeFromUrl(authResponse.getHeaders().get(HttpHeader.LOCATION));
|
||||
logger.trace("code: {}", code);
|
||||
|
||||
/**
|
||||
* Step 5) With code get token
|
||||
*/
|
||||
if (oAuthFactory.getOAuthClientService(oAuthClientServiceId) != null) {
|
||||
oAuthFactory.ungetOAuthService(oAuthClientServiceId);
|
||||
}
|
||||
OAuthClientService oAuthClientService = oAuthFactory.createOAuthClientService(oAuthClientServiceId,
|
||||
aqr.tokenEndpoint, loginUrl, aqr.clientId, aqr.clientSecret, aqr.scopes(), false);
|
||||
this.oAuthClientService = oAuthClientService;
|
||||
|
||||
oAuthClientService.addExtraAuthField(CODE_VERIFIER, codeVerifier);
|
||||
tokenResponse = oAuthClientService.getAccessTokenResponseByAuthorizationCode(code, aqr.returnUrl);
|
||||
|
||||
return true;
|
||||
} catch (ExecutionException | OAuthException | IOException | OAuthResponseException | InterruptedException
|
||||
| HttpResponseException | TimeoutException | NoSuchAlgorithmException e) {
|
||||
logger.warn("Exception at login: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh the existing token
|
||||
*
|
||||
* @return true if token has successfully been refreshed
|
||||
*/
|
||||
private boolean refreshTokenROW() {
|
||||
logger.trace("refreshToken");
|
||||
OAuthClientService oAuthClientService = this.oAuthClientService;
|
||||
if (oAuthClientService == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
AccessTokenResponse tokenResponse;
|
||||
tokenResponse = oAuthClientService.getAccessTokenResponse();
|
||||
if (tokenResponse != null) {
|
||||
this.tokenResponse = tokenResponse;
|
||||
return true;
|
||||
}
|
||||
} catch (OAuthException | IOException | OAuthResponseException e) {
|
||||
logger.warn("Exception refreshing token: ", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private OAuthSettingsQueryResponse getOAuthSettings()
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
String uuidString = UUID.randomUUID().toString();
|
||||
|
||||
String oAuthSettingsUrl = "https://" + EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ API_OAUTH_CONFIG;
|
||||
Request oAuthSettingsRequest = httpClient.newRequest(oAuthSettingsUrl);
|
||||
oAuthSettingsRequest.header(HEADER_ACP_SUBSCRIPTION_KEY, OCP_APIM_KEYS.get(bridgeConfiguration.getRegion()));
|
||||
oAuthSettingsRequest.header(HEADER_X_USER_AGENT,
|
||||
String.format(X_USER_AGENT, RandomUserAgentVersionGenerator.getRandomUserAgentVersion(), BRAND_BMW,
|
||||
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
|
||||
oAuthSettingsRequest.header(HEADER_X_IDENTITY_PROVIDER, AUTH_PROVIDER);
|
||||
oAuthSettingsRequest.header(HEADER_X_CORRELATION_ID, uuidString);
|
||||
oAuthSettingsRequest.header(HEADER_BMW_CORRELATION_ID, uuidString);
|
||||
oAuthSettingsRequest.header(HEADER_BMW_SESSION_ID, SESSION_ID);
|
||||
|
||||
ContentResponse oAuthSettingsRepsonse = oAuthSettingsRequest.send();
|
||||
if (oAuthSettingsRepsonse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + oAuthSettingsRequest.getURI() + ", Error: "
|
||||
+ oAuthSettingsRepsonse.getStatus() + ", Message: " + oAuthSettingsRepsonse.getContentAsString(),
|
||||
oAuthSettingsRepsonse);
|
||||
}
|
||||
OAuthSettingsQueryResponse aqr = JsonStringDeserializer
|
||||
.deserializeString(oAuthSettingsRepsonse.getContentAsString(), OAuthSettingsQueryResponse.class);
|
||||
return aqr;
|
||||
}
|
||||
|
||||
private String generateToken(int length) {
|
||||
String bytes = StringUtils.getRandomAlphabetic(length).toLowerCase();
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes.getBytes());
|
||||
}
|
||||
|
||||
private String generateCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
||||
}
|
||||
|
||||
private String getAuthCode(String response) {
|
||||
String[] keys = response.split("&");
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (keys[i].startsWith(AUTHORIZATION)) {
|
||||
String authCode = keys[i].split("=")[1];
|
||||
authCode = authCode.split("\"")[0];
|
||||
return authCode;
|
||||
}
|
||||
}
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
|
||||
private String codeFromUrl(String encodedUrl) {
|
||||
final MultiMap<@Nullable String> tokenMap = new MultiMap<>();
|
||||
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
|
||||
final StringBuilder codeFound = new StringBuilder();
|
||||
tokenMap.forEach((key, value) -> {
|
||||
if (!value.isEmpty()) {
|
||||
String val = value.get(0);
|
||||
if (key.endsWith(CODE) && (val != null)) {
|
||||
codeFound.append(val.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
return codeFound.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
*
|
||||
* deserialization of a JSON string to a Java Object
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface JsonStringDeserializer {
|
||||
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(JsonStringDeserializer.class);
|
||||
|
||||
static final Gson GSON = new Gson();
|
||||
|
||||
public static List<VehicleBase> getVehicleBaseList(String vehicleBaseJson) {
|
||||
try {
|
||||
VehicleBase[] vehicleBaseArray = deserializeString(vehicleBaseJson, VehicleBase[].class);
|
||||
return Arrays.asList(vehicleBaseArray);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static VehicleStateContainer getVehicleState(String vehicleStateJson) {
|
||||
try {
|
||||
VehicleStateContainer vehicleState = deserializeString(vehicleStateJson, VehicleStateContainer.class);
|
||||
vehicleState.setRawStateJson(vehicleStateJson);
|
||||
return vehicleState;
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new VehicleStateContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChargingStatisticsContainer getChargingStatistics(String chargeStatisticsJson) {
|
||||
try {
|
||||
ChargingStatisticsContainer chargeStatistics = deserializeString(chargeStatisticsJson,
|
||||
ChargingStatisticsContainer.class);
|
||||
return chargeStatistics;
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ChargingStatisticsContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ChargingSessionsContainer getChargingSessions(String chargeSessionsJson) {
|
||||
try {
|
||||
return deserializeString(chargeSessionsJson, ChargingSessionsContainer.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ChargingSessionsContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static ExecutionStatusContainer getExecutionStatus(String executionStatusJson) {
|
||||
try {
|
||||
return deserializeString(executionStatusJson, ExecutionStatusContainer.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
LOGGER.debug("JsonSyntaxException {}", e.getMessage());
|
||||
return new ExecutionStatusContainer();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T deserializeString(String toBeDeserialized, Class<@NonNull T> deserializedClass) {
|
||||
return GSON.fromJson(toBeDeserialized, deserializedClass);
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class is for local testing. You have to configure a connected account with username = "testuser" and password =
|
||||
* vehicle to be tested (e.g. BEV, ICE, BEV2, MILD_HYBRID,...)
|
||||
* The respective files are loaded from the resources folder
|
||||
*
|
||||
* You have to set the environment variable "ENVIRONMENT" to the value "test"
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWFileProxy implements MyBMWProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWFileProxy.class);
|
||||
private String vehicleToBeTested;
|
||||
|
||||
private static final String RESPONSES = "responses" + File.separator;
|
||||
private static final String VEHICLES_BASE = File.separator + "vehicles_base.json";
|
||||
private static final String VEHICLES_STATE = File.separator + "vehicles_state.json";
|
||||
private static final String CHARGING_SESSIONS = File.separator + "charging_sessions.json";
|
||||
private static final String CHARGING_STATISTICS = File.separator + "charging_statistics.json";
|
||||
private static final String REMOTE_SERVICES_CALL = File.separator + "remote_service_call.json";
|
||||
private static final String REMOTE_SERVICES_STATE = File.separator + "remote_service_status.json";
|
||||
|
||||
public MyBMWFileProxy(HttpClient httpClient, MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - initialize");
|
||||
vehicleToBeTested = bridgeConfiguration.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - update bridge");
|
||||
vehicleToBeTested = bridgeConfiguration.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Vehicle> requestVehicles() throws NetworkException {
|
||||
List<Vehicle> vehicles = new ArrayList<>();
|
||||
List<VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
|
||||
Vehicle vehicle = new Vehicle();
|
||||
vehicle.setVehicleBase(vehicleBase);
|
||||
vehicle.setVehicleState(vehicleState);
|
||||
vehicles.add(vehicle);
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
|
||||
String vehicleResponseString = requestVehiclesBaseJson(brand);
|
||||
return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String requestVehiclesBaseJson(String brand) throws NetworkException {
|
||||
String vehicleResponseString = fileToString(VEHICLES_BASE);
|
||||
return vehicleResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleBase> requestVehiclesBase() throws NetworkException {
|
||||
List<VehicleBase> vehicles = new ArrayList<>();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
vehicles.addAll(requestVehiclesBase(brand));
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param config
|
||||
* @param props
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
|
||||
return "".getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param baseVehicle
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
|
||||
return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = fileToString(VEHICLES_STATE);
|
||||
return vehicleStateResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = fileToString(CHARGING_STATISTICS);
|
||||
return chargeStatisticsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = fileToString(CHARGING_SESSIONS);
|
||||
return chargeSessionsResponseString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException {
|
||||
return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_CALL));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
|
||||
throws NetworkException {
|
||||
return JsonStringDeserializer.getExecutionStatus(fileToString(REMOTE_SERVICES_STATE));
|
||||
}
|
||||
|
||||
private String fileToString(String filename) {
|
||||
logger.trace("reading file {}", RESPONSES + vehicleToBeTested + filename);
|
||||
try (BufferedReader br = new BufferedReader(
|
||||
new InputStreamReader(Objects.requireNonNull(MyBMWFileProxy.class.getClassLoader())
|
||||
.getResourceAsStream(RESPONSES + vehicleToBeTested + filename), "UTF-8"))) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
String sCurrentLine;
|
||||
|
||||
while ((sCurrentLine = br.readLine()) != null) {
|
||||
buf.append(sCurrentLine);
|
||||
}
|
||||
logger.trace("successful");
|
||||
return buf.toString();
|
||||
} catch (IOException e) {
|
||||
logger.error("file {} could not be loaded: {}", filename, e.getMessage());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNextQuota() {
|
||||
return Instant.now();
|
||||
}
|
||||
}
|
||||
@@ -1,501 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
|
||||
import org.openhab.binding.mybmw.internal.handler.auth.MyBMWTokenController;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
import org.openhab.binding.mybmw.internal.utils.Converter;
|
||||
import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.binding.mybmw.internal.utils.RandomUserAgentVersionGenerator;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MyBMWHttpProxy} This class holds the important constants for the BMW Connected Drive Authorization.
|
||||
* They are taken from the Bimmercode from github
|
||||
* {@link https://github.com/bimmerconnected/bimmer_connected}
|
||||
* File defining these constants
|
||||
* {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
|
||||
* https://customer.bmwgroup.com/one/app/oauth.js
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - edit and send of charge profile
|
||||
* @author Martin Grassl - refactoring
|
||||
* @author Mark Herwege - extended log anonymization
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
private static final Pattern TIME_PATTERN = Pattern.compile("\\b(\\d{1,2}:\\d{2}:\\d{2})\\b");
|
||||
private static final Pattern DURATION_PATTERN = Pattern.compile("\\b(\\d+)");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxy.class);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
private MyBMWBridgeConfiguration bridgeConfiguration;
|
||||
MyBMWTokenController myBMWTokenController;
|
||||
|
||||
private Instant nextQuota = Instant.now();
|
||||
|
||||
/**
|
||||
* URLs taken from
|
||||
* https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
|
||||
*/
|
||||
private final String vehicleUrl;
|
||||
private final String vehicleStateUrl;
|
||||
private final String remoteCommandUrl;
|
||||
private final String remoteStatusUrl;
|
||||
|
||||
public MyBMWHttpProxy(MyBMWBridgeHandler bridgeHandler, HttpClient httpClient, OAuthFactory oAuthFactory,
|
||||
MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWHttpProxy - initialize");
|
||||
this.httpClient = httpClient;
|
||||
|
||||
myBMWTokenController = new MyBMWTokenController(bridgeHandler, bridgeConfiguration, httpClient, oAuthFactory);
|
||||
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
|
||||
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.API_VEHICLES;
|
||||
|
||||
vehicleStateUrl = vehicleUrl + "/state";
|
||||
|
||||
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
|
||||
remoteStatusUrl = remoteCommandUrl + "eventStatus";
|
||||
logger.trace("MyBMWHttpProxy - ready");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
myBMWTokenController.setBridgeConfiguration(bridgeConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
* requests all vehicles
|
||||
*
|
||||
* @return list of vehicles
|
||||
*/
|
||||
@Override
|
||||
public List<Vehicle> requestVehicles() throws NetworkException {
|
||||
List<Vehicle> vehicles = new ArrayList<>();
|
||||
List<VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
|
||||
Vehicle vehicle = new Vehicle();
|
||||
vehicle.setVehicleBase(vehicleBase);
|
||||
vehicle.setVehicleState(vehicleState);
|
||||
vehicles.add(vehicle);
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
* @return the vehicles of one brand
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException {
|
||||
String vehicleResponseString = requestVehiclesBaseJson(brand);
|
||||
return JsonStringDeserializer.getVehicleBaseList(vehicleResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the raw JSON for the vehicle
|
||||
*
|
||||
* @param brand
|
||||
* @return the base vehicle information as JSON string
|
||||
*/
|
||||
@Override
|
||||
public String requestVehiclesBaseJson(String brand) throws NetworkException {
|
||||
byte[] vehicleResponse = get(vehicleUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String vehicleResponseString = new String(vehicleResponse, Charset.defaultCharset());
|
||||
return vehicleResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @return the list of vehicles
|
||||
*/
|
||||
@Override
|
||||
public List<VehicleBase> requestVehiclesBase() throws NetworkException {
|
||||
List<VehicleBase> vehicles = new ArrayList<>();
|
||||
|
||||
for (String brand : BimmerConstants.REQUESTED_BRANDS) {
|
||||
try {
|
||||
vehicles.addAll(requestVehiclesBase(brand));
|
||||
|
||||
Thread.sleep(10000);
|
||||
} catch (NetworkException ne) {
|
||||
logger.warn("error retrieving the base vehicles for brand {}: {}", brand, ne.getMessage());
|
||||
throw ne;
|
||||
} catch (InterruptedException ie) {
|
||||
throw new NetworkException(ie);
|
||||
}
|
||||
}
|
||||
|
||||
return vehicles;
|
||||
}
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param vin the vin of the vehicle
|
||||
* @param brand the brand of the vehicle
|
||||
* @param props the image properties
|
||||
* @return the image as a byte array
|
||||
*/
|
||||
@Override
|
||||
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
|
||||
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.IMAGE_URL.replace(BimmerConstants.PARAM_VIN, vin) + props.viewport;
|
||||
return get(localImageUrl, brand, vin, HTTPConstants.CONTENT_TYPE_IMAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the vehicle state
|
||||
*/
|
||||
@Override
|
||||
public VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException {
|
||||
String vehicleStateResponseString = requestVehicleStateJson(vin, brand);
|
||||
return JsonStringDeserializer.getVehicleState(vehicleStateResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request the raw state as JSON for one specific vehicle
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the vehicle state as string
|
||||
*/
|
||||
@Override
|
||||
public String requestVehicleStateJson(String vin, String brand) throws NetworkException {
|
||||
byte[] vehicleStateResponse = get(vehicleStateUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String vehicleStateResponseString = new String(vehicleStateResponse, Charset.defaultCharset());
|
||||
return vehicleStateResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge statistics
|
||||
*/
|
||||
@Override
|
||||
public ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException {
|
||||
String chargeStatisticsResponseString = requestChargeStatisticsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingStatistics(new String(chargeStatisticsResponseString));
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles as JSON
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge statistics as JSON string
|
||||
*/
|
||||
@Override
|
||||
public String requestChargeStatisticsJson(String vin, String brand) throws NetworkException {
|
||||
MultiMap<@Nullable String> chargeStatisticsParams = new MultiMap<>();
|
||||
chargeStatisticsParams.put("vin", vin);
|
||||
chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
|
||||
String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.CHARGING_STATISTICS + params;
|
||||
byte[] chargeStatisticsResponse = get(chargeStatisticsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeStatisticsResponseString = new String(chargeStatisticsResponse);
|
||||
return chargeStatisticsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge sessions
|
||||
*/
|
||||
@Override
|
||||
public ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException {
|
||||
String chargeSessionsResponseString = requestChargeSessionsJson(vin, brand);
|
||||
return JsonStringDeserializer.getChargingSessions(chargeSessionsResponseString);
|
||||
}
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles as JSON string
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @return the charge sessions as JSON string
|
||||
*/
|
||||
@Override
|
||||
public String requestChargeSessionsJson(String vin, String brand) throws NetworkException {
|
||||
MultiMap<@Nullable String> chargeSessionsParams = new MultiMap<>();
|
||||
chargeSessionsParams.put("vin", vin);
|
||||
chargeSessionsParams.put("maxResults", "40");
|
||||
chargeSessionsParams.put("include_date_picker", "true");
|
||||
String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.CHARGING_SESSIONS + params;
|
||||
byte[] chargeSessionsResponse = get(chargeSessionsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeSessionsResponseString = new String(chargeSessionsResponse);
|
||||
return chargeSessionsResponseString;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute a remote service call
|
||||
*
|
||||
* @param vin
|
||||
* @param brand
|
||||
* @param service the service which should be executed
|
||||
* @return the running service execution for status checks
|
||||
*/
|
||||
@Override
|
||||
public ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException {
|
||||
String executionUrl = remoteCommandUrl + vin + "/" + service.getCommand();
|
||||
|
||||
byte[] response = post(executionUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON, service.getBody());
|
||||
|
||||
return JsonStringDeserializer.getExecutionStatus(new String(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* check the status of a service call
|
||||
*
|
||||
* @param brand
|
||||
* @param eventid the ID of the currently running service execution
|
||||
* @return the running service execution for status checks
|
||||
*/
|
||||
@Override
|
||||
public ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId)
|
||||
throws NetworkException {
|
||||
String executionUrl = remoteStatusUrl + Constants.QUESTION + "eventId=" + eventId;
|
||||
|
||||
byte[] response = post(executionUrl, brand, null, HTTPConstants.CONTENT_TYPE_JSON, null);
|
||||
|
||||
return JsonStringDeserializer.getExecutionStatus(new String(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares a GET request to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private byte[] get(String url, final String brand, @Nullable String vin, String contentType)
|
||||
throws NetworkException {
|
||||
return call(url, false, brand, vin, contentType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares a POST request to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @param body
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private byte[] post(String url, final String brand, @Nullable String vin, String contentType, @Nullable String body)
|
||||
throws NetworkException {
|
||||
return call(url, true, brand, vin, contentType, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the real call to the backend
|
||||
*
|
||||
* @param url
|
||||
* @param post boolean value indicating if it is a post request
|
||||
* @param brand
|
||||
* @param vin
|
||||
* @param contentType
|
||||
* @param body
|
||||
* @return byte array of the response body
|
||||
*/
|
||||
private synchronized byte[] call(final String url, final boolean post, final String brand,
|
||||
final @Nullable String vin, final String contentType, final @Nullable String body) throws NetworkException {
|
||||
byte[] responseByteArray = "".getBytes();
|
||||
|
||||
if (Instant.now().isBefore(nextQuota)) {
|
||||
throw new NetworkException(url, -1, "Quota Exceeded, waiting for quota renewal", body);
|
||||
}
|
||||
|
||||
// return in case of unknown brand
|
||||
if (!BimmerConstants.REQUESTED_BRANDS.contains(brand.toLowerCase())) {
|
||||
logger.warn("Unknown Brand {}", brand);
|
||||
throw new NetworkException("Unknown Brand " + brand);
|
||||
}
|
||||
|
||||
// if no token is available, no request can be triggered
|
||||
AccessTokenResponse tokenResponse = myBMWTokenController.getToken();
|
||||
String bearerToken = tokenResponse.getAccessToken();
|
||||
bearerToken = (bearerToken == null) || bearerToken.isEmpty() ? Constants.EMPTY
|
||||
: new StringBuilder(tokenResponse.getTokenType()).append(Constants.SPACE).append(bearerToken)
|
||||
.toString();
|
||||
if (tokenResponse.isExpired(Instant.now(), 1)) {
|
||||
logger.warn("The login failed, no token is available");
|
||||
throw new NetworkException("The login failed, no token is available");
|
||||
}
|
||||
|
||||
final Request req;
|
||||
|
||||
if (post) {
|
||||
req = httpClient.POST(url);
|
||||
} else {
|
||||
req = httpClient.newRequest(url);
|
||||
}
|
||||
|
||||
req.header(HttpHeader.AUTHORIZATION, bearerToken);
|
||||
req.header(HTTPConstants.HEADER_X_USER_AGENT,
|
||||
String.format(BimmerConstants.X_USER_AGENT, RandomUserAgentVersionGenerator.getRandomUserAgentVersion(),
|
||||
brand.toLowerCase(), BimmerConstants.APP_VERSIONS.get(bridgeConfiguration.getRegion()),
|
||||
bridgeConfiguration.getRegion()));
|
||||
req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.getLanguage());
|
||||
req.header(HttpHeader.ACCEPT, contentType);
|
||||
req.header(HTTPConstants.HEADER_BMW_VIN, vin);
|
||||
|
||||
try {
|
||||
ContentResponse response = req.timeout(HTTPConstants.HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() >= 300) {
|
||||
setNextQuotaAttempt(response);
|
||||
responseByteArray = "".getBytes();
|
||||
NetworkException exception = new NetworkException(url, response.getStatus(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()), body);
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(exception.getUrl(), vin), exception.getReason(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(body));
|
||||
throw exception;
|
||||
} else {
|
||||
responseByteArray = response.getContent();
|
||||
|
||||
// don't print images
|
||||
if (!HTTPConstants.CONTENT_TYPE_IMAGE.equals(contentType)) {
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(response.getContentAsString()),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(body));
|
||||
}
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(vin));
|
||||
throw new NetworkException(url, -1, null, body, e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logResponse(ResponseContentAnonymizer.replaceVin(url, vin), e.getMessage(),
|
||||
ResponseContentAnonymizer.anonymizeResponseContent(vin));
|
||||
throw new NetworkException(url, -1, null, body, e);
|
||||
}
|
||||
|
||||
return responseByteArray;
|
||||
}
|
||||
|
||||
private void setNextQuotaAttempt(ContentResponse response) {
|
||||
int status = response.getStatus();
|
||||
String message = JsonStringDeserializer.deserializeString(response.getContentAsString(),
|
||||
ResponseErrorContainer.class).message;
|
||||
Duration delay = Duration.ofSeconds(5); // minimum wait of 5 seconds
|
||||
switch (status) {
|
||||
case 403:
|
||||
// response contains the amount of time before quota is replenished
|
||||
try {
|
||||
Matcher matcher = TIME_PATTERN.matcher(message);
|
||||
if (matcher.find()) {
|
||||
LocalTime time = LocalTime.parse(matcher.group(1), DateTimeFormatter.ISO_LOCAL_TIME);
|
||||
delay = Duration.between(LocalTime.MIDNIGHT, time);
|
||||
}
|
||||
} catch (DateTimeParseException e) {
|
||||
// we couldn't parse, continue with default
|
||||
}
|
||||
break;
|
||||
case 429:
|
||||
// response may contain the number of seconds to wait before quota is replenished
|
||||
Matcher matcher = DURATION_PATTERN.matcher(message);
|
||||
if (matcher.find()) {
|
||||
delay = Duration.ofSeconds(Long.valueOf(matcher.group(1)));
|
||||
} else {
|
||||
delay = Duration.ofMinutes(5);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
logger.debug("next call allowed in {}:{}:{}", delay.toHours(), delay.toMinutesPart(), delay.toSecondsPart());
|
||||
nextQuota = Instant.now().plus(delay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getNextQuota() {
|
||||
return nextQuota;
|
||||
}
|
||||
|
||||
private void logResponse(@Nullable String url, @Nullable String fingerprint, @Nullable String body) {
|
||||
logger.debug("###### Request URL - BEGIN ######");
|
||||
logger.debug("{}", url);
|
||||
logger.debug("###### Request Body - BEGIN ######");
|
||||
logger.debug("{}", body);
|
||||
logger.debug("###### Response Data - BEGIN ######");
|
||||
logger.debug("{}", fingerprint);
|
||||
logger.debug("###### Response Data - END ######");
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
|
||||
/**
|
||||
* this is the interface for requesting the myBMW responses
|
||||
*
|
||||
* @author Martin Grassl - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MyBMWProxy {
|
||||
|
||||
void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration);
|
||||
|
||||
List<Vehicle> requestVehicles() throws NetworkException;
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
*
|
||||
* @param brand
|
||||
*/
|
||||
List<VehicleBase> requestVehiclesBase(String brand) throws NetworkException;
|
||||
|
||||
String requestVehiclesBaseJson(String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request vehicles for all possible brands
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
List<VehicleBase> requestVehiclesBase() throws NetworkException;
|
||||
|
||||
/**
|
||||
* request the vehicle image
|
||||
*
|
||||
* @param config
|
||||
* @param props
|
||||
* @return
|
||||
*/
|
||||
byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request the state for one specific vehicle
|
||||
*
|
||||
* @param baseVehicle
|
||||
* @return
|
||||
*/
|
||||
VehicleStateContainer requestVehicleState(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestVehicleStateJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request charge statistics for electric vehicles
|
||||
*
|
||||
*/
|
||||
ChargingStatisticsContainer requestChargeStatistics(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestChargeStatisticsJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
/**
|
||||
* request charge sessions for electric vehicles
|
||||
*
|
||||
*/
|
||||
ChargingSessionsContainer requestChargeSessions(String vin, String brand) throws NetworkException;
|
||||
|
||||
String requestChargeSessionsJson(String vin, String brand) throws NetworkException;
|
||||
|
||||
ExecutionStatusContainer executeRemoteServiceCall(String vin, String brand, RemoteService service)
|
||||
throws NetworkException;
|
||||
|
||||
ExecutionStatusContainer executeRemoteServiceStatusCall(String brand, String eventId) throws NetworkException;
|
||||
|
||||
Instant getNextQuota();
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NetworkException} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extend Exception
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NetworkException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 123L;
|
||||
|
||||
private String url = "";
|
||||
private int status = -1;
|
||||
private String reason = "";
|
||||
private String body = "";
|
||||
|
||||
public NetworkException() {
|
||||
}
|
||||
|
||||
public NetworkException(String url, int status, @Nullable String reason, @Nullable String body) {
|
||||
super(reason);
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
this.reason = reason != null ? reason : "";
|
||||
this.body = body != null ? body : "";
|
||||
}
|
||||
|
||||
public NetworkException(String url, int status, @Nullable String reason, @Nullable String body, Throwable cause) {
|
||||
super(cause);
|
||||
this.url = url;
|
||||
this.status = status;
|
||||
this.reason = reason != null ? reason : "";
|
||||
this.body = body != null ? body : "";
|
||||
}
|
||||
|
||||
public NetworkException(String message) {
|
||||
super(message);
|
||||
this.reason = message;
|
||||
}
|
||||
|
||||
public NetworkException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public NetworkException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.reason = message;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "NetworkException [url=" + url + ", status=" + status + ", reason=" + reason + ", body=" + body + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* anonymizes all occurrencies of locations and vins
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactoring & extension for any occurrence
|
||||
* @author Mark Herwege - extended log anonymization
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ResponseContentAnonymizer {
|
||||
|
||||
static final String ANONYMOUS_VIN = "anonymousVin";
|
||||
static final String VIN_PATTERN = "\"vin\":";
|
||||
static final String VEHICLE_CHARGING_LOCATION_PATTERN = "\"subtitle\":";
|
||||
static final String VEHICLE_LOCATION_PATTERN = "\"location\":";
|
||||
static final String VEHICLE_LOCATION_LATITUDE_PATTERN = "latitude";
|
||||
static final String VEHICLE_LOCATION_LONGITUDE_PATTERN = "longitude";
|
||||
static final String VEHICLE_LOCATION_FORMATTED_PATTERN = "formatted";
|
||||
static final String VEHICLE_LOCATION_HEADING_PATTERN = "heading";
|
||||
static final String VEHICLE_LOCATION_LATITUDE = "1.1";
|
||||
static final String VEHICLE_LOCATION_LONGITUDE = "2.2";
|
||||
static final String ANONYMOUS_ADDRESS = "anonymousAddress";
|
||||
static final String VEHICLE_LOCATION_HEADING = "-1";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_START = "\\\"location\\\"";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_END = "\\\"heading\\\"";
|
||||
static final String RAW_VEHICLE_LOCATION_PATTERN_REPLACER = "\"location\":{\"coordinates\":{\"latitude\":"
|
||||
+ VEHICLE_LOCATION_LATITUDE + ",\"longitude\":" + VEHICLE_LOCATION_LONGITUDE
|
||||
+ "},\"address\":{\"formatted\":\"" + ANONYMOUS_ADDRESS + "\"},";
|
||||
|
||||
static final String CLOSING_BRACKET = "}";
|
||||
static final String QUOTE = "\"";
|
||||
static final String CLOSE_VALUE = "\":";
|
||||
static final String COMMA = ",";
|
||||
|
||||
/**
|
||||
* anonymizes the responseContent
|
||||
* <p>
|
||||
* - vin
|
||||
* </p>
|
||||
* <p>
|
||||
* - location
|
||||
* </p>
|
||||
*
|
||||
* @param responseContent
|
||||
* @return
|
||||
*/
|
||||
public static String anonymizeResponseContent(@Nullable String responseContent) {
|
||||
if (responseContent == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String anonymizedVinString = replaceVins(responseContent);
|
||||
|
||||
String anonymizedLocationString = replaceLocations(anonymizedVinString);
|
||||
|
||||
String anonymizedRawLocationString = replaceRawLocations(anonymizedLocationString);
|
||||
|
||||
String anonymizedChargingLocationString = replaceChargingLocations(anonymizedRawLocationString);
|
||||
|
||||
return anonymizedChargingLocationString;
|
||||
}
|
||||
|
||||
static String replaceChargingLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(VEHICLE_CHARGING_LOCATION_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(VEHICLE_CHARGING_LOCATION_PATTERN);
|
||||
replacedString.append(replaceChargingLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceChargingLocation(String responseContent) {
|
||||
String[] subtitleStrings = responseContent.split(" • ", 2);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
|
||||
replacedString.append("\"");
|
||||
replacedString.append(ANONYMOUS_ADDRESS);
|
||||
if (subtitleStrings.length > 1) {
|
||||
replacedString.append(" • ");
|
||||
replacedString.append(subtitleStrings[1]);
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceRawLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_START));
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(replaceRawLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* this just replaces a string
|
||||
*
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
static String replaceRawLocation(String stringToBeReplaced) {
|
||||
String[] stringParts = stringToBeReplaced.split(Pattern.quote(RAW_VEHICLE_LOCATION_PATTERN_END));
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_REPLACER);
|
||||
replacedString.append(RAW_VEHICLE_LOCATION_PATTERN_END);
|
||||
replacedString.append(stringParts[1]);
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceLocations(String stringToBeReplaced) {
|
||||
String[] locationStrings = stringToBeReplaced.split(VEHICLE_LOCATION_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(locationStrings[0]);
|
||||
for (int i = 1; locationStrings.length > 0 && i < locationStrings.length && locationStrings[i] != null; i++) {
|
||||
replacedString.append(VEHICLE_LOCATION_PATTERN);
|
||||
replacedString.append(replaceLocation(locationStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceLocation(String responseContent) {
|
||||
String stringToBeReplaced = responseContent;
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
// latitude
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LATITUDE_PATTERN,
|
||||
VEHICLE_LOCATION_LATITUDE);
|
||||
|
||||
// longitude
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_LONGITUDE_PATTERN,
|
||||
VEHICLE_LOCATION_LONGITUDE);
|
||||
|
||||
// formatted address
|
||||
stringToBeReplaced = replaceStringValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_FORMATTED_PATTERN,
|
||||
ANONYMOUS_ADDRESS);
|
||||
|
||||
// heading
|
||||
stringToBeReplaced = replaceNumberValue(stringToBeReplaced, replacedString, VEHICLE_LOCATION_HEADING_PATTERN,
|
||||
VEHICLE_LOCATION_HEADING);
|
||||
|
||||
replacedString.append(stringToBeReplaced);
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceNumberValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
|
||||
String replacerValue) {
|
||||
int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
|
||||
+ (replacerPattern.length() + CLOSE_VALUE.length());
|
||||
int endIndex = -1;
|
||||
|
||||
// in an object, the comma comes after the value or a closing bracket
|
||||
if (stringToBeReplaced.indexOf(COMMA, startIndex) < stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex)) {
|
||||
endIndex = stringToBeReplaced.indexOf(COMMA, startIndex);
|
||||
} else {
|
||||
endIndex = stringToBeReplaced.indexOf(CLOSING_BRACKET, startIndex);
|
||||
}
|
||||
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(replacerValue);
|
||||
|
||||
return stringToBeReplaced.substring(endIndex);
|
||||
}
|
||||
|
||||
static String replaceStringValue(String stringToBeReplaced, StringBuffer replacedString, String replacerPattern,
|
||||
String replacerValue) {
|
||||
// the startIndex is the String after the first quote of the value after the key
|
||||
// detect end of key
|
||||
int startIndex = stringToBeReplaced.indexOf(replacerPattern, 1)
|
||||
+ (replacerPattern.length() + CLOSE_VALUE.length());
|
||||
// detect start of value
|
||||
startIndex = stringToBeReplaced.indexOf(QUOTE, startIndex) + 1;
|
||||
|
||||
// detect end of value
|
||||
int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
|
||||
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(replacerValue);
|
||||
|
||||
return stringToBeReplaced.substring(endIndex);
|
||||
}
|
||||
|
||||
static String replaceVins(String stringToBeReplaced) {
|
||||
String[] vinStrings = stringToBeReplaced.split(VIN_PATTERN);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(vinStrings[0]);
|
||||
for (int i = 1; vinStrings.length > 0 && i < vinStrings.length; i++) {
|
||||
replacedString.append(VIN_PATTERN);
|
||||
replacedString.append(replaceVin(vinStrings[i]));
|
||||
}
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static String replaceVin(String stringToBeReplaced) {
|
||||
// the vin is between two quotes
|
||||
int startIndex = stringToBeReplaced.indexOf(QUOTE) + 1;
|
||||
int endIndex = stringToBeReplaced.indexOf(QUOTE, startIndex);
|
||||
|
||||
StringBuffer replacedString = new StringBuffer();
|
||||
replacedString.append(stringToBeReplaced.substring(0, startIndex));
|
||||
replacedString.append(ANONYMOUS_VIN);
|
||||
replacedString.append(stringToBeReplaced.substring(endIndex));
|
||||
|
||||
return replacedString.toString();
|
||||
}
|
||||
|
||||
static @Nullable String replaceVin(@Nullable String stringToBeReplaced, @Nullable String vin) {
|
||||
if (stringToBeReplaced == null) {
|
||||
return null;
|
||||
}
|
||||
return vin != null ? stringToBeReplaced.replace(vin, ANONYMOUS_VIN) : stringToBeReplaced;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
|
||||
/**
|
||||
* The {@link ResponseErrorContainer} Data Transfer Object
|
||||
*
|
||||
* @author Mark Herwege - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ResponseErrorContainer {
|
||||
public String status = Constants.EMPTY;
|
||||
public String message = Constants.EMPTY;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResponseError [status=" + status + ", message=" + message + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.enums;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* execution state of a remote command
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum ExecutionState {
|
||||
READY,
|
||||
INITIATED,
|
||||
PENDING,
|
||||
DELIVERED,
|
||||
EXECUTED,
|
||||
ERROR,
|
||||
TIMEOUT
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.handler.enums;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_AIR_CONDITIONING_START;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_AIR_CONDITIONING_STOP;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_DOOR_LOCK;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_DOOR_UNLOCK;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_HORN;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_LIGHT_FLASH;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_START_CHARGING;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_STOP_CHARGING;
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_VEHICLE_FINDER;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
*
|
||||
* possible remote services
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
* @author Mark Herwege - electric charging commands
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum RemoteService {
|
||||
LIGHT_FLASH("Flash Lights", REMOTE_SERVICE_LIGHT_FLASH, REMOTE_SERVICE_LIGHT_FLASH, ""),
|
||||
VEHICLE_FINDER("Vehicle Finder", REMOTE_SERVICE_VEHICLE_FINDER, REMOTE_SERVICE_VEHICLE_FINDER, ""),
|
||||
DOOR_LOCK("Door Lock", REMOTE_SERVICE_DOOR_LOCK, REMOTE_SERVICE_DOOR_LOCK, ""),
|
||||
DOOR_UNLOCK("Door Unlock", REMOTE_SERVICE_DOOR_UNLOCK, REMOTE_SERVICE_DOOR_UNLOCK, ""),
|
||||
HORN_BLOW("Horn Blow", REMOTE_SERVICE_HORN, REMOTE_SERVICE_HORN, ""),
|
||||
CLIMATE_NOW_START("Start Climate", REMOTE_SERVICE_AIR_CONDITIONING_START, "climate-now", "{\"action\": \"START\"}"),
|
||||
CLIMATE_NOW_STOP("Stop Climate", REMOTE_SERVICE_AIR_CONDITIONING_STOP, "climate-now", "{\"action\": \"STOP\"}"),
|
||||
START_CHARGING("Start Charging", REMOTE_SERVICE_START_CHARGING, "start-charging", ""),
|
||||
STOP_CHARGING("Stop Charging", REMOTE_SERVICE_STOP_CHARGING, "stop-charging", "");
|
||||
|
||||
private final String label;
|
||||
private final String id;
|
||||
private final String command;
|
||||
private final String body;
|
||||
|
||||
RemoteService(final String label, final String id, String command, String body) {
|
||||
this.label = label;
|
||||
this.id = id;
|
||||
this.command = command;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link BimmerConstants} This class holds the important constants for the BMW Connected Drive Authorization. They
|
||||
* are taken from the Bimmercode from github <a href="https://github.com/bimmerconnected/bimmer_connected">
|
||||
* https://github.com/bimmerconnected/bimmer_connected</a>.
|
||||
* File defining these constants
|
||||
* <a href="https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py">
|
||||
* https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py</a>
|
||||
* <a href="https://customer.bmwgroup.com/one/app/oauth.js">https://customer.bmwgroup.com/one/app/oauth.js</a>
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - update to v2 API
|
||||
* @author Mark Herwege - remove China
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface BimmerConstants {
|
||||
|
||||
static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
|
||||
static final String REGION_ROW = "ROW";
|
||||
|
||||
static final String BRAND_BMW = "bmw";
|
||||
static final String BRAND_BMWI = "bmw_i";
|
||||
static final String BRAND_MINI = "mini";
|
||||
static final List<String> REQUESTED_BRANDS = List.of(BRAND_BMW, BRAND_MINI);
|
||||
|
||||
static final String OAUTH_ENDPOINT = "/gcdm/oauth/authenticate";
|
||||
static final String AUTH_PROVIDER = "gcdm";
|
||||
|
||||
static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
|
||||
static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
|
||||
static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, EADRAX_SERVER_NORTH_AMERICA,
|
||||
REGION_ROW, EADRAX_SERVER_ROW);
|
||||
|
||||
static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362";
|
||||
static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa";
|
||||
static final Map<String, String> OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA,
|
||||
REGION_ROW, OCP_APIM_KEY_ROW);
|
||||
|
||||
// Http variables
|
||||
static final String APP_VERSION_NORTH_AMERICA = "4.9.2(36892)";
|
||||
static final String APP_VERSION_ROW = "4.9.2(36892)";
|
||||
static final Map<String, String> APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW,
|
||||
APP_VERSION_ROW);
|
||||
static final String USER_AGENT = "Dart/2.16 (dart:io)";
|
||||
// see const.py of bimmer_constants: user-agent; brand; app_version; region
|
||||
static final String X_USER_AGENT = "android(AP2A.%s);%s;%s;%s";
|
||||
|
||||
static final String AUTHORIZATION_CODE = "authorization_code";
|
||||
static final String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
// Parameters for API Requests
|
||||
static final String TIRE_GUARD_MODE = "tireGuardMode";
|
||||
static final String APP_DATE_TIME = "appDateTime";
|
||||
static final String APP_TIMEZONE = "apptimezone";
|
||||
|
||||
// API endpoints
|
||||
static final String API_OAUTH_CONFIG = "/eadrax-ucs/v1/presentation/oauth/config";
|
||||
static final String API_VEHICLES = "/eadrax-vcs/v4/vehicles";
|
||||
static final String API_REMOTE_SERVICE_BASE_URL = "/eadrax-vrccs/v3/presentation/remote-commands/"; // '/{vin}/{service_type}'
|
||||
static final String API_POI = "/eadrax-dcs/v1/send-to-car/send-to-car";
|
||||
static final String CHARGING_STATISTICS = "/eadrax-chs/v2/charging-statistics?";
|
||||
static final String CHARGING_SESSIONS = "/eadrax-chs/v2/charging-sessions?";
|
||||
static final String PARAM_VIN = "$vin$";
|
||||
static final String IMAGE_URL = "/eadrax-ics/v3/presentation/vehicles/" + PARAM_VIN + "/images?carView=";
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
|
||||
|
||||
/**
|
||||
* The {@link ChargingProfileUtils} utility functions for charging profiles
|
||||
*
|
||||
* @author Norbert Truchsess - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargingProfileUtils {
|
||||
|
||||
// Charging
|
||||
public static class TimedChannel {
|
||||
public final String time;
|
||||
public final @Nullable String timer;
|
||||
public final boolean hasDays;
|
||||
|
||||
TimedChannel(final String time, @Nullable final String timer, final boolean hasDays) {
|
||||
this.time = time;
|
||||
this.timer = timer;
|
||||
this.hasDays = hasDays;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<ProfileKey, TimedChannel> TIMED_CHANNELS = new HashMap<>() {
|
||||
{
|
||||
put(ProfileKey.WINDOWSTART, new TimedChannel(CHARGE_WINDOW_START, null, false));
|
||||
put(ProfileKey.WINDOWEND, new TimedChannel(CHARGE_WINDOW_END, null, false));
|
||||
put(ProfileKey.TIMER1, new TimedChannel(CHARGE_TIMER1 + CHARGE_DEPARTURE, CHARGE_TIMER1, true));
|
||||
put(ProfileKey.TIMER2, new TimedChannel(CHARGE_TIMER2 + CHARGE_DEPARTURE, CHARGE_TIMER2, true));
|
||||
put(ProfileKey.TIMER3, new TimedChannel(CHARGE_TIMER3 + CHARGE_DEPARTURE, CHARGE_TIMER3, true));
|
||||
put(ProfileKey.TIMER4, new TimedChannel(CHARGE_TIMER4 + CHARGE_DEPARTURE, CHARGE_TIMER4, true));
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<DayOfWeek, String> DAY_CHANNELS = new HashMap<>() {
|
||||
{
|
||||
put(DayOfWeek.MONDAY, CHARGE_DAY_MON);
|
||||
put(DayOfWeek.TUESDAY, CHARGE_DAY_TUE);
|
||||
put(DayOfWeek.WEDNESDAY, CHARGE_DAY_WED);
|
||||
put(DayOfWeek.THURSDAY, CHARGE_DAY_THU);
|
||||
put(DayOfWeek.FRIDAY, CHARGE_DAY_FRI);
|
||||
put(DayOfWeek.SATURDAY, CHARGE_DAY_SAT);
|
||||
put(DayOfWeek.SUNDAY, CHARGE_DAY_SUN);
|
||||
}
|
||||
};
|
||||
|
||||
public static class ChargeKeyDay {
|
||||
public final ProfileKey key;
|
||||
public final DayOfWeek day;
|
||||
|
||||
ChargeKeyDay(final ProfileKey key, final DayOfWeek day) {
|
||||
this.key = key;
|
||||
this.day = day;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<String, ProfileKey> CHARGE_ENABLED_CHANNEL_KEYS = new HashMap<>() {
|
||||
{
|
||||
TIMED_CHANNELS.forEach((key, channel) -> {
|
||||
put(channel.timer + CHARGE_ENABLED, key);
|
||||
});
|
||||
put(CHARGE_PROFILE_CLIMATE, ProfileKey.CLIMATE);
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<String, ProfileKey> CHARGE_TIME_CHANNEL_KEYS = new HashMap<>() {
|
||||
{
|
||||
TIMED_CHANNELS.forEach((key, channel) -> {
|
||||
put(channel.time, key);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static final Map<String, ChargeKeyDay> CHARGE_DAYS_CHANNEL_KEYS = new HashMap<>() {
|
||||
{
|
||||
DAY_CHANNELS.forEach((dayOfWeek, dayChannel) -> {
|
||||
put(CHARGE_TIMER1 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER1, dayOfWeek));
|
||||
put(CHARGE_TIMER2 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER2, dayOfWeek));
|
||||
put(CHARGE_TIMER3 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
|
||||
put(CHARGE_TIMER4 + dayChannel, new ChargeKeyDay(ProfileKey.TIMER3, dayOfWeek));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static @Nullable TimedChannel getTimedChannel(ProfileKey key) {
|
||||
return TIMED_CHANNELS.get(key);
|
||||
}
|
||||
|
||||
public static @Nullable String getDaysChannel(DayOfWeek day) {
|
||||
return DAY_CHANNELS.get(day);
|
||||
}
|
||||
|
||||
public static @Nullable ProfileKey getEnableKey(final String id) {
|
||||
return CHARGE_ENABLED_CHANNEL_KEYS.get(id);
|
||||
}
|
||||
|
||||
public static @Nullable ChargeKeyDay getKeyDay(final String id) {
|
||||
return CHARGE_DAYS_CHANNEL_KEYS.get(id);
|
||||
}
|
||||
|
||||
public static @Nullable ProfileKey getTimeKey(final String id) {
|
||||
return CHARGE_TIME_CHANNEL_KEYS.get(id);
|
||||
}
|
||||
|
||||
public static String formatDays(final Set<DayOfWeek> weekdays) {
|
||||
return weekdays.stream().map(day -> Constants.DAYS.get(day)).collect(Collectors.joining(Constants.COMMA));
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER1;
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER2;
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER3;
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.TIMER4;
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.WINDOWEND;
|
||||
import static org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey.WINDOWSTART;
|
||||
import static org.openhab.binding.mybmw.internal.utils.Constants.NULL_LOCAL_TIME;
|
||||
import static org.openhab.binding.mybmw.internal.utils.Constants.TIME_FORMATER;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingMode;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.ChargingPreference;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.Time;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.Timer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link ChargingProfileWrapper} Wrapper for ChargingProfiles
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - add ChargeProfileActions
|
||||
* @author Martin Grassl - refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargingProfileWrapper {
|
||||
private final Logger logger = LoggerFactory.getLogger(ChargingProfileWrapper.class);
|
||||
|
||||
private static final String CHARGING_WINDOW = "CHARGING_WINDOW";
|
||||
private static final String WEEKLY_PLANNER = "WEEKLY_PLANNER";
|
||||
private static final String ACTIVATE = "ACTIVATE";
|
||||
// not used private static final String DEACTIVATE = "DEACTIVATE";
|
||||
|
||||
public enum ProfileKey {
|
||||
CLIMATE,
|
||||
TIMER1,
|
||||
TIMER2,
|
||||
TIMER3,
|
||||
TIMER4,
|
||||
WINDOWSTART,
|
||||
WINDOWEND
|
||||
}
|
||||
|
||||
private Optional<ChargingMode> mode = Optional.empty();
|
||||
private Optional<ChargingPreference> preference = Optional.empty();
|
||||
private Optional<String> controlType = Optional.empty();
|
||||
private Optional<ChargingSettings> chargeSettings = Optional.empty();
|
||||
|
||||
private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
|
||||
private final Map<ProfileKey, LocalTime> times = new HashMap<>();
|
||||
private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
|
||||
|
||||
public ChargingProfileWrapper(final ChargingProfile profile) {
|
||||
setPreference(profile.getChargingPreference());
|
||||
setMode(profile.getChargingMode());
|
||||
controlType = Optional.of(profile.getChargingControlType());
|
||||
chargeSettings = Optional.of(profile.getChargingSettings());
|
||||
setEnabled(ProfileKey.CLIMATE, profile.isClimatisationOn());
|
||||
|
||||
addTimer(TIMER1, profile.getTimerId(1));
|
||||
addTimer(TIMER2, profile.getTimerId(2));
|
||||
if (profile.getChargingControlType().equals(WEEKLY_PLANNER)) {
|
||||
addTimer(TIMER3, profile.getTimerId(3));
|
||||
addTimer(TIMER4, profile.getTimerId(4));
|
||||
}
|
||||
|
||||
if (CHARGING_WINDOW.equals(profile.getChargingPreference())) {
|
||||
addTime(WINDOWSTART, profile.getReductionOfChargeCurrent().getStart());
|
||||
addTime(WINDOWEND, profile.getReductionOfChargeCurrent().getEnd());
|
||||
} else {
|
||||
preference.ifPresent(pref -> {
|
||||
if (ChargingPreference.CHARGING_WINDOW.equals(pref)) {
|
||||
addTime(WINDOWSTART, null);
|
||||
addTime(WINDOWEND, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Boolean isEnabled(final ProfileKey key) {
|
||||
return enabled.get(key);
|
||||
}
|
||||
|
||||
public void setEnabled(final ProfileKey key, @Nullable final Boolean enabled) {
|
||||
if (enabled == null) {
|
||||
this.enabled.remove(key);
|
||||
} else {
|
||||
this.enabled.put(key, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable String getMode() {
|
||||
return mode.map(m -> m.name()).orElse(null);
|
||||
}
|
||||
|
||||
public @Nullable String getControlType() {
|
||||
return controlType.get();
|
||||
}
|
||||
|
||||
public @Nullable ChargingSettings getChargingSettings() {
|
||||
return chargeSettings.get();
|
||||
}
|
||||
|
||||
public void setMode(final @Nullable String mode) {
|
||||
if (mode != null) {
|
||||
try {
|
||||
this.mode = Optional.of(ChargingMode.valueOf(mode));
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn("unexpected value for chargingMode: {}", mode);
|
||||
}
|
||||
}
|
||||
this.mode = Optional.empty();
|
||||
}
|
||||
|
||||
public @Nullable String getPreference() {
|
||||
return preference.map(pref -> pref.name()).orElse(null);
|
||||
}
|
||||
|
||||
public void setPreference(final @Nullable String preference) {
|
||||
if (preference != null) {
|
||||
try {
|
||||
this.preference = Optional.of(ChargingPreference.valueOf(preference));
|
||||
return;
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn("unexpected value for chargingPreference: {}", preference);
|
||||
}
|
||||
}
|
||||
this.preference = Optional.empty();
|
||||
}
|
||||
|
||||
public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
|
||||
return daysOfWeek.get(key);
|
||||
}
|
||||
|
||||
public LocalTime getTime(final ProfileKey key) {
|
||||
LocalTime t = times.get(key);
|
||||
if (t != null) {
|
||||
return t;
|
||||
} else {
|
||||
logger.debug("Profile not valid - Key {} doesn't contain boolean value", key);
|
||||
return Constants.NULL_LOCAL_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
private void addTime(final ProfileKey key, @Nullable final Time time) {
|
||||
try {
|
||||
times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(Converter.getTime(time), TIME_FORMATER));
|
||||
} catch (DateTimeParseException dtpe) {
|
||||
logger.warn("unexpected value for {} time: {}", key.name(), time);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTimer(final ProfileKey key, @Nullable final Timer timer) {
|
||||
if (timer == null) {
|
||||
enabled.put(key, false);
|
||||
addTime(key, null);
|
||||
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||
} else {
|
||||
enabled.put(key, ACTIVATE.equals(timer.action));
|
||||
addTime(key, timer.timeStamp);
|
||||
final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
|
||||
if (timer.timerWeekDays != null) {
|
||||
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
|
||||
for (String day : timer.timerWeekDays) {
|
||||
try {
|
||||
daySet.add(DayOfWeek.valueOf(day.toUpperCase()));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.warn("unexpected value for {} day: {}", key.name(), day);
|
||||
}
|
||||
daysOfWeek.put(key, daySet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Length;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link Constants} General Constant Definitions
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Norbert Truchsess - contributor
|
||||
* @author Martin Grassl - rename drivetrain options
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Constants {
|
||||
// For Vehicle Status
|
||||
public static final String NO_ENTRIES = "-";
|
||||
public static final String OPEN = "Open";
|
||||
public static final String CLOSED = "Closed";
|
||||
public static final String LOCKED = "Locked";
|
||||
public static final String UNLOCKED = "Unlocked";
|
||||
public static final String CONNECTED = "Connected";
|
||||
public static final String UNCONNECTED = "Not connected";
|
||||
public static final String UNDEF = UnDefType.UNDEF.toFullString();
|
||||
public static final String NULL_TIME = "00:00";
|
||||
public static final String KILOMETERS_JSON = "KILOMETERS";
|
||||
public static final String KM_JSON = "km";
|
||||
public static final String MI_JSON = "mi";
|
||||
public static final String UNIT_PRECENT_JSON = "%";
|
||||
public static final String UNIT_LITER_JSON = "l";
|
||||
public static final Unit<Length> KILOMETRE_UNIT = MetricPrefix.KILO(SIUnits.METRE);
|
||||
public static final int INT_UNDEF = -1;
|
||||
|
||||
// Services in Discovery
|
||||
public static final String ENABLED = "ENABLED";
|
||||
|
||||
// General Constants for String concatenation
|
||||
public static final String NULL = "null";
|
||||
public static final String SPACE = " ";
|
||||
public static final String UNDERLINE = "_";
|
||||
public static final String HYPHEN = " - ";
|
||||
public static final String PLUS = "+";
|
||||
public static final String EMPTY = "";
|
||||
public static final String COMMA = ",";
|
||||
public static final String QUESTION = "?";
|
||||
public static final String COLON = ":";
|
||||
public static final String SEMICOLON = ";";
|
||||
public static final String TILDE = "~";
|
||||
|
||||
public static final String ANONYMOUS = "anonymous";
|
||||
public static final String EMPTY_JSON = "{}";
|
||||
public static final String LANGUAGE_AUTODETECT = "AUTODETECT";
|
||||
|
||||
// Time Constants for DateTime channels
|
||||
public static final LocalDate EPOCH_DAY = LocalDate.ofEpochDay(0);
|
||||
public static final DateTimeFormatter TIME_FORMATER = DateTimeFormatter.ofPattern("HH:mm");
|
||||
public static final LocalTime NULL_LOCAL_TIME = LocalTime.parse(NULL_TIME, TIME_FORMATER);
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static final Map<DayOfWeek, String> DAYS = new HashMap<>() {
|
||||
{
|
||||
put(DayOfWeek.MONDAY, "Mon");
|
||||
put(DayOfWeek.TUESDAY, "Tue");
|
||||
put(DayOfWeek.WEDNESDAY, "Wed");
|
||||
put(DayOfWeek.THURSDAY, "Thu");
|
||||
put(DayOfWeek.FRIDAY, "Fri");
|
||||
put(DayOfWeek.SATURDAY, "Sat");
|
||||
put(DayOfWeek.SUNDAY, "Sun");
|
||||
}
|
||||
};
|
||||
|
||||
// Drive Train definitions from json
|
||||
public static final String DRIVETRAIN_BEV = "ELECTRIC";
|
||||
public static final String DRIVETRAIN_REX_EXTENSION = "(+ REX)";
|
||||
public static final String DRIVETRAIN_MILD_HYBRID = "MILD_HYBRID";
|
||||
public static final String DRIVETRAIN_CONV = "COMBUSTION";
|
||||
public static final String DRIVETRAIN_PHEV = "PLUGIN_HYBRID";
|
||||
|
||||
// Carging States
|
||||
public static final String DEFAULT = "DEFAULT";
|
||||
public static final String NOT_CHARGING_STATE = "NOT_CHARGING";
|
||||
public static final String CHARGING_STATE = "CHARGING";
|
||||
public static final String PLUGGED_STATE = "PLUGGED_IN";
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.Time;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Converter} Conversion Helpers
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extract some methods to other classes
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface Converter {
|
||||
static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
|
||||
|
||||
static final String SPLIT_HYPHEN = "-";
|
||||
static final String SPLIT_BRACKET = "\\(";
|
||||
|
||||
static State zonedToLocalDateTime(@Nullable String input, ZoneId timezone) {
|
||||
if (input != null && !input.isEmpty()) {
|
||||
try {
|
||||
String localTimeString = Instant.parse(input).atZone(timezone)
|
||||
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||
return DateTimeType.valueOf(localTimeString);
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a string into a unified format
|
||||
* - string is Capitalized
|
||||
* - null is empty string
|
||||
* - single character remains
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
*/
|
||||
static String toTitleCase(@Nullable String input) {
|
||||
if (input == null || input.isEmpty()) {
|
||||
return toTitleCase(Constants.UNDEF);
|
||||
} else if (input.length() == 1) {
|
||||
return input;
|
||||
} else {
|
||||
// first, replace all underscores with spaces and make it lower case
|
||||
String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
|
||||
|
||||
//
|
||||
String converted = toTitleCase(lower, Constants.SPACE);
|
||||
converted = toTitleCase(converted, SPLIT_HYPHEN);
|
||||
converted = toTitleCase(converted, SPLIT_BRACKET);
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
|
||||
private static String toTitleCase(String input, String splitter) {
|
||||
// first, split all parts by the splitting string
|
||||
String[] arr = input.split(splitter);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
|
||||
}
|
||||
sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure distance between 2 coordinates
|
||||
*
|
||||
* @param sourceLatitude
|
||||
* @param sourceLongitude
|
||||
* @param destinationLatitude
|
||||
* @param destinationLongitude
|
||||
* @return distance
|
||||
*/
|
||||
static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
|
||||
double destinationLongitude) {
|
||||
double earthRadius = 6378.137; // Radius of earth in KM
|
||||
double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
|
||||
double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
|
||||
* Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return earthRadius * c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Easy function but there's some measures behind:
|
||||
* Guessing the range of the Vehicle on Map. If you can drive x kilometers with
|
||||
* your Vehicle it's not feasible to
|
||||
* project this x km Radius on Map. The roads to be taken are causing some
|
||||
* overhead because they are not a straight
|
||||
* line from Location A to B.
|
||||
* I've taken some measurements to calculate the overhead factor based on Google
|
||||
* Maps
|
||||
* Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
|
||||
* Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
|
||||
* After measuring more distances you'll find out that the outcome is between
|
||||
* 70% and 90%. So
|
||||
*
|
||||
* This depends also on the roads of a concrete route but this is only a guess
|
||||
* without any Route Navigation behind
|
||||
*
|
||||
* In future it's foreseen to replace this with BMW RangeMap Service which isn't
|
||||
* running at the moment.
|
||||
*
|
||||
* @param range
|
||||
* @return mapping from air-line distance to "real road" distance
|
||||
*/
|
||||
static int guessRangeRadius(double range) {
|
||||
return (int) (range * 0.8);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if a string is a valid integer
|
||||
*
|
||||
* @param fullString
|
||||
* @return
|
||||
*/
|
||||
static int parseIntegerString(String fullString) {
|
||||
int index = -1;
|
||||
try {
|
||||
index = Integer.parseInt(fullString);
|
||||
} catch (NumberFormatException nfe) {
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static State getConnectionState(boolean connected) {
|
||||
if (connected) {
|
||||
return StringType.valueOf(Constants.CONNECTED);
|
||||
} else {
|
||||
return StringType.valueOf(Constants.UNCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
static String getCurrentISOTime() {
|
||||
return ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
|
||||
}
|
||||
|
||||
static String getTime(Time t) {
|
||||
StringBuffer time = new StringBuffer();
|
||||
if (t.getHour() < 10) {
|
||||
time.append("0");
|
||||
}
|
||||
time.append(Integer.toString(t.getHour())).append(":");
|
||||
if (t.getMinute() < 10) {
|
||||
time.append("0");
|
||||
}
|
||||
time.append(Integer.toString(t.getMinute()));
|
||||
return time.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link HTTPConstants} interface contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - added image content type
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface HTTPConstants {
|
||||
static final int HTTP_TIMEOUT_SEC = 10;
|
||||
|
||||
static final String CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
static final String CONTENT_TYPE_JSON = "application/json";
|
||||
static final String CONTENT_TYPE_IMAGE = "image/png";
|
||||
static final String KEEP_ALIVE = "Keep-Alive";
|
||||
static final String CLIENT_ID = "client_id";
|
||||
static final String RESPONSE_TYPE = "response_type";
|
||||
static final String TOKEN = "token";
|
||||
static final String CODE = "code";
|
||||
static final String CODE_VERIFIER = "code_verifier";
|
||||
static final String STATE = "state";
|
||||
static final String NONCE = "nonce";
|
||||
static final String REDIRECT_URI = "redirect_uri";
|
||||
static final String AUTHORIZATION = "authorization";
|
||||
static final String GRANT_TYPE = "grant_type";
|
||||
static final String SCOPE = "scope";
|
||||
static final String CREDENTIALS = "Credentials";
|
||||
static final String USERNAME = "username";
|
||||
static final String PASSWORD = "password";
|
||||
static final String HCAPTCHA_TOKEN = "hcaptchatoken";
|
||||
static final String CONTENT_LENGTH = "Content-Length";
|
||||
static final String CODE_CHALLENGE = "code_challenge";
|
||||
static final String CODE_CHALLENGE_METHOD = "code_challenge_method";
|
||||
static final String ACCESS_TOKEN = "access_token";
|
||||
static final String TOKEN_TYPE = "token_type";
|
||||
static final String EXPIRES_IN = "expires_in";
|
||||
static final String CHUNKED = "chunked";
|
||||
|
||||
// HTTP headers for BMW API
|
||||
static final String HEADER_ACP_SUBSCRIPTION_KEY = "ocp-apim-subscription-key";
|
||||
static final String HEADER_X_USER_AGENT = "x-user-agent";
|
||||
static final String HEADER_X_IDENTITY_PROVIDER = "x-identity-provider";
|
||||
static final String HEADER_X_CORRELATION_ID = "x-correlation-id";
|
||||
static final String HEADER_BMW_CORRELATION_ID = "bmw-correlation-id";
|
||||
static final String HEADER_BMW_SESSION_ID = "bmw-session-id";
|
||||
static final String HEADER_BMW_VIN = "bmw-vin";
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ImageProperties} Properties of current Vehicle Image
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - fix default viewport as "default" is not available anymore
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ImageProperties {
|
||||
public static final int RETRY_COUNTER = 5;
|
||||
public int failCounter = 0;
|
||||
public String viewport = "VehicleStatus"; // default view
|
||||
|
||||
public ImageProperties(String viewport) {
|
||||
this.viewport = viewport;
|
||||
}
|
||||
|
||||
public ImageProperties() {
|
||||
}
|
||||
|
||||
public void failed() {
|
||||
failCounter++;
|
||||
}
|
||||
|
||||
public boolean failLimitReached() {
|
||||
return failCounter > RETRY_COUNTER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return viewport;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link RandomUserAgentVersionGenerator} generates random version numbers for the X-User-Agent in the format
|
||||
* xxxxxx.xxx
|
||||
*
|
||||
* @author Martin Grassl - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface RandomUserAgentVersionGenerator {
|
||||
|
||||
/**
|
||||
* this method generates the random UserAgent version
|
||||
*
|
||||
* @return formatted String xxxxxx.xxx
|
||||
*/
|
||||
static String getRandomUserAgentVersion() {
|
||||
int decimalPlaces = 3;
|
||||
int min = 250000;
|
||||
int max = 999999;
|
||||
|
||||
double random = min + (max - min) * Math.random();
|
||||
|
||||
// with the US locale we ensure that the decimal separator is a dot
|
||||
return String.format(Locale.US, "%." + decimalPlaces + "f", random);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
|
||||
/**
|
||||
* Helper class for Remote Service Commands
|
||||
*
|
||||
* @author Norbert Truchsess - Initial contribution
|
||||
* @author Martin Grassl - small refactoring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RemoteServiceUtils {
|
||||
|
||||
private static final Map<String, RemoteService> COMMAND_SERVICES = Stream.of(RemoteService.values())
|
||||
.collect(Collectors.toUnmodifiableMap(RemoteService::getId, service -> service));
|
||||
|
||||
public static Optional<RemoteService> getRemoteServiceFromCommand(final String command) {
|
||||
return Optional.ofNullable(COMMAND_SERVICES.get(command));
|
||||
}
|
||||
|
||||
public static List<CommandOption> getOptions(final boolean isElectric) {
|
||||
return Stream.of(RemoteService.values()).map(service -> new CommandOption(service.getId(), service.getLabel()))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2026 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mybmw.internal.utils;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleStatusUtils} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - refactor for v2 and extract some methods to other classes
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleStatusUtils {
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(VehicleStatusUtils.class);
|
||||
|
||||
/**
|
||||
* the date can be empty
|
||||
*
|
||||
* @param requiredServices
|
||||
* @return
|
||||
*/
|
||||
public static State getNextServiceDate(List<RequiredService> requiredServices) {
|
||||
ZonedDateTime farFuture = ZonedDateTime.now().plusYears(100);
|
||||
ZonedDateTime serviceDate = farFuture;
|
||||
for (RequiredService requiredService : requiredServices) {
|
||||
if (requiredService.getDateTime() != null && !requiredService.getDateTime().isEmpty()) {
|
||||
ZonedDateTime d = ZonedDateTime.parse(requiredService.getDateTime());
|
||||
if (d.isBefore(serviceDate)) {
|
||||
serviceDate = d;
|
||||
} // else skip
|
||||
}
|
||||
}
|
||||
if (serviceDate.equals(farFuture)) {
|
||||
return UnDefType.UNDEF;
|
||||
} else {
|
||||
return DateTimeType.valueOf(serviceDate.format(DateTimeFormatter.ISO_INSTANT));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the mileage can be empty
|
||||
*
|
||||
* @param requiredServices
|
||||
* @return
|
||||
*/
|
||||
public static State getNextServiceMileage(List<RequiredService> requiredServices) {
|
||||
int serviceMileage = Integer.MAX_VALUE;
|
||||
for (RequiredService requiredService : requiredServices) {
|
||||
if (requiredService.getMileage() > 0) {
|
||||
if (requiredService.getMileage() < serviceMileage) {
|
||||
serviceMileage = requiredService.getMileage();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serviceMileage != Integer.MAX_VALUE) {
|
||||
return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
|
||||
} else {
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates the mapping of thing type
|
||||
*
|
||||
* @param driveTrain
|
||||
* @param model
|
||||
* @return
|
||||
*/
|
||||
public static VehicleType vehicleType(String driveTrain, String model) {
|
||||
if (Constants.DRIVETRAIN_BEV.equals(driveTrain)) {
|
||||
if (model.endsWith(Constants.DRIVETRAIN_REX_EXTENSION)) {
|
||||
return VehicleType.ELECTRIC_REX;
|
||||
} else {
|
||||
return VehicleType.ELECTRIC;
|
||||
}
|
||||
} else if (Constants.DRIVETRAIN_PHEV.equals(driveTrain)) {
|
||||
return VehicleType.PLUGIN_HYBRID;
|
||||
} else if (Constants.DRIVETRAIN_CONV.equals(driveTrain)
|
||||
|| Constants.DRIVETRAIN_MILD_HYBRID.equals(driveTrain)) {
|
||||
return VehicleType.CONVENTIONAL;
|
||||
}
|
||||
LOGGER.warn("Unknown Vehicle Type: {} | {}", model, driveTrain);
|
||||
return VehicleType.UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="mybmw" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>MyBMW</name>
|
||||
<description>Provides access to your Vehicle Data like MyBMW App</description>
|
||||
<connection>cloud</connection>
|
||||
|
||||
</addon:addon>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:mybmw:bridge">
|
||||
<parameter name="userName" type="text" required="true">
|
||||
<label>Username</label>
|
||||
<description>MyBMW Username</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<description>MyBMW Password</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="region" type="text" required="true">
|
||||
<label>Region</label>
|
||||
<description>Select Region in order to connect to the appropriate BMW Server</description>
|
||||
<options>
|
||||
<option value="NORTH_AMERICA">North America</option>
|
||||
<option value="ROW">Rest of the World</option>
|
||||
</options>
|
||||
<default>ROW</default>
|
||||
</parameter>
|
||||
<parameter name="language" type="text">
|
||||
<label>Language Settings</label>
|
||||
<description>Channel data can be returned in the desired language like en, de, fr ...</description>
|
||||
<advanced>true</advanced>
|
||||
<default>AUTODETECT</default>
|
||||
</parameter>
|
||||
<parameter name="hcaptchatoken" type="text">
|
||||
<label>Captcha-Token</label>
|
||||
<description>Captcha-Token for login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="callbackIP" type="text">
|
||||
<label>Callback IP Address</label>
|
||||
<description>IP address for openHAB callback URL, defaults to IP of openHAB host</description>
|
||||
<context>network-address</context>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="callbackPort" type="integer">
|
||||
<label>Callback Port Number</label>
|
||||
<description>Port Number for openHAB callback URL, default 8090</description>
|
||||
<default>8090</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<config-description:config-descriptions
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
|
||||
|
||||
<config-description uri="thing-type:mybmw:vehicle">
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>Vehicle Identification Number (VIN)</label>
|
||||
<description>Unique VIN given by BMW</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" min="0" unit="min" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Data refresh rate for your vehicle data</description>
|
||||
<default>5</default>
|
||||
</parameter>
|
||||
<parameter name="vehicleBrand" type="text" required="true">
|
||||
<label>Brand of the Vehicle</label>
|
||||
<description>Vehicle brand like BMW or Mini</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -1,275 +0,0 @@
|
||||
# add-on
|
||||
|
||||
addon.mybmw.name = MyBMW
|
||||
addon.mybmw.description = Provides access to your Vehicle Data like MyBMW App
|
||||
|
||||
# thing types
|
||||
thing-type.config.mybmw.bridge.language.description = Channel data can be returned in the desired language like en, de, fr ...
|
||||
thing-type.config.mybmw.bridge.language.label = Language Settings
|
||||
thing-type.config.mybmw.bridge.password.description = MyBMW Password
|
||||
thing-type.config.mybmw.bridge.password.label = Password
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token for the Login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
|
||||
thing-type.config.mybmw.bridge.region.description = Select Region in order to connect to the appropriate BMW Server
|
||||
thing-type.config.mybmw.bridge.region.label = Region
|
||||
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = North America
|
||||
thing-type.config.mybmw.bridge.region.option.ROW = Rest of the World
|
||||
thing-type.config.mybmw.bridge.userName.description = MyBMW Username
|
||||
thing-type.config.mybmw.bridge.userName.label = Username
|
||||
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.description = Data refresh rate for your vehicle data. If set to 0 no refresh will be triggered automatically.
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.description = Vehicle brand like BMW or Mini
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.label = Brand of the Vehicle
|
||||
thing-type.config.mybmw.vehicle.vin.description = Unique VIN given by BMW
|
||||
thing-type.config.mybmw.vehicle.vin.label = Vehicle Identification Number (VIN)
|
||||
thing-type.mybmw.account.description = Your BMW account data
|
||||
thing-type.mybmw.account.label = MyBMW Account
|
||||
thing-type.mybmw.bev_rex.description = Battery Electric Vehicle with Range Extender (BEV_REX)
|
||||
thing-type.mybmw.bev_rex.label = Electric Vehicle with REX
|
||||
thing-type.mybmw.bev.description = Battery Electric Vehicle (BEV)
|
||||
thing-type.mybmw.bev.label = Electric Vehicle
|
||||
thing-type.mybmw.conv.description = Conventional Fuel Vehicle (CONV)
|
||||
thing-type.mybmw.conv.label = Conventional Vehicle
|
||||
thing-type.mybmw.phev.description = Conventional Fuel Vehicle with supporting Electric Engine (PHEV)
|
||||
thing-type.mybmw.phev.label = Plug-In-Hybrid Electric Vehicle
|
||||
|
||||
# channel group types
|
||||
channel-group-type.mybmw.charge-statistic.description = Charging statistics of current month
|
||||
channel-group-type.mybmw.charge-statistic.label = Charging Statistics
|
||||
channel-group-type.mybmw.check-control-values.description = Shows current active CheckControl messages
|
||||
channel-group-type.mybmw.check-control-values.label = Check Control Messages
|
||||
channel-group-type.mybmw.conv-range-values.description = Provides Mileage, remaining range and fuel level values
|
||||
channel-group-type.mybmw.conv-range-values.label = Range and Fuel Data
|
||||
channel-group-type.mybmw.door-values.description = Detailed Status of all Doors and Windows
|
||||
channel-group-type.mybmw.door-values.label = Detailed Door Status
|
||||
channel-group-type.mybmw.ev-range-values.description = Provides Mileage, remaining range and charge level values
|
||||
channel-group-type.mybmw.ev-range-values.label = Range and Charge Data
|
||||
channel-group-type.mybmw.ev-vehicle-status.description = Overall vehicle status
|
||||
channel-group-type.mybmw.ev-vehicle-status.label = Vehicle Status
|
||||
channel-group-type.mybmw.ev-vehicle-update.description = Force the update of the vehicle data
|
||||
channel-group-type.mybmw.ev-vehicle-update.label = Force Vehicle Update
|
||||
channel-group-type.mybmw.hybrid-range-values.description = Provides mileage, remaining fuel and range data for hybrid vehicles
|
||||
channel-group-type.mybmw.hybrid-range-values.label = Range, Charge / Fuel Data
|
||||
channel-group-type.mybmw.image-values.description = Provides an image of your vehicle
|
||||
channel-group-type.mybmw.image-values.label = Vehicle Image
|
||||
|
||||
channel-group-type.mybmw.location-values.description = Coordinates and heading of the vehicle
|
||||
channel-group-type.mybmw.location-values.label = Vehicle Location
|
||||
channel-group-type.mybmw.profile-values.description = Scheduled charging profiles
|
||||
channel-group-type.mybmw.profile-values.label = Electric Charging Profile
|
||||
channel-group-type.mybmw.remote-services.description = Remote control of the vehicle
|
||||
channel-group-type.mybmw.remote-services.label = Remote Services
|
||||
channel-group-type.mybmw.service-values.description = Future vehicle service schedules
|
||||
channel-group-type.mybmw.service-values.label = Vehicle Services
|
||||
channel-group-type.mybmw.session-values.description = Past charging sessions
|
||||
channel-group-type.mybmw.session-values.label = Electric Charging Sessions
|
||||
channel-group-type.mybmw.tire-pressures.description = Current and wanted pressure for all tires
|
||||
channel-group-type.mybmw.tire-pressures.label = Tire Pressure
|
||||
channel-group-type.mybmw.vehicle-status.description = Overall vehicle status
|
||||
channel-group-type.mybmw.vehicle-status.label = Vehicle Status
|
||||
channel-group-type.mybmw.vehicle-update.description = Force the update of the vehicle data
|
||||
channel-group-type.mybmw.vehicle-update.label = Force Vehicle Update
|
||||
|
||||
|
||||
# channel types
|
||||
channel-type.mybmw.address-channel.label = Address
|
||||
channel-type.mybmw.charging-info-channel.label = Charging Information
|
||||
channel-type.mybmw.charging-remaining-channel.label = Remaining Charging Time
|
||||
channel-type.mybmw.charging-status-channel.label = Charging Status
|
||||
channel-type.mybmw.charging-update-channel.label = Force update of the charging data
|
||||
channel-type.mybmw.check-control-channel.label = Check Control
|
||||
channel-type.mybmw.checkcontrol-details-channel.label = CheckControl Details
|
||||
channel-type.mybmw.checkcontrol-name-channel.label = CheckControl Description
|
||||
channel-type.mybmw.checkcontrol-severity-channel.label = Severity Level
|
||||
|
||||
channel-type.mybmw.doors-channel.label = Overall Door Status
|
||||
channel-type.mybmw.driver-front-channel.label = Driver Door
|
||||
channel-type.mybmw.driver-rear-channel.label = Driver Door Rear
|
||||
channel-type.mybmw.estimated-fuel-l-100km-channel.label = Estimated consumption l/100km
|
||||
channel-type.mybmw.estimated-fuel-mpg-channel.label = Estimated consumption mpg
|
||||
channel-type.mybmw.front-left-current-channel.label = Tire Pressure Front Left
|
||||
channel-type.mybmw.front-left-target-channel.label = Tire Pressure Front Left Target
|
||||
channel-type.mybmw.front-right-current-channel.label = Tire Pressure Front Right
|
||||
channel-type.mybmw.front-right-target-channel.label = Tire Pressure Front Right Target
|
||||
channel-type.mybmw.gps-channel.label = GPS Coordinates
|
||||
channel-type.mybmw.heading-channel.label = Heading Angle
|
||||
channel-type.mybmw.home-distance-channel.description = Computed distance between vehicle and home location
|
||||
channel-type.mybmw.home-distance-channel.label = Distance From Home
|
||||
channel-type.mybmw.hood-channel.label = Hood
|
||||
|
||||
channel-type.mybmw.image-update-channel.label = Force update of the image
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontLeft = Left Side View
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontRight = Right Side View
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontView = Front View
|
||||
channel-type.mybmw.image-view-channel.command.option.RearView = Rear View
|
||||
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Side View
|
||||
channel-type.mybmw.image-view-channel.label = Image Viewport
|
||||
channel-type.mybmw.last-fetched-channel.label = Last Openhab Update Timestamp
|
||||
channel-type.mybmw.last-update-channel.label = Last Car Status Timestamp
|
||||
channel-type.mybmw.lock-channel.label = Doors Locked
|
||||
channel-type.mybmw.mileage-channel.label = Total Distance Driven
|
||||
|
||||
channel-type.mybmw.next-service-date-channel.label = Next Service Date
|
||||
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.next-service-mileage-channel.label = Mileage Till Next Service
|
||||
channel-type.mybmw.passenger-front-channel.label = Passenger Door
|
||||
channel-type.mybmw.passenger-rear-channel.label = Passenger Door Rear
|
||||
channel-type.mybmw.plug-connection-channel.label = Plug Connection Status
|
||||
channel-type.mybmw.png-channel.label = Rendered Vehicle Image
|
||||
|
||||
channel-type.mybmw.profile-climate-channel.label = A/C at Departure Time
|
||||
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Weekly Schedule
|
||||
channel-type.mybmw.profile-control-channel.description = Charging plan selection
|
||||
channel-type.mybmw.profile-control-channel.label = Charging Plan
|
||||
channel-type.mybmw.profile-limit-channel.description = Limited charging activated
|
||||
channel-type.mybmw.profile-limit-channel.label = Charging Energy Limited
|
||||
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Use Charging Preference
|
||||
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Immediate Charging
|
||||
channel-type.mybmw.profile-mode-channel.description = Mode for selecting immediate or delyed charging
|
||||
channel-type.mybmw.profile-mode-channel.label = Charge Mode
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Charging Window
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = No Selection
|
||||
channel-type.mybmw.profile-prefs-channel.description = Preferences for delayed charging
|
||||
channel-type.mybmw.profile-prefs-channel.label = Charge Preferences
|
||||
channel-type.mybmw.profile-target-channel.description = SOC charging target
|
||||
channel-type.mybmw.profile-target-channel.label = SOC Target
|
||||
|
||||
channel-type.mybmw.range-electric-channel.label = Electric Range
|
||||
channel-type.mybmw.range-fuel-channel.label = Fuel Range
|
||||
channel-type.mybmw.range-hybrid-channel.label = Hybrid Range
|
||||
channel-type.mybmw.range-radius-electric-channel.label = Electric Range Radius
|
||||
channel-type.mybmw.range-radius-fuel-channel.label = Fuel Range Radius
|
||||
channel-type.mybmw.range-radius-hybrid-channel.label = Hybrid Range Radius
|
||||
channel-type.mybmw.raw-channel.label = Raw Data
|
||||
|
||||
channel-type.mybmw.rear-left-current-channel.label = Tire Pressure Rear Left
|
||||
channel-type.mybmw.rear-left-target-channel.label = Tire Pressure Rear Left Target
|
||||
channel-type.mybmw.rear-right-current-channel.label = Tire Pressure Rear Right
|
||||
channel-type.mybmw.rear-right-target-channel.label = Tire Pressure Rear Right Target
|
||||
channel-type.mybmw.remaining-fuel-channel.label = Remaining Fuel
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Start Climate now
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Stop Climate now
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-lock = Lock vehicle
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Unlock vehicle
|
||||
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Blow horn
|
||||
channel-type.mybmw.remote-command-channel.command.option.light-flash = Flash lights
|
||||
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Find vehicle
|
||||
channel-type.mybmw.remote-command-channel.label = Remote Command
|
||||
channel-type.mybmw.remote-state-channel.label = Service Execution State
|
||||
channel-type.mybmw.service-date-channel.label = Service Date
|
||||
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.service-details-channel.label = Service Details
|
||||
channel-type.mybmw.service-mileage-channel.label = Mileage until Service
|
||||
channel-type.mybmw.service-name-channel.label = Service Name
|
||||
channel-type.mybmw.session-energy-channel.label = Charged Energy in Session
|
||||
channel-type.mybmw.session-issue-channel.label = Issues during Session
|
||||
channel-type.mybmw.session-status-channel.label = Session Status
|
||||
channel-type.mybmw.session-subtitle-channel.label = Session Details
|
||||
channel-type.mybmw.session-title-channel.label = Session Title
|
||||
channel-type.mybmw.soc-channel.label = Battery Charge Level
|
||||
|
||||
channel-type.mybmw.state-update-channel.label = Force update of the vehicle state data
|
||||
channel-type.mybmw.statistic-energy-channel.description = Total energy charged in current month
|
||||
channel-type.mybmw.statistic-energy-channel.label = Energy Charged
|
||||
channel-type.mybmw.statistic-sessions-channel.description = Number of charging sessions this month
|
||||
channel-type.mybmw.statistic-sessions-channel.label = Charge Sessions
|
||||
channel-type.mybmw.statistic-title-channel.label = Charge Statistic Month
|
||||
channel-type.mybmw.sunroof-channel.label = Sunroof
|
||||
|
||||
channel-type.mybmw.timer1-day-fri-channel.description = Friday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-fri-channel.label = T1 Friday
|
||||
channel-type.mybmw.timer1-day-mon-channel.description = Monday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-mon-channel.label = T1 Monday
|
||||
channel-type.mybmw.timer1-day-sat-channel.description = Saturday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-sat-channel.label = T1 Saturday
|
||||
channel-type.mybmw.timer1-day-sun-channel.description = Sunday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-sun-channel.label = T1 Sunday
|
||||
channel-type.mybmw.timer1-day-thu-channel.description = Thursday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-thu-channel.label = T1 Thursday
|
||||
channel-type.mybmw.timer1-day-tue-channel.description = Tuesday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-tue-channel.label = T1 Tuesday
|
||||
channel-type.mybmw.timer1-day-wed-channel.description = Wednesday scheduled for timer 1
|
||||
channel-type.mybmw.timer1-day-wed-channel.label = T1 Wednesday
|
||||
channel-type.mybmw.timer1-departure-channel.description = Departure time for regular schedule timer 1
|
||||
channel-type.mybmw.timer1-departure-channel.label = T1 Departure Time
|
||||
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.timer1-enabled-channel.description = Timer 1 enabled
|
||||
channel-type.mybmw.timer1-enabled-channel.label = T1 Enabled
|
||||
channel-type.mybmw.timer2-day-fri-channel.description = Friday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-fri-channel.label = T2 Friday
|
||||
channel-type.mybmw.timer2-day-mon-channel.description = Monday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-mon-channel.label = T2 Monday
|
||||
channel-type.mybmw.timer2-day-sat-channel.description = Saturday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-sat-channel.label = T2 Saturday
|
||||
channel-type.mybmw.timer2-day-sun-channel.description = Sunday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-sun-channel.label = T2 Sunday
|
||||
channel-type.mybmw.timer2-day-thu-channel.description = Thursday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-thu-channel.label = T2 Thursday
|
||||
channel-type.mybmw.timer2-day-tue-channel.description = Tuesday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-tue-channel.label = T2 Tuesday
|
||||
channel-type.mybmw.timer2-day-wed-channel.description = Wednesday scheduled for timer 2
|
||||
channel-type.mybmw.timer2-day-wed-channel.label = T2 Wednesday
|
||||
channel-type.mybmw.timer2-departure-channel.description = Departure time for regular schedule timer 2
|
||||
channel-type.mybmw.timer2-departure-channel.label = T2 Departure Time
|
||||
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.timer2-enabled-channel.description = Timer 2 enabled
|
||||
channel-type.mybmw.timer2-enabled-channel.label = T2 Enabled
|
||||
channel-type.mybmw.timer3-day-fri-channel.description = Friday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-fri-channel.label = T3 Friday
|
||||
channel-type.mybmw.timer3-day-mon-channel.description = Monday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-mon-channel.label = T3 Monday
|
||||
channel-type.mybmw.timer3-day-sat-channel.description = Saturday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-sat-channel.label = T3 Saturday
|
||||
channel-type.mybmw.timer3-day-sun-channel.description = Sunday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-sun-channel.label = T3 Sunday
|
||||
channel-type.mybmw.timer3-day-thu-channel.description = Thursday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-thu-channel.label = T3 Thursday
|
||||
channel-type.mybmw.timer3-day-tue-channel.description = Tuesday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-tue-channel.label = T3 Tuesday
|
||||
channel-type.mybmw.timer3-day-wed-channel.description = Wednesday scheduled for timer 3
|
||||
channel-type.mybmw.timer3-day-wed-channel.label = T3 Wednesday
|
||||
channel-type.mybmw.timer3-departure-channel.description = Departure time for regular schedule timer 3
|
||||
channel-type.mybmw.timer3-departure-channel.label = T3 Departure Time
|
||||
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.timer3-enabled-channel.description = Timer 3 enabled
|
||||
channel-type.mybmw.timer3-enabled-channel.label = T3 Enabled
|
||||
channel-type.mybmw.timer4-day-fri-channel.description = Friday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-fri-channel.label = T4 Friday
|
||||
channel-type.mybmw.timer4-day-mon-channel.description = Monday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-mon-channel.label = T4 Monday
|
||||
channel-type.mybmw.timer4-day-sat-channel.description = Saturday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-sat-channel.label = T4 Saturday
|
||||
channel-type.mybmw.timer4-day-sun-channel.description = Sunday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-sun-channel.label = T4 Sunday
|
||||
channel-type.mybmw.timer4-day-thu-channel.description = Thursday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-thu-channel.label = T4 Thursday
|
||||
channel-type.mybmw.timer4-day-tue-channel.description = Tuesday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-tue-channel.label = T4 Tuesday
|
||||
channel-type.mybmw.timer4-day-wed-channel.description = Wednesday scheduled for timer 4
|
||||
channel-type.mybmw.timer4-day-wed-channel.label = T4 Wednesday
|
||||
channel-type.mybmw.timer4-departure-channel.description = Departure time for regular schedule timer 4
|
||||
channel-type.mybmw.timer4-departure-channel.label = T4 Departure Time
|
||||
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.timer4-enabled-channel.description = Timer 4 enabled
|
||||
channel-type.mybmw.timer4-enabled-channel.label = T4 Enabled
|
||||
channel-type.mybmw.trunk-channel.label = Trunk
|
||||
channel-type.mybmw.window-driver-front-channel.label = Driver Window
|
||||
channel-type.mybmw.window-driver-rear-channel.label = Driver Rear Window
|
||||
channel-type.mybmw.window-end-channel.description = End time of charging window
|
||||
channel-type.mybmw.window-end-channel.label = Window End Time
|
||||
channel-type.mybmw.window-end-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.window-passenger-front-channel.label = Passenger Window
|
||||
channel-type.mybmw.window-passenger-rear-channel.label = Passenger Rear Window
|
||||
channel-type.mybmw.window-start-channel.description = Start time of charging window
|
||||
channel-type.mybmw.window-start-channel.label = Window Start Time
|
||||
channel-type.mybmw.window-start-channel.state.pattern = %1$tH:%1$tM
|
||||
channel-type.mybmw.windows-channel.label = Overall Window Status
|
||||
|
||||
mybmw.status.authorization-needed = You need to complete a captcha to continue: {0}
|
||||
mybmw.status.user-details-missing = Username or password configuration is missing
|
||||
mybmw.status.region-missing = Region configuration is missing
|
||||
mybmw.status.ip-missing = The callback ip of the the openHAB host could not be retrieved
|
||||
mybmw.status.vehicle-retrieval-error = Retrieving vehicles failed
|
||||
mybmw.status.network-error = Retrieving vehicles failed, possible network error
|
||||
mybmw.status.quota-error = Out of quota, refresh at {0}
|
||||
@@ -1,275 +0,0 @@
|
||||
# add-on
|
||||
|
||||
addon.mybmw.name = MyBMW
|
||||
addon.mybmw.description = Fahrzeugdaten über die MyBMW App
|
||||
|
||||
# thing types
|
||||
thing-type.config.mybmw.bridge.language.description = Daten werden für die gewünschte Sprache angefordert (en, de, fr ...)
|
||||
thing-type.config.mybmw.bridge.language.label = Sprachauswahl
|
||||
thing-type.config.mybmw.bridge.password.description = Passwort für die MyBMW App
|
||||
thing-type.config.mybmw.bridge.password.label = Passwort
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token für den Login (siehe https\://bimmer-connected.readthedocs.io/en/stable/captcha.html)
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
|
||||
thing-type.config.mybmw.bridge.region.description = Auswahl Ihrer Region
|
||||
thing-type.config.mybmw.bridge.region.label = Region
|
||||
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Nordamerika
|
||||
thing-type.config.mybmw.bridge.region.option.ROW = Rest der Welt
|
||||
thing-type.config.mybmw.bridge.userName.description = Benutzername für die MyBMW App
|
||||
thing-type.config.mybmw.bridge.userName.label = Benutzername
|
||||
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.description = Aktualisierungsrate für Ihre Fahrzeugdaten. Wenn auf 0 gesetzt, wird keine Aktualisierung automatisch ausgelöst.
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.label = Datenaktualisierung in Minuten
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.description = Fahrzeugmarke wie z.B. BMW oder Mini.
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.label = Marke des Fahrzeugs
|
||||
thing-type.config.mybmw.vehicle.vin.description = VIN des Fahrzeugs
|
||||
thing-type.config.mybmw.vehicle.vin.label = Fahrzeug Identifikationsnummer (VIN)
|
||||
thing-type.mybmw.account.description = Kontodaten für das BMW Benutzerkonto
|
||||
thing-type.mybmw.account.label = MyBMW Benutzerkonto
|
||||
thing-type.mybmw.bev_rex.description = Elektrofahrzeug mit Range Extender (bev_rex)
|
||||
thing-type.mybmw.bev_rex.label = Elektrofahrzeug mit REX
|
||||
thing-type.mybmw.bev.description = Batterieelektrisches Fahrzeug (bev)
|
||||
thing-type.mybmw.bev.label = Elektrofahrzeug
|
||||
thing-type.mybmw.conv.description = Konventionelles Benzin/Diesel Fahrzeug (conv)
|
||||
thing-type.mybmw.conv.label = Konventionelles Fahrzeug
|
||||
thing-type.mybmw.phev.description = Konventionelles Fahrzeug mit Elektromotor (phev)
|
||||
thing-type.mybmw.phev.label = Plug-in-Hybrid Elektrofahrzeug
|
||||
|
||||
# channel group types
|
||||
channel-group-type.mybmw.charge-statistic.description = Statistik der Ladevorgänge im Monat
|
||||
channel-group-type.mybmw.charge-statistic.label = Elektrische Ladestatistik
|
||||
channel-group-type.mybmw.check-control-values.description = Aktuelle Warnungen des Fahrzeugs
|
||||
channel-group-type.mybmw.check-control-values.label = Warnungen
|
||||
channel-group-type.mybmw.conv-range-values.description = Tachostand, Reichweite und Tankfüllung des Fahrzeugs
|
||||
channel-group-type.mybmw.conv-range-values.label = Verbrenner Reichweiten und Füllstände
|
||||
channel-group-type.mybmw.door-values.description = Zeigt die Details der Türen und Fenster des Fahrzeugs
|
||||
channel-group-type.mybmw.door-values.label = Details aller Türen
|
||||
channel-group-type.mybmw.ev-range-values.description = Tachostand, Reichweiten und Ladestand des Fahrzeugs
|
||||
channel-group-type.mybmw.ev-range-values.label = Elektro- Reichweiten und Batterieladung
|
||||
channel-group-type.mybmw.ev-vehicle-status.description = Gesamtzustand des Fahrzeugs
|
||||
channel-group-type.mybmw.ev-vehicle-status.label = Fahrzeug Zustand
|
||||
channel-group-type.mybmw.ev-vehicle-update.description = Die Fahrzeugdaten manuell aktualisieren
|
||||
channel-group-type.mybmw.ev-vehicle-update.label = Fahrzeugaktualisierung erzwingen
|
||||
channel-group-type.mybmw.hybrid-range-values.description = Tachostand, Reichweite, Ladezustand und Tankfüllung für hybride Fahrzeuge
|
||||
channel-group-type.mybmw.hybrid-range-values.label = Hybride Reichweiten und Füllstände
|
||||
channel-group-type.mybmw.image-values.description = Bild des Fahrzeug basierend auf der ausgewählten Ansicht
|
||||
channel-group-type.mybmw.image-values.label = Fahrzeug Bild
|
||||
|
||||
channel-group-type.mybmw.location-values.description = Koordinaten und Ausrichtung des Fahrzeugs
|
||||
channel-group-type.mybmw.location-values.label = Fahrzeug Standort
|
||||
channel-group-type.mybmw.profile-values.description = Zeitplanung der Ladevorgänge
|
||||
channel-group-type.mybmw.profile-values.label = Elektrisches Ladeprofil
|
||||
channel-group-type.mybmw.remote-services.description = Fernsteuerung des Fahrzeugs
|
||||
channel-group-type.mybmw.remote-services.label = Fernsteuerung
|
||||
channel-group-type.mybmw.service-values.description = Anstehende Wartungstermine des Fahrzeugs
|
||||
channel-group-type.mybmw.service-values.label = Wartung
|
||||
channel-group-type.mybmw.session-values.description = Liste der letzten Ladevorgänge
|
||||
channel-group-type.mybmw.session-values.label = Elektrische Ladevorgänge
|
||||
channel-group-type.mybmw.tire-pressures.description = Reifen Luftdruck Ist und Sollwerte
|
||||
channel-group-type.mybmw.tire-pressures.label = Reifen Luftdruck
|
||||
channel-group-type.mybmw.vehicle-status.description = Gesamtzustand des Fahrzeugs
|
||||
channel-group-type.mybmw.vehicle-status.label = Fahrzeug Zustand
|
||||
channel-group-type.mybmw.vehicle-update.description = Aktualisierung der Fahrzeugdaten erzwingen
|
||||
channel-group-type.mybmw.vehicle-update.label = Fahrzeugaktualisierung erzwingen
|
||||
|
||||
|
||||
# channel types
|
||||
channel-type.mybmw.address-channel.label = Adresse
|
||||
channel-type.mybmw.charging-info-channel.label = Ladeinformationen
|
||||
channel-type.mybmw.charging-remaining-channel.label = Verbleibende Ladezeit
|
||||
channel-type.mybmw.charging-status-channel.label = Ladezustand
|
||||
channel-type.mybmw.charging-update-channel.label = Update der Ladedaten erzwingen
|
||||
channel-type.mybmw.check-control-channel.label = Warnung Aktiv
|
||||
channel-type.mybmw.checkcontrol-details-channel.label = Warnung Details
|
||||
channel-type.mybmw.checkcontrol-name-channel.label = Warnung
|
||||
channel-type.mybmw.checkcontrol-severity-channel.label = Warnung Priorität
|
||||
|
||||
channel-type.mybmw.doors-channel.label = Gesamtzustand der Türen
|
||||
channel-type.mybmw.driver-front-channel.label = Fahrertür
|
||||
channel-type.mybmw.driver-rear-channel.label = Fahrertür Hinten
|
||||
channel-type.mybmw.estimated-fuel-l-100km-channel.label = Geschätzter Verbrauch l/100km
|
||||
channel-type.mybmw.estimated-fuel-mpg-channel.label = Geschätzter Verbrauch mpg
|
||||
channel-type.mybmw.front-left-current-channel.label = Reifen Luftdruck Vorne Links
|
||||
channel-type.mybmw.front-left-target-channel.label = Reifen Luftdruck vorne links
|
||||
channel-type.mybmw.front-right-current-channel.label = Reifen Luftdruck Vorne Rechts
|
||||
channel-type.mybmw.front-right-target-channel.label = Reifen Luftdruck vorne rechts
|
||||
channel-type.mybmw.gps-channel.label = Koordinaten
|
||||
channel-type.mybmw.heading-channel.label = Ausrichtung
|
||||
channel-type.mybmw.home-distance-channel.description = Berechnete Entfernung zwischen Fahrzeug und Heimatort
|
||||
channel-type.mybmw.home-distance-channel.label = Entfernung von Zuhause
|
||||
channel-type.mybmw.hood-channel.label = Frontklappe
|
||||
|
||||
channel-type.mybmw.image-update-channel.label = Update des Bildes erzwingen
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontLeft = Linke Seitenansicht
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontRight = Rechte Seitenansicht
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontView = Frontansicht
|
||||
channel-type.mybmw.image-view-channel.command.option.RearView = Rückansicht
|
||||
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Front Seitenansicht
|
||||
channel-type.mybmw.image-view-channel.label = Fahrzeug Ansicht
|
||||
channel-type.mybmw.last-fetched-channel.label = Zeitstempel letztes openHAB Update
|
||||
channel-type.mybmw.last-update-channel.label = Zeitstempel letzter Fahrzeugstatus
|
||||
channel-type.mybmw.lock-channel.label = Fahrzeug Abgeschlossen
|
||||
channel-type.mybmw.mileage-channel.label = Tachostand
|
||||
|
||||
channel-type.mybmw.next-service-date-channel.label = Nächster Service Termin
|
||||
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.next-service-mileage-channel.label = Nächster Service in Kilometern
|
||||
channel-type.mybmw.passenger-front-channel.label = Beifahrertür
|
||||
channel-type.mybmw.passenger-rear-channel.label = Beifahrertür Hinten
|
||||
channel-type.mybmw.plug-connection-channel.label = Ladestecker
|
||||
channel-type.mybmw.png-channel.label = Fahrzeug Bild
|
||||
|
||||
channel-type.mybmw.profile-climate-channel.label = Klimatisierung bei Abfahrt
|
||||
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Wochenplan
|
||||
channel-type.mybmw.profile-control-channel.description = Ladeplan Auswahl
|
||||
channel-type.mybmw.profile-control-channel.label = Ladeplan
|
||||
channel-type.mybmw.profile-limit-channel.description = Limitiertes Laden aktiviert
|
||||
channel-type.mybmw.profile-limit-channel.label = Ladung Limitiert
|
||||
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Ladeverzögerung
|
||||
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Sofort Laden
|
||||
channel-type.mybmw.profile-mode-channel.description = Modus für sofortiges oder verzögertes Laden
|
||||
channel-type.mybmw.profile-mode-channel.label = Ladeprofil
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Laden im Zeitfenster
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = Keine Präferenz
|
||||
channel-type.mybmw.profile-prefs-channel.description = Einstellungen für verzögerte Ladung
|
||||
channel-type.mybmw.profile-prefs-channel.label = Ladeprofil Präferenz
|
||||
channel-type.mybmw.profile-target-channel.description = Erwünschter Batterie Ladezustand
|
||||
channel-type.mybmw.profile-target-channel.label = Ziel Ladezustand
|
||||
|
||||
channel-type.mybmw.range-electric-channel.label = Elektrische Reichweite
|
||||
channel-type.mybmw.range-fuel-channel.label = Verbrenner Reichweite
|
||||
channel-type.mybmw.range-hybrid-channel.label = Hybride Reichweite
|
||||
channel-type.mybmw.range-radius-electric-channel.label = Elektrischer Reichweiten-Radius
|
||||
channel-type.mybmw.range-radius-fuel-channel.label = Verbrenner Reichweiten-Radius
|
||||
channel-type.mybmw.range-radius-hybrid-channel.label = Hybrider Reichweiten-Radius
|
||||
channel-type.mybmw.raw-channel.label = Rohdaten
|
||||
|
||||
channel-type.mybmw.rear-left-current-channel.label = Reifen Luftdruck Hinten Links
|
||||
channel-type.mybmw.rear-left-target-channel.label = Reifen Luftdruck hinten links
|
||||
channel-type.mybmw.rear-right-current-channel.label = Reifen Luftdruck Hinten Rechts
|
||||
channel-type.mybmw.rear-right-target-channel.label = Reifen Luftdruck hinten rechts
|
||||
channel-type.mybmw.remaining-fuel-channel.label = Tankstand
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Klimaanlage jetzt einschalten
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Klimaanlage jetzt ausschalten
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-lock = Fahrzeug verriegeln
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Fahrzeug entriegeln
|
||||
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Hupe betätigen
|
||||
channel-type.mybmw.remote-command-channel.command.option.light-flash = Lampen blinken lassen
|
||||
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Fahrzeug finden
|
||||
channel-type.mybmw.remote-command-channel.label = Kommando Auswahl
|
||||
channel-type.mybmw.remote-state-channel.label = Ausführungszustand
|
||||
channel-type.mybmw.service-date-channel.label = Service Termin
|
||||
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.service-details-channel.label = Service Details
|
||||
channel-type.mybmw.service-mileage-channel.label = Fahrstrecke bis zum nächsten Service
|
||||
channel-type.mybmw.service-name-channel.label = Service
|
||||
channel-type.mybmw.session-energy-channel.label = Energie Geladen
|
||||
channel-type.mybmw.session-issue-channel.label = Ladevorgang Probleme
|
||||
channel-type.mybmw.session-status-channel.label = Ladevorgang Zustand
|
||||
channel-type.mybmw.session-subtitle-channel.label = Ladevorgang Details
|
||||
channel-type.mybmw.session-title-channel.label = Ladevorgang Beschreibung
|
||||
channel-type.mybmw.soc-channel.label = Batterie Ladestand
|
||||
|
||||
channel-type.mybmw.state-update-channel.label = Aktualisierung der Fahrzeugzustandsdaten erzwingen
|
||||
channel-type.mybmw.statistic-energy-channel.description = Geladene Energie in diesem Monat
|
||||
channel-type.mybmw.statistic-energy-channel.label = Energie Geladen Monat
|
||||
channel-type.mybmw.statistic-sessions-channel.description = Anzahl der Ladevorgänge in diesem Monat
|
||||
channel-type.mybmw.statistic-sessions-channel.label = Ladevorgänge Monat
|
||||
channel-type.mybmw.statistic-title-channel.label = Ladestatistik Monat
|
||||
channel-type.mybmw.sunroof-channel.label = Schiebedach
|
||||
|
||||
channel-type.mybmw.timer1-day-fri-channel.description = Freitags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-fri-channel.label = Zeitprofil 1 - Freitag
|
||||
channel-type.mybmw.timer1-day-mon-channel.description = Montags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-mon-channel.label = Zeitprofil 1 - Montag
|
||||
channel-type.mybmw.timer1-day-sat-channel.description = Samstags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-sat-channel.label = Zeitprofil 1 - Samstag
|
||||
channel-type.mybmw.timer1-day-sun-channel.description = Sonntags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-sun-channel.label = Zeitprofil 1 - Sonntag
|
||||
channel-type.mybmw.timer1-day-thu-channel.description = Donnerstags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-thu-channel.label = Zeitprofil 1 - Donnerstag
|
||||
channel-type.mybmw.timer1-day-tue-channel.description = Dienstags Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-tue-channel.label = Zeitprofil 1 - Dienstag
|
||||
channel-type.mybmw.timer1-day-wed-channel.description = Mittwochs Planung für Timer 1
|
||||
channel-type.mybmw.timer1-day-wed-channel.label = Zeitprofil 1 - Mittwoch
|
||||
channel-type.mybmw.timer1-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 1
|
||||
channel-type.mybmw.timer1-departure-channel.label = Zeitprofil 1 - Abfahrtszeit
|
||||
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer1-enabled-channel.description = Timer 1 aktiviert
|
||||
channel-type.mybmw.timer1-enabled-channel.label = Zeitprofil 1 - Aktiviert
|
||||
channel-type.mybmw.timer2-day-fri-channel.description = Freitags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-fri-channel.label = Zeitprofil 2 - Freitag
|
||||
channel-type.mybmw.timer2-day-mon-channel.description = Montags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-mon-channel.label = Zeitprofil 2 - Montag
|
||||
channel-type.mybmw.timer2-day-sat-channel.description = Samstags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-sat-channel.label = Zeitprofil 2 - Samstag
|
||||
channel-type.mybmw.timer2-day-sun-channel.description = Sonntags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-sun-channel.label = Zeitprofil 2 - Sonntag
|
||||
channel-type.mybmw.timer2-day-thu-channel.description = Donnerstags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-thu-channel.label = Zeitprofil 2 - Donnerstag
|
||||
channel-type.mybmw.timer2-day-tue-channel.description = Dienstags Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-tue-channel.label = Zeitprofil 2 - Dienstag
|
||||
channel-type.mybmw.timer2-day-wed-channel.description = Mittwochs Planung für Timer 2
|
||||
channel-type.mybmw.timer2-day-wed-channel.label = Zeitprofil 2 - Mittwoch
|
||||
channel-type.mybmw.timer2-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 2
|
||||
channel-type.mybmw.timer2-departure-channel.label = Zeitprofil 2 - Abfahrtszeit
|
||||
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer2-enabled-channel.description = Timer 2 aktiviert
|
||||
channel-type.mybmw.timer2-enabled-channel.label = Zeitprofil 2 - Aktiviert
|
||||
channel-type.mybmw.timer3-day-fri-channel.description = Freitags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-fri-channel.label = Zeitprofil 3 - Freitag
|
||||
channel-type.mybmw.timer3-day-mon-channel.description = Montags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-mon-channel.label = Zeitprofil 3 - Montag
|
||||
channel-type.mybmw.timer3-day-sat-channel.description = Samstags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-sat-channel.label = Zeitprofil 3 - Samstag
|
||||
channel-type.mybmw.timer3-day-sun-channel.description = Sonntags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-sun-channel.label = Zeitprofil 3 - Sonntag
|
||||
channel-type.mybmw.timer3-day-thu-channel.description = Donnerstags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-thu-channel.label = Zeitprofil 3 - Donnerstag
|
||||
channel-type.mybmw.timer3-day-tue-channel.description = Dienstags Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-tue-channel.label = Zeitprofil 3 - Dienstag
|
||||
channel-type.mybmw.timer3-day-wed-channel.description = Mittwochs Planung für Timer 3
|
||||
channel-type.mybmw.timer3-day-wed-channel.label = Zeitprofil 3 - Mittwoch
|
||||
channel-type.mybmw.timer3-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 3
|
||||
channel-type.mybmw.timer3-departure-channel.label = Zeitprofil 3 - Abfahrtszeit
|
||||
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer3-enabled-channel.description = Timer 3 aktiviert
|
||||
channel-type.mybmw.timer3-enabled-channel.label = Zeitprofil 3 - Aktiviert
|
||||
channel-type.mybmw.timer4-day-fri-channel.description = Freitags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-fri-channel.label = Zeitprofil 4 - Freitag
|
||||
channel-type.mybmw.timer4-day-mon-channel.description = Montags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-mon-channel.label = Zeitprofil 4 - Montag
|
||||
channel-type.mybmw.timer4-day-sat-channel.description = Samstags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-sat-channel.label = Zeitprofil 4 - Samstag
|
||||
channel-type.mybmw.timer4-day-sun-channel.description = Sonntags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-sun-channel.label = Zeitprofil 4 - Sonntag
|
||||
channel-type.mybmw.timer4-day-thu-channel.description = Donnerstags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-thu-channel.label = Zeitprofil 4 - Donnerstag
|
||||
channel-type.mybmw.timer4-day-tue-channel.description = Dienstags Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-tue-channel.label = Zeitprofil 4 - Dienstag
|
||||
channel-type.mybmw.timer4-day-wed-channel.description = Mittwochs Planung für Timer 4
|
||||
channel-type.mybmw.timer4-day-wed-channel.label = Zeitprofil 4 - Mittwoch
|
||||
channel-type.mybmw.timer4-departure-channel.description = Abfahrtszeit für regelmäßige Planung Timer 4
|
||||
channel-type.mybmw.timer4-departure-channel.label = Zeitprofil 4 - Abfahrtszeit
|
||||
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer4-enabled-channel.description = Timer 4 aktiviert
|
||||
channel-type.mybmw.timer4-enabled-channel.label = Zeitprofil 4 - Aktiviert
|
||||
channel-type.mybmw.trunk-channel.label = Heckklappe
|
||||
channel-type.mybmw.window-driver-front-channel.label = Fahrertür Fenster
|
||||
channel-type.mybmw.window-driver-rear-channel.label = Fahrertür Hinten Fenster
|
||||
channel-type.mybmw.window-end-channel.description = Endzeit des Ladefensters
|
||||
channel-type.mybmw.window-end-channel.label = Ladefenster Endzeit
|
||||
channel-type.mybmw.window-end-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.window-passenger-front-channel.label = Beifahrertür Fenster
|
||||
channel-type.mybmw.window-passenger-rear-channel.label = Beifahrertür Hinten Fenster
|
||||
channel-type.mybmw.window-start-channel.description = Startzeit des Ladefensters
|
||||
channel-type.mybmw.window-start-channel.label = Ladefenster Startzeit
|
||||
channel-type.mybmw.window-start-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.windows-channel.label = Gesamtzustand der Fenster
|
||||
|
||||
mybmw.status.authorization-needed = Bitte ein Captcha abschließen um fortzufahren\: {0}
|
||||
mybmw.status.user-details-missing = Benutzername oder Passwortkonfiguration fehlt
|
||||
mybmw.status.region-missing = Konfiguration der Region fehlt
|
||||
mybmw.status.ip-missing = Die Callback IP des openHAB Hosts konnte nicht abgerufen werden
|
||||
mybmw.status.vehicle-retrieval-error = Abrufen von Fahrzeugen fehlgeschlagen
|
||||
mybmw.status.network-error = Abrufen der Fahrzeuge fehlgeschlagen, möglicher Netzwerkfehler
|
||||
mybmw.status.quota-error = Fehlendes Kontingent, aktualisieren bei {0}
|
||||
@@ -1,266 +0,0 @@
|
||||
# add-on
|
||||
|
||||
addon.mybmw.name = MyBMW
|
||||
addon.mybmw.description = Provides access to your Vehicle Data like MyBMW App
|
||||
|
||||
# thing types
|
||||
thing-type.config.mybmw.bridge.language.description = Channel data can be returned in the desired language like en, de, fr ...
|
||||
thing-type.config.mybmw.bridge.language.label = Kieliasetukset
|
||||
thing-type.config.mybmw.bridge.password.description = MyBMW Salasana
|
||||
thing-type.config.mybmw.bridge.password.label = Salasana
|
||||
thing-type.config.mybmw.bridge.region.description = Valitse alue muodostaaksesi yhteyden sopivaan BMW-palvelimeen
|
||||
thing-type.config.mybmw.bridge.region.label = Alue
|
||||
thing-type.config.mybmw.bridge.region.option.CHINA = Kiina
|
||||
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Pohjois-Amerikka
|
||||
thing-type.config.mybmw.bridge.region.option.ROW = Muu maailma
|
||||
thing-type.config.mybmw.bridge.userName.description = MyBMW Käyttäjätunnus
|
||||
thing-type.config.mybmw.bridge.userName.label = Käyttäjätunnus
|
||||
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.description = Tietojen päivittämisnopeus ajoneuvotiedoissa. Jos arvo on 0, päivitystä ei tapahdu automaattisesti.
|
||||
thing-type.config.mybmw.vehicle.refreshInterval.label = Päivitysväli
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.description = Automerkki kuten BMW tai Mini
|
||||
thing-type.config.mybmw.vehicle.vehicleBrand.label = Ajoneuvon merkki
|
||||
thing-type.config.mybmw.vehicle.vin.description = BMW\:n antama VIN
|
||||
thing-type.config.mybmw.vehicle.vin.label = Ajoneuvon valmistenumero (VIN)
|
||||
thing-type.mybmw.account.description = BMW-tilisi tiedot
|
||||
thing-type.mybmw.account.label = MyBMW tili
|
||||
thing-type.mybmw.bev_rex.description = Akkukäyttöinen sähköajoneuvo kantaman pidentäjällä (BEV_REX)
|
||||
thing-type.mybmw.bev_rex.label = Sähköauto REX\: llä
|
||||
thing-type.mybmw.bev.description = Akkukäyttöinen sähköajoneuvo (BEV)
|
||||
thing-type.mybmw.bev.label = Sähköajoneuvo
|
||||
thing-type.mybmw.conv.description = Perinteisellä polttoaineella varustettu ajoneuvo (CONV)
|
||||
thing-type.mybmw.conv.label = Perinteinen ajoneuvo
|
||||
thing-type.mybmw.phev.description = Ladattava hybridi sähköajoneuvo
|
||||
thing-type.mybmw.phev.label = Ladattava hybridi (PHEV)
|
||||
|
||||
# channel group types
|
||||
channel-group-type.mybmw.charge-statistic.description = Kuluvan kuukauden lataustilastot
|
||||
channel-group-type.mybmw.charge-statistic.label = Lataustilastot
|
||||
channel-group-type.mybmw.check-control-values.description = Näyttää nykyiset aktiiviset CheckControl-viestit
|
||||
channel-group-type.mybmw.check-control-values.label = CheckControl-viestit
|
||||
channel-group-type.mybmw.conv-range-values.description = Näyttää ajokilometrimäärän, jäljellä olevan toimintamatkan ja polttoainetason arvot
|
||||
channel-group-type.mybmw.conv-range-values.label = Toimintamatka ja polttoainetiedot
|
||||
channel-group-type.mybmw.door-values.description = Kaikkien ovien ja ikkunoiden yksityiskohtainen tila
|
||||
channel-group-type.mybmw.door-values.label = Yksityiskohtainen oven tila
|
||||
channel-group-type.mybmw.ev-range-values.description = Näyttää mittarilukeman, jäljellä oleva toimintamatkan ja lataustason arvot
|
||||
channel-group-type.mybmw.ev-range-values.label = Kantama- ja lataustiedot
|
||||
channel-group-type.mybmw.ev-vehicle-status.description = Ajoneuvon yleistila
|
||||
channel-group-type.mybmw.ev-vehicle-status.label = Ajoneuvon tila
|
||||
channel-group-type.mybmw.ev-vehicle-update.description = Pakota ajoneuvon tietojen päivitys
|
||||
channel-group-type.mybmw.ev-vehicle-update.label = Pakota Ajoneuvon Päivitys
|
||||
channel-group-type.mybmw.hybrid-range-values.description = Näyttää ajokilometrit, jäljellä olevan polttoaineen ja ajomatkan hybridiautoille
|
||||
channel-group-type.mybmw.hybrid-range-values.label = Toimintamatka, lataus/polttoainetiedot
|
||||
channel-group-type.mybmw.image-values.description = Näyttää ajoneuvosi kuvan
|
||||
channel-group-type.mybmw.image-values.label = Ajoneuvon kuva
|
||||
|
||||
channel-group-type.mybmw.location-values.description = Ajoneuvon koordinaatit ja suunta
|
||||
channel-group-type.mybmw.location-values.label = Ajoneuvon sijainti
|
||||
channel-group-type.mybmw.profile-values.description = Ajoitetut latausprofiilit
|
||||
channel-group-type.mybmw.profile-values.label = Sähkölatausprofiili
|
||||
channel-group-type.mybmw.remote-services.description = Ajoneuvon etäohjaus
|
||||
channel-group-type.mybmw.remote-services.label = Etäpalvelut
|
||||
channel-group-type.mybmw.service-values.description = Auton tulevat huoltoaikataulut
|
||||
channel-group-type.mybmw.service-values.label = Ajoneuvopalvelut
|
||||
channel-group-type.mybmw.session-values.description = Aiemmat lataustapahtumat
|
||||
channel-group-type.mybmw.session-values.label = Lataustapahtumat
|
||||
channel-group-type.mybmw.tire-pressures.description = Nykyiset ja halutut paineet kaikkiin renkaisiin
|
||||
channel-group-type.mybmw.tire-pressures.label = Rengaspaine
|
||||
channel-group-type.mybmw.vehicle-status.description = Ajoneuvon yleistila
|
||||
channel-group-type.mybmw.vehicle-status.label = Ajoneuvon Tila
|
||||
channel-group-type.mybmw.vehicle-update.description = Pakota ajoneuvon tietojen päivitys
|
||||
channel-group-type.mybmw.vehicle-update.label = Pakota Ajoneuvon Päivitys
|
||||
|
||||
|
||||
# channel types
|
||||
channel-type.mybmw.address-channel.label = Osoite
|
||||
channel-type.mybmw.charging-info-channel.label = Lataustiedot
|
||||
channel-type.mybmw.charging-remaining-channel.label = Jäljellä Oleva Maksuaika
|
||||
channel-type.mybmw.charging-status-channel.label = Lataustila
|
||||
channel-type.mybmw.charging-update-channel.label = Pakota lataustietojen päivitys
|
||||
channel-type.mybmw.check-control-channel.label = Check Control
|
||||
channel-type.mybmw.checkcontrol-details-channel.label = Check Control tiedot
|
||||
channel-type.mybmw.checkcontrol-name-channel.label = Check Control kuvaus
|
||||
channel-type.mybmw.checkcontrol-severity-channel.label = Vakavuustaso
|
||||
|
||||
channel-type.mybmw.doors-channel.label = Oven yleistila
|
||||
channel-type.mybmw.driver-front-channel.label = Kuljettajan ovi
|
||||
channel-type.mybmw.driver-rear-channel.label = Kuljettajan puoleinen takaovi
|
||||
channel-type.mybmw.estimated-fuel-l-100km-channel.label = Arvioitu kulutus l/100 km
|
||||
channel-type.mybmw.estimated-fuel-mpg-channel.label = Arvioitu kulutus mpg
|
||||
channel-type.mybmw.front-left-current-channel.label = Rengaspaine vasen etu
|
||||
channel-type.mybmw.front-left-target-channel.label = Rengaspaine vasen etu tavoite
|
||||
channel-type.mybmw.front-right-current-channel.label = Rengaspaine oikea etu
|
||||
channel-type.mybmw.front-right-target-channel.label = Rengaspaine oikea etu tavoite
|
||||
channel-type.mybmw.gps-channel.label = GPS koordinaatit
|
||||
channel-type.mybmw.heading-channel.label = Suuntakulma
|
||||
channel-type.mybmw.home-distance-channel.description = Ajoneuvon ja kotipaikan välinen laskennallinen etäisyys
|
||||
channel-type.mybmw.home-distance-channel.label = Etäisyys Kotipaikasta
|
||||
channel-type.mybmw.hood-channel.label = Konepelti
|
||||
|
||||
channel-type.mybmw.image-update-channel.label = Pakota kuvan päivitys
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontLeft = Vasen Sivunäkymä
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontRight = Oikea Sivunäkymä
|
||||
channel-type.mybmw.image-view-channel.command.option.FrontView = Etu Näkymä
|
||||
channel-type.mybmw.image-view-channel.command.option.RearView = Taka-Näkymä
|
||||
channel-type.mybmw.image-view-channel.command.option.VehicleStatus = Näkymä edestä
|
||||
channel-type.mybmw.image-view-channel.label = Kuvan katseluportti
|
||||
channel-type.mybmw.last-fetched-channel.label = Viimeisin Openhab Päivityksen Aikaleima
|
||||
channel-type.mybmw.last-update-channel.label = Viimeisen Auton Tilan Aikaleima
|
||||
channel-type.mybmw.lock-channel.label = Ovet lukittu
|
||||
channel-type.mybmw.mileage-channel.label = Ajettu kokonaismatka
|
||||
|
||||
channel-type.mybmw.next-service-date-channel.label = Seuraava huoltopäivämäärä
|
||||
channel-type.mybmw.next-service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.next-service-mileage-channel.label = Kilometrit seuraavaan huoltoon
|
||||
channel-type.mybmw.passenger-front-channel.label = Matkustajan ovi
|
||||
channel-type.mybmw.passenger-rear-channel.label = Matkustajan puoleinen takaovi
|
||||
channel-type.mybmw.plug-connection-channel.label = Pistokeyhteyden tila
|
||||
channel-type.mybmw.png-channel.label = Renderoidun ajoneuvon kuva
|
||||
|
||||
channel-type.mybmw.profile-climate-channel.label = Ilmastointi lähtöaikana
|
||||
channel-type.mybmw.profile-control-channel.command.option.weeklyPlanner = Viikkoaikataulu
|
||||
channel-type.mybmw.profile-control-channel.description = Lataussuunnitelman valinta
|
||||
channel-type.mybmw.profile-control-channel.label = Lataussuunnitelma
|
||||
channel-type.mybmw.profile-limit-channel.description = Rajoitettu lataus aktivoitu
|
||||
channel-type.mybmw.profile-limit-channel.label = Latausenergia rajoitettu
|
||||
channel-type.mybmw.profile-mode-channel.command.option.delayedCharging = Käytä latausasetusta
|
||||
channel-type.mybmw.profile-mode-channel.command.option.immediateCharging = Välitön lataus
|
||||
channel-type.mybmw.profile-mode-channel.description = Välittömän tai viivästetyn latauksen tilan valinta
|
||||
channel-type.mybmw.profile-mode-channel.label = Lataustila
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.chargingWindow = Latausikkuna
|
||||
channel-type.mybmw.profile-prefs-channel.command.option.noPreSelection = Ei valintaa
|
||||
channel-type.mybmw.profile-prefs-channel.description = Viivästetyn latauksen asetukset
|
||||
channel-type.mybmw.profile-prefs-channel.label = Latausasetukset
|
||||
channel-type.mybmw.profile-target-channel.description = Lataustilan tavoite
|
||||
channel-type.mybmw.profile-target-channel.label = Lataustilan tavoite
|
||||
|
||||
channel-type.mybmw.range-electric-channel.label = Sähköinen toimintamatka
|
||||
channel-type.mybmw.range-fuel-channel.label = Toimintamatka polttoaineella
|
||||
channel-type.mybmw.range-hybrid-channel.label = Yhdistetty toimintamatka
|
||||
channel-type.mybmw.range-radius-electric-channel.label = Sähköinen toimintasäde
|
||||
channel-type.mybmw.range-radius-fuel-channel.label = Toimintamatka polttoaineella
|
||||
channel-type.mybmw.range-radius-hybrid-channel.label = Yhdistetty toimintamatka
|
||||
channel-type.mybmw.raw-channel.label = Raakadata
|
||||
|
||||
channel-type.mybmw.rear-left-current-channel.label = Rengaspaine vasen taka
|
||||
channel-type.mybmw.rear-left-target-channel.label = Rengaspaine vasen taka tavoite
|
||||
channel-type.mybmw.rear-right-current-channel.label = Rengaspaine oikea taka
|
||||
channel-type.mybmw.rear-right-target-channel.label = Rengaspaine oikea taka tavoite
|
||||
channel-type.mybmw.remaining-fuel-channel.label = Jäljellä oleva polttoaine
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-start = Aloita ilmasto nyt
|
||||
channel-type.mybmw.remote-command-channel.command.option.climate-now-stop = Pysäytä ilmasto nyt
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-lock = Lukitse ajoneuvo
|
||||
channel-type.mybmw.remote-command-channel.command.option.door-unlock = Avaa ajoneuvon lukitus
|
||||
channel-type.mybmw.remote-command-channel.command.option.horn-blow = Soita äänitorvea
|
||||
channel-type.mybmw.remote-command-channel.command.option.light-flash = Väläytä valoja
|
||||
channel-type.mybmw.remote-command-channel.command.option.vehicle-finder = Etsi ajoneuvo
|
||||
channel-type.mybmw.remote-command-channel.label = Etäkomento
|
||||
channel-type.mybmw.remote-state-channel.label = Etäkomenmon tila
|
||||
channel-type.mybmw.service-date-channel.label = Huollon päiväys
|
||||
channel-type.mybmw.service-date-channel.state.pattern = %1$tb %1$tY
|
||||
channel-type.mybmw.service-details-channel.label = Huollon yksityiskohdat
|
||||
channel-type.mybmw.service-mileage-channel.label = Kilometrit huoltoon asti
|
||||
channel-type.mybmw.service-name-channel.label = Palvelun nimi
|
||||
channel-type.mybmw.session-energy-channel.label = Ladattu energia
|
||||
channel-type.mybmw.session-issue-channel.label = Ongelmia latauksen aikana
|
||||
channel-type.mybmw.session-status-channel.label = Latauksen tila
|
||||
channel-type.mybmw.session-subtitle-channel.label = Latauksen yksityiskohdat
|
||||
channel-type.mybmw.session-title-channel.label = Latauksen nimi
|
||||
channel-type.mybmw.soc-channel.label = Akun lataustaso
|
||||
|
||||
channel-type.mybmw.state-update-channel.label = Pakota ajoneuvon tietojen päivitys
|
||||
channel-type.mybmw.statistic-energy-channel.description = Kuluvan kuukauden ladattu kokonaisenergia
|
||||
channel-type.mybmw.statistic-energy-channel.label = Latauskustannus
|
||||
channel-type.mybmw.statistic-sessions-channel.description = Latauskertojen määrä tässä kuussa
|
||||
channel-type.mybmw.statistic-sessions-channel.label = Latauskerrat
|
||||
channel-type.mybmw.statistic-title-channel.label = Lataustilastokuukausi
|
||||
channel-type.mybmw.sunroof-channel.label = Kattoluukku
|
||||
|
||||
channel-type.mybmw.timer1-day-fri-channel.description = Perjantaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-fri-channel.label = T1 Perjantai
|
||||
channel-type.mybmw.timer1-day-mon-channel.description = Maanantaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-mon-channel.label = T1 Maanantai
|
||||
channel-type.mybmw.timer1-day-sat-channel.description = Lauantaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-sat-channel.label = T1 Lauantai
|
||||
channel-type.mybmw.timer1-day-sun-channel.description = Sunnuntaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-sun-channel.label = T1 Sunnuntai
|
||||
channel-type.mybmw.timer1-day-thu-channel.description = Torstaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-thu-channel.label = T1 Torstai
|
||||
channel-type.mybmw.timer1-day-tue-channel.description = Tiistaina ajastin 1
|
||||
channel-type.mybmw.timer1-day-tue-channel.label = T1 Tiistai
|
||||
channel-type.mybmw.timer1-day-wed-channel.description = Keskiviikkona ajastin 1
|
||||
channel-type.mybmw.timer1-day-wed-channel.label = T1 Keskiviikko
|
||||
channel-type.mybmw.timer1-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 1
|
||||
channel-type.mybmw.timer1-departure-channel.label = T1 Lähtöaika
|
||||
channel-type.mybmw.timer1-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer1-enabled-channel.description = Ajastin 1 käytössä
|
||||
channel-type.mybmw.timer1-enabled-channel.label = T1 Käytössä
|
||||
channel-type.mybmw.timer2-day-fri-channel.description = Perjantaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-fri-channel.label = T2 Perjantai
|
||||
channel-type.mybmw.timer2-day-mon-channel.description = Maanantaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-mon-channel.label = T2 Maanantai
|
||||
channel-type.mybmw.timer2-day-sat-channel.description = Lauantaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-sat-channel.label = T2 Lauantai
|
||||
channel-type.mybmw.timer2-day-sun-channel.description = Sunnuntaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-sun-channel.label = T2 Sunnuntai
|
||||
channel-type.mybmw.timer2-day-thu-channel.description = Torstaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-thu-channel.label = T2 Torstai
|
||||
channel-type.mybmw.timer2-day-tue-channel.description = Tiistaina ajastin 2
|
||||
channel-type.mybmw.timer2-day-tue-channel.label = T2 Tiistai
|
||||
channel-type.mybmw.timer2-day-wed-channel.description = Keskiviikkona ajastin 2
|
||||
channel-type.mybmw.timer2-day-wed-channel.label = T2 Keskiviikko
|
||||
channel-type.mybmw.timer2-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 2
|
||||
channel-type.mybmw.timer2-departure-channel.label = T2 Lähtöaika
|
||||
channel-type.mybmw.timer2-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer2-enabled-channel.description = Ajastin 2 käytössä
|
||||
channel-type.mybmw.timer2-enabled-channel.label = T2 Käytössä
|
||||
channel-type.mybmw.timer3-day-fri-channel.description = Perjantaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-fri-channel.label = T3 Perjantai
|
||||
channel-type.mybmw.timer3-day-mon-channel.description = Maanantaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-mon-channel.label = T3 Maanantai
|
||||
channel-type.mybmw.timer3-day-sat-channel.description = Lauantaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-sat-channel.label = T3 Lauantai
|
||||
channel-type.mybmw.timer3-day-sun-channel.description = Sunnuntaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-sun-channel.label = T3 Sunnuntai
|
||||
channel-type.mybmw.timer3-day-thu-channel.description = Torstaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-thu-channel.label = T3 Torstai
|
||||
channel-type.mybmw.timer3-day-tue-channel.description = Tiistaina ajastin 3
|
||||
channel-type.mybmw.timer3-day-tue-channel.label = T3 Tiistai
|
||||
channel-type.mybmw.timer3-day-wed-channel.description = Keskiviikkona ajastin 3
|
||||
channel-type.mybmw.timer3-day-wed-channel.label = T3 Keskiviikko
|
||||
channel-type.mybmw.timer3-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 3
|
||||
channel-type.mybmw.timer3-departure-channel.label = T3 Lähtöaika
|
||||
channel-type.mybmw.timer3-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer3-enabled-channel.description = Ajastin 3 käytössä
|
||||
channel-type.mybmw.timer3-enabled-channel.label = T3 Käytössä
|
||||
channel-type.mybmw.timer4-day-fri-channel.description = Perjantaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-fri-channel.label = T4 Perjantai
|
||||
channel-type.mybmw.timer4-day-mon-channel.description = Maanantaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-mon-channel.label = T4 Maanantai
|
||||
channel-type.mybmw.timer4-day-sat-channel.description = Lauantaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-sat-channel.label = T4 Lauantai
|
||||
channel-type.mybmw.timer4-day-sun-channel.description = Sunnuntaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-sun-channel.label = T4 Sunnuntai
|
||||
channel-type.mybmw.timer4-day-thu-channel.description = Torstaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-thu-channel.label = T4 Torstai
|
||||
channel-type.mybmw.timer4-day-tue-channel.description = Tiistaina ajastin 4
|
||||
channel-type.mybmw.timer4-day-tue-channel.label = T4 Tiistai
|
||||
channel-type.mybmw.timer4-day-wed-channel.description = Keskiviikkona ajastin 4
|
||||
channel-type.mybmw.timer4-day-wed-channel.label = T4 Keskiviikko
|
||||
channel-type.mybmw.timer4-departure-channel.description = Lähtöaika normaalille aikatauluajastimelle 4
|
||||
channel-type.mybmw.timer4-departure-channel.label = T4 Lähtöaika
|
||||
channel-type.mybmw.timer4-departure-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.timer4-enabled-channel.description = Ajastin 4 käytössä
|
||||
channel-type.mybmw.timer4-enabled-channel.label = T4 Käytössä
|
||||
channel-type.mybmw.trunk-channel.label = Tavaratila
|
||||
channel-type.mybmw.window-driver-front-channel.label = Kuljettajan ikkuna
|
||||
channel-type.mybmw.window-driver-rear-channel.label = Kuljettajan puoleinen takaikkuna
|
||||
channel-type.mybmw.window-end-channel.description = Lataustapahtuman päättymisaika
|
||||
channel-type.mybmw.window-end-channel.label = Window End Time
|
||||
channel-type.mybmw.window-end-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.window-passenger-front-channel.label = Matkustajan ikkuna
|
||||
channel-type.mybmw.window-passenger-rear-channel.label = Matkustajan puoleinen takaikkuna
|
||||
channel-type.mybmw.window-start-channel.description = Lataustapahtuman alkamisaika
|
||||
channel-type.mybmw.window-start-channel.label = Latauksen aloitusaika
|
||||
channel-type.mybmw.window-start-channel.state.pattern = %1$tH\:%1$tM
|
||||
channel-type.mybmw.windows-channel.label = Overall Window Status
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mybmw"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="account">
|
||||
<label>MyBMW Account</label>
|
||||
<description>Your BMW account data</description>
|
||||
<semantic-equipment-tag>WebService</semantic-equipment-tag>
|
||||
<config-description-ref uri="thing-type:mybmw:bridge"/>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mybmw"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<channel-group-type id="charge-statistic">
|
||||
<label>Charging Statistics</label>
|
||||
<description>Charging statistics of current month</description>
|
||||
<channels>
|
||||
<channel id="title" typeId="statistic-title-channel"/>
|
||||
<channel id="energy" typeId="statistic-energy-channel"/>
|
||||
<channel id="sessions" typeId="statistic-sessions-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mybmw"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
<channel-type id="statistic-title-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Charge Statistic Month</label>
|
||||
</channel-type>
|
||||
<channel-type id="statistic-energy-channel">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy Charged</label>
|
||||
<description>Total energy charged in current month</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="statistic-sessions-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Charge Sessions</label>
|
||||
<description>Number of charging sessions this month</description>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
</tags>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||