BMW ConnectedDrive binding removal (#12634)

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
This commit is contained in:
Bernd Weymann 2022-04-22 10:47:53 +02:00 committed by GitHub
parent 3123810b98
commit 3aaebde800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
179 changed files with 0 additions and 13624 deletions

View File

@ -43,7 +43,6 @@
/bundles/org.openhab.binding.bluetooth.govee/ @cpmeister
/bundles/org.openhab.binding.bluetooth.roaming/ @cpmeister
/bundles/org.openhab.binding.bluetooth.ruuvitag/ @ssalonen
/bundles/org.openhab.binding.bmwconnecteddrive/ @weymann @ntruchsess
/bundles/org.openhab.binding.boschindego/ @jofleck
/bundles/org.openhab.binding.boschshc/ @stefan-kaestle @coeing @GerdZanker
/bundles/org.openhab.binding.bosesoundtouch/ @marvkis @tratho

View File

@ -206,11 +206,6 @@
<artifactId>org.openhab.binding.bluetooth.ruuvitag</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.bmwconnecteddrive</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.boschindego</artifactId>

View File

@ -1,13 +0,0 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -1,998 +0,0 @@
# BMW ConnectedDrive Binding
The binding provides a connection between [BMW's ConnectedDrive Portal](https://www.bmw-connecteddrive.com/country-region-select/country-region-selection.html) and openHAB.
All vehicles connected to an account will be detected by the discovery with the correct type
* Conventional Fuel Vehicle
* Plugin-Hybrid Electrical Vehicle
* Battery Electric Vehicle with Range Extender
* Battery Electric Vehicle
In addition properties are attached with information and services provided by this vehicle.
The provided data depends on
1. the [Thing Type](#things) and
2. the [Properties](#properties) mentioned in Services
Different channel groups are clustering all informations.
Check for each group if it's supported for this Vehicle.
Please note **this isn't a real-time binding**.
If a door is opened the state isn't transmitted and changed immediately.
This isn't a flaw in the binding itself because the state in BMW's own ConnectedDrive App is also updated with some delay.
## Supported Things
### Bridge
The bridge establishes the connection between BMW's ConnectedDrive Portal and openHAB.
| Name | Bridge Type ID | Description |
|----------------------------|----------------|------------------------------------------------------------|
| BMW ConnectedDrive Account | `account` | Access to BMW ConnectedDrive Portal for a specific user |
### Things
Four different vehicle types are provided.
They differ in the supported channel groups & channels.
Conventional Fuel Vehicles have no _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` | status, range, location, service, check, charge, image |
| BMW Electric Vehicle with REX | `bev_rex` | status, range, location, service, check, charge, image |
| BMW Plug-In-Hybrid Electric Vehicle | `phev` | status, range, location, service, check, charge, image |
| BMW Conventional Vehicle | `conv` | status, range, location, service, check, image |
#### Properties
<img align="right" src="./doc/properties.png" width="500" height="225"/>
For each vehicle properties are available.
Basically 3 types of information are registered as properties
* Informations regarding your dealer with address and phone number
* Which services are available / not available
* Vehicle properties like color, model type, drive train and construction year
In the right picture can see in *Services Activated* e.g. the *DoorLock* and *DoorUnlock* services are mentioned.
This ensures channel group [Remote Services](#remote-services) is supporting door lock and unlock remote control.
In *Services Supported* the entry *LastDestination* is mentioned.
So it's valid to connect channel group [Last Destinations](#destinations) in order to display and select the last navigation destinations.
| Property Key | Property Value | Supported Channel Groups |
|--------------------|---------------------|------------------------------|
| servicesSupported | Statistics | last-trip, lifetime |
| servicesSupported | LastDestinations | destinations |
| servicesActivated | _list of services_ | remote |
## Discovery
Auto discovery is starting after the bridge towards BMW's ConnectedDrive 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 | BMW ConnectedDrive Username |
| password | text | BMW ConnectedDrive Password |
| region | text | Select region in order to connect to the appropriate BMW server. |
The region Configuration has 3 different options
* _NORTH_AMERICA_
* _CHINA_
* _ROW_ (Rest of World)
#### Advanced Configuration
| Parameter | Type | Description |
|-----------------|---------|--------------------------------------------------------------------|
| preferMyBmw | boolean | Prefer *MyBMW* API instead of *BMW Connected Drive* |
### Thing Configuration
Same configuration is needed for all things
| Parameter | Type | Description |
|-----------------|---------|---------------------------------------|
| vin | text | Vehicle Identification Number (VIN) |
| refreshInterval | integer | Refresh Interval in Minutes |
| units | text | Unit Selection. See below. |
| imageSize | integer | Image Size |
| imageViewport | text | Image Viewport |
The unit configuration has 3 options
* _AUTODETECT_ selects miles for US & UK, kilometer otherwise
* _METRIC_ selects directly kilometers
* _IMPERIAL_ selects directly miles
The _imageVieport_ allows to show the vehicle from different angels.
Possible options are
* _FRONT_
* _REAR_
* _SIDE_
* _DASHBOARD_
* _DRIVERDOOR_
## 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
#### Vehicle Status
Reflects overall status of the vehicle.
* Channel Group ID is **status**
* Available for all vehicles
* Read-only values
| Channel Label | Channel ID | Type | Description |
|---------------------------|---------------------|---------------|------------------------------------------------|
| Overall Door Status | doors | String | Combined status for all doors |
| Overall Window Status | windows | String | Combined status for all windows |
| Doors Locked | lock | String | Status if doors are locked or unlocked |
| Next Service Date | service-date | DateTime | Date of upcoming service |
| Mileage till Next Service | service-mileage | Number:Length | Mileage till upcoming service |
| Check Control | check-control | String | Presence of active warning messages |
| Plug Connection Status | plug-connection | String | Only available for phev, bev_rex and bev |
| Charging Status | charge | String | Only available for phev, bev_rex and bev |
| Last Status Timestamp | last-update | DateTime | Date and time of last status update |
| Last Status Update Reason | last-update-reason | DateTime | Date and time of last status update |
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
* _Active_ - at least one warning message is active
* _Not Active_ - no warning message is active
* _Undef_ - no data for warnings delivered
Charging Status values
* _Charging_
* _Error_
* _Finished Fully Charged_
* _Finished Not Full_
* _Invalid_
* _Not Charging_
* _Charging Goal reached_
* _Waiting For Charging_
Last update reasons
* _CHARGING_DONE_
* _CHARGING_INTERRUPED_
* _CHARGING_PAUSED
* _CHARGING_STARTED_
* _CYCLIC_RECHARGING_
* _DISCONNECTED_
* _DOOR_STATE_CHANGED_
* _NO_CYCLIC_RECHARGING_
* _NO_LSC_TRIGGER_
* _ON_DEMAND_
* _PREDICTION_UPDATE_
* _TEMPORARY_POWER_SUPPLY_FAILURE_
* _UNKNOWN_
* _VEHICLE_MOVING_
* _VEHICLE_SECURED_
* _VEHICLE_SHUTDOWN_
* _VEHICLE_SHUTDOWN_SECURED_
* _VEHICLE_UNSECURED_
#### Services
Group for all upcoming services with description, service date and/or service mileage.
If more than one service is scheduled in the future the channel _name_ contains all future services as options.
* Channel Group ID is **service**
* Available for all vehicles
* Read/Write access
| Channel Label | Channel ID | Type | Access |
|--------------------------------|---------------------|----------------|------------|
| Service Name | name | String | Read/Write |
| Service Details | details | String | Read |
| Service Date | date | Number | Read |
| Mileage till Service | mileage | Number:Length | Read |
#### Check Control
Group for all current active CheckControl messages.
If more than one message is active the channel _name_ contains all active messages as options.
* Channel Group ID is **check**
* Available for all vehicles
* Read/Write access
| Channel Label | Channel ID | Type | Access |
|---------------------------------|---------------------|----------------|------------|
| CheckControl Description | name | String | Read/Write |
| CheckControl Details | details | String | Read |
| Mileage Occurrence | mileage | Number:Length | Read |
#### Doors Details
Detailed status of all doors and windows.
* Channel Group ID is **doors**
* Available for all vehicles if corresponding sensors are built-in
* Read-only values
| Channel Label | Channel ID | Type |
|----------------------------|-------------------------|---------------|
| Driver Door | driver-front | String |
| Driver Door Rear | driver-rear | String |
| Passenger Door | passenger-front | String |
| Passenger Door Rear | passenger-rear | String |
| Trunk | trunk | String |
| Hood | hood | String |
| Driver Window | win-driver-front | String |
| Driver Rear Window | win-driver-rear | String |
| Passenger Window | win-passenger-front | String |
| Passenger Rear Window | win-passenger-rear | String |
| Rear Window | win-rear | String |
| Sunroof | sunroof | String |
Possible states
* _Undef_ - no status data available
* _Invalid_ - this door / window isn't applicable for this vehicle
* _Closed_ - the door / window is closed
* _Open_ - the door / window is open
* _Intermediate_ - window in intermediate position, not applicable for doors
#### Range Data
Based on vehicle type some channels are present or not.
Conventional fuel vehicles don't provide *Electric Range* and battery electric vehicles don't show *Fuel Range*.
Hybrid vehicles have both and in addition *Hybrid Range*.
See description [Range vs Range Radius](#range-vs-range-radius) to get more information.
* Channel Group ID is **range**
* Availability according to table
* Read-only values
| Channel Label | Channel ID | Type | conv | phev | bev_rex | bev |
|---------------------------|-------------------------|----------------------|------|------|---------|-----|
| Mileage | mileage | Number:Length | X | X | X | X |
| Fuel Range | range-fuel | Number:Length | X | X | X | |
| Battery Range | range-electric | Number:Length | | X | X | X |
| Max Battery Range | range-electric-max | Number:Length | | X | X | X |
| Hybrid Range | range-hybrid | Number:Length | | X | X | |
| Battery Charge Level | soc | Number:Dimensionless | | X | X | X |
| Max Battery Capacity | soc-max | Number:Power | | | X | X | X |
| Remaining Fuel | remaining-fuel | Number:Volume | X | X | X | |
| Fuel Range Radius | range-radius-fuel | Number:Length | X | X | X | |
| Electric Range Radius | range-radius-electric | Number:Length | | X | X | X |
| Hybrid Range Radius | range-radius-hybrid | Number:Length | | X | X | |
| Max Hybrid Range Radius | range-radius-hybrid-max | Number:Length | | X | X | |
#### Charge Profile
Charging options with date and time for preferred time windows and charging modes.
* Channel Group ID is **charge**
* Available for electric and hybrid vehicles
* Read/Write access for UI. Use [Charge Profile Editing Action](#charge-profile-editing) in rules
* There are 3 timers *T1, T2 and T3* available. Replace *X* with number 1,2 or 3 to target the correct timer
* Additional override Timer *OT* defines a single departure besides the 3 predefined schedule timers
| Channel Label | Channel Group ID | Channel ID | Type |
|----------------------------|------------------|---------------------------|----------|
| Charge Mode | charge | profile-mode | String |
| Charge Preferences | charge | profile-prefs | String |
| Window Start Time | charge | window-start | DateTime |
| Window End Time | charge | window-end | DateTime |
| A/C at Departure | charge | profile-climate | Switch |
| T*X* Enabled | charge | timer*X*-enabled | Switch |
| T*X* Departure Time | charge | timer*X*-departure | DateTime |
| T*X* Days | charge | timer*X*-days | String |
| T*X* Monday | charge | timer*X*-day-mon | Switch |
| T*X* Tuesday | charge | timer*X*-day-tue | Switch |
| T*X* Wednesday | charge | timer*X*-day-wed | Switch |
| T*X* Thursday | charge | timer*X*-day-thu | Switch |
| T*X* Friday | charge | timer*X*-day-fri | Switch |
| T*X* Saturday | charge | timer*X*-day-sat | Switch |
| T*X* Sunday | charge | timer*X*-day-sun | Switch |
| OT Enabled | charge | override-enabled | Switch |
| OT Departure Time | charge | override-departure | DateTime |
The channel _profile-mode_ supports
* *IMMEDIATE_CHARGING*
* *DELAYED_CHARGING*
The channel _profile-prefs_ supports
* *NO_PRESELECTION*
* *CHARGING_WINDOW*
#### Location
GPS location and heading of the vehicle.
* Channel Group ID is **location**
* Available for all vehicles with built-in GPS sensor. Function can be enabled/disabled in the head unit
* Read-only values
| Channel Label | Channel ID | Type |
|-----------------|---------------------|--------------|
| GPS Coordinates | gps | Location |
| Heading | heading | Number:Angle |
#### Last Trip
Statistic values of duration, distance and consumption of the last trip.
* Channel Group ID is **last-trip**
* Available if *Statistics* is present in *Services Supported*. See [Vehicle Properties](#properties) for further details
* Read-only values
* Depending on units configuration in [Thing Configuration](#thing-configuration) average values are given for 100 kilometers or miles
| Channel Label | Channel ID | Type |
|-----------------------------------------|------------------------------|---------------|
| Last Trip Date | date | DateTime |
| Last Trip Duration | duration | Number:Time |
| Last Trip Distance | distance | Number:Length |
| Distance since Charge | distance-since-charging | Number:Length |
| Avg. Power Consumption | avg-consumption | Number:Power |
| Avg. Power Recuperation | avg-recuperation | Number:Power |
| Avg. Combined Consumption | avg-combined-consumption | Number:Volume |
#### Lifetime Statistics
Providing lifetime consumption values.
* Channel Group ID is **lifetime**
* Available if *Statistics* is present in *Services Supported*. See [Vehicle Properties](#properties) for further details
* Read-only values
* Depending on units configuration in [Thing Configuration](#thing-configuration) average values are given for 100 kilometers or miles
| Channel Label | Channel ID | Type |
|-----------------------------------------|------------------------------|---------------|
| Total Electric Distance | total-driven-distance | Number:Length |
| Longest 1-Charge Distance | single-longest-distance | Number:Length |
| Avg. Power Consumption | avg-consumption | Number:Power |
| Avg. Power Recuperation | avg-recuperation | Number:Power |
| Avg. Combined Consumption | avg-combined-consumption | Number:Volume |
#### Remote Services
Remote control of the vehicle.
Send a *command* to the vehicle and the *state* is reporting the execution progress.
Only one command can be executed each time.
Parallel execution isn't supported.
* Channel Group ID is **remote**
* Available for all commands mentioned in *Services Activated*. See [Vehicle Properties](#properties) for further details
* Read/Write access
| Channel Label | Channel ID | Type | Access |
|-------------------------|---------------------|---------|--------|
| Remote Service Command | command | String | Write |
| Service Execution State | state | String | Read |
The channel _command_ provides options
* _Flash Lights_
* _Vehicle Finder_
* _Door Lock_
* _Door Unlock_
* _Horn Blow_
* _Climate Control_
* _Start Charging_
* _Send Charging Profile_
The channel _state_ shows the progress of the command execution in the following order
1) _Initiated_
2) _Pending_
3) _Delivered_
4) _Executed_
#### Destinations
Shows the last destinations stored in the navigation system.
If several last destinations are stored in the navigation system the channel _name_ contains all addresses as options.
* Channel Group ID is **destination**
* Available if *LastDestinations* is present in *Services Supported*. Check [Vehicle Properties](#properties) for further details
* Read/Write access
| Channel Label | Channel ID | Type | Access |
|----------------------|---------------|-----------|-------------|
| Name | name | String | Read/Write |
| GPS Coordinates | gps | Location | Read |
#### Image
Image representation of the vehicle. Size and viewport are writable and can be
The possible values are the same mentioned in [Thing Configuration](#thing-configuration).
* Channel Group ID is **image**
* Available for all vehicles
* Read/Write access
| Channel Label | Channel ID | Type | Access |
|----------------------------|---------------------|--------|----------|
| Rendered Vehicle Image | png | Image | Read |
| Image Viewport | view | String | Write |
| Image Picture Size | size | Number | Write |
## Actions
Get the _Actions_ object for your vehicle using the Thing ID
* bmwconnecteddrive - Binding ID, don't change!
* bev_rex - [Thing UID](#things) of your car
* user - Thing ID of the [Bridge](#bridge)
* i3 - Thing ID of your car
```
val profile = getActions("bmwconnecteddrive", "bmwconnecteddrive:bev_rex:user:i3")
```
### Charge Profile Editing
Like in the Charge Profile Channels 3 Timers are provided. Replace *X* with 1, 2 or 3 to address the right timer.
| Function | Parameters | Returns | Description |
|---------------------------------------|------------------|---------------------------|------------------------------------------------------------|
| getClimatizationEnabled | void | Boolean | Returns the enabled state of climatization |
| setClimatizationEnabled | Boolean | void | Sets the enabled state of climatization |
| getChargingMode | void | String | Gets the charging-mode, see valid options below |
| setChargingMode | String | void | Sets the charging-mode, see valid options below |
| getPreferredWindowStart | void | LocalTime | Returns the preferred charging-window start time |
| setPreferredWindowStart | LocalTime | void | Sets the preferred charging-window start time |
| getPreferredWindowEnd | void | LocalTime | Returns the preferred charging-window end time |
| setPreferredWindowEnd | LocalTime | void | Sets the preferred charging-window end time |
| getTimer*X*Enabled | void | Boolean | Returns the enabled state of timer*X* |
| setTimer*X*Enabled | Boolean | void | Returns the enabled state of timer*X* |
| getTimer*X*Departure | void | LocalTime | Returns the departure time of timer*X* |
| setTimer*X*Departure | LocalTime | void | Sets the timer*X* departure time |
| getTimer*X*Days | void | Set<DayOfWeek> | Returns the days of week timer*X* is enabled for |
| setTimer*X*Days | Set<DayOfWeek> | void | sets the days of week timer*X* is enabled for |
| getOverrideTimerEnabled | void | Boolean | Returns the enabled state of override timer |
| setOverrideTimerEnabled | Boolean | void | Sets the enabled state of override timer |
| getOverrideTimerDeparture | void | LocalTime | Returns the departure time of override timer |
| setOverrideTimerDeparture | LocalTime | void | Sets the override timer departure time |
| getOverrideTimerDays | void | Set<DayOfWeek> | Returns the days of week the overrideTimer is enabled for |
| setOverrideTimerDays | Set<DayOfWeek> | void | Sets the days of week the overrideTimer is enabled for |
| cancelEditChargeProfile | void | void | Cancel current edit of charging profile |
| sendChargeProfile | void | void | Sends the charging profile to the vehicle |
Values for valid charging mode get/set
* *IMMEDIATE_CHARGING*
* *DELAYED_CHARGING*
## Further Descriptions
### Dynamic Data
<img align="right" src="./doc/ServiceOptions.png" width="400" height="350"/>
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)
* Last Destinations delivered in group [Destinations](#destinations)
The channel id _name_ shows the first element as default.
All other possibilities are attached as options.
The picture on the right shows the _Service Name_ item and all four possible options.
Select the desired service and the corresponding _Service Date & Milage_ will be shown.
### TroubleShooting
BMW has a high range of vehicles supported by ConnectedDrive.
In case of any issues with this binding help to resolve it!
Please perform the following steps:
* Can you [log into ConnectedDrive](https://www.bmw-connecteddrive.com/country-region-select/country-region-selection.html) with your credentials? Please note this isn't the BMW Customer portal - it's the ConnectedDrive portal
* Is the vehicle listed in your account? There's a one-to-one relation from user to vehicle
If the access to the portal is working and the vehicle is listed some debug data is needed in order to identify the issue.
#### Generate Debug Fingerprint
If you checked the above pre-conditions you need to get the debug fingerprint from the logs.
First [enable debug logging](https://www.openhab.org/docs/administration/logging.html#defining-what-to-log) for the binding.
```
log:set DEBUG org.openhab.binding.bmwconnecteddrive
```
The debug fingerprint is generated immediately after the vehicle thing is initialized the first time, e.g. after openHAB startup.
To force a new fingerprint disable the thing shortly and enable it again.
Personal data is eliminated from the log entries so it should be possible to share them in public.
Data like
* Dealer Properties
* Vehicle Identification Number (VIN)
* Location latitude / longitude
are anonymized.
You'll find the fingerprint in the logs with the command
```
grep "Troubleshoot Fingerprint Data" openhab.log
```
After the corresponding fingerprint is generated please [follow the instructions to raise an issue](https://community.openhab.org/t/how-to-file-an-issue/68464) and attach the fingerprint data!
Your feedback is highly appreciated!
### Range vs Range Radius
<img align="right" src="./doc/range-radius.png" width="400" height="350"/>
You will observe differences in the vehicle range and range radius values.
While range is indicating the possible distance to be driven on roads the range radius indicates the reachable range on the map.
The right picture shows the distance between Kassel and Frankfurt in Germany.
While the air-line distance is ~145 kilometer the route distance is ~192 kilometer.
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/configuration/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 the three configuration parameters in the Things section
* YOUR_USERNAME - with your ConnectedDrive login username
* YOUR_PASSWORD - with your ConnectedDrive password credentials
* VEHICLE_VIN - the vehicle identification number
In addition search for all occurrences of *i3* and replace it with your Vehicle Identification like *x3* or *535d* and you're ready to go!
### Things File
```
Bridge bmwconnecteddrive:account:user "BMW ConnectedDrive Account" [userName="YOUR_USERNAME",password="YOUR_PASSWORD",region="ROW"] {
Thing bev_rex i3 "BMW i3 94h REX" [ vin="VEHICLE_VIN",units="AUTODETECT",imageSize=600,imageViewport="FRONT",refreshInterval=5]
}
```
### Items File
```
Number:Length i3Mileage "Odometer [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#mileage" }
Number:Length i3Range "Range [%d %unit%]" <motion> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#hybrid"}
Number:Length i3RangeElectric "Electric Range [%d %unit%]" <motion> (i3,long) {channel="bmwconnecteddrive:bev_rex:user:i3:range#electric"}
Number:Length i3RangeFuel "Fuel Range [%d %unit%]" <motion> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#fuel"}
Number:Dimensionless i3BatterySoc "Battery Charge [%.1f %%]" <battery> (i3,long) {channel="bmwconnecteddrive:bev_rex:user:i3:range#soc"}
Number:Volume i3Fuel "Fuel [%.1f %unit%]" <oil> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#remaining-fuel"}
Number:Length i3RadiusElectric "Electric Radius [%d %unit%]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#radius-electric" }
Number:Length i3RadiusHybrid "Hybrid Radius [%d %unit%]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:range#radius-hybrid" }
String i3DoorStatus "Door Status [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#doors" }
String i3WindowStatus "Window Status [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#windows" }
String i3LockStatus "Lock Status [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#lock" }
DateTime i3NextServiceDate "Next Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#service-date" }
String i3NextServiceMileage "Next Service Mileage [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#service-mileage" }
String i3CheckControl "Check Control [%s]" <error> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#check-control" }
String i3ChargingStatus "Charging [%s]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#charge" }
DateTime i3LastUpdate "Update [%1$tA, %1$td.%1$tm. %1$tH:%1$tM]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:status#last-update"}
DateTime i3TripDateTime "Trip Date [%1$tA, %1$td.%1$tm. %1$tH:%1$tM]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#date"}
Number:Time i3TripDuration "Trip Duration [%d %unit%]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#duration"}
Number:Length i3TripDistance "Distance [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#distance" }
Number:Length i3TripDistanceSinceCharge "Distance since last Charge [%d %unit%]" <line> (i3,long) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#distance-since-charging" }
Number:Energy i3AvgTripConsumption "Average Consumption [%.1f %unit%]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#avg-consumption" }
Number:Volume i3AvgTripCombined "Average Combined Consumption [%.1f %unit%]" <oil> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#avg-combined-consumption" }
Number:Energy i3AvgTripRecuperation "Average Recuperation [%.1f %unit%]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:last-trip#avg-recuperation" }
Number:Length i3TotalElectric "Electric Distance Driven [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:lifetime#total-driven-distance" }
Number:Length i3LongestEVTrip "Longest Electric Trip [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:lifetime#single-longest-distance" }
Number:Energy i3AvgConsumption "Average Consumption [%.1f %unit%]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:lifetime#avg-consumption" }
Number:Volume i3AvgCombined "Average Combined Consumption [%.1f %unit%]" <oil> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:lifetime#avg-combined-consumption" }
Number:Energy i3AvgRecuperation "Average Recuperation [%.1f %unit%]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:lifetime#avg-recuperation" }
Location i3Location "Location [%s]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:location#gps" }
Number:Angle i3Heading "Heading [%.1f %unit%]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:location#heading" }
String i3RemoteCommand "Command [%s]" <switch> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:remote#command" }
String i3RemoteState "Remote Execution State [%s]" <status> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:remote#state" }
String i3DriverDoor "Driver Door [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#driver-front" }
String i3DriverDoorRear "Driver Door Rear [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#driver-rear" }
String i3PassengerDoor "Passenger Door [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#passenger-front" }
String i3PassengerDoorRear "Passenger Door Rear [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#passenger-rear" }
String i3Hood "Hood [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#hood" }
String i3Trunk "Trunk [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#trunk" }
String i3DriverWindow "Driver Window [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#win-driver-front" }
String i3DriverWindowRear "Driver Window Rear [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#win-driver-rear" }
String i3PassengerWindow "Passenger Window [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#win-passenger-front" }
String i3PassengerWindowRear "Passenger Window Rear [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#win-passenger-rear" }
String i3RearWindow "Rear Window [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#win-rear" }
String i3Sunroof "Sunroof [%s]" <lock> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:doors#sunroof" }
String i3ServiceName "Service Name [%s]" <text> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:service#name" }
String i3ServiceDetails "Service Details [%s]" <text> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:service#details" }
Number:Length i3ServiceMileage "Service Mileage [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:service#mileage" }
DateTime i3ServiceDate "Service Date [%1$tb %1$tY]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:service#date" }
String i3CCName "CheckControl Name [%s]" <text> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:check#name" }
String i3CCDetails "CheckControl Details [%s]" <text> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:check#details" }
Number:Length i3CCMileage "CheckControl Mileage [%d %unit%]" <line> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:check#mileage" }
String i3DestName "Destination [%s]" <house> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:destination#name" }
Location i3DestLocation "GPS [%s]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:destination#gps" }
Switch i3ChargeProfileClimate "Charge Profile Climatization" <temperature> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#profile-climate" }
String i3ChargeProfileMode "Charge Profile Mode [%s]" <energy> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#profile-mode" }
DateTime i3ChargeWindowStart "Charge Window Start [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#window-start" }
Number i3ChargeWindowStartHour "Charge Window Start Hour [%d]" <time> (i3)
Number i3ChargeWindowStartMinute "Charge Window Start Minute [%d]" <time> (i3)
DateTime i3ChargeWindowEnd "Charge Window End [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#window-end" }
Number i3ChargeWindowEndHour "Charge Window End Hour [%d]" <time> (i3)
Number i3ChargeWindowEndMinute "Charge Window End Minute [%d]" <time> (i3)
DateTime i3Timer1Departure "Timer 1 Departure [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-departure" }
Number i3Timer1DepartureHour "Timer 1 Departure Hour [%d]" <time> (i3)
Number i3Timer1DepartureMinute "Timer 1 Departure Minute [%d]" <time> (i3)
String i3Timer1Days "Timer 1 Days [%s]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-days" }
Switch i3Timer1DayMon "Timer 1 Monday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-mon" }
Switch i3Timer1DayTue "Timer 1 Tuesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-tue" }
Switch i3Timer1DayWed "Timer 1 Wednesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-wed" }
Switch i3Timer1DayThu "Timer 1 Thursday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-thu" }
Switch i3Timer1DayFri "Timer 1 Friday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-fri" }
Switch i3Timer1DaySat "Timer 1 Saturday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-sat" }
Switch i3Timer1DaySun "Timer 1 Sunday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-day-sun" }
Switch i3Timer1Enabled "Timer 1 Enabled" <switch> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer1-enabled" }
DateTime i3Timer2Departure "Timer 2 Departure [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-departure" }
Number i3Timer2DepartureHour "Timer 2 Departure Hour [%d]" <time> (i3)
Number i3Timer2DepartureMinute "Timer 2 Departure Minute [%d]" <time> (i3)
String i3Timer2Days "Timer 2 Days [%s]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-days" }
Switch i3Timer2DayMon "Timer 2 Monday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-mon" }
Switch i3Timer2DayTue "Timer 2 Tuesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-tue" }
Switch i3Timer2DayWed "Timer 2 Wednesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-wed" }
Switch i3Timer2DayThu "Timer 2 Thursday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-thu" }
Switch i3Timer2DayFri "Timer 2 Friday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-fri" }
Switch i3Timer2DaySat "Timer 2 Saturday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-sat" }
Switch i3Timer2DaySun "Timer 2 Sunday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-day-sun" }
Switch i3Timer2Enabled "Timer 2 Enabled" <switch> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer2-enabled" }
DateTime i3Timer3Departure "Timer 3 Departure [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-departure" }
Number i3Timer3DepartureHour "Timer 3 Departure Hour [%d]" <time> (i3)
Number i3Timer3DepartureMinute "Timer 3 Departure Minute [%d]" <time> (i3)
String i3Timer3Days "Timer 3 Days [%s]" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-days" }
Switch i3Timer3DayMon "Timer 3 Monday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-mon" }
Switch i3Timer3DayTue "Timer 3 Tuesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-tue" }
Switch i3Timer3DayWed "Timer 3 Wednesday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-wed" }
Switch i3Timer3DayThu "Timer 3 Thursday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-thu" }
Switch i3Timer3DayFri "Timer 3 Friday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-fri" }
Switch i3Timer3DaySat "Timer 3 Saturday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-sat" }
Switch i3Timer3DaySun "Timer 3 Sunday" <calendar> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-day-sun" }
Switch i3Timer3Enabled "Timer 3 Enabled" <switch> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#timer3-enabled" }
Switch i3OverrideEnabled "Override Timer Enabled" <switch> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#override-enabled"}
DateTime i3OverrideDeparture "Override Timer Departure [%1$tH:%1$tM]" <time> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:charge#override-departure" }
Number i3OverrideDepartureHour "Override Timer Departure Hour [%d]" <time> (i3)
Number i3OverrideDepartureMinute "Override Timer Departure Minute [%d]" <time> (i3)
Image i3Image "Image" (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:image#png" }
String i3ImageViewport "Image Viewport [%s]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:image#view" }
Number i3ImageSize "Image Size [%d]" <zoom> (i3) {channel="bmwconnecteddrive:bev_rex:user:i3:image#size" }
```
### Sitemap File
```
sitemap BMW label="BMW" {
Frame label="BMW i3" {
Image item=i3Image
}
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="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="Remote Services" {
Selection item=i3RemoteCommand
Text item=i3RemoteState
}
Frame label="Last Trip" {
Text item=i3TripDateTime
Text item=i3TripDuration
Text item=i3TripDistance
Text item=i3TripDistanceSinceCharge
Text item=i3AvgTripConsumption
Text item=i3AvgTripRecuperation
Text item=i3AvgTripCombined
}
Frame label="Lifetime" {
Text item=i3TotalElectric
Text item=i3LongestEVTrip
Text item=i3AvgConsumption
Text item=i3AvgRecuperation
Text item=i3AvgCombined
}
Frame label="Services" {
Text item=i3ServiceName
Text item=i3ServiceMileage
Text item=i3ServiceDate
}
Frame label="CheckControl" {
Text item=i3CCName
Text item=i3CCMileage
}
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
Setpoint item=i3ChargeWindowStartHour maxValue=23 step=1 icon="time"
Setpoint item=i3ChargeWindowStartMinute maxValue=55 step=5 icon="time"
Text item=i3ChargeWindowEnd
Setpoint item=i3ChargeWindowEndHour maxValue=23 step=1 icon="time"
Setpoint item=i3ChargeWindowEndMinute maxValue=55 step=5 icon="time"
Text item=i3Timer1Departure
Setpoint item=i3Timer1DepartureHour maxValue=23 step=1 icon="time"
Setpoint item=i3Timer1DepartureMinute maxValue=55 step=5 icon="time"
Text item=i3Timer1Days
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
Setpoint item=i3Timer2DepartureHour maxValue=23 step=1 icon="time"
Setpoint item=i3Timer2DepartureMinute maxValue=55 step=5 icon="time"
Text item=i3Timer2Days
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
Setpoint item=i3Timer3DepartureHour maxValue=23 step=1 icon="time"
Setpoint item=i3Timer3DepartureMinute maxValue=55 step=5 icon="time"
Text item=i3Timer3Days
Switch item=i3Timer3DayMon
Switch item=i3Timer3DayTue
Switch item=i3Timer3DayWed
Switch item=i3Timer3DayThu
Switch item=i3Timer3DayFri
Switch item=i3Timer3DaySat
Switch item=i3Timer3DaySun
Switch item=i3Timer3Enabled
Switch item=i3OverrideEnabled
Text item=i3OverrideDeparture
Setpoint item=i3OverrideDepartureHour maxValue=23 step=1 icon="time"
Setpoint item=i3OverrideDepartureMinute maxValue=55 step=5 icon="time"
}
Frame label="Last Destinations" {
Text item=i3DestName
Text item=i3DestLocation
}
Frame label="Image Properties" {
Text item=i3ImageViewport
Text item=i3ImageSize
}
}
```
### Rules File
```
rule "i3ChargeWindowStartSetpoint"
when
Item i3ChargeWindowStartMinute changed or
Item i3ChargeWindowStartHour changed
then
val hour = (i3ChargeWindowStartHour.state as Number).intValue
val minute = (i3ChargeWindowStartMinute.state as Number).intValue
val time = (i3ChargeWindowStart.state as DateTimeType).zonedDateTime
i3ChargeWindowStart.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3ChargeWindowStart"
when
Item i3ChargeWindowStart changed
then
val time = (i3ChargeWindowStart.state as DateTimeType).zonedDateTime
i3ChargeWindowStartMinute.sendCommand(time.minute)
i3ChargeWindowStartHour.sendCommand(time.hour)
end
rule "i3ChargeWindowEndSetpoint"
when
Item i3ChargeWindowEndMinute changed or
Item i3ChargeWindowEndHour changed
then
val hour = (i3ChargeWindowEndHour.state as Number).intValue
val minute = (i3ChargeWindowEndMinute.state as Number).intValue
val time = (i3ChargeWindowEnd.state as DateTimeType).zonedDateTime
i3ChargeWindowEnd.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3ChargeWindowEnd"
when
Item i3ChargeWindowEnd changed
then
val time = (i3ChargeWindowEnd.state as DateTimeType).zonedDateTime
i3ChargeWindowEndMinute.sendCommand(time.minute)
i3ChargeWindowEndHour.sendCommand(time.hour)
end
rule "i3Timer1DepartureSetpoint"
when
Item i3Timer1DepartureMinute changed or
Item i3Timer1DepartureHour changed
then
val hour = (i3Timer1DepartureHour.state as Number).intValue
val minute = (i3Timer1DepartureMinute.state as Number).intValue
val time = (i3Timer1Departure.state as DateTimeType).zonedDateTime
i3Timer1Departure.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3Timer1Departure"
when
Item i3Timer1Departure changed
then
val time = (i3Timer1Departure.state as DateTimeType).zonedDateTime
i3Timer1DepartureMinute.sendCommand(time.minute)
i3Timer1DepartureHour.sendCommand(time.hour)
end
rule "i3Timer2DepartureSetpoint"
when
Item i3Timer2DepartureMinute changed or
Item i3Timer2DepartureHour changed
then
val hour = (i3Timer2DepartureHour.state as Number).intValue
val minute = (i3Timer2DepartureMinute.state as Number).intValue
val time = (i3Timer2Departure.state as DateTimeType).zonedDateTime
i3Timer2Departure.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3Timer2Departure"
when
Item i3Timer2Departure changed
then
val time = (i3Timer2Departure.state as DateTimeType).zonedDateTime
i3Timer2DepartureMinute.sendCommand(time.minute)
i3Timer2DepartureHour.sendCommand(time.hour)
end
rule "i3Timer3DepartureSetpoint"
when
Item i3Timer3DepartureMinute changed or
Item i3Timer3DepartureHour changed
then
val hour = (i3Timer3DepartureHour.state as Number).intValue
val minute = (i3Timer3DepartureMinute.state as Number).intValue
val time = (i3Timer3Departure.state as DateTimeType).zonedDateTime
i3Timer3Departure.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3Timer3Departure"
when
Item i3Timer3Departure changed
then
val time = (i3Timer3Departure.state as DateTimeType).zonedDateTime
i3Timer3DepartureMinute.sendCommand(time.minute)
i3Timer3DepartureHour.sendCommand(time.hour)
end
rule "i3OverrideDepartureSetpoint"
when
Item i3OverrideDepartureMinute changed or
Item i3OverrideDepartureHour changed
then
val hour = (i3OverrideDepartureHour.state as Number).intValue
val minute = (i3OverrideDepartureMinute.state as Number).intValue
val time = (i3OverrideDeparture.state as DateTimeType).zonedDateTime
i3OverrideDeparture.sendCommand(new DateTimeType(time.withHour(hour).withMinute(minute)))
end
rule "i3OverrideDeparture"
when
Item i3OverrideDeparture changed
then
val time = (i3OverrideDeparture.state as DateTimeType).zonedDateTime
i3OverrideDepartureMinute.sendCommand(time.minute)
i3OverrideDepartureHour.sendCommand(time.hour)
end
```
### Action example
```
val profile = getActions("bmwconnecteddrive", "bmwconnecteddrive:bev_rex:user:i3")
val now = ZonedDateTime.now.toLocalTime
profile.setChargingMode("DELAYED_CHARGING")
profile.setTimer1Departure(now.minusHours(2))
profile.setTimer1Days(java.util.Set())
profile.setTimer1Enabled(true)
profile.setTimer2Enabled(false)
profile.setTimer3Enabled(false)
profile.setPreferredWindowStart(now.minusHours(6))
profile.setPreferredWindowEnd(now.minusHours(2))
profile.sendChargeProfile()
```
## Credits
This work is based on the project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
Also a [manual installation based on python](https://community.openhab.org/t/script-to-access-the-bmw-connecteddrive-portal-via-oh/37345) was already available for openHAB.
This binding is basically a port to openHAB based on these concept works!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 KiB

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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>3.3.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.bmwconnecteddrive</artifactId>
<name>openHAB Add-ons :: Bundles :: BMWConnectedDrive Binding</name>
</project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.bmwconnecteddrive-${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-bmwconnecteddrive" description="BMWConnectedDrive Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bmwconnecteddrive/${project.version}</bundle>
</feature>
</features>

View File

@ -1,45 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link ConnectedDriveConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class ConnectedDriveConfiguration {
/**
* Depending on the location the correct server needs to be called
*/
public String region = Constants.EMPTY;
/**
* BMW Connected Drive Username
*/
public String userName = Constants.EMPTY;
/**
* BMW Connected Drive Password
*/
public String password = Constants.EMPTY;
/**
* Prefer MyBMW API instead of BMW Connected Drive
*/
public boolean preferMyBmw = false;
}

View File

@ -1,207 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link ConnectedDriveConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
@NonNullByDefault
public class ConnectedDriveConstants {
private static final String BINDING_ID = "bmwconnecteddrive";
// Units
public static final String UNITS_AUTODETECT = "AUTODETECT";
public static final String UNITS_IMPERIAL = "IMPERIAL";
public static final String UNITS_METRIC = "METRIC";
public static final String VIN = "vin";
public static final int DEFAULT_IMAGE_SIZE_PX = 1024;
public static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 5;
public static final String DEFAULT_IMAGE_VIEWPORT = "FRONT";
// See constants from bimmer-connected
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
public enum VehicleType {
CONVENTIONAL("conv"),
PLUGIN_HYBRID("phev"),
ELECTRIC_REX("bev_rex"),
ELECTRIC("bev");
private final String type;
VehicleType(String s) {
type = s;
}
@Override
public String toString() {
return type;
}
}
public enum ChargingMode {
IMMEDIATE_CHARGING,
DELAYED_CHARGING
}
public enum ChargingPreference {
NO_PRESELECTION,
CHARGING_WINDOW
}
public static final Set<String> FUEL_VEHICLES = Set.of(VehicleType.CONVENTIONAL.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
public static final Set<String> ELECTRIC_VEHICLES = Set.of(VehicleType.ELECTRIC.toString(),
VehicleType.PLUGIN_HYBRID.toString(), VehicleType.ELECTRIC_REX.toString());
// Countries with Mileage display
public static final Set<String> IMPERIAL_COUNTRIES = Set.of("US", "GB");
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_CONNECTED_DRIVE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final ThingTypeUID THING_TYPE_CONV = new ThingTypeUID(BINDING_ID,
VehicleType.CONVENTIONAL.toString());
public static final ThingTypeUID THING_TYPE_PHEV = new ThingTypeUID(BINDING_ID,
VehicleType.PLUGIN_HYBRID.toString());
public static final ThingTypeUID THING_TYPE_BEV_REX = new ThingTypeUID(BINDING_ID,
VehicleType.ELECTRIC_REX.toString());
public static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, VehicleType.ELECTRIC.toString());
public static final Set<ThingTypeUID> SUPPORTED_THING_SET = Set.of(THING_TYPE_CONNECTED_DRIVE_ACCOUNT,
THING_TYPE_CONV, THING_TYPE_PHEV, THING_TYPE_BEV_REX, THING_TYPE_BEV);
// Thing Group definitions
public static final String CHANNEL_GROUP_STATUS = "status";
public static final String CHANNEL_GROUP_SERVICE = "service";
public static final String CHANNEL_GROUP_CHECK_CONTROL = "check";
public static final String CHANNEL_GROUP_DOORS = "doors";
public static final String CHANNEL_GROUP_RANGE = "range";
public static final String CHANNEL_GROUP_LOCATION = "location";
public static final String CHANNEL_GROUP_LAST_TRIP = "last-trip";
public static final String CHANNEL_GROUP_LIFETIME = "lifetime";
public static final String CHANNEL_GROUP_REMOTE = "remote";
public static final String CHANNEL_GROUP_CHARGE = "charge";
public static final String CHANNEL_GROUP_VEHICLE_IMAGE = "image";
public static final String CHANNEL_GROUP_DESTINATION = "destination";
// Generic Constants for several groups
public static final String NAME = "name";
public static final String DETAILS = "details";
public static final String DATE = "date";
public static final String MILEAGE = "mileage";
public static final String GPS = "gps";
public static final String HEADING = "heading";
// Status
public static final String DOORS = "doors";
public static final String WINDOWS = "windows";
public static final String LOCK = "lock";
public static final String SERVICE_DATE = "service-date";
public static final String SERVICE_MILEAGE = "service-mileage";
public static final String CHECK_CONTROL = "check-control";
public static final String PLUG_CONNECTION = "plug-connection";
public static final String CHARGE_STATUS = "charge";
public static final String CHARGE_END_REASON = "reason";
public static final String CHARGE_REMAINING = "remaining";
public static final String LAST_UPDATE = "last-update";
public static final String LAST_UPDATE_REASON = "last-update-reason";
// Door Details
public static final String DOOR_DRIVER_FRONT = "driver-front";
public static final String DOOR_DRIVER_REAR = "driver-rear";
public static final String DOOR_PASSENGER_FRONT = "passenger-front";
public static final String DOOR_PASSENGER_REAR = "passenger-rear";
public static final String HOOD = "hood";
public static final String TRUNK = "trunk";
public static final String WINDOW_DOOR_DRIVER_FRONT = "win-driver-front";
public static final String WINDOW_DOOR_DRIVER_REAR = "win-driver-rear";
public static final String WINDOW_DOOR_PASSENGER_FRONT = "win-passenger-front";
public static final String WINDOW_DOOR_PASSENGER_REAR = "win-passenger-rear";
public static final String WINDOW_REAR = "win-rear";
public static final String SUNROOF = "sunroof";
// Charge Profile
public static final String CHARGE_PROFILE_CLIMATE = "profile-climate";
public static final String CHARGE_PROFILE_MODE = "profile-mode";
public static final String CHARGE_PROFILE_PREFERENCE = "profile-prefs";
public static final String CHARGE_WINDOW_START = "window-start";
public static final String CHARGE_WINDOW_END = "window-end";
public static final String CHARGE_TIMER1 = "timer1";
public static final String CHARGE_TIMER2 = "timer2";
public static final String CHARGE_TIMER3 = "timer3";
public static final String CHARGE_OVERRIDE = "override";
public static final String CHARGE_DEPARTURE = "-departure";
public static final String CHARGE_ENABLED = "-enabled";
public static final String CHARGE_DAYS = "-days";
public static final String CHARGE_DAY_MON = "-day-mon";
public static final String CHARGE_DAY_TUE = "-day-tue";
public static final String CHARGE_DAY_WED = "-day-wed";
public static final String CHARGE_DAY_THU = "-day-thu";
public static final String CHARGE_DAY_FRI = "-day-fri";
public static final String CHARGE_DAY_SAT = "-day-sat";
public static final String CHARGE_DAY_SUN = "-day-sun";
// Range
public static final String RANGE_HYBRID = "hybrid";
public static final String RANGE_HYBRID_MAX = "hybrid-max";
public static final String RANGE_ELECTRIC = "electric";
public static final String RANGE_ELECTRIC_MAX = "electric-max";
public static final String SOC = "soc";
public static final String SOC_MAX = "soc-max";
public static final String RANGE_FUEL = "fuel";
public static final String REMAINING_FUEL = "remaining-fuel";
public static final String RANGE_RADIUS_ELECTRIC = "radius-electric";
public static final String RANGE_RADIUS_ELECTRIC_MAX = "radius-electric-max";
public static final String RANGE_RADIUS_FUEL = "radius-fuel";
public static final String RANGE_RADIUS_HYBRID = "radius-hybrid";
public static final String RANGE_RADIUS_HYBRID_MAX = "radius-hybrid-max";
// Last Trip
public static final String DURATION = "duration";
public static final String DISTANCE = "distance";
public static final String DISTANCE_SINCE_CHARGING = "distance-since-charging";
public static final String AVG_CONSUMPTION = "avg-consumption";
public static final String AVG_COMBINED_CONSUMPTION = "avg-combined-consumption";
public static final String AVG_RECUPERATION = "avg-recuperation";
// Lifetime + Average Consumptions
public static final String TOTAL_DRIVEN_DISTANCE = "total-driven-distance";
public static final String SINGLE_LONGEST_DISTANCE = "single-longest-distance";
// Image
public static final String IMAGE_FORMAT = "png";
public static final String IMAGE_VIEWPORT = "view";
public static final String IMAGE_SIZE = "size";
// Remote Services
public static final String REMOTE_SERVICE_LIGHT_FLASH = "light";
public static final String REMOTE_SERVICE_VEHICLE_FINDER = "finder";
public static final String REMOTE_SERVICE_DOOR_LOCK = "lock";
public static final String REMOTE_SERVICE_DOOR_UNLOCK = "unlock";
public static final String REMOTE_SERVICE_HORN = "horn";
public static final String REMOTE_SERVICE_AIR_CONDITIONING = "climate";
public static final String REMOTE_SERVICE_CHARGE_NOW = "charge-now";
public static final String REMOTE_SERVICE_CHARGING_CONTROL = "charge-control";
public static final String REMOTE_SERVICE_COMMAND = "command";
public static final String REMOTE_STATE = "state";
}

View File

@ -1,76 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.handler.BMWConnectedDriveOptionProvider;
import org.openhab.binding.bmwconnecteddrive.internal.handler.ConnectedDriveBridgeHandler;
import org.openhab.binding.bmwconnecteddrive.internal.handler.VehicleHandler;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link ConnectedDriveHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.bmwconnecteddrive", service = ThingHandlerFactory.class)
public class ConnectedDriveHandlerFactory extends BaseThingHandlerFactory {
private final HttpClientFactory httpClientFactory;
private final BMWConnectedDriveOptionProvider optionProvider;
private boolean imperial = false;
@Activate
public ConnectedDriveHandlerFactory(final @Reference HttpClientFactory hcf,
final @Reference BMWConnectedDriveOptionProvider op, final @Reference LocaleProvider lp,
final @Reference TimeZoneProvider timeZoneProvider) {
httpClientFactory = hcf;
optionProvider = op;
imperial = IMPERIAL_COUNTRIES.contains(lp.getLocale().getCountry());
Converter.setTimeZoneProvider(timeZoneProvider);
}
@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 ConnectedDriveBridgeHandler((Bridge) thing, httpClientFactory);
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
VehicleHandler vh = new VehicleHandler(thing, optionProvider, thingTypeUID.getId(), imperial);
return vh;
}
return null;
}
}

View File

@ -1,57 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link VehicleConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class VehicleConfiguration {
/**
* Vehicle Identification Number (VIN)
*/
public String vin = Constants.EMPTY;
/**
* Data refresh rate in minutes
*/
public int refreshInterval = ConnectedDriveConstants.DEFAULT_REFRESH_INTERVAL_MINUTES;
/**
* Either Auto Detect Miles units (UK & US) or select Format directly
* <option value="AUTODETECT">Auto Detect</option>
* <option value="METRIC">Metric</option>
* <option value="IMPERIAL">Imperial</option>
*/
public String units = ConnectedDriveConstants.UNITS_AUTODETECT;
/**
* image size - width & length (square)
*/
public int imageSize = ConnectedDriveConstants.DEFAULT_IMAGE_SIZE_PX;
/**
* image viewport defined as options in thing xml
* <option value="FRONT">Front</option>
* <option value="REAR">Rear</option>
* <option value="SIDE">Slide</option>
* <option value="DASHBOARD">Dashboard</option>
* <option value="DRIVERDOOR">Driver Door</option>
*/
public String imageViewport = ConnectedDriveConstants.DEFAULT_IMAGE_VIEWPORT;
}

View File

@ -1,406 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.action;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey.*;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.handler.VehicleHandler;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
/**
* The {@link BMWConnectedDriveActions} provides actions for VehicleHandler
*
* @author Norbert Truchsess - Initial contribution
*/
@ThingActionsScope(name = "bmwconnecteddrive")
@NonNullByDefault
public class BMWConnectedDriveActions implements ThingActions {
private Optional<VehicleHandler> handler = Optional.empty();
private Optional<ChargeProfileWrapper> profile = Optional.empty();
@RuleAction(label = "getTimer1Departure", description = "returns the departure time of timer1")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getTimer1Departure() {
return getTime(TIMER1);
}
@RuleAction(label = "setTimer1Departure", description = "sets the timer1 departure time")
public void setTimer1Departure(@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(TIMER1, time);
}
@RuleAction(label = "getTimer1Enabled", description = "returns the enabled state of timer1")
public @ActionOutput(name = "enabled", type = "java.util.Optional<java.lang.Boolean>") Optional<Boolean> getTimer1Enabled() {
return getEnabled(TIMER1);
}
@RuleAction(label = "setTimer1Enabled", description = "sets the enabled state of timer1")
public void setTimer1Enabled(@ActionInput(name = "enabled", type = "java.lang.Boolean") @Nullable Boolean enabled) {
setEnabled(TIMER1, enabled);
}
@RuleAction(label = "getTimer2Departure", description = "returns the departure time of timer2")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getTimer2Departure() {
return getTime(TIMER2);
}
@RuleAction(label = "setTimer2Departure", description = "sets the timer2 departure time")
public void setTimer2Departure(@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(TIMER2, time);
}
@RuleAction(label = "getTimer2Enabled", description = "returns the enabled state of timer2")
public @ActionOutput(name = "enabled", type = "java.util.Optional<java.lang.Boolean>") Optional<Boolean> getTimer2Enabled() {
return getEnabled(TIMER2);
}
@RuleAction(label = "setTimer2Enabled", description = "sets the enabled state of timer2")
public void setTimer2Enabled(@ActionInput(name = "enabled", type = "java.lang.Boolean") @Nullable Boolean enabled) {
setEnabled(TIMER2, enabled);
}
@RuleAction(label = "getTimer3Departure", description = "returns the departure time of timer3")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getTimer3Departure() {
return getTime(TIMER3);
}
@RuleAction(label = "setTimer3Departure", description = "sets the timer3 departure time")
public void setTimer3Departure(@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(TIMER3, time);
}
@RuleAction(label = "getTimer3Enabled", description = "returns the enabled state of timer3")
public @ActionOutput(name = "enabled", type = "java.util.Optional<java.lang.Boolean>") Optional<Boolean> getTimer3Enabled() {
return getEnabled(TIMER3);
}
@RuleAction(label = "setTimer3Enabled", description = "sets the enabled state of timer3")
public void setTimer3Enabled(@ActionInput(name = "enabled", type = "java.lang.Boolean") @Nullable Boolean enabled) {
setEnabled(TIMER3, enabled);
}
@RuleAction(label = "getOverrideTimerDeparture", description = "returns the departure time of overrideTimer")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getOverrideTimerDeparture() {
return getTime(OVERRIDE);
}
@RuleAction(label = "setOverrideTimerDeparture", description = "sets the overrideTimer departure time")
public void setOverrideTimerDeparture(
@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(OVERRIDE, time);
}
@RuleAction(label = "getOverrideTimerEnabled", description = "returns the enabled state of overrideTimer")
public @ActionOutput(name = "enabled", type = "java.util.Optional<java.lang.Boolean>") Optional<Boolean> getOverrideTimerEnabled() {
return getEnabled(OVERRIDE);
}
@RuleAction(label = "setOverrideTimerEnabled", description = "sets the enabled state of overrideTimer")
public void setOverrideTimerEnabled(
@ActionInput(name = "enabled", type = "java.lang.Boolean") @Nullable Boolean enabled) {
setEnabled(OVERRIDE, enabled);
}
@RuleAction(label = "getPreferredWindowStart", description = "returns the preferred charging-window start time")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getPreferredWindowStart() {
return getTime(WINDOWSTART);
}
@RuleAction(label = "setPreferredWindowStart", description = "sets the preferred charging-window start time")
public void setPreferredWindowStart(
@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(WINDOWSTART, time);
}
@RuleAction(label = "getPreferredWindowEnd", description = "returns the preferred charging-window end time")
public @ActionOutput(name = "time", type = "java.util.Optional<java.time.LocalTime>") Optional<LocalTime> getPreferredWindowEnd() {
return getTime(WINDOWEND);
}
@RuleAction(label = "setPreferredWindowEnd", description = "sets the preferred charging-window end time")
public void setPreferredWindowEnd(
@ActionInput(name = "time", type = "java.time.LocalTime") @Nullable LocalTime time) {
setTime(WINDOWEND, time);
}
@RuleAction(label = "getClimatizationEnabled", description = "returns the enabled state of climatization")
public @ActionOutput(name = "enabled", type = "java.util.Optional<java.lang.Boolean>") Optional<Boolean> getClimatizationEnabled() {
return getEnabled(CLIMATE);
}
@RuleAction(label = "setClimatizationEnabled", description = "sets the enabled state of climatization")
public void setClimatizationEnabled(
@ActionInput(name = "enabled", type = "java.lang.Boolean") @Nullable Boolean enabled) {
setEnabled(CLIMATE, enabled);
}
@RuleAction(label = "getChargingMode", description = "gets the charging-mode")
public @ActionOutput(name = "mode", type = "java.util.Optional<java.lang.String>") Optional<String> getChargingMode() {
return getProfile().map(profile -> profile.getMode());
}
@RuleAction(label = "setChargingMode", description = "sets the charging-mode")
public void setChargingMode(@ActionInput(name = "mode", type = "java.lang.String") @Nullable String mode) {
getProfile().ifPresent(profile -> profile.setMode(mode));
}
@RuleAction(label = "getTimer1Days", description = "returns the days of week timer1 is enabled for")
public @ActionOutput(name = "days", type = "java.util.Optional<java.util.Set<java.time.DayOfWeek>>") Optional<Set<DayOfWeek>> getTimer1Days() {
return getDays(TIMER1);
}
@RuleAction(label = "setTimer1Days", description = "sets the days of week timer1 is enabled for")
public void setTimer1Days(
@ActionInput(name = "days", type = "java.util.Set<java.time.DayOfWeek>") @Nullable Set<DayOfWeek> days) {
setDays(TIMER1, days);
}
@RuleAction(label = "getTimer2Days", description = "returns the days of week timer2 is enabled for")
public @ActionOutput(name = "days", type = "java.util.Optional<java.util.Set<java.time.DayOfWeek>>") Optional<Set<DayOfWeek>> getTimer2Days() {
return getDays(TIMER2);
}
@RuleAction(label = "setTimer2Days", description = "sets the days of week timer2 is enabled for")
public void setTimer2Days(
@ActionInput(name = "days", type = "java.util.Set<java.time.DayOfWeek>") @Nullable Set<DayOfWeek> days) {
setDays(TIMER2, days);
}
@RuleAction(label = "getTimer3Days", description = "returns the days of week timer3 is enabled for")
public @ActionOutput(name = "days", type = "java.util.Optional<java.util.Set<java.time.DayOfWeek>>") Optional<Set<DayOfWeek>> getTimer3Days() {
return getDays(TIMER3);
}
@RuleAction(label = "setTimer3Days", description = "sets the days of week timer3 is enabled for")
public void setTimer3Days(
@ActionInput(name = "days", type = "java.util.Set<java.time.DayOfWeek>") @Nullable Set<DayOfWeek> days) {
setDays(TIMER3, days);
}
@RuleAction(label = "getOverrideTimerDays", description = "returns the days of week the overrideTimer is enabled for")
public @ActionOutput(name = "days", type = "java.util.Optional<java.util.Set<java.time.DayOfWeek>>") Optional<Set<DayOfWeek>> getOverrideTimerDays() {
return getDays(OVERRIDE);
}
@RuleAction(label = "setOverrideTimerDays", description = "sets the days of week the overrideTimer is enabled for")
public void setOverrideTimerDays(
@ActionInput(name = "days", type = "java.util.Set<java.time.DayOfWeek>") @Nullable Set<DayOfWeek> days) {
setDays(OVERRIDE, days);
}
@RuleAction(label = "sendChargeProfile", description = "sends the charging profile to the vehicle")
public void sendChargeProfile() {
handler.ifPresent(handle -> handle.sendChargeProfile(getProfile()));
}
@RuleAction(label = "cancel", description = "cancel current edit of charging profile")
public void cancelEditChargeProfile() {
profile = Optional.empty();
}
public static Optional<LocalTime> getTimer1Departure(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer1Departure();
}
public static void setTimer1Departure(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setTimer1Departure(time);
}
public static Optional<Boolean> getTimer1Enabled(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer1Enabled();
}
public static void setTimer1Enabled(ThingActions actions, @Nullable Boolean enabled) {
((BMWConnectedDriveActions) actions).setTimer1Enabled(enabled);
}
public static Optional<LocalTime> getTimer2Departure(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer2Departure();
}
public static void setTimer2Departure(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setTimer2Departure(time);
}
public static Optional<Boolean> getTimer2Enabled(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer2Enabled();
}
public static void setTimer2Enabled(ThingActions actions, @Nullable Boolean enabled) {
((BMWConnectedDriveActions) actions).setTimer2Enabled(enabled);
}
public static Optional<LocalTime> getTimer3Departure(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer3Departure();
}
public static void setTimer3Departure(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setTimer3Departure(time);
}
public static Optional<Boolean> getTimer3Enabled(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer3Enabled();
}
public static void setTimer3Enabled(ThingActions actions, @Nullable Boolean enabled) {
((BMWConnectedDriveActions) actions).setTimer3Enabled(enabled);
}
public static Optional<LocalTime> getOverrideTimerDeparture(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getOverrideTimerDeparture();
}
public static void setOverrideTimerDeparture(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setOverrideTimerDeparture(time);
}
public static Optional<Boolean> getOverrideTimerEnabled(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getOverrideTimerEnabled();
}
public static void setOverrideTimerEnabled(ThingActions actions, @Nullable Boolean enabled) {
((BMWConnectedDriveActions) actions).setOverrideTimerEnabled(enabled);
}
public static Optional<LocalTime> getPreferredWindowStart(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getPreferredWindowStart();
}
public static void setPreferredWindowStart(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setPreferredWindowStart(time);
}
public static Optional<LocalTime> getPreferredWindowEnd(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getPreferredWindowEnd();
}
public static void setPreferredWindowEnd(ThingActions actions, @Nullable LocalTime time) {
((BMWConnectedDriveActions) actions).setPreferredWindowEnd(time);
}
public static Optional<Boolean> getClimatizationEnabled(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getClimatizationEnabled();
}
public static void setClimatizationEnabled(ThingActions actions, @Nullable Boolean enabled) {
((BMWConnectedDriveActions) actions).setClimatizationEnabled(enabled);
}
public static Optional<String> getChargingMode(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getChargingMode();
}
public static void setChargingMode(ThingActions actions, @Nullable String mode) {
((BMWConnectedDriveActions) actions).setChargingMode(mode);
}
public static Optional<Set<DayOfWeek>> getTimer1Days(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer1Days();
}
public static void setTimer1Days(ThingActions actions, @Nullable Set<DayOfWeek> days) {
((BMWConnectedDriveActions) actions).setTimer1Days(days);
}
public static Optional<Set<DayOfWeek>> getTimer2Days(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer2Days();
}
public static void setTimer2Days(ThingActions actions, @Nullable Set<DayOfWeek> days) {
((BMWConnectedDriveActions) actions).setTimer2Days(days);
}
public static Optional<Set<DayOfWeek>> getTimer3Days(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getTimer3Days();
}
public static void setTimer3Days(ThingActions actions, @Nullable Set<DayOfWeek> days) {
((BMWConnectedDriveActions) actions).setTimer3Days(days);
}
public static Optional<Set<DayOfWeek>> getOverrideTimerDays(ThingActions actions) {
return ((BMWConnectedDriveActions) actions).getOverrideTimerDays();
}
public static void setOverrideTimerDays(ThingActions actions, @Nullable Set<DayOfWeek> days) {
((BMWConnectedDriveActions) actions).setOverrideTimerDays(days);
}
public static void sendChargeProfile(ThingActions actions) {
((BMWConnectedDriveActions) actions).sendChargeProfile();
}
public static void cancelEditChargeProfile(ThingActions actions) {
((BMWConnectedDriveActions) actions).cancelEditChargeProfile();
}
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof VehicleHandler) {
this.handler = Optional.of((VehicleHandler) handler);
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler.get();
}
private Optional<ChargeProfileWrapper> getProfile() {
if (profile.isEmpty()) {
profile = handler.flatMap(handle -> handle.getChargeProfileWrapper());
}
return profile;
}
private Optional<LocalTime> getTime(ProfileKey key) {
return getProfile().map(profile -> profile.getTime(key));
}
private void setTime(ProfileKey key, @Nullable LocalTime time) {
getProfile().ifPresent(profile -> profile.setTime(key, time));
}
private Optional<Boolean> getEnabled(ProfileKey key) {
return getProfile().map(profile -> profile.isEnabled(key));
}
private void setEnabled(ProfileKey key, @Nullable Boolean enabled) {
getProfile().ifPresent(profile -> profile.setEnabled(key, enabled));
}
private Optional<Set<DayOfWeek>> getDays(ProfileKey key) {
return getProfile().map(profile -> profile.getDays(key));
}
private void setDays(ProfileKey key, @Nullable Set<DayOfWeek> days) {
getProfile().ifPresent(profile -> {
profile.setDays(key, days);
});
}
}

View File

@ -1,181 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.discovery;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.SUPPORTED_THING_SET;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants;
import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.VehiclesContainer;
import org.openhab.binding.bmwconnecteddrive.internal.handler.ConnectedDriveBridgeHandler;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VehicleDiscovery} requests data from ConnectedDrive and is identifying the Vehicles after response
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
private static final int DISCOVERY_TIMEOUT = 10;
private Optional<ConnectedDriveBridgeHandler> bridgeHandler = Optional.empty();
public VehicleDiscovery() {
super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
}
public void onResponse(VehiclesContainer container) {
bridgeHandler.ifPresent(bridge -> {
final ThingUID bridgeUID = bridge.getThing().getUID();
container.vehicles.forEach(vehicle -> {
// the DriveTrain field in the delivered json is defining the Vehicle Type
String vehicleType = vehicle.driveTrain.toLowerCase();
SUPPORTED_THING_SET.forEach(entry -> {
if (entry.getId().equals(vehicleType)) {
ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
Map<String, String> properties = new HashMap<>();
// Dealer
if (vehicle.dealer != null) {
properties.put("dealer", vehicle.dealer.name);
properties.put("dealerAddress", vehicle.dealer.street + " " + vehicle.dealer.country + " "
+ vehicle.dealer.postalCode + " " + vehicle.dealer.city);
properties.put("dealerPhone", vehicle.dealer.phone);
}
// Services & Support
properties.put("servicesActivated", getObject(vehicle, Constants.ACTIVATED));
String servicesSupported = getObject(vehicle, Constants.SUPPORTED);
String servicesNotSupported = getObject(vehicle, Constants.NOT_SUPPORTED);
if (vehicle.statisticsAvailable) {
servicesSupported += Constants.STATISTICS;
} else {
servicesNotSupported += Constants.STATISTICS;
}
properties.put(Constants.SERVICES_SUPPORTED, servicesSupported);
properties.put("servicesNotSupported", servicesNotSupported);
properties.put("supportBreakdownNumber", vehicle.breakdownNumber);
// Vehicle Properties
if (vehicle.supportedChargingModes != null) {
properties.put("vehicleChargeModes",
String.join(Constants.SPACE, vehicle.supportedChargingModes));
}
if (vehicle.hasAlarmSystem) {
properties.put("vehicleAlarmSystem", "Available");
} else {
properties.put("vehicleAlarmSystem", "Not Available");
}
properties.put("vehicleBrand", vehicle.brand);
properties.put("vehicleBodytype", vehicle.bodytype);
properties.put("vehicleColor", vehicle.color);
properties.put("vehicleConstructionYear", Short.toString(vehicle.yearOfConstruction));
properties.put("vehicleDriveTrain", vehicle.driveTrain);
properties.put("vehicleModel", vehicle.model);
if (vehicle.chargingControl != null) {
properties.put("vehicleChargeControl", Converter.toTitleCase(vehicle.model));
}
// Update Properties for already created Things
bridge.getThing().getThings().forEach(vehicleThing -> {
Configuration c = vehicleThing.getConfiguration();
if (c.containsKey(ConnectedDriveConstants.VIN)) {
String thingVIN = c.get(ConnectedDriveConstants.VIN).toString();
if (vehicle.vin.equals(thingVIN)) {
vehicleThing.setProperties(properties);
}
}
});
// Properties needed for functional THing
properties.put(ConnectedDriveConstants.VIN, vehicle.vin);
properties.put("refreshInterval",
Integer.toString(ConnectedDriveConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
properties.put("units", ConnectedDriveConstants.UNITS_AUTODETECT);
properties.put("imageSize", Integer.toString(ConnectedDriveConstants.DEFAULT_IMAGE_SIZE_PX));
properties.put("imageViewport", ConnectedDriveConstants.DEFAULT_IMAGE_VIEWPORT);
String vehicleLabel = vehicle.brand + " " + vehicle.model;
Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withRepresentationProperty(ConnectedDriveConstants.VIN).withLabel(vehicleLabel)
.withProperties(convertedProperties).build());
}
});
});
});
};
/**
* Get all field names from a DTO with a specific value
* Used to get e.g. all services which are "ACTIVATED"
*
* @param DTO Object
* @param compare String which needs to map with the value
* @return String with all field names matching this value separated with Spaces
*/
public String getObject(Object dto, String compare) {
StringBuilder buf = new StringBuilder();
for (Field field : dto.getClass().getDeclaredFields()) {
try {
Object value = field.get(dto);
if (compare.equals(value)) {
buf.append(Converter.capitalizeFirst(field.getName()) + Constants.SPACE);
}
} catch (IllegalArgumentException | IllegalAccessException e) {
logger.debug("Field {} not found {}", compare, e.getMessage());
}
}
return buf.toString();
}
@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof ConnectedDriveBridgeHandler) {
bridgeHandler = Optional.of((ConnectedDriveBridgeHandler) handler);
bridgeHandler.get().setDiscoveryService(this);
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler.orElse(null);
}
@Override
protected void startScan() {
bridgeHandler.ifPresent(ConnectedDriveBridgeHandler::requestVehicles);
}
@Override
public void deactivate() {
super.deactivate();
}
}

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.*;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
/**
* The {@link Destination} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Destination {
public float lat;
public float lon;
public String country;
public String city;
public String street;
public String streetNumber;
public String type;
public String createdAt;
public String getAddress() {
StringBuilder buf = new StringBuilder();
if (street != null) {
buf.append(street);
if (streetNumber != null) {
buf.append(SPACE).append(streetNumber);
}
}
if (city != null) {
if (buf.length() > 0) {
buf.append(COMMA).append(SPACE).append(city);
} else {
buf.append(city);
}
}
if (buf.length() == 0) {
return UNDEF;
} else {
return Converter.toTitleCase(buf.toString());
}
}
public String getCoordinates() {
return lat + Constants.COMMA + lon;
}
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto;
import java.util.List;
/**
* The {@link DestinationContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class DestinationContainer {
public List<Destination> destinations;
}

View File

@ -1,38 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
/**
* The {@link NetworkError} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class NetworkError {
public String url;
public int status;
public String reason;
public String params;
@Override
public String toString() {
return new StringBuilder(url).append(Constants.HYPHEN).append(status).append(Constants.HYPHEN).append(reason)
.append(params).toString();
}
public String toJson() {
return Converter.getGson().toJson(this);
}
}

View File

@ -1,34 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.auth;
import com.google.gson.annotations.SerializedName;
/**
* The {@link Timer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class AuthResponse {
@SerializedName("access_token")
public String accessToken;
@SerializedName("token_type")
public String tokenType;
@SerializedName("expires_in")
public int expiresIn;
@Override
public String toString() {
return "Token " + accessToken + " type " + tokenType + " expires in " + expiresIn;
}
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.charge;
/**
* The {@link ChargeProfile} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
public class ChargeProfile {
public WeeklyPlanner weeklyPlanner;
public WeeklyPlanner twoTimesTimer;
}

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.charge;
/**
* The {@link ChargingWindow} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class ChargingWindow {
public String startTime;// ":"11:00",
public String endTime;// ":"17:00"}}
}

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.charge;
import java.util.List;
/**
* The {@link Timer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
public class Timer {
public String departureTime;// ": "05:00",
public Boolean timerEnabled;// ": false,
public List<String> weekdays;
/**
* "MONDAY",
* "TUESDAY",
* "WEDNESDAY",
* "THURSDAY",
* "FRIDAY"
* ] '
*/
}

View File

@ -1,30 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.charge;
/**
* The {@link WeeklyPlanner} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
public class WeeklyPlanner {
public Boolean climatizationEnabled; // ": true,
public String chargingMode;// ": "IMMEDIATE_CHARGING",
public String chargingPreferences; // ": "CHARGING_WINDOW",
public Timer timer1; // : {
public Timer timer2;// ": {
public Timer timer3;// ":{"departureTime":"00:00","timerEnabled":false,"weekdays":[]},"
public Timer overrideTimer;// ":{"departureTime":"12:00","timerEnabled":false,"weekdays":["SATURDAY"]},"
public ChargingWindow preferredChargingWindow;// ":{"startTime":"11:00","endTime":"17:00"}}
}

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.compat;
/**
* The {@link CBSMessageCompat} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CBSMessageCompat {
public String description; // "Nächster Wechsel spätestens zum angegebenen Termin.",
public String text; // "Bremsflüssigkeit",
public int id; // 3,
public String status; // "OK",
public String messageType; // "CBS",
public String date; // "2021-11"
public int unitOfLengthRemaining; // ": "2000"
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.compat;
/**
* The {@link CCMMessageCompat} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CCMMessageCompat {
public String text;// "Laden nicht möglich"
public int id;// 804,
public String status;// "NULL",
public String messageType;// "CCM",
public int unitOfLengthRemaining = -1; // "18312"
}

View File

@ -1,142 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.compat;
import com.google.gson.annotations.SerializedName;
/**
* The {@link VehicleAttributes} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class VehicleAttributes {
// Windows & Doors
@SerializedName("door_driver_front")
public String doorDriverFront;// "CLOSED",
@SerializedName("door_driver_rear")
public String doorDriverRear;// "CLOSED",
@SerializedName("door_passenger_front")
public String doorPassengerFront;// "CLOSED",
@SerializedName("door_passenger_rear")
public String doorPassengerRear;// "CLOSED",
@SerializedName("hood_state")
public String hoodState;// "CLOSED",
@SerializedName("trunk_state")
public String trunkState;// "CLOSED",
@SerializedName("window_driver_front")
public String winDriverFront;// "CLOSED",
@SerializedName("window_driver_rear")
public String winDriverRear;// "CLOSED",
@SerializedName("window_passenger_front")
public String winPassengerFront;// "CLOSED",
@SerializedName("window_passenger_rear")
public String winPassengerRear;// "CLOSED",
@SerializedName("sunroof_state")
public String sunroofState;// "CLOSED",
@SerializedName("door_lock_state")
public String doorLockState;// "SECURED",
public String shdStatusUnified;// "CLOSED",
// Charge Status
public String chargingHVStatus;// "INVALID",
public String lastChargingEndReason;// "CHARGING_GOAL_REACHED",
public String connectorStatus;// "DISCONNECTED",
public String chargingLogicCurrentlyActive;// "NOT_CHARGING",
public String chargeNowAllowed;// "NOT_ALLOWED",
@SerializedName("charging_status")
public String chargingStatus;// "NOCHARGING",
public String lastChargingEndResult;// "SUCCESS",
public String chargingSystemStatus;// "NOCHARGING",
public String lastUpdateReason;// "VEHCSHUTDOWN_SECURED"
// Range
public int mileage;// "17236",
public double beMaxRangeElectric;// "209.0",
public double beMaxRangeElectricKm;// "209.0",
public double beRemainingRangeElectric;// "179.0",
public double beRemainingRangeElectricKm;// "179.0",
public double beMaxRangeElectricMile;// "129.0",
public double beRemainingRangeElectricMile;// "111.0",
public double beRemainingRangeFuelKm;// "67.0",
public double beRemainingRangeFuelMile;// "41.0",
public double beRemainingRangeFuel;// "67.0",
@SerializedName("kombi_current_remaining_range_fuel")
public double kombiRemainingRangeFuel;// "67.0",
public double chargingLevelHv;// "89.0",
@SerializedName("soc_hv_percent")
public double socHvPercent;// "82.6",
@SerializedName("remaining_fuel")
public double remainingFuel;// "4",
public double fuelPercent;// "47",
// Last Status update
public String updateTime;// "22.08.2020 12:55:46 UTC",
@SerializedName("updateTime_converted")
public String updateTimeConverted;// "22.08.2020 13:55",
@SerializedName("updateTime_converted_date")
public String updateTimeConvertedDate;// "22.08.2020",
@SerializedName("updateTime_converted_time")
public String updateTimeConvertedTime;// "13:55",
@SerializedName("updateTime_converted_timestamp")
public String updateTimeConvertedTimestamp;// "1598104546000",
// Last Trip Update
@SerializedName("Segment_LastTrip_time_segment_end")
public String lastTripEnd;// "22.08.2020 14:52:00 UTC",
@SerializedName("Segment_LastTrip_time_segment_end_formatted")
public String lastTripEndFormatted;// "22.08.2020 14:52",
@SerializedName("Segment_LastTrip_time_segment_end_formatted_date")
public String lastTripEndFormattedDate;// "22.08.2020",
@SerializedName("Segment_LastTrip_time_segment_end_formatted_time")
public String lastTripEndFormattedTime;// "14:52",
// Location
@SerializedName("gps_lat")
public float gpsLat;// "43.21",
@SerializedName("gps_lng")
public float gpsLon;// "8.765",
public int heading;// "41",
public String unitOfLength;// "km",
public String unitOfEnergy;// "kWh",
@SerializedName("vehicle_tracking")
public String vehicleTracking;// "1",
@SerializedName("head_unit_pu_software")
public String headunitSoftware;// "07/16",
@SerializedName("check_control_messages")
public String checkControlMessages;// "",
@SerializedName("sunroof_position")
public String sunroofPosition;// "0",
@SerializedName("single_immediate_charging")
public String singleImmediateCharging;// "isUnused",
public String unitOfCombustionConsumption;// "l/100km",
@SerializedName("Segment_LastTrip_ratio_electric_driven_distance")
public String lastTripElectricRation;// "100",
@SerializedName("condition_based_services")
public String conditionBasedServices;// "00003,OK,2021-11,;00017,OK,2021-11,;00001,OK,2021-11,;00032,OK,2021-11,",
@SerializedName("charging_inductive_positioning")
public String chargingInductivePositioning;// "not_positioned",
@SerializedName("lsc_trigger")
public String lscTrigger;// "VEHCSHUTDOWN_SECURED",
@SerializedName("lights_parking")
public String lightsParking;// "OFF",
public String prognosisWhileChargingStatus;// "NOT_NEEDED",
@SerializedName("head_unit")
public String headunit;// "EntryNav",
@SerializedName("battery_size_max")
public String batterySizeMax;// "33200",
@SerializedName("charging_connection_type")
public String chargingConnectionType;// "CONDUCTIVE",
public String unitOfElectricConsumption;// "kWh/100km",
}

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.compat;
/**
* The {@link VehicleAttributesContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class VehicleAttributesContainer {
public VehicleAttributes attributesMap;
public VehicleMessages vehicleMessages;
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.compat;
import java.util.List;
/**
* The {@link VehicleMessages} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
* @param <CBSMessage>
*/
public class VehicleMessages {
public List<CCMMessageCompat> ccmMessages;
public List<CBSMessageCompat> cbsMessages;
}

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.discovery;
/**
* The {@link Dealer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Dealer {
public String name;
public String street;
public String postalCode;
public String city;
public String country;
public String phone;
}

View File

@ -1,58 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.discovery;
import java.util.List;
/**
* The {@link Vehicle} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Vehicle {
public String vin;
public String model;
public String driveTrain;
public String brand;
public short yearOfConstruction;
public String bodytype;
public String color;
public boolean statisticsCommunityEnabled;
public boolean statisticsAvailable;
public boolean hasAlarmSystem;
public Dealer dealer;
public String breakdownNumber;
public List<String> supportedChargingModes;
public String chargingControl;// ": "WEEKLY_PLANNER",
// Remote Services
public String vehicleFinder; // ACTIVATED
public String hornBlow; // ACTIVATED
public String lightFlash; // ACTIVATED
public String doorLock; // ACTIVATED
public String doorUnlock; // ACTIVATED
public String climateNow; // ACTIVATED
public String sendPoi; // ACTIVATED
public String remote360; // SUPPORTED
public String climateControl; // SUPPORTED
public String chargeNow; // SUPPORTED
public String lastDestinations; // SUPPORTED
public String carCloud; // SUPPORTED
public String remoteSoftwareUpgrade; // SUPPORTED
public String climateNowRES;// ": "NOT_SUPPORTED",
public String climateControlRES;// ": "NOT_SUPPORTED",
public String smartSolution;// ": "NOT_SUPPORTED",
public String ipa;// ": "NOT_SUPPORTED",
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.discovery;
import java.util.List;
/**
* The {@link VehiclesContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class VehiclesContainer {
public List<Vehicle> vehicles;
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.navigation;
/**
* The {@link NavigationContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class NavigationContainer {
// "latitude": 56.789,
// "longitude": 8.765,
// "isoCountryCode": "DEU",
// "auxPowerRegular": 1.4,
// "auxPowerEcoPro": 1.2,
// "auxPowerEcoProPlus": 0.4,
// "soc": 25.952999114990234,
// "pendingUpdate": false,
// "vehicleTracking": true,
public double socmax;// ": 29.84
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.remote;
/**
* The {@link ExecutionStatus} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class ExecutionStatus {
public String serviceType;// ": "DOOR_UNLOCK",
public String status;// ": "EXECUTED",
public String eventId;// ": "5639303536333926DA7B9400@bmw.de",
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.remote;
/**
* The {@link ExecutionStatusContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class ExecutionStatusContainer {
public ExecutionStatus executionStatus;
public String eventId;
public String creationTime;
public String eventStatus;
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link AllTrips} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class AllTrips {
public CommunityPowerEntry avgElectricConsumption;
public CommunityPowerEntry avgRecuperation;
public CommunityChargeCycleEntry chargecycleRange;
public CommunityEletricDistanceEntry totalElectricDistance;
public CommunityPowerEntry avgCombinedConsumption;
public float savedCO2;// ":461.083,"
public float savedCO2greenEnergy;// ":2712.255,"
public float totalSavedFuel;// ":0,"
public String resetDate;// ":"2020-08-24T14:40:40+0000","
public int batterySizeMax;// ":33200
}

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link AllTripsContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class AllTripsContainer {
public AllTrips allTrips;
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link CommunityChargeCycleEntry} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CommunityChargeCycleEntry {
public float communityAverage;// ": 194.21,
public float communityHigh;// ": 270,
public float userAverage;// ": 57.3,
public float userHigh;// ": 185.48,
public float userCurrentChargeCycle;// ": 68
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link CommunityEletricDistanceEntry} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CommunityEletricDistanceEntry {
public float communityLow;// ": 19,
public float communityAverage;// ": 40850.56,
public float communityHigh;// ": 193006,
public float userTotal;// ": 16629.4
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link CommunityPowerEntry} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CommunityPowerEntry {
public float communityLow;// ": 11.05,
public float communityAverage;// ": 16.28,
public float communityHigh;// ": 21.99,
public float userAverage;// ": 16.46
}

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link LastTrip} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class LastTrip {
public float efficiencyValue;// ": 0.98,
public float totalDistance;// ": 2,
public float electricDistance;// ": 2,
public float avgElectricConsumption;// ": 7,
public float avgRecuperation;// ": 6,
public float drivingModeValue;// ": 0.87,
public float accelerationValue;// ": 0.99,
public float anticipationValue;// ": 0.99,
public float totalConsumptionValue;// ": 1.25,
public float auxiliaryConsumptionValue;// ": 0.78,
public float avgCombinedConsumption;// ": 0,
public float electricDistanceRatio;// ": 100,
public float savedFuel;// ": 0,
public String date;// ": "2020-08-24T17:55:00+0000",
public float duration;// ": 5
}

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.statistics;
/**
* The {@link LastTripContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class LastTripContainer {
public LastTrip lastTrip;
}

View File

@ -1,68 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
/**
* The {@link CBSMessage} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CBSMessage {
public String cbsType;// ": "BRAKE_FLUID",
public String cbsState;// ": "OK",
public String cbsDueDate;// ": "2021-11",
public String cbsDescription;// ": "Next change due at the latest by the stated date."
public int cbsRemainingMileage = -1; // 46000
public String cbsTypeConverted = null;
public String cbsDescriptionConverted = null;
public String getDueDate() {
if (cbsDueDate == null) {
return Constants.NULL_DATE;
} else {
return cbsDueDate + Constants.UTC_APPENDIX;
}
}
public String getType() {
if (cbsTypeConverted == null) {
if (cbsType == null) {
cbsTypeConverted = Constants.INVALID;
} else {
cbsTypeConverted = Converter.toTitleCase(cbsType);
}
}
return cbsTypeConverted;
}
public String getDescription() {
if (cbsDescriptionConverted == null) {
if (cbsDescription == null) {
cbsDescriptionConverted = Constants.INVALID;
} else {
cbsDescriptionConverted = cbsDescription;
}
}
return cbsDescriptionConverted;
}
@Override
public String toString() {
return new StringBuilder(cbsDueDate).append(Constants.HYPHEN).append(cbsRemainingMileage)
.append(Constants.HYPHEN).append(cbsType).toString();
}
}

View File

@ -1,30 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link CCMMessage} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class CCMMessage {
// if necessary. Perform reset after adjustment. See Owner's Handbook for further
// information.",
public String ccmDescriptionShort = Constants.INVALID;// ": "Tyre pressure notification",
public String ccmDescriptionLong = Constants.INVALID;// ": "You can continue driving. Check tyre pressure when tyres
// are cold and adjust
public int ccmId = -1;// ": 955,
public int ccmMileage = -1;// ": 41544
}

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link Doors} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Doors {
public String doorDriverFront = Constants.UNDEF;// ": "CLOSED",
public String doorDriverRear = Constants.UNDEF;// ": "CLOSED",
public String doorPassengerFront = Constants.UNDEF;// ": "CLOSED",
public String doorPassengerRear = Constants.UNDEF;// ": "CLOSED",
public String trunk = Constants.UNDEF;// ": "CLOSED",
public String hood = Constants.UNDEF;// ": "CLOSED",
}

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link Position} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Position {
public float lat;// ": 46.55605,
public float lon;// ": 10.495669,
public int heading;// ": 219,
public String status;// ": "OK"
public String getCoordinates() {
return new StringBuilder(Float.toString(lat)).append(Constants.COMMA).append(Float.toString(lon)).toString();
}
@Override
public String toString() {
return getCoordinates();
}
}

View File

@ -1,73 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import java.util.ArrayList;
import java.util.List;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import com.google.gson.annotations.SerializedName;
/**
* The {@link VehicleStatus} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class VehicleStatus {
public int mileage = Constants.INT_UNDEF;// ": 17273,
public double remainingFuel = Constants.INT_UNDEF;// ": 4,
public double remainingRangeElectric = Constants.INT_UNDEF;// ": 148,
public double remainingRangeElectricMls;// ": 91,
public double remainingRangeFuel = Constants.INT_UNDEF;// ": 70,"
public double remainingRangeFuelMls;// ":43,"
public double maxRangeElectric = Constants.INT_UNDEF;// ":216,"
public double maxRangeElectricMls;// ":134,"
public double maxFuel;// ":8.5,
public double chargingLevelHv;// ":71,
public String vin;// : "ANONYMOUS",
public String updateReason;// ": "VEHICLE_SHUTDOWN_SECURED",
public String updateTime;// ": "2020-08-24 T15:55:32+0000",
public String doorDriverFront = Constants.UNDEF;// ": "CLOSED",
public String doorDriverRear = Constants.UNDEF;// ": "CLOSED",
public String doorPassengerFront = Constants.UNDEF;// ": "CLOSED",
public String doorPassengerRear = Constants.UNDEF;// ": "CLOSED",
public String windowDriverFront = Constants.UNDEF;// ": "CLOSED",
public String windowDriverRear = Constants.UNDEF;// ": "CLOSED",
public String windowPassengerFront = Constants.UNDEF;// ": "CLOSED",
public String windowPassengerRear = Constants.UNDEF;// ": "CLOSED",
public String sunroof = Constants.UNDEF;// ": "CLOSED",
public String trunk = Constants.UNDEF;// ": "CLOSED",
public String rearWindow = Constants.UNDEF;// ": "INVALID",
public String hood = Constants.UNDEF;// ": "CLOSED",
public String doorLockState;// ": "SECURED",
public String parkingLight;// ": "OFF",
public String positionLight;// ": "ON",
public String connectionStatus;// ": "DISCONNECTED",
public String chargingStatus;// ": "INVALID","
public String lastChargingEndReason;// ": "CHARGING_GOAL_REACHED",
public String lastChargingEndResult;// ": "SUCCESS","
public Double chargingTimeRemaining;// ": "45",
public Position position;
public String internalDataTimeUTC;// ": "2020-08-24 T15:55:32",
public boolean singleImmediateCharging;// ":false,
public String chargingConnectionType;// ": "CONDUCTIVE",
public String chargingInductivePositioning;// ": "NOT_POSITIONED",
public String vehicleCountry;// ": "DE","+"
@SerializedName("DCS_CCH_Activation")
public String dcsCchActivation;// ": "NA",
@SerializedName("DCS_CCH_Ongoing")
public boolean dcsCchOngoing;// ":false
public List<CCMMessage> checkControlMessages = new ArrayList<CCMMessage>();// ":[],
public List<CBSMessage> cbsData = new ArrayList<CBSMessage>();
}

View File

@ -1,22 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
/**
* The {@link VehicleStatusContainer} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class VehicleStatusContainer {
public VehicleStatus vehicleStatus;
}

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.dto.status;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link Windows} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
public class Windows {
public String windowDriverFront = Constants.UNDEF;// ": "CLOSED",
public String windowDriverRear = Constants.UNDEF;// ": "CLOSED",
public String windowPassengerFront = Constants.UNDEF;// ": "CLOSED",
public String windowPassengerRear = Constants.UNDEF;// ": "CLOSED",
public String sunroof = Constants.UNDEF;// ": "CLOSED",
public String rearWindow = Constants.UNDEF;// ": "INVALID",
}

View File

@ -1,42 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Bernd Weymann - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, BMWConnectedDriveOptionProvider.class })
@NonNullByDefault
public class BMWConnectedDriveOptionProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public BMWConnectedDriveOptionProvider(final @Reference EventPublisher eventPublisher, //
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ByteResponseCallback} Interface for all raw byte results from ASYNC REST API
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public interface ByteResponseCallback extends ResponseCallback {
public void onResponse(byte[] result);
}

View File

@ -1,231 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.ANONYMOUS;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConfiguration;
import org.openhab.binding.bmwconnecteddrive.internal.discovery.VehicleDiscovery;
import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.Dealer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.VehiclesContainer;
import org.openhab.binding.bmwconnecteddrive.internal.utils.BimmerConstants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
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.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonParseException;
/**
* The {@link ConnectedDriveBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class ConnectedDriveBridgeHandler extends BaseBridgeHandler implements StringResponseCallback {
private final Logger logger = LoggerFactory.getLogger(ConnectedDriveBridgeHandler.class);
private HttpClientFactory httpClientFactory;
private Optional<VehicleDiscovery> discoveryService = Optional.empty();
private Optional<ConnectedDriveProxy> proxy = Optional.empty();
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
private Optional<String> troubleshootFingerprint = Optional.empty();
public ConnectedDriveBridgeHandler(Bridge bridge, HttpClientFactory hcf) {
super(bridge);
httpClientFactory = hcf;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// no commands available
}
@Override
public void initialize() {
troubleshootFingerprint = Optional.empty();
updateStatus(ThingStatus.UNKNOWN);
ConnectedDriveConfiguration config = getConfigAs(ConnectedDriveConfiguration.class);
logger.debug("Prefer MyBMW API {}", config.preferMyBmw);
if (!checkConfiguration(config)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
} else {
proxy = Optional.of(new ConnectedDriveProxy(httpClientFactory, config));
// give the system some time to create all predefined Vehicles
// check with API call if bridge is online
initializerJob = Optional.of(scheduler.schedule(this::requestVehicles, 2, TimeUnit.SECONDS));
Bridge b = super.getThing();
List<Thing> children = b.getThings();
logger.debug("Update {} things", children.size());
children.forEach(entry -> {
ThingHandler th = entry.getHandler();
if (th != null) {
th.dispose();
th.initialize();
} else {
logger.debug("Handler is null");
}
});
}
}
public static boolean checkConfiguration(ConnectedDriveConfiguration config) {
if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
return false;
} else {
return BimmerConstants.AUTH_SERVER_MAP.containsKey(config.region);
}
}
@Override
public void dispose() {
initializerJob.ifPresent(job -> job.cancel(true));
}
public void requestVehicles() {
proxy.ifPresent(prox -> prox.requestVehicles(this));
}
// https://www.bmw-connecteddrive.de/api/me/vehicles/v2?all=true&brand=BM
public String getDiscoveryFingerprint() {
return troubleshootFingerprint.map(fingerprint -> {
VehiclesContainer container = null;
try {
container = Converter.getGson().fromJson(fingerprint, VehiclesContainer.class);
if (container != null) {
if (container.vehicles != null) {
if (container.vehicles.isEmpty()) {
return Constants.EMPTY_JSON;
} else {
container.vehicles.forEach(entry -> {
entry.vin = ANONYMOUS;
entry.breakdownNumber = ANONYMOUS;
if (entry.dealer != null) {
Dealer d = entry.dealer;
d.city = ANONYMOUS;
d.country = ANONYMOUS;
d.name = ANONYMOUS;
d.phone = ANONYMOUS;
d.postalCode = ANONYMOUS;
d.street = ANONYMOUS;
}
});
return Converter.getGson().toJson(container);
}
} else {
logger.debug("container.vehicles is null");
}
}
} catch (JsonParseException jpe) {
logger.debug("Cannot parse fingerprint {}", jpe.getMessage());
}
// Not a VehiclesContainer or Vehicles is empty so deliver fingerprint as it is
return fingerprint;
}).orElse(Constants.INVALID);
}
private void logFingerPrint() {
logger.debug("###### Discovery Troubleshoot Fingerprint Data - BEGIN ######");
logger.debug("### Discovery Result ###");
logger.debug("{}", getDiscoveryFingerprint());
logger.debug("###### Discovery Troubleshoot Fingerprint Data - END ######");
}
/**
* There's only the Vehicles response available
*/
@Override
public void onResponse(@Nullable String response) {
boolean firstResponse = troubleshootFingerprint.isEmpty();
if (response != null) {
updateStatus(ThingStatus.ONLINE);
troubleshootFingerprint = discoveryService.map(discovery -> {
try {
VehiclesContainer container = Converter.getGson().fromJson(response, VehiclesContainer.class);
if (container != null) {
if (container.vehicles != null) {
discovery.onResponse(container);
container.vehicles.forEach(entry -> {
entry.vin = ANONYMOUS;
entry.breakdownNumber = ANONYMOUS;
if (entry.dealer != null) {
Dealer d = entry.dealer;
d.city = ANONYMOUS;
d.country = ANONYMOUS;
d.name = ANONYMOUS;
d.phone = ANONYMOUS;
d.postalCode = ANONYMOUS;
d.street = ANONYMOUS;
}
});
}
} else {
troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
}
} catch (JsonParseException jpe) {
logger.debug("Fingerprint parse exception {}", jpe.getMessage());
}
// Unparseable or not a VehiclesContainer:
return response;
});
} else {
troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
}
if (firstResponse) {
logFingerPrint();
}
}
@Override
public void onError(NetworkError error) {
boolean firstResponse = troubleshootFingerprint.isEmpty();
troubleshootFingerprint = Optional.of(error.toJson());
if (firstResponse) {
logFingerPrint();
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(VehicleDiscovery.class);
}
public Optional<ConnectedDriveProxy> getProxy() {
return proxy;
}
public void setDiscoveryService(VehicleDiscovery discoveryService) {
this.discoveryService = Optional.of(discoveryService);
}
}

View File

@ -1,469 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.HTTPConstants.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
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.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.UrlEncoded;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConfiguration;
import org.openhab.binding.bmwconnecteddrive.internal.VehicleConfiguration;
import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
import org.openhab.binding.bmwconnecteddrive.internal.dto.auth.AuthResponse;
import org.openhab.binding.bmwconnecteddrive.internal.handler.simulation.Injector;
import org.openhab.binding.bmwconnecteddrive.internal.utils.BimmerConstants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ImageProperties;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link ConnectedDriveProxy} This class holds the important constants for the BMW Connected Drive Authorization.
* They
* are taken from the Bimmercode from github {@link https://github.com/bimmerconnected/bimmer_connected}
* File defining these constants
* {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
* https://customer.bmwgroup.com/one/app/oauth.js
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
@NonNullByDefault
public class ConnectedDriveProxy {
private final Logger logger = LoggerFactory.getLogger(ConnectedDriveProxy.class);
private Optional<RemoteServiceHandler> remoteServiceHandler = Optional.empty();
private final Token token = new Token();
private final HttpClient httpClient;
private final HttpClient authHttpClient;
private final ConnectedDriveConfiguration configuration;
/**
* URLs taken from https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/const.py
*/
final String baseUrl;
final String vehicleUrl;
final String legacyUrl;
final String remoteCommandUrl;
final String remoteStatusUrl;
final String navigationAPIUrl;
final String vehicleStatusAPI = "/status";
final String lastTripAPI = "/statistics/lastTrip";
final String allTripsAPI = "/statistics/allTrips";
final String chargeAPI = "/chargingprofile";
final String destinationAPI = "/destinations";
final String imageAPI = "/image";
final String rangeMapAPI = "/rangemap";
final String serviceExecutionAPI = "/executeService";
final String serviceExecutionStateAPI = "/serviceExecutionStatus";
public static final String REMOTE_SERVICE_EADRAX_BASE_URL = "/eadrax-vrccs/v2/presentation/remote-commands/"; // '/{vin}/{service_type}'
final String remoteServiceEADRXstatusUrl = REMOTE_SERVICE_EADRAX_BASE_URL + "eventStatus?eventId={event_id}";
final String vehicleEADRXPoiUrl = "/eadrax-dcs/v1/send-to-car/send-to-car";
public ConnectedDriveProxy(HttpClientFactory httpClientFactory, ConnectedDriveConfiguration config) {
httpClient = httpClientFactory.getCommonHttpClient();
authHttpClient = httpClientFactory.createHttpClient(AUTH_HTTP_CLIENT_NAME);
configuration = config;
vehicleUrl = "https://" + BimmerConstants.API_SERVER_MAP.get(configuration.region) + "/webapi/v1/user/vehicles";
baseUrl = vehicleUrl + "/";
legacyUrl = "https://" + BimmerConstants.API_SERVER_MAP.get(configuration.region) + "/api/vehicle/dynamic/v1/";
navigationAPIUrl = "https://" + BimmerConstants.API_SERVER_MAP.get(configuration.region)
+ "/api/vehicle/navigation/v1/";
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(configuration.region)
+ REMOTE_SERVICE_EADRAX_BASE_URL;
remoteStatusUrl = remoteCommandUrl + "eventStatus";
}
public synchronized void call(final String url, final boolean post, final @Nullable String encoding,
final @Nullable String params, final ResponseCallback callback) {
// only executed in "simulation mode"
// SimulationTest.testSimulationOff() assures Injector is off when releasing
if (Injector.isActive()) {
if (url.equals(baseUrl)) {
((StringResponseCallback) callback).onResponse(Injector.getDiscovery());
} else if (url.endsWith(vehicleStatusAPI)) {
((StringResponseCallback) callback).onResponse(Injector.getStatus());
} else {
logger.debug("Simulation of {} not supported", url);
}
return;
}
final Request req;
final String completeUrl;
if (post) {
completeUrl = url;
req = httpClient.POST(url);
if (encoding != null) {
if (CONTENT_TYPE_URL_ENCODED.equals(encoding)) {
req.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED, params, StandardCharsets.UTF_8));
} else if (CONTENT_TYPE_JSON_ENCODED.equals(encoding)) {
req.header(HttpHeader.CONTENT_TYPE, encoding);
req.content(new StringContentProvider(CONTENT_TYPE_JSON_ENCODED, params, StandardCharsets.UTF_8));
}
}
} else {
completeUrl = params == null ? url : url + Constants.QUESTION + params;
req = httpClient.newRequest(completeUrl);
}
req.header(HttpHeader.AUTHORIZATION, getToken().getBearerToken());
req.header(HttpHeader.REFERER, BimmerConstants.LEGACY_REFERER_URL);
req.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send(new BufferingResponseListener() {
@NonNullByDefault({})
@Override
public void onComplete(Result result) {
if (result.getResponse().getStatus() != 200) {
NetworkError error = new NetworkError();
error.url = completeUrl;
error.status = result.getResponse().getStatus();
if (result.getResponse().getReason() != null) {
error.reason = result.getResponse().getReason();
} else {
error.reason = result.getFailure().getMessage();
}
error.params = result.getRequest().getParams().toString();
logger.debug("HTTP Error {}", error.toString());
callback.onError(error);
} else {
if (callback instanceof StringResponseCallback) {
((StringResponseCallback) callback).onResponse(getContentAsString());
} else if (callback instanceof ByteResponseCallback) {
((ByteResponseCallback) callback).onResponse(getContent());
} else {
logger.error("unexpected reponse type {}", callback.getClass().getName());
}
}
}
});
}
public void get(String url, @Nullable String coding, @Nullable String params, ResponseCallback callback) {
call(url, false, coding, params, callback);
}
public void post(String url, @Nullable String coding, @Nullable String params, ResponseCallback callback) {
call(url, true, coding, params, callback);
}
public void requestVehicles(StringResponseCallback callback) {
get(vehicleUrl, null, null, callback);
}
public void requestVehcileStatus(VehicleConfiguration config, StringResponseCallback callback) {
get(baseUrl + config.vin + vehicleStatusAPI, null, null, callback);
}
public void requestLegacyVehcileStatus(VehicleConfiguration config, StringResponseCallback callback) {
// see https://github.com/jupe76/bmwcdapi/search?q=dynamic%2Fv1
get(legacyUrl + config.vin + "?offset=-60", null, null, callback);
}
public void requestLNavigation(VehicleConfiguration config, StringResponseCallback callback) {
// see https://github.com/jupe76/bmwcdapi/search?q=dynamic%2Fv1
get(navigationAPIUrl + config.vin, null, null, callback);
}
public void requestLastTrip(VehicleConfiguration config, StringResponseCallback callback) {
get(baseUrl + config.vin + lastTripAPI, null, null, callback);
}
public void requestAllTrips(VehicleConfiguration config, StringResponseCallback callback) {
get(baseUrl + config.vin + allTripsAPI, null, null, callback);
}
public void requestChargingProfile(VehicleConfiguration config, StringResponseCallback callback) {
get(baseUrl + config.vin + chargeAPI, null, null, callback);
}
public void requestDestinations(VehicleConfiguration config, StringResponseCallback callback) {
get(baseUrl + config.vin + destinationAPI, null, null, callback);
}
public void requestRangeMap(VehicleConfiguration config, @Nullable MultiMap<String> params,
StringResponseCallback callback) {
get(baseUrl + config.vin + rangeMapAPI, CONTENT_TYPE_URL_ENCODED,
UrlEncoded.encode(params, StandardCharsets.UTF_8, false), callback);
}
public void requestImage(VehicleConfiguration config, ImageProperties props, ByteResponseCallback callback) {
final String localImageUrl = baseUrl + config.vin + imageAPI;
final MultiMap<String> dataMap = new MultiMap<String>();
dataMap.add("width", Integer.toString(props.size));
dataMap.add("height", Integer.toString(props.size));
dataMap.add("view", props.viewport);
get(localImageUrl, CONTENT_TYPE_URL_ENCODED, UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false),
callback);
}
RemoteServiceHandler getRemoteServiceHandler(VehicleHandler vehicleHandler) {
remoteServiceHandler = Optional.of(new RemoteServiceHandler(vehicleHandler, this));
return remoteServiceHandler.get();
}
// Token handling
/**
* Gets new token if old one is expired or invalid. In case of error the token remains.
* So if token refresh fails the corresponding requests will also fail and update the
* Thing status accordingly.
*
* @return token
*/
public Token getToken() {
if (!token.isValid()) {
if (configuration.preferMyBmw) {
if (!updateToken()) {
if (!updateLegacyToken()) {
logger.debug("Authorization failed!");
}
}
} else {
if (!updateLegacyToken()) {
if (!updateToken()) {
logger.debug("Authorization failed!");
}
}
}
}
remoteServiceHandler.ifPresent(serviceHandler -> {
serviceHandler.setMyBmwApiUsage(token.isMyBmwApiUsage());
});
return token;
}
public synchronized boolean updateToken() {
if (BimmerConstants.REGION_CHINA.equals(configuration.region)) {
// region China currently not supported for MyBMW API
logger.debug("Region {} not supported yet for MyBMW Login", BimmerConstants.REGION_CHINA);
return false;
}
if (!startAuthClient()) {
return false;
} // else continue
String authUri = "https://" + BimmerConstants.AUTH_SERVER_MAP.get(configuration.region)
+ BimmerConstants.OAUTH_ENDPOINT;
Request authRequest = authHttpClient.POST(authUri);
authRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
MultiMap<String> authChallenge = getTokenBaseValues();
authChallenge.addAllValues(getTokenAuthValues());
String authEncoded = UrlEncoded.encode(authChallenge, Charset.defaultCharset(), false);
authRequest.content(new StringContentProvider(authEncoded));
try {
ContentResponse authResponse = authRequest.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
String authResponseString = URLDecoder.decode(authResponse.getContentAsString(), Charset.defaultCharset());
String authCode = getAuthCode(authResponseString);
if (!Constants.EMPTY.equals(authCode)) {
MultiMap<String> codeChallenge = getTokenBaseValues();
codeChallenge.put(AUTHORIZATION, authCode);
Request codeRequest = authHttpClient.POST(authUri).followRedirects(false);
codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
String codeEncoded = UrlEncoded.encode(codeChallenge, Charset.defaultCharset(), false);
codeRequest.content(new StringContentProvider(codeEncoded));
ContentResponse codeResponse = codeRequest.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
String code = ConnectedDriveProxy.codeFromUrl(codeResponse.getHeaders().get(HttpHeader.LOCATION));
// Get Token
String tokenUrl = "https://" + BimmerConstants.AUTH_SERVER_MAP.get(configuration.region)
+ BimmerConstants.TOKEN_ENDPOINT;
Request tokenRequest = authHttpClient.POST(tokenUrl).followRedirects(false);
tokenRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
tokenRequest.header(HttpHeader.AUTHORIZATION,
BimmerConstants.AUTHORIZATION_VALUE_MAP.get(configuration.region));
String tokenEncoded = UrlEncoded.encode(getTokenValues(code), Charset.defaultCharset(), false);
tokenRequest.content(new StringContentProvider(tokenEncoded));
ContentResponse tokenResponse = tokenRequest.timeout(HTTP_TIMEOUT_SEC, TimeUnit.SECONDS).send();
AuthResponse authResponseJson = Converter.getGson().fromJson(tokenResponse.getContentAsString(),
AuthResponse.class);
token.setToken(authResponseJson.accessToken);
token.setType(authResponseJson.tokenType);
token.setExpiration(authResponseJson.expiresIn);
token.setMyBmwApiUsage(true);
return true;
}
} catch (InterruptedException | ExecutionException |
TimeoutException e) {
logger.debug("Authorization exception: {}", e.getMessage());
}
return false;
}
private boolean startAuthClient() {
if (!authHttpClient.isStarted()) {
try {
authHttpClient.start();
} catch (Exception e) {
logger.error("Auth HttpClient start failed!");
return false;
}
}
return true;
}
private MultiMap<String> getTokenBaseValues() {
MultiMap<String> baseValues = new MultiMap<String>();
baseValues.add(CLIENT_ID, Constants.EMPTY + BimmerConstants.CLIENT_ID.get(configuration.region));
baseValues.add(RESPONSE_TYPE, CODE);
baseValues.add(REDIRECT_URI, BimmerConstants.REDIRECT_URI_VALUE);
baseValues.add("state", Constants.EMPTY + BimmerConstants.STATE.get(configuration.region));
baseValues.add("nonce", "login_nonce");
baseValues.add(SCOPE, BimmerConstants.SCOPE_VALUES);
return baseValues;
}
private MultiMap<String> getTokenAuthValues() {
MultiMap<String> authValues = new MultiMap<String>();
authValues.add(GRANT_TYPE, "authorization_code");
authValues.add(USERNAME, configuration.userName);
authValues.add(PASSWORD, configuration.password);
return authValues;
}
private MultiMap<String> getTokenValues(String code) {
MultiMap<String> tokenValues = new MultiMap<String>();
tokenValues.put(CODE, code);
tokenValues.put("code_verifier", Constants.EMPTY + BimmerConstants.CODE_VERIFIER.get(configuration.region));
tokenValues.put(REDIRECT_URI, BimmerConstants.REDIRECT_URI_VALUE);
tokenValues.put(GRANT_TYPE, "authorization_code");
return tokenValues;
}
private String getAuthCode(String response) {
String[] keys = response.split("&");
for (int i = 0; i < keys.length; i++) {
if (keys[i].startsWith(AUTHORIZATION)) {
String authCode = keys[i].split("=")[1];
authCode = authCode.split("\"")[0];
return authCode;
}
}
return Constants.EMPTY;
}
public synchronized boolean updateLegacyToken() {
logger.debug("updateLegacyToken");
try {
/**
* The authorization with Jetty HttpClient doens't work anymore
* When calling Jetty with same headers and content a ConcurrentExcpetion is thrown
* So fallback legacy authorization will stay on java.net handling
*/
String authUri = "https://" + BimmerConstants.AUTH_SERVER_MAP.get(configuration.region)
+ BimmerConstants.OAUTH_ENDPOINT;
URL url = new URL(authUri);
HttpURLConnection.setFollowRedirects(false);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty(HttpHeader.CONTENT_TYPE.toString(), CONTENT_TYPE_URL_ENCODED);
con.setRequestProperty(HttpHeader.CONNECTION.toString(), KEEP_ALIVE);
con.setRequestProperty(HttpHeader.HOST.toString(),
BimmerConstants.API_SERVER_MAP.get(configuration.region));
con.setRequestProperty(HttpHeader.AUTHORIZATION.toString(),
BimmerConstants.LEGACY_AUTHORIZATION_VALUE_MAP.get(configuration.region));
con.setRequestProperty(CREDENTIALS, BimmerConstants.LEGACY_CREDENTIAL_VALUES);
con.setDoOutput(true);
OutputStream os = con.getOutputStream();
byte[] input = getAuthEncodedData().getBytes("utf-8");
os.write(input, 0, input.length);
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
token.setMyBmwApiUsage(false);
return tokenFromUrl(con.getHeaderField(HttpHeader.LOCATION.toString()));
} catch (IOException e) {
logger.warn("{}", e.getMessage());
}
return false;
}
public boolean tokenFromUrl(String encodedUrl) {
final MultiMap<String> tokenMap = new MultiMap<String>();
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
tokenMap.forEach((key, value) -> {
if (value.size() > 0) {
String val = value.get(0);
if (key.endsWith(ACCESS_TOKEN)) {
token.setToken(val.toString());
} else if (key.equals(EXPIRES_IN)) {
token.setExpiration(Integer.parseInt(val.toString()));
} else if (key.equals(TOKEN_TYPE)) {
token.setType(val.toString());
}
}
});
logger.info("Token valid? {}", token.isValid());
return token.isValid();
}
public static String codeFromUrl(String encodedUrl) {
final MultiMap<String> tokenMap = new MultiMap<String>();
UrlEncoded.decodeTo(encodedUrl, tokenMap, StandardCharsets.US_ASCII);
final StringBuilder codeFound = new StringBuilder();
tokenMap.forEach((key, value) -> {
if (value.size() > 0) {
String val = value.get(0);
if (key.endsWith(CODE)) {
codeFound.append(val.toString());
}
}
});
return codeFound.toString();
}
private String getAuthEncodedData() {
MultiMap<String> dataMap = new MultiMap<String>();
dataMap.add(CLIENT_ID, BimmerConstants.LEGACY_CLIENT_ID);
dataMap.add(RESPONSE_TYPE, TOKEN);
dataMap.add(REDIRECT_URI, BimmerConstants.LEGACY_REDIRECT_URI_VALUE);
dataMap.add(SCOPE, BimmerConstants.LEGACY_SCOPE_VALUES);
dataMap.add(USERNAME, configuration.userName);
dataMap.add(PASSWORD, configuration.password);
return UrlEncoded.encode(dataMap, Charset.defaultCharset(), false);
}
}

View File

@ -1,264 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.HTTPConstants.*;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.UrlEncoded;
import org.openhab.binding.bmwconnecteddrive.internal.VehicleConfiguration;
import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
import org.openhab.binding.bmwconnecteddrive.internal.dto.remote.ExecutionStatusContainer;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.binding.bmwconnecteddrive.internal.utils.HTTPConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonSyntaxException;
/**
* The {@link RemoteServiceHandler} handles executions of remote services towards your Vehicle
*
* @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
@NonNullByDefault
public class RemoteServiceHandler implements StringResponseCallback {
private final Logger logger = LoggerFactory.getLogger(RemoteServiceHandler.class);
private static final String SERVICE_TYPE = "serviceType";
private static final String EVENT_ID = "eventId";
private static final String DATA = "data";
// after 6 retries the state update will give up
private static final int GIVEUP_COUNTER = 6;
private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
private final ConnectedDriveProxy proxy;
private final VehicleHandler handler;
private final String legacyServiceExecutionAPI;
private final String legacyServiceExecutionStateAPI;
private final String serviceExecutionAPI;
private final String serviceExecutionStateAPI;
private int counter = 0;
private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
private Optional<String> serviceExecuting = Optional.empty();
private Optional<String> executingEventId = Optional.empty();
private boolean myBmwApiUsage = false;
public enum ExecutionState {
READY,
INITIATED,
PENDING,
DELIVERED,
EXECUTED,
ERROR,
}
public enum RemoteService {
LIGHT_FLASH(REMOTE_SERVICE_LIGHT_FLASH, "Flash Lights", "light-flash"),
VEHICLE_FINDER(REMOTE_SERVICE_VEHICLE_FINDER, "Vehicle Finder", "vehicle-finder"),
DOOR_LOCK(REMOTE_SERVICE_DOOR_LOCK, "Door Lock", "door-lock"),
DOOR_UNLOCK(REMOTE_SERVICE_DOOR_UNLOCK, "Door Unlock", "door-unlock"),
HORN_BLOW(REMOTE_SERVICE_HORN, "Horn Blow", "horn-blow"),
CLIMATE_NOW(REMOTE_SERVICE_AIR_CONDITIONING, "Climate Control", "air-conditioning"),
CHARGE_NOW(REMOTE_SERVICE_CHARGE_NOW, "Start Charging", "charge-now"),
CHARGING_CONTROL(REMOTE_SERVICE_CHARGING_CONTROL, "Send Charging Profile", "charging-control");
private final String command;
private final String label;
private final String remoteCommand;
RemoteService(final String command, final String label, final String remoteCommand) {
this.command = command;
this.label = label;
this.remoteCommand = remoteCommand;
}
public String getCommand() {
return command;
}
public String getLabel() {
return label;
}
public String getRemoteCommand() {
return remoteCommand;
}
}
public RemoteServiceHandler(VehicleHandler vehicleHandler, ConnectedDriveProxy connectedDriveProxy) {
handler = vehicleHandler;
proxy = connectedDriveProxy;
final VehicleConfiguration config = handler.getConfiguration().get();
legacyServiceExecutionAPI = proxy.baseUrl + config.vin + proxy.serviceExecutionAPI;
legacyServiceExecutionStateAPI = proxy.baseUrl + config.vin + proxy.serviceExecutionStateAPI;
serviceExecutionAPI = proxy.remoteCommandUrl + config.vin + "/";
serviceExecutionStateAPI = proxy.remoteStatusUrl;
}
boolean execute(RemoteService service, String... data) {
synchronized (this) {
if (serviceExecuting.isPresent()) {
logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
// only one service executing
return false;
}
serviceExecuting = Optional.of(service.name());
}
if (myBmwApiUsage) {
final MultiMap<String> dataMap = new MultiMap<String>();
if (data.length > 0) {
dataMap.add(DATA, data[0]);
proxy.post(serviceExecutionAPI + service.getRemoteCommand(), CONTENT_TYPE_JSON_ENCODED,
"{CHARGING_PROFILE:" + data[0] + "}", this);
} else {
proxy.post(serviceExecutionAPI + service.getRemoteCommand(), null, null, this);
}
} else {
final MultiMap<String> dataMap = new MultiMap<String>();
dataMap.add(SERVICE_TYPE, service.name());
if (data.length > 0) {
dataMap.add(DATA, data[0]);
}
proxy.post(legacyServiceExecutionAPI, CONTENT_TYPE_URL_ENCODED,
UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false), this);
}
return true;
}
public void getState() {
synchronized (this) {
serviceExecuting.ifPresentOrElse(service -> {
if (counter >= GIVEUP_COUNTER) {
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
reset();
// immediately refresh data
handler.getData();
}
counter++;
if (myBmwApiUsage) {
final MultiMap<String> dataMap = new MultiMap<String>();
dataMap.add(EVENT_ID, executingEventId.get());
final String encoded = dataMap == null || dataMap.isEmpty() ? null
: UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false);
proxy.post(serviceExecutionStateAPI + Constants.QUESTION + encoded, null, null, this);
} else {
final MultiMap<String> dataMap = new MultiMap<String>();
dataMap.add(SERVICE_TYPE, service);
proxy.get(legacyServiceExecutionStateAPI, CONTENT_TYPE_URL_ENCODED,
UrlEncoded.encode(dataMap, StandardCharsets.UTF_8, false), this);
}
}, () -> {
logger.warn("No Service executed to get state");
});
stateJob = Optional.empty();
}
}
@Override
public void onResponse(@Nullable String result) {
if (result != null) {
try {
ExecutionStatusContainer esc = Converter.getGson().fromJson(result, ExecutionStatusContainer.class);
if (esc != null) {
if (esc.executionStatus != null) {
// handling of BMW ConnectedDrive updates
String status = esc.executionStatus.status;
if (status != null) {
synchronized (this) {
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null), status);
if (ExecutionState.EXECUTED.name().equals(status)) {
// refresh loop ends - update of status handled in the normal refreshInterval.
// Earlier
// update doesn't show better results!
reset();
return;
}
}
}
} else if (esc.eventId != null) {
// store event id for further MyBMW updates
executingEventId = Optional.of(esc.eventId);
} else if (esc.eventStatus != null) {
// update status for MyBMW API
synchronized (this) {
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null), esc.eventStatus);
if (ExecutionState.EXECUTED.name().equals(esc.eventStatus)) {
// refresh loop ends - update of status handled in the normal refreshInterval.
// Earlier
// update doesn't show better results!
reset();
return;
}
}
}
}
} catch (JsonSyntaxException jse) {
logger.debug("RemoteService response is unparseable: {} {}", result, jse.getMessage());
}
}
// schedule even if no result is present until retries exceeded
synchronized (this) {
stateJob.ifPresent(job -> {
if (!job.isDone()) {
job.cancel(true);
}
});
stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
}
}
@Override
public void onError(NetworkError error) {
synchronized (this) {
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
ExecutionState.ERROR.name() + Constants.SPACE + Integer.toString(error.status));
reset();
}
}
private void reset() {
serviceExecuting = Optional.empty();
executingEventId = Optional.empty();
counter = 0;
}
public void cancel() {
synchronized (this) {
stateJob.ifPresent(action -> {
if (!action.isDone()) {
action.cancel(true);
}
stateJob = Optional.empty();
});
}
}
public void setMyBmwApiUsage(boolean b) {
myBmwApiUsage = b;
}
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
/**
* The {@link ResponseCallback} Marker Interface for all ASYNC REST API callbacks
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public interface ResponseCallback {
public void onError(NetworkError error);
}

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link StringResponseCallback} Interface for all String results from ASYNC REST API
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public interface StringResponseCallback extends ResponseCallback {
public void onResponse(@Nullable String result);
}

View File

@ -1,63 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
/**
* The {@link Token} BMW ConnectedDrive Token storage
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class Token {
private String token = Constants.EMPTY;
private String tokenType = Constants.EMPTY;
private long expiration = 0;
private boolean myBmwApiUsage = false;
public boolean isMyBmwApiUsage() {
return myBmwApiUsage;
}
public void setMyBmwApiUsage(boolean myBmwAppUsage) {
this.myBmwApiUsage = myBmwAppUsage;
}
public String getBearerToken() {
return new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
}
public void setToken(String token) {
this.token = token;
}
public void setExpiration(int expiration) {
this.expiration = System.currentTimeMillis() / 1000 + expiration;
}
public void setType(String type) {
tokenType = type;
}
public boolean isValid() {
return (!token.equals(Constants.EMPTY) && !tokenType.equals(Constants.EMPTY)
&& (this.expiration - System.currentTimeMillis() / 1000) > 1);
}
@Override
public String toString() {
return tokenType + token;
}
}

View File

@ -1,544 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.measure.quantity.Length;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.VehicleType;
import org.openhab.binding.bmwconnecteddrive.internal.dto.Destination;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.AllTrips;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.LastTrip;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CBSMessage;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CCMMessage;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Doors;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Position;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Windows;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils.TimedChannel;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.binding.bmwconnecteddrive.internal.utils.RemoteServiceUtils;
import org.openhab.binding.bmwconnecteddrive.internal.utils.VehicleStatusUtils;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonSyntaxException;
/**
* The {@link VehicleChannelHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send of charge profile
*/
@NonNullByDefault
public abstract class VehicleChannelHandler extends BaseThingHandler {
protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
protected boolean imperial = false;
protected boolean hasFuel = false;
protected boolean isElectric = false;
protected boolean isHybrid = false;
// List Interfaces
protected List<CBSMessage> serviceList = new ArrayList<CBSMessage>();
protected String selectedService = Constants.UNDEF;
protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
protected String selectedCC = Constants.UNDEF;
protected List<Destination> destinationList = new ArrayList<Destination>();
protected String selectedDestination = Constants.UNDEF;
protected BMWConnectedDriveOptionProvider optionProvider;
// Data Caches
protected Optional<String> vehicleStatusCache = Optional.empty();
protected Optional<String> lastTripCache = Optional.empty();
protected Optional<String> allTripsCache = Optional.empty();
protected Optional<String> chargeProfileCache = Optional.empty();
protected Optional<String> rangeMapCache = Optional.empty();
protected Optional<String> destinationCache = Optional.empty();
protected Optional<byte[]> imageCache = Optional.empty();
public VehicleChannelHandler(Thing thing, BMWConnectedDriveOptionProvider op, String type, boolean imperial) {
super(thing);
optionProvider = op;
this.imperial = imperial;
hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString());
isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
|| type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
isHybrid = hasFuel && isElectric;
setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
}
private void setOptions(final String group, final String id, List<StateOption> options) {
optionProvider.setStateOptions(new ChannelUID(thing.getUID(), group, id), options);
}
protected void updateChannel(final String group, final String id, final State state) {
updateState(new ChannelUID(thing.getUID(), group, id), state);
}
protected void updateCheckControls(List<CCMMessage> ccl) {
if (ccl.isEmpty()) {
// No Check Control available - show not active
CCMMessage ccm = new CCMMessage();
ccm.ccmDescriptionLong = Constants.NO_ENTRIES;
ccm.ccmDescriptionShort = Constants.NO_ENTRIES;
ccm.ccmId = -1;
ccm.ccmMileage = -1;
ccl.add(ccm);
}
// add all elements to options
checkControlList = ccl;
List<StateOption> ccmDescriptionOptions = new ArrayList<>();
List<StateOption> ccmDetailsOptions = new ArrayList<>();
List<StateOption> ccmMileageOptions = new ArrayList<>();
boolean isSelectedElementIn = false;
int index = 0;
for (CCMMessage ccEntry : checkControlList) {
ccmDescriptionOptions.add(new StateOption(Integer.toString(index), ccEntry.ccmDescriptionShort));
ccmDetailsOptions.add(new StateOption(Integer.toString(index), ccEntry.ccmDescriptionLong));
ccmMileageOptions.add(new StateOption(Integer.toString(index), Integer.toString(ccEntry.ccmMileage)));
if (selectedCC.equals(ccEntry.ccmDescriptionShort)) {
isSelectedElementIn = true;
}
index++;
}
setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
setOptions(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, ccmDetailsOptions);
setOptions(CHANNEL_GROUP_CHECK_CONTROL, MILEAGE, ccmMileageOptions);
// if current selected item isn't anymore in the list select first entry
if (!isSelectedElementIn) {
selectCheckControl(0);
}
}
protected void selectCheckControl(int index) {
if (index >= 0 && index < checkControlList.size()) {
CCMMessage ccEntry = checkControlList.get(index);
selectedCC = ccEntry.ccmDescriptionShort;
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.ccmDescriptionShort));
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.ccmDescriptionLong));
updateChannel(CHANNEL_GROUP_CHECK_CONTROL, MILEAGE, QuantityType.valueOf(
Converter.round(ccEntry.ccmMileage), imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
}
}
protected void updateServices(List<CBSMessage> sl) {
// if list is empty add "undefined" element
if (sl.isEmpty()) {
CBSMessage cbsm = new CBSMessage();
cbsm.cbsType = Constants.NO_ENTRIES;
cbsm.cbsDescription = Constants.NO_ENTRIES;
sl.add(cbsm);
}
// add all elements to options
serviceList = sl;
List<StateOption> serviceNameOptions = new ArrayList<>();
List<StateOption> serviceDetailsOptions = new ArrayList<>();
List<StateOption> serviceDateOptions = new ArrayList<>();
List<StateOption> serviceMileageOptions = new ArrayList<>();
boolean isSelectedElementIn = false;
int index = 0;
for (CBSMessage serviceEntry : serviceList) {
// create StateOption with "value = list index" and "label = human readable string"
serviceNameOptions.add(new StateOption(Integer.toString(index), serviceEntry.getType()));
serviceDetailsOptions.add(new StateOption(Integer.toString(index), serviceEntry.getDescription()));
serviceDateOptions.add(new StateOption(Integer.toString(index), serviceEntry.getDueDate()));
serviceMileageOptions
.add(new StateOption(Integer.toString(index), Integer.toString(serviceEntry.cbsRemainingMileage)));
if (selectedService.equals(serviceEntry.getType())) {
isSelectedElementIn = true;
}
index++;
}
setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
setOptions(CHANNEL_GROUP_SERVICE, DETAILS, serviceDetailsOptions);
setOptions(CHANNEL_GROUP_SERVICE, DATE, serviceDateOptions);
setOptions(CHANNEL_GROUP_SERVICE, MILEAGE, serviceMileageOptions);
// if current selected item isn't anymore in the list select first entry
if (!isSelectedElementIn) {
selectService(0);
}
}
protected void selectService(int index) {
if (index >= 0 && index < serviceList.size()) {
CBSMessage serviceEntry = serviceList.get(index);
selectedService = serviceEntry.cbsType;
updateChannel(CHANNEL_GROUP_SERVICE, NAME,
StringType.valueOf(Converter.toTitleCase(serviceEntry.getType())));
updateChannel(CHANNEL_GROUP_SERVICE, DETAILS,
StringType.valueOf(Converter.toTitleCase(serviceEntry.getDescription())));
updateChannel(CHANNEL_GROUP_SERVICE, DATE,
DateTimeType.valueOf(Converter.getLocalDateTime(serviceEntry.getDueDate())));
updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
QuantityType.valueOf(Converter.round(serviceEntry.cbsRemainingMileage),
imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
}
}
protected void updateDestinations(List<Destination> dl) {
// if list is empty add "undefined" element
if (dl.isEmpty()) {
Destination dest = new Destination();
dest.city = Constants.NO_ENTRIES;
dest.lat = -1;
dest.lon = -1;
dl.add(dest);
}
// add all elements to options
destinationList = dl;
List<StateOption> destinationNameOptions = new ArrayList<>();
List<StateOption> destinationGPSOptions = new ArrayList<>();
boolean isSelectedElementIn = false;
int index = 0;
for (Destination destination : destinationList) {
destinationNameOptions.add(new StateOption(Integer.toString(index), destination.getAddress()));
destinationGPSOptions.add(new StateOption(Integer.toString(index), destination.getCoordinates()));
if (selectedDestination.equals(destination.getAddress())) {
isSelectedElementIn = true;
}
index++;
}
setOptions(CHANNEL_GROUP_DESTINATION, NAME, destinationNameOptions);
setOptions(CHANNEL_GROUP_DESTINATION, GPS, destinationGPSOptions);
// if current selected item isn't anymore in the list select first entry
if (!isSelectedElementIn) {
selectDestination(0);
}
}
protected void selectDestination(int index) {
if (index >= 0 && index < destinationList.size()) {
Destination destinationEntry = destinationList.get(index);
// update selected Item
selectedDestination = destinationEntry.getAddress();
// update coordinates according to new set location
updateChannel(CHANNEL_GROUP_DESTINATION, NAME, StringType.valueOf(destinationEntry.getAddress()));
updateChannel(CHANNEL_GROUP_DESTINATION, GPS, PointType.valueOf(destinationEntry.getCoordinates()));
}
}
protected void updateAllTrips(AllTrips allTrips) {
QuantityType<Length> qtTotalElectric = QuantityType
.valueOf(Converter.round(allTrips.totalElectricDistance.userTotal), Constants.KILOMETRE_UNIT);
QuantityType<Length> qtLongestElectricRange = QuantityType
.valueOf(Converter.round(allTrips.chargecycleRange.userHigh), Constants.KILOMETRE_UNIT);
QuantityType<Length> qtDistanceSinceCharge = QuantityType
.valueOf(Converter.round(allTrips.chargecycleRange.userCurrentChargeCycle), Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_LIFETIME, TOTAL_DRIVEN_DISTANCE,
imperial ? Converter.getMiles(qtTotalElectric) : qtTotalElectric);
updateChannel(CHANNEL_GROUP_LIFETIME, SINGLE_LONGEST_DISTANCE,
imperial ? Converter.getMiles(qtLongestElectricRange) : qtLongestElectricRange);
updateChannel(CHANNEL_GROUP_LAST_TRIP, DISTANCE_SINCE_CHARGING,
imperial ? Converter.getMiles(qtDistanceSinceCharge) : qtDistanceSinceCharge);
// Conversion from kwh/100km to kwh/10mi has to be done manually
double avgConsumotion = imperial ? allTrips.avgElectricConsumption.userAverage * Converter.MILES_TO_KM_RATIO
: allTrips.avgElectricConsumption.userAverage;
double avgCombinedConsumption = imperial
? allTrips.avgCombinedConsumption.userAverage * Converter.MILES_TO_KM_RATIO
: allTrips.avgCombinedConsumption.userAverage;
double avgRecuperation = imperial ? allTrips.avgRecuperation.userAverage * Converter.MILES_TO_KM_RATIO
: allTrips.avgRecuperation.userAverage;
updateChannel(CHANNEL_GROUP_LIFETIME, AVG_CONSUMPTION,
QuantityType.valueOf(Converter.round(avgConsumotion), Units.KILOWATT_HOUR));
updateChannel(CHANNEL_GROUP_LIFETIME, AVG_COMBINED_CONSUMPTION,
QuantityType.valueOf(Converter.round(avgCombinedConsumption), Units.LITRE));
updateChannel(CHANNEL_GROUP_LIFETIME, AVG_RECUPERATION,
QuantityType.valueOf(Converter.round(avgRecuperation), Units.KILOWATT_HOUR));
}
protected void updateLastTrip(LastTrip trip) {
// Whyever the Last Trip DateTime is delivered without offest - so LocalTime
updateChannel(CHANNEL_GROUP_LAST_TRIP, DATE,
DateTimeType.valueOf(Converter.getLocalDateTimeWithoutOffest(trip.date)));
updateChannel(CHANNEL_GROUP_LAST_TRIP, DURATION, QuantityType.valueOf(trip.duration, Units.MINUTE));
QuantityType<Length> qtTotalDistance = QuantityType.valueOf(Converter.round(trip.totalDistance),
Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_LAST_TRIP, DISTANCE,
imperial ? Converter.getMiles(qtTotalDistance) : qtTotalDistance);
// Conversion from kwh/100km to kwh/10mi has to be done manually
double avgConsumtption = imperial ? trip.avgElectricConsumption * Converter.MILES_TO_KM_RATIO
: trip.avgElectricConsumption;
double avgCombinedConsumption = imperial ? trip.avgCombinedConsumption * Converter.MILES_TO_KM_RATIO
: trip.avgCombinedConsumption;
double avgRecuperation = imperial ? trip.avgRecuperation * Converter.MILES_TO_KM_RATIO : trip.avgRecuperation;
updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_CONSUMPTION,
QuantityType.valueOf(Converter.round(avgConsumtption), Units.KILOWATT_HOUR));
updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_COMBINED_CONSUMPTION,
QuantityType.valueOf(Converter.round(avgCombinedConsumption), Units.LITRE));
updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_RECUPERATION,
QuantityType.valueOf(Converter.round(avgRecuperation), Units.KILOWATT_HOUR));
}
protected void updateChargeProfileFromContent(String content) {
ChargeProfileWrapper.fromJson(content).ifPresent(this::updateChargeProfile);
}
protected void updateChargeProfile(ChargeProfileWrapper wrapper) {
updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_PREFERENCE,
StringType.valueOf(Converter.toTitleCase(wrapper.getPreference())));
updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_MODE,
StringType.valueOf(Converter.toTitleCase(wrapper.getMode())));
final Boolean climate = wrapper.isEnabled(ProfileKey.CLIMATE);
updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_CLIMATE,
climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
updateTimedState(wrapper, ProfileKey.WINDOWSTART);
updateTimedState(wrapper, ProfileKey.WINDOWEND);
updateTimedState(wrapper, ProfileKey.TIMER1);
updateTimedState(wrapper, ProfileKey.TIMER2);
updateTimedState(wrapper, ProfileKey.TIMER3);
updateTimedState(wrapper, ProfileKey.OVERRIDE);
}
protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
if (timed != null) {
final LocalTime time = profile.getTime(key);
updateChannel(CHANNEL_GROUP_CHARGE, timed.time, time == null ? UnDefType.UNDEF
: new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
if (timed.timer != null) {
final Boolean enabled = profile.isEnabled(key);
updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + CHARGE_ENABLED,
enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
if (timed.hasDays) {
final Set<DayOfWeek> days = profile.getDays(key);
updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + CHARGE_DAYS,
days == null ? UnDefType.UNDEF : StringType.valueOf(ChargeProfileUtils.formatDays(days)));
EnumSet.allOf(DayOfWeek.class).forEach(day -> {
updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + ChargeProfileUtils.getDaysChannel(day),
days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
});
}
}
}
}
protected void updateDoors(Doors doorState) {
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
StringType.valueOf(Converter.toTitleCase(doorState.doorDriverFront)));
updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
StringType.valueOf(Converter.toTitleCase(doorState.doorDriverRear)));
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
StringType.valueOf(Converter.toTitleCase(doorState.doorPassengerFront)));
updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
StringType.valueOf(Converter.toTitleCase(doorState.doorPassengerRear)));
updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(doorState.trunk)));
updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(doorState.hood)));
}
protected void updateWindows(Windows windowState) {
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
StringType.valueOf(Converter.toTitleCase(windowState.windowDriverFront)));
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
StringType.valueOf(Converter.toTitleCase(windowState.windowDriverRear)));
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
StringType.valueOf(Converter.toTitleCase(windowState.windowPassengerFront)));
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
StringType.valueOf(Converter.toTitleCase(windowState.windowPassengerRear)));
updateChannel(CHANNEL_GROUP_DOORS, WINDOW_REAR,
StringType.valueOf(Converter.toTitleCase(windowState.rearWindow)));
updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(windowState.sunroof)));
}
protected void updatePosition(Position pos) {
updateChannel(CHANNEL_GROUP_LOCATION, GPS, PointType.valueOf(pos.getCoordinates()));
updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
}
protected void updateVehicleStatus(VehicleStatus vStatus) {
// Vehicle Status
updateChannel(CHANNEL_GROUP_STATUS, LOCK, StringType.valueOf(Converter.toTitleCase(vStatus.doorLockState)));
// Service Updates
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
DateTimeType.valueOf(Converter.getLocalDateTime(VehicleStatusUtils.getNextServiceDate(vStatus))));
updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
QuantityType.valueOf(Converter.round(VehicleStatusUtils.getNextServiceMileage(vStatus)),
imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
// CheckControl Active?
updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.checkControlActive(vStatus))));
// last update Time
updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
DateTimeType.valueOf(Converter.getLocalDateTime(VehicleStatusUtils.getUpdateTime(vStatus))));
// last update reason
updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE_REASON,
StringType.valueOf(Converter.toTitleCase(vStatus.updateReason)));
Doors doorState = null;
try {
doorState = Converter.getGson().fromJson(Converter.getGson().toJson(vStatus), Doors.class);
} catch (JsonSyntaxException jse) {
logger.debug("Doors parse exception {}", jse.getMessage());
}
if (doorState != null) {
updateChannel(CHANNEL_GROUP_STATUS, DOORS, StringType.valueOf(VehicleStatusUtils.checkClosed(doorState)));
updateDoors(doorState);
}
Windows windowState = null;
try {
windowState = Converter.getGson().fromJson(Converter.getGson().toJson(vStatus), Windows.class);
} catch (JsonSyntaxException jse) {
logger.debug("Windows parse exception {}", jse.getMessage());
}
if (windowState != null) {
updateChannel(CHANNEL_GROUP_STATUS, WINDOWS,
StringType.valueOf(VehicleStatusUtils.checkClosed(windowState)));
updateWindows(windowState);
}
// Range values
// based on unit of length decide if range shall be reported in km or miles
double totalRange = 0;
double maxTotalRange = 0;
if (isElectric) {
totalRange += vStatus.remainingRangeElectric;
QuantityType<Length> qtElectricRange = QuantityType.valueOf(vStatus.remainingRangeElectric,
Constants.KILOMETRE_UNIT);
QuantityType<Length> qtElectricRadius = QuantityType
.valueOf(Converter.guessRangeRadius(vStatus.remainingRangeElectric), Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC,
imperial ? Converter.getMiles(qtElectricRange) : qtElectricRange);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC,
imperial ? Converter.getMiles(qtElectricRadius) : qtElectricRadius);
maxTotalRange += vStatus.maxRangeElectric;
QuantityType<Length> qtMaxElectricRange = QuantityType.valueOf(vStatus.maxRangeElectric,
Constants.KILOMETRE_UNIT);
QuantityType<Length> qtMaxElectricRadius = QuantityType
.valueOf(Converter.guessRangeRadius(vStatus.maxRangeElectric), Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC_MAX,
imperial ? Converter.getMiles(qtMaxElectricRange) : qtMaxElectricRange);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC_MAX,
imperial ? Converter.getMiles(qtMaxElectricRadius) : qtMaxElectricRadius);
}
if (hasFuel) {
totalRange += vStatus.remainingRangeFuel;
maxTotalRange += vStatus.remainingRangeFuel;
QuantityType<Length> qtFuelRange = QuantityType.valueOf(vStatus.remainingRangeFuel,
Constants.KILOMETRE_UNIT);
QuantityType<Length> qtFuelRadius = QuantityType
.valueOf(Converter.guessRangeRadius(vStatus.remainingRangeFuel), Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, imperial ? Converter.getMiles(qtFuelRange) : qtFuelRange);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL,
imperial ? Converter.getMiles(qtFuelRadius) : qtFuelRadius);
}
if (isHybrid) {
QuantityType<Length> qtHybridRange = QuantityType.valueOf(totalRange, Constants.KILOMETRE_UNIT);
QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(totalRange),
Constants.KILOMETRE_UNIT);
QuantityType<Length> qtMaxHybridRange = QuantityType.valueOf(maxTotalRange, Constants.KILOMETRE_UNIT);
QuantityType<Length> qtMaxHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(maxTotalRange),
Constants.KILOMETRE_UNIT);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID,
imperial ? Converter.getMiles(qtHybridRange) : qtHybridRange);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID,
imperial ? Converter.getMiles(qtHybridRadius) : qtHybridRadius);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID_MAX,
imperial ? Converter.getMiles(qtMaxHybridRange) : qtMaxHybridRange);
updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID_MAX,
imperial ? Converter.getMiles(qtMaxHybridRadius) : qtMaxHybridRadius);
}
updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
QuantityType.valueOf(vStatus.mileage, imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
if (isElectric) {
updateChannel(CHANNEL_GROUP_RANGE, SOC, QuantityType.valueOf(vStatus.chargingLevelHv, Units.PERCENT));
}
if (hasFuel) {
updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
QuantityType.valueOf(vStatus.remainingFuel, Units.LITRE));
}
// Charge Values
if (isElectric) {
if (vStatus.connectionStatus != null) {
updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
StringType.valueOf(Converter.toTitleCase(vStatus.connectionStatus)));
} else {
updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION, UnDefType.NULL);
}
if (vStatus.chargingStatus != null) {
if (Constants.INVALID.equals(vStatus.chargingStatus)) {
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
StringType.valueOf(Converter.toTitleCase(vStatus.lastChargingEndReason)));
} else {
// State INVALID is somehow misleading. Instead show the Last Charging End Reason
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
StringType.valueOf(Converter.toTitleCase(vStatus.chargingStatus)));
}
} else {
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS, UnDefType.NULL);
}
if (vStatus.chargingTimeRemaining != null) {
try {
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING,
QuantityType.valueOf(vStatus.chargingTimeRemaining, Units.MINUTE));
} catch (NumberFormatException nfe) {
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING, UnDefType.UNDEF);
}
} else {
updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING, UnDefType.NULL);
}
}
}
}

View File

@ -1,819 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
import java.util.ArrayList;
import java.util.Collection;
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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.VehicleConfiguration;
import org.openhab.binding.bmwconnecteddrive.internal.action.BMWConnectedDriveActions;
import org.openhab.binding.bmwconnecteddrive.internal.dto.DestinationContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributesContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.navigation.NavigationContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.AllTrips;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.AllTripsContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.LastTrip;
import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.LastTripContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatusContainer;
import org.openhab.binding.bmwconnecteddrive.internal.handler.RemoteServiceHandler.ExecutionState;
import org.openhab.binding.bmwconnecteddrive.internal.handler.RemoteServiceHandler.RemoteService;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils.ChargeKeyDay;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
import org.openhab.binding.bmwconnecteddrive.internal.utils.ImageProperties;
import org.openhab.binding.bmwconnecteddrive.internal.utils.RemoteServiceUtils;
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.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
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.BridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import com.google.gson.JsonSyntaxException;
/**
* The {@link VehicleHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - edit & send charge profile
*/
@NonNullByDefault
public class VehicleHandler extends VehicleChannelHandler {
private int legacyMode = Constants.INT_UNDEF; // switch to legacy API in case of 404 Errors
private Optional<ConnectedDriveProxy> proxy = Optional.empty();
private Optional<RemoteServiceHandler> remote = Optional.empty();
private Optional<VehicleConfiguration> configuration = Optional.empty();
private Optional<ConnectedDriveBridgeHandler> bridgeHandler = Optional.empty();
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
private Optional<ScheduledFuture<?>> editTimeout = Optional.empty();
private Optional<List<ResponseCallback>> callbackCounter = Optional.empty();
private ImageProperties imageProperties = new ImageProperties();
VehicleStatusCallback vehicleStatusCallback = new VehicleStatusCallback();
StringResponseCallback oldVehicleStatusCallback = new LegacyVehicleStatusCallback();
StringResponseCallback navigationCallback = new NavigationStatusCallback();
StringResponseCallback lastTripCallback = new LastTripCallback();
StringResponseCallback allTripsCallback = new AllTripsCallback();
StringResponseCallback chargeProfileCallback = new ChargeProfilesCallback();
StringResponseCallback rangeMapCallback = new RangeMapCallback();
DestinationsCallback destinationCallback = new DestinationsCallback();
ByteResponseCallback imageCallback = new ImageCallback();
private Optional<ChargeProfileWrapper> chargeProfileEdit = Optional.empty();
private Optional<String> chargeProfileSent = Optional.empty();
public VehicleHandler(Thing thing, BMWConnectedDriveOptionProvider op, String type, boolean imperial) {
super(thing, op, type, imperial);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String group = channelUID.getGroupId();
// Refresh of Channels with cached values
if (command instanceof RefreshType) {
if (CHANNEL_GROUP_LAST_TRIP.equals(group)) {
lastTripCache.ifPresent(lastTrip -> lastTripCallback.onResponse(lastTrip));
} else if (CHANNEL_GROUP_LIFETIME.equals(group)) {
allTripsCache.ifPresent(allTrips -> allTripsCallback.onResponse(allTrips));
} else if (CHANNEL_GROUP_DESTINATION.equals(group)) {
destinationCache.ifPresent(destination -> destinationCallback.onResponse(destination));
} else if (CHANNEL_GROUP_STATUS.equals(group)) {
vehicleStatusCache.ifPresent(vehicleStatus -> vehicleStatusCallback.onResponse(vehicleStatus));
} else if (CHANNEL_GROUP_CHARGE.equals(group)) {
chargeProfileEdit.ifPresentOrElse(this::updateChargeProfile,
() -> chargeProfileCache.ifPresent(this::updateChargeProfileFromContent));
} else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
imageCache.ifPresent(image -> imageCallback.onResponse(image));
}
// Check for Channel Group and corresponding Actions
} else if (CHANNEL_GROUP_REMOTE.equals(group)) {
// Executing Remote Services
if (command instanceof StringType) {
String serviceCommand = ((StringType) command).toFullString();
remote.ifPresent(remot -> {
switch (serviceCommand) {
case REMOTE_SERVICE_LIGHT_FLASH:
case REMOTE_SERVICE_AIR_CONDITIONING:
case REMOTE_SERVICE_DOOR_LOCK:
case REMOTE_SERVICE_DOOR_UNLOCK:
case REMOTE_SERVICE_HORN:
case REMOTE_SERVICE_VEHICLE_FINDER:
case REMOTE_SERVICE_CHARGE_NOW:
RemoteServiceUtils.getRemoteService(serviceCommand)
.ifPresentOrElse(service -> remot.execute(service), () -> {
logger.debug("Remote service execution {} unknown", serviceCommand);
});
break;
case REMOTE_SERVICE_CHARGING_CONTROL:
sendChargeProfile(chargeProfileEdit);
break;
default:
logger.debug("Remote service execution {} unknown", serviceCommand);
break;
}
});
}
} else if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
// Image Change
configuration.ifPresent(config -> {
if (command instanceof StringType) {
if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
String newViewport = command.toString();
synchronized (imageProperties) {
if (!imageProperties.viewport.equals(newViewport)) {
imageProperties = new ImageProperties(newViewport, imageProperties.size);
imageCache = Optional.empty();
proxy.ifPresent(prox -> prox.requestImage(config, imageProperties, imageCallback));
}
}
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport));
}
}
if (command instanceof DecimalType) {
if (command instanceof DecimalType) {
int newImageSize = ((DecimalType) command).intValue();
if (channelUID.getIdWithoutGroup().equals(IMAGE_SIZE)) {
synchronized (imageProperties) {
if (imageProperties.size != newImageSize) {
imageProperties = new ImageProperties(imageProperties.viewport, newImageSize);
imageCache = Optional.empty();
proxy.ifPresent(prox -> prox.requestImage(config, imageProperties, imageCallback));
}
}
}
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_SIZE, new DecimalType(newImageSize));
}
}
});
} else if (CHANNEL_GROUP_DESTINATION.equals(group)) {
if (command instanceof StringType) {
int index = Converter.getIndex(command.toFullString());
if (index != -1) {
selectDestination(index);
} else {
logger.debug("Cannot select Destination index {}", command.toFullString());
}
}
} else if (CHANNEL_GROUP_SERVICE.equals(group)) {
if (command instanceof StringType) {
int index = Converter.getIndex(command.toFullString());
if (index != -1) {
selectService(index);
} else {
logger.debug("Cannot select Service index {}", command.toFullString());
}
}
} else if (CHANNEL_GROUP_CHECK_CONTROL.equals(group)) {
if (command instanceof StringType) {
int index = Converter.getIndex(command.toFullString());
if (index != -1) {
selectCheckControl(index);
} else {
logger.debug("Cannot select CheckControl index {}", command.toFullString());
}
}
} else if (CHANNEL_GROUP_CHARGE.equals(group)) {
handleChargeProfileCommand(channelUID, command);
}
}
@Override
public void initialize() {
callbackCounter = Optional.of(new ArrayList<ResponseCallback>());
updateStatus(ThingStatus.UNKNOWN);
final VehicleConfiguration config = getConfigAs(VehicleConfiguration.class);
configuration = Optional.of(config);
Bridge bridge = getBridge();
if (bridge != null) {
BridgeHandler handler = bridge.getHandler();
if (handler != null) {
bridgeHandler = Optional.of(((ConnectedDriveBridgeHandler) handler));
proxy = ((ConnectedDriveBridgeHandler) handler).getProxy();
remote = proxy.map(prox -> prox.getRemoteServiceHandler(this));
} else {
logger.debug("Bridge Handler null");
}
} else {
logger.debug("Bridge null");
}
// get Image after init with config values
synchronized (imageProperties) {
imageProperties = new ImageProperties(config.imageViewport, config.imageSize);
}
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf((config.imageViewport)));
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_SIZE, new DecimalType((config.imageSize)));
// check imperial setting is different to AutoDetect
if (!UNITS_AUTODETECT.equals(config.units)) {
imperial = UNITS_IMPERIAL.equals(config.units);
}
// start update schedule
startSchedule(config.refreshInterval);
}
private void startSchedule(int interval) {
refreshJob.ifPresentOrElse(job -> {
if (job.isCancelled()) {
refreshJob = Optional
.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
} // else - scheduler is already running!
}, () -> {
refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
});
}
@Override
public void dispose() {
refreshJob.ifPresent(job -> job.cancel(true));
editTimeout.ifPresent(job -> job.cancel(true));
remote.ifPresent(RemoteServiceHandler::cancel);
}
public void getData() {
proxy.ifPresentOrElse(prox -> {
configuration.ifPresentOrElse(config -> {
if (legacyMode == 1) {
prox.requestLegacyVehcileStatus(config, oldVehicleStatusCallback);
} else {
prox.requestVehcileStatus(config, vehicleStatusCallback);
}
addCallback(vehicleStatusCallback);
prox.requestLNavigation(config, navigationCallback);
addCallback(navigationCallback);
if (isSupported(Constants.STATISTICS)) {
prox.requestLastTrip(config, lastTripCallback);
prox.requestAllTrips(config, allTripsCallback);
addCallback(lastTripCallback);
addCallback(allTripsCallback);
}
if (isSupported(Constants.LAST_DESTINATIONS)) {
prox.requestDestinations(config, destinationCallback);
addCallback(destinationCallback);
}
if (isElectric) {
prox.requestChargingProfile(config, chargeProfileCallback);
addCallback(chargeProfileCallback);
}
synchronized (imageProperties) {
if (!imageCache.isPresent() && !imageProperties.failLimitReached()) {
prox.requestImage(config, imageProperties, imageCallback);
addCallback(imageCallback);
}
}
}, () -> {
logger.warn("ConnectedDrive Configuration isn't present");
});
}, () -> {
logger.warn("ConnectedDrive Proxy isn't present");
});
}
private synchronized void addCallback(ResponseCallback rc) {
callbackCounter.ifPresent(counter -> counter.add(rc));
}
private synchronized void removeCallback(ResponseCallback rc) {
callbackCounter.ifPresent(counter -> {
counter.remove(rc);
// all necessary callbacks received => print and set to empty
if (counter.isEmpty()) {
logFingerPrint();
callbackCounter = Optional.empty();
}
});
}
private void logFingerPrint() {
final String vin = configuration.map(config -> config.vin).orElse("");
logger.debug("###### Vehicle Troubleshoot Fingerprint Data - BEGIN ######");
logger.debug("### Discovery Result ###");
bridgeHandler.ifPresent(handler -> {
logger.debug("{}", handler.getDiscoveryFingerprint());
});
vehicleStatusCache.ifPresentOrElse(vehicleStatus -> {
logger.debug("### Vehicle Status ###");
// Anonymous data for VIN and Position
try {
VehicleStatusContainer container = Converter.getGson().fromJson(vehicleStatus,
VehicleStatusContainer.class);
if (container != null) {
VehicleStatus status = container.vehicleStatus;
if (status != null) {
status.vin = Constants.ANONYMOUS;
if (status.position != null) {
status.position.lat = -1;
status.position.lon = -1;
status.position.heading = -1;
}
}
}
logger.debug("{}", Converter.getGson().toJson(container));
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}, () -> {
logger.debug("### Vehicle Status Empty ###");
});
lastTripCache.ifPresentOrElse(lastTrip -> {
logger.debug("### Last Trip ###");
logger.debug("{}", lastTrip.replaceAll(vin, Constants.ANONYMOUS));
}, () -> {
logger.debug("### Last Trip Empty ###");
});
allTripsCache.ifPresentOrElse(allTrips -> {
logger.debug("### All Trips ###");
logger.debug("{}", allTrips.replaceAll(vin, Constants.ANONYMOUS));
}, () -> {
logger.debug("### All Trips Empty ###");
});
if (isElectric) {
chargeProfileCache.ifPresentOrElse(chargeProfile -> {
logger.debug("### Charge Profile ###");
logger.debug("{}", chargeProfile.replaceAll(vin, Constants.ANONYMOUS));
}, () -> {
logger.debug("### Charge Profile Empty ###");
});
}
destinationCache.ifPresentOrElse(destination -> {
logger.debug("### Charge Profile ###");
try {
DestinationContainer container = Converter.getGson().fromJson(destination, DestinationContainer.class);
if (container != null) {
if (container.destinations != null) {
container.destinations.forEach(entry -> {
entry.lat = 0;
entry.lon = 0;
entry.city = Constants.ANONYMOUS;
entry.street = Constants.ANONYMOUS;
entry.streetNumber = Constants.ANONYMOUS;
entry.country = Constants.ANONYMOUS;
});
logger.debug("{}", Converter.getGson().toJson(container));
}
} else {
logger.debug("### Destinations Empty ###");
}
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}, () -> {
logger.debug("### Charge Profile Empty ###");
});
rangeMapCache.ifPresentOrElse(rangeMap -> {
logger.debug("### Range Map ###");
logger.debug("{}", rangeMap.replaceAll(vin, Constants.ANONYMOUS));
}, () -> {
logger.debug("### Range Map Empty ###");
});
logger.debug("###### Vehicle Troubleshoot Fingerprint Data - END ######");
}
/**
* Don't stress ConnectedDrive with unnecessary requests. One call at the beginning is done to check the response.
* After cache has e.g. a proper error response it will be shown in the fingerprint
*
* @return
*/
private boolean isSupported(String service) {
final String services = thing.getProperties().get(Constants.SERVICES_SUPPORTED);
if (services != null) {
if (services.contains(service)) {
return true;
}
}
// if cache is empty give it a try one time to collected Troubleshoot data
return lastTripCache.isEmpty() || allTripsCache.isEmpty() || destinationCache.isEmpty();
}
public void updateRemoteExecutionStatus(@Nullable String service, @Nullable String status) {
if (RemoteService.CHARGING_CONTROL.toString().equals(service)
&& ExecutionState.EXECUTED.name().equals(status)) {
saveChargeProfileSent();
}
updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE, StringType
.valueOf(Converter.toTitleCase((service == null ? "-" : service) + Constants.SPACE + status)));
}
public Optional<VehicleConfiguration> getConfiguration() {
return configuration;
}
public ScheduledExecutorService getScheduler() {
return scheduler;
}
/**
* Callbacks for ConnectedDrive Portal
*
* @author Bernd Weymann
*
*/
public class ChargeProfilesCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
chargeProfileCache = Optional.of(content);
if (chargeProfileEdit.isEmpty()) {
updateChargeProfileFromContent(content);
}
}
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
chargeProfileCache = Optional.of(Converter.getGson().toJson(error));
removeCallback(this);
}
}
public class RangeMapCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
rangeMapCache = Optional.ofNullable(content);
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
rangeMapCache = Optional.of(Converter.getGson().toJson(error));
removeCallback(this);
}
}
public class DestinationsCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
destinationCache = Optional.ofNullable(content);
if (content != null) {
try {
DestinationContainer dc = Converter.getGson().fromJson(content, DestinationContainer.class);
if (dc != null && dc.destinations != null) {
updateDestinations(dc.destinations);
}
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
destinationCache = Optional.of(Converter.getGson().toJson(error));
removeCallback(this);
}
}
public class ImageCallback implements ByteResponseCallback {
@Override
public void onResponse(byte[] content) {
if (content.length > 0) {
imageCache = Optional.of(content);
String contentType = HttpUtil.guessContentTypeFromData(content);
updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(content, contentType));
} else {
synchronized (imageProperties) {
imageProperties.failed();
}
}
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
synchronized (imageProperties) {
imageProperties.failed();
}
removeCallback(this);
}
}
public class AllTripsCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
allTripsCache = Optional.of(content);
try {
AllTripsContainer atc = Converter.getGson().fromJson(content, AllTripsContainer.class);
if (atc != null) {
AllTrips at = atc.allTrips;
if (at != null) {
updateAllTrips(at);
}
}
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
allTripsCache = Optional.of(Converter.getGson().toJson(error));
removeCallback(this);
}
}
public class LastTripCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
lastTripCache = Optional.of(content);
try {
LastTripContainer lt = Converter.getGson().fromJson(content, LastTripContainer.class);
if (lt != null) {
LastTrip trip = lt.lastTrip;
if (trip != null) {
updateLastTrip(trip);
}
}
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
removeCallback(this);
}
/**
* Store Error Report in cache variable. Via Fingerprint Channel error is logged and Issue can be raised
*/
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
lastTripCache = Optional.of(Converter.getGson().toJson(error));
removeCallback(this);
}
}
/**
* The VehicleStatus is supported by all Vehicle Types so it's used to reflect the Thing Status
*/
public class VehicleStatusCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
// switch to non legacy mode
legacyMode = 0;
updateStatus(ThingStatus.ONLINE);
vehicleStatusCache = Optional.of(content);
try {
VehicleStatusContainer status = Converter.getGson().fromJson(content, VehicleStatusContainer.class);
if (status != null) {
VehicleStatus vStatus = status.vehicleStatus;
if (vStatus == null) {
return;
}
updateVehicleStatus(vStatus);
updateCheckControls(vStatus.checkControlMessages);
updateServices(vStatus.cbsData);
updatePosition(vStatus.position);
}
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
removeCallback(this);
}
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
// only if legacyMode isn't set yet try legacy API
if (error.status != 200 && legacyMode == Constants.INT_UNDEF) {
logger.debug("VehicleStatus not found - try legacy API");
proxy.get().requestLegacyVehcileStatus(configuration.get(), oldVehicleStatusCallback);
}
vehicleStatusCache = Optional.of(Converter.getGson().toJson(error));
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
removeCallback(this);
}
}
/**
* Fallback API if origin isn't supported.
* This comes from the Community Discussion where a Vehicle from 2015 answered with "404"
* https://community.openhab.org/t/bmw-connecteddrive-binding/105124
*
* Selection of API was discussed here
* https://community.openhab.org/t/bmw-connecteddrive-bmw-i3/103876
*
* I figured out that only one API was working for this Vehicle. So this backward compatible Callback is introduced.
* The delivered data is converted into the origin dto object so no changes in previous functional code needed
*/
public class LegacyVehicleStatusCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
try {
VehicleAttributesContainer vac = Converter.getGson().fromJson(content,
VehicleAttributesContainer.class);
vehicleStatusCallback.onResponse(Converter.transformLegacyStatus(vac));
legacyMode = 1;
logger.debug("VehicleStatus switched to legacy mode");
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
}
@Override
public void onError(NetworkError error) {
vehicleStatusCallback.onError(error);
}
}
public class NavigationStatusCallback implements StringResponseCallback {
@Override
public void onResponse(@Nullable String content) {
if (content != null) {
try {
NavigationContainer nav = Converter.getGson().fromJson(content, NavigationContainer.class);
updateChannel(CHANNEL_GROUP_RANGE, SOC_MAX, QuantityType.valueOf(nav.socmax, Units.KILOWATT_HOUR));
} catch (JsonSyntaxException jse) {
logger.debug("{}", jse.getMessage());
}
}
removeCallback(this);
}
@Override
public void onError(NetworkError error) {
logger.debug("{}", error.toString());
removeCallback(this);
}
}
private void handleChargeProfileCommand(ChannelUID channelUID, Command command) {
if (chargeProfileEdit.isEmpty()) {
chargeProfileEdit = getChargeProfileWrapper();
}
chargeProfileEdit.ifPresent(profile -> {
boolean processed = false;
final String id = channelUID.getIdWithoutGroup();
if (command instanceof StringType) {
final String stringCommand = ((StringType) command).toFullString();
switch (id) {
case CHARGE_PROFILE_PREFERENCE:
profile.setPreference(stringCommand);
updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_PREFERENCE,
StringType.valueOf(Converter.toTitleCase(profile.getPreference())));
processed = true;
break;
case CHARGE_PROFILE_MODE:
profile.setMode(stringCommand);
updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_MODE,
StringType.valueOf(Converter.toTitleCase(profile.getMode())));
processed = true;
break;
default:
break;
}
} else if (command instanceof OnOffType) {
final ProfileKey enableKey = ChargeProfileUtils.getEnableKey(id);
if (enableKey != null) {
profile.setEnabled(enableKey, OnOffType.ON.equals(command));
updateTimedState(profile, enableKey);
processed = true;
} else {
final ChargeKeyDay chargeKeyDay = ChargeProfileUtils.getKeyDay(id);
if (chargeKeyDay != null) {
profile.setDayEnabled(chargeKeyDay.key, chargeKeyDay.day, OnOffType.ON.equals(command));
updateTimedState(profile, chargeKeyDay.key);
processed = true;
}
}
} else if (command instanceof DateTimeType) {
DateTimeType dtt = (DateTimeType) command;
logger.debug("Accept {} for ID {}", dtt.toFullString(), id);
final ProfileKey key = ChargeProfileUtils.getTimeKey(id);
if (key != null) {
profile.setTime(key, dtt.getZonedDateTime().toLocalTime());
updateTimedState(profile, key);
processed = true;
}
}
if (processed) {
// cancel current timer and add another 5 mins - valid for each edit
editTimeout.ifPresent(timeout -> timeout.cancel(true));
// start edit timer with 5 min timeout
editTimeout = Optional.of(scheduler.schedule(() -> {
editTimeout = Optional.empty();
chargeProfileEdit = Optional.empty();
chargeProfileCache.ifPresent(this::updateChargeProfileFromContent);
}, 5, TimeUnit.MINUTES));
} else {
logger.debug("unexpected command {} not processed", command.toFullString());
}
});
}
private void saveChargeProfileSent() {
editTimeout.ifPresent(timeout -> {
timeout.cancel(true);
editTimeout = Optional.empty();
});
chargeProfileSent.ifPresent(sent -> {
chargeProfileCache = Optional.of(sent);
chargeProfileSent = Optional.empty();
chargeProfileEdit = Optional.empty();
chargeProfileCache.ifPresent(this::updateChargeProfileFromContent);
});
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(BMWConnectedDriveActions.class);
}
public Optional<ChargeProfileWrapper> getChargeProfileWrapper() {
return chargeProfileCache.flatMap(cache -> {
return ChargeProfileWrapper.fromJson(cache).map(wrapper -> {
return wrapper;
}).or(() -> {
logger.debug("cannot parse charging profile: {}", cache);
return Optional.empty();
});
}).or(() -> {
logger.debug("No ChargeProfile recieved so far - cannot start editing");
return Optional.empty();
});
}
public void sendChargeProfile(Optional<ChargeProfileWrapper> profile) {
profile.map(profil -> profil.getJson()).ifPresent(json -> {
logger.debug("sending charging profile: {}", json);
chargeProfileSent = Optional.of(json);
remote.ifPresent(rem -> rem.execute(RemoteService.CHARGING_CONTROL, json));
});
}
}

View File

@ -1,43 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.handler.simulation;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link Injector} Simulates feedback of the ConnectedDrive Portal
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class Injector {
private static boolean active = false;
// copy discovery json here
private static String discovery = "";
// copy vehicle status json here
private static String status = "";
public static boolean isActive() {
return active;
}
public static String getDiscovery() {
return discovery;
}
public static String getStatus() {
return status;
}
}

View File

@ -1,101 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link BimmerConstants} This class holds the important constants for the BMW Connected Drive Authorization. They
* are taken from the Bimmercode from github {@link https://github.com/bimmerconnected/bimmer_connected}
* File defining these constants
* {@link https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/account.py}
* https://customer.bmwgroup.com/one/app/oauth.js
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class BimmerConstants {
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/country_selector.py
public static final String REGION_NORTH_AMERICA = "NORTH_AMERICA";
public static final String REGION_CHINA = "CHINA";
public static final String REGION_ROW = "ROW";
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/country_selector.py
public static final String LEGACY_AUTH_SERVER_NORTH_AMERICA = "login.bmwusa.com/gcdm";
public static final String LEGACY_AUTH_SERVER_CHINA = "customer.bmwgroup.cn/gcdm";
public static final String LEGACY_AUTH_SERVER_ROW = "customer.bmwgroup.com/gcdm";
public static final Map<String, String> LEGACY_AUTH_SERVER_MAP = Map.of(REGION_NORTH_AMERICA,
LEGACY_AUTH_SERVER_NORTH_AMERICA, REGION_CHINA, LEGACY_AUTH_SERVER_CHINA, REGION_ROW,
LEGACY_AUTH_SERVER_ROW);
public static final String OAUTH_ENDPOINT = "/oauth/authenticate";
public static final String TOKEN_ENDPOINT = "/oauth/token";
public static final String API_SERVER_NORTH_AMERICA = "b2vapi.bmwgroup.us";
public static final String API_SERVER_CHINA = "b2vapi.bmwgroup.cn:8592";
public static final String API_SERVER_ROW = "b2vapi.bmwgroup.com";
public static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us";
public static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com";
public static final String EADRAX_SERVER_CHINA = Constants.EMPTY;
public static final Map<String, String> EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA,
EADRAX_SERVER_NORTH_AMERICA, REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW);
public static final Map<String, String> API_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, API_SERVER_NORTH_AMERICA,
REGION_CHINA, API_SERVER_CHINA, REGION_ROW, API_SERVER_ROW);
// see https://github.com/bimmerconnected/bimmer_connected/pull/252/files
public static final Map<String, String> LEGACY_AUTHORIZATION_VALUE_MAP = Map.of(REGION_NORTH_AMERICA,
"Basic ZDc2NmI1MzctYTY1NC00Y2JkLWEzZGMtMGNhNTY3MmQ3ZjhkOjE1ZjY5N2Y2LWE1ZDUtNGNhZC05OWQ5LTNhMTViYzdmMzk3Mw==",
REGION_CHINA,
"Basic blF2NkNxdHhKdVhXUDc0eGYzQ0p3VUVQOjF6REh4NnVuNGNEanliTEVOTjNreWZ1bVgya0VZaWdXUGNRcGR2RFJwSUJrN3JPSg==",
REGION_ROW,
"Basic ZDc2NmI1MzctYTY1NC00Y2JkLWEzZGMtMGNhNTY3MmQ3ZjhkOjE1ZjY5N2Y2LWE1ZDUtNGNhZC05OWQ5LTNhMTViYzdmMzk3Mw==");
public static final String LEGACY_CREDENTIAL_VALUES = "nQv6CqtxJuXWP74xf3CJwUEP:1zDHx6un4cDjybLENN3kyfumX2kEYigWPcQpdvDRpIBk7rOJ";
public static final String LEGACY_REDIRECT_URI_VALUE = "https://www.bmw-connecteddrive.com/app/static/external-dispatch.html";
public static final String LEGACY_SCOPE_VALUES = "authenticate_user vehicle_data remote_services";
public static final String LEGACY_CLIENT_ID = "dbf0a542-ebd1-4ff0-a9a7-55172fbfce35";
public static final String LEGACY_REFERER_URL = "https://www.bmw-connecteddrive.de/app/index.html";
public static final String AUTH_SERVER_NORTH_AMERICA = "login.bmwusa.com/gcdm";
public static final String AUTH_SERVER_CHINA = "customer.bmwgroup.cn/gcdm";
public static final String AUTH_SERVER_ROW = "customer.bmwgroup.com/gcdm";
public static final Map<String, String> AUTH_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, AUTH_SERVER_NORTH_AMERICA,
REGION_CHINA, AUTH_SERVER_CHINA, REGION_ROW, AUTH_SERVER_ROW);
public static final Map<String, String> AUTHORIZATION_VALUE_MAP = Map.of(REGION_NORTH_AMERICA,
"Basic NTQzOTRhNGItYjZjMS00NWZlLWI3YjItOGZkM2FhOTI1M2FhOmQ5MmYzMWMwLWY1NzktNDRmNS1hNzdkLTk2NmY4ZjAwZTM1MQ==",
REGION_CHINA,
"Basic blF2NkNxdHhKdVhXUDc0eGYzQ0p3VUVQOjF6REh4NnVuNGNEanliTEVOTjNreWZ1bVgya0VZaWdXUGNRcGR2RFJwSUJrN3JPSg==",
REGION_ROW,
"Basic MzFjMzU3YTAtN2ExZC00NTkwLWFhOTktMzNiOTcyNDRkMDQ4OmMwZTMzOTNkLTcwYTItNGY2Zi05ZDNjLTg1MzBhZjY0ZDU1Mg==");
public static final Map<String, String> CODE_VERIFIER = Map.of(REGION_NORTH_AMERICA,
"BKDarcVUpgymBDCgHDH0PwwMfzycDxu1joeklioOhwXA", REGION_CHINA, Constants.EMPTY, REGION_ROW,
"7PsmfPS5MpaNt0jEcPpi-B7M7u0gs1Nzw6ex0Y9pa-0");
public static final Map<String, String> CLIENT_ID = Map.of(REGION_NORTH_AMERICA,
"54394a4b-b6c1-45fe-b7b2-8fd3aa9253aa", REGION_CHINA, Constants.EMPTY, REGION_ROW,
"31c357a0-7a1d-4590-aa99-33b97244d048");
public static final Map<String, String> STATE = Map.of(REGION_NORTH_AMERICA, "rgastJbZsMtup49-Lp0FMQ", REGION_CHINA,
Constants.EMPTY, REGION_ROW, "cEG9eLAIi6Nv-aaCAniziE_B6FPoobva3qr5gukilYw");
public static final String REDIRECT_URI_VALUE = "com.bmw.connected://oauth";
public static final String SCOPE_VALUES = "openid profile email offline_access smacc vehicle_data perseus dlm svds cesim vsapi remote_services fupo authenticate_user";
}

View File

@ -1,136 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
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.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey;
/**
* The {@link ChargeProfileUtils} utility functions for charging profiles
*
* @author Norbert Truchsess - initial contribution
*/
@NonNullByDefault
public class ChargeProfileUtils {
// Charging
public static class TimedChannel {
public final String time;
public final @Nullable String timer;
public final boolean hasDays;
TimedChannel(final String time, @Nullable final String timer, final boolean hasDays) {
this.time = time;
this.timer = timer;
this.hasDays = hasDays;
}
}
@SuppressWarnings("serial")
private static final Map<ProfileKey, TimedChannel> TIMED_CHANNELS = new HashMap<>() {
{
put(ProfileKey.WINDOWSTART, new TimedChannel(CHARGE_WINDOW_START, null, false));
put(ProfileKey.WINDOWEND, new TimedChannel(CHARGE_WINDOW_END, null, false));
put(ProfileKey.TIMER1, new TimedChannel(CHARGE_TIMER1 + CHARGE_DEPARTURE, CHARGE_TIMER1, true));
put(ProfileKey.TIMER2, new TimedChannel(CHARGE_TIMER2 + CHARGE_DEPARTURE, CHARGE_TIMER2, true));
put(ProfileKey.TIMER3, new TimedChannel(CHARGE_TIMER3 + CHARGE_DEPARTURE, CHARGE_TIMER3, true));
put(ProfileKey.OVERRIDE, new TimedChannel(CHARGE_OVERRIDE + CHARGE_DEPARTURE, CHARGE_OVERRIDE, false));
}
};
@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));
});
}
};
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));
}
}

View File

@ -1,299 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey.*;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.*;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.ChargingMode;
import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.ChargingPreference;
import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.ChargeProfile;
import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.ChargingWindow;
import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.Timer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.WeeklyPlanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonSyntaxException;
/**
* The {@link ChargeProfileWrapper} Wrapper for ChargeProfiles
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - add ChargeProfileActions
*/
@NonNullByDefault
public class ChargeProfileWrapper {
private static final Logger LOGGER = LoggerFactory.getLogger(ChargeProfileWrapper.class);
public enum ProfileType {
WEEKLY,
TWO_TIMES,
EMPTY
}
public enum ProfileKey {
CLIMATE,
TIMER1,
TIMER2,
TIMER3,
TIMER4,
OVERRIDE,
WINDOWSTART,
WINDOWEND
}
protected final ProfileType type;
private Optional<ChargingMode> mode = Optional.empty();
private Optional<ChargingPreference> preference = 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 static Optional<ChargeProfileWrapper> fromJson(final String content) {
try {
final ChargeProfile cp = Converter.getGson().fromJson(content, ChargeProfile.class);
if (cp != null) {
return Optional.of(new ChargeProfileWrapper(cp));
}
} catch (JsonSyntaxException jse) {
LOGGER.debug("ChargeProfile unparsable: {}", content);
}
return Optional.empty();
}
private ChargeProfileWrapper(final ChargeProfile profile) {
final WeeklyPlanner planner;
if (profile.weeklyPlanner != null) {
type = ProfileType.WEEKLY;
planner = profile.weeklyPlanner;
} else if (profile.twoTimesTimer != null) {
type = ProfileType.TWO_TIMES;
planner = profile.twoTimesTimer;
// timer days not supported
} else {
type = ProfileType.EMPTY;
return;
}
setPreference(planner.chargingPreferences);
setMode(planner.chargingMode);
setEnabled(CLIMATE, planner.climatizationEnabled);
addTimer(TIMER1, planner.timer1);
addTimer(TIMER2, planner.timer2);
if (planner.preferredChargingWindow != null) {
addTime(WINDOWSTART, planner.preferredChargingWindow.startTime);
addTime(WINDOWEND, planner.preferredChargingWindow.endTime);
} else {
preference.ifPresent(pref -> {
if (ChargingPreference.CHARGING_WINDOW.equals(pref)) {
addTime(WINDOWSTART, null);
addTime(WINDOWEND, null);
}
});
}
if (isWeekly()) {
addTimer(TIMER3, planner.timer3);
addTimer(OVERRIDE, planner.overrideTimer);
}
}
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 void setMode(final @Nullable String mode) {
if (mode != null) {
try {
this.mode = Optional.of(ChargingMode.valueOf(mode));
return;
} catch (IllegalArgumentException iae) {
LOGGER.warn("unexpected value for chargingMode: {}", mode);
}
}
this.mode = Optional.empty();
}
public @Nullable String getPreference() {
return preference.map(pref -> pref.name()).orElse(null);
}
public void setPreference(final @Nullable String preference) {
if (preference != null) {
try {
this.preference = Optional.of(ChargingPreference.valueOf(preference));
return;
} catch (IllegalArgumentException iae) {
LOGGER.warn("unexpected value for chargingPreference: {}", preference);
}
}
this.preference = Optional.empty();
}
public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
return daysOfWeek.get(key);
}
public void setDays(final ProfileKey key, final @Nullable Set<DayOfWeek> days) {
if (days == null) {
daysOfWeek.remove(key);
} else {
daysOfWeek.put(key, days);
}
}
public void setDayEnabled(final ProfileKey key, final DayOfWeek day, final boolean enabled) {
final Set<DayOfWeek> days = daysOfWeek.get(key);
if (days == null) {
daysOfWeek.put(key, enabled ? EnumSet.of(day) : EnumSet.noneOf(DayOfWeek.class));
} else {
if (enabled) {
days.add(day);
} else {
days.remove(day);
}
}
}
public @Nullable LocalTime getTime(final ProfileKey key) {
return times.get(key);
}
public void setTime(final ProfileKey key, @Nullable LocalTime time) {
if (time == null) {
times.remove(key);
} else {
times.put(key, time);
}
}
public String getJson() {
final ChargeProfile profile = new ChargeProfile();
final WeeklyPlanner planner = new WeeklyPlanner();
preference.ifPresent(pref -> planner.chargingPreferences = pref.name());
planner.climatizationEnabled = isEnabled(CLIMATE);
preference.ifPresent(pref -> {
if (ChargingPreference.CHARGING_WINDOW.equals(pref)) {
planner.chargingMode = getMode();
final LocalTime start = getTime(WINDOWSTART);
final LocalTime end = getTime(WINDOWEND);
if (start != null || end != null) {
planner.preferredChargingWindow = new ChargingWindow();
planner.preferredChargingWindow.startTime = start == null ? null : start.format(TIME_FORMATER);
planner.preferredChargingWindow.endTime = end == null ? null : end.format(TIME_FORMATER);
}
}
});
planner.timer1 = getTimer(TIMER1);
planner.timer2 = getTimer(TIMER2);
if (isWeekly()) {
planner.timer3 = getTimer(TIMER3);
planner.overrideTimer = getTimer(OVERRIDE);
profile.weeklyPlanner = planner;
} else if (isTwoTimes()) {
profile.twoTimesTimer = planner;
}
return Converter.getGson().toJson(profile);
}
private void addTime(final ProfileKey key, @Nullable final String time) {
try {
times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(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);
if (isWeekly()) {
daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
}
} else {
enabled.put(key, timer.timerEnabled);
addTime(key, timer.departureTime);
if (isWeekly()) {
final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
if (timer.weekdays != null) {
for (String day : timer.weekdays) {
try {
daySet.add(DayOfWeek.valueOf(day));
} catch (IllegalArgumentException iae) {
LOGGER.warn("unexpected value for {} day: {}", key.name(), day);
}
}
}
daysOfWeek.put(key, daySet);
}
}
}
private @Nullable Timer getTimer(final ProfileKey key) {
final Timer timer = new Timer();
timer.timerEnabled = enabled.get(key);
final LocalTime time = times.get(key);
timer.departureTime = time == null ? null : time.format(TIME_FORMATER);
if (isWeekly()) {
final Set<DayOfWeek> days = daysOfWeek.get(key);
if (days != null) {
timer.weekdays = new ArrayList<>();
for (DayOfWeek day : days) {
timer.weekdays.add(day.name());
}
}
}
return timer.timerEnabled == null && timer.departureTime == null && timer.weekdays == null ? null : timer;
}
private boolean isWeekly() {
return ProfileType.WEEKLY.equals(type);
}
private boolean isTwoTimes() {
return ProfileType.TWO_TIMES.equals(type);
}
}

View File

@ -1,96 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.UnDefType;
/**
* The {@link Constants} General Constant Definitions
*
* @author Bernd Weymann - Initial contribution
* @author Norbert Truchsess - contributor
*/
@NonNullByDefault
public class Constants {
// For Vehicle Status
public static final String OK = "Ok";
public static final String ACTIVE = "Active";
public static final String NOT_ACTIVE = "Not Active";
public static final String NO_ENTRIES = "No Entries";
public static final String OPEN = "Open";
public static final String INVALID = "Invalid";
public static final String CLOSED = "Closed";
public static final String INTERMEDIATE = "Intermediate";
public static final String UNDEF = UnDefType.UNDEF.toFullString();
public static final String UTC_APPENDIX = "-01T12:00:00";
public static final String NULL_DATE = "1900-01-01T00:00:00";
public static final String NULL_TIME = "00:00";
public static final int INT_UNDEF = -1;
public static final Unit<Length> KILOMETRE_UNIT = MetricPrefix.KILO(SIUnits.METRE);
// Services to query
public static final String SERVICES_SUPPORTED = "servicesSupported";
public static final String STATISTICS = "Statistics";
public static final String LAST_DESTINATIONS = "LastDestinations";
// Services in Discovery
public static final String ACTIVATED = "ACTIVATED";
public static final String SUPPORTED = "SUPPORTED";
public static final String NOT_SUPPORTED = "NOT_SUPPORTED";
// 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 ANONYMOUS = "Anonymous";
public static final int MILES_TO_FEET_FACTOR = 5280;
public static final String EMPTY_JSON = "{}";
// 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");
}
};
}

View File

@ -1,302 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import javax.measure.quantity.Length;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributes;
import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributesContainer;
import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleMessages;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CBSMessage;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CCMMessage;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Position;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatusContainer;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link Converter} Conversion Helpers
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class Converter {
public static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
public static final DateTimeFormatter SERVICE_DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter SERVICE_DATE_OUTPUT_PATTERN = DateTimeFormatter.ofPattern("MMM yyyy");
public static final String LOCAL_DATE_INPUT_PATTERN_STRING = "dd.MM.yyyy HH:mm";
public static final DateTimeFormatter LOCAL_DATE_INPUT_PATTERN = DateTimeFormatter
.ofPattern(LOCAL_DATE_INPUT_PATTERN_STRING);
public static final String DATE_INPUT_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ss";
public static final DateTimeFormatter DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern(DATE_INPUT_PATTERN_STRING);
public static final String DATE_INPUT_ZONE_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ssZ";
public static final DateTimeFormatter DATE_INPUT_ZONE_PATTERN = DateTimeFormatter
.ofPattern(DATE_INPUT_ZONE_PATTERN_STRING);
public static final DateTimeFormatter DATE_OUTPUT_PATTERN = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static final Gson GSON = new Gson();
private static final double SCALE = 10;
public static final double MILES_TO_KM_RATIO = 1.60934;
private static final String SPLIT_HYPHEN = "-";
private static final String SPLIT_BRACKET = "\\(";
public static Optional<TimeZoneProvider> timeZoneProvider = Optional.empty();
public static double round(double value) {
return Math.round(value * SCALE) / SCALE;
}
public static String getLocalDateTimeWithoutOffest(@Nullable String input) {
if (input == null) {
return Constants.NULL_DATE;
}
LocalDateTime ldt;
if (input.contains(Constants.PLUS)) {
ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_ZONE_PATTERN);
} else {
ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_PATTERN);
}
return ldt.format(Converter.DATE_INPUT_PATTERN);
}
public static String getLocalDateTime(@Nullable String input) {
if (input == null) {
return Constants.NULL_DATE;
}
LocalDateTime ldt;
if (input.contains(Constants.PLUS)) {
ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_ZONE_PATTERN);
} else {
try {
ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_PATTERN);
} catch (DateTimeParseException dtpe) {
ldt = LocalDateTime.parse(input, Converter.LOCAL_DATE_INPUT_PATTERN);
}
}
ZonedDateTime zdtUTC = ldt.atZone(ZoneId.of("UTC"));
ZonedDateTime zdtLZ;
zdtLZ = zdtUTC.withZoneSameInstant(ZoneId.systemDefault());
if (timeZoneProvider.isPresent()) {
zdtLZ = zdtUTC.withZoneSameInstant(timeZoneProvider.get().getTimeZone());
} else {
zdtLZ = zdtUTC.withZoneSameInstant(ZoneId.systemDefault());
}
return zdtLZ.format(Converter.DATE_INPUT_PATTERN);
}
public static void setTimeZoneProvider(TimeZoneProvider tzp) {
timeZoneProvider = Optional.of(tzp);
}
public static String toTitleCase(@Nullable String input) {
if (input == null) {
return toTitleCase(Constants.UNDEF);
} else {
String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
String converted = toTitleCase(lower, Constants.SPACE);
converted = toTitleCase(converted, SPLIT_HYPHEN);
converted = toTitleCase(converted, SPLIT_BRACKET);
return converted;
}
}
private static String toTitleCase(String input, String splitter) {
String[] arr = input.split(splitter);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (i > 0) {
sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
}
sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
}
return sb.toString().trim();
}
public static String capitalizeFirst(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
public static Gson getGson() {
return GSON;
}
/**
* Measure distance between 2 coordinates
*
* @param sourceLatitude
* @param sourceLongitude
* @param destinationLatitude
* @param destinationLongitude
* @return distance
*/
public static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
double destinationLongitude) {
double earthRadius = 6378.137; // Radius of earth in KM
double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
* Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadius * c;
}
/**
* Easy function but there's some measures behind:
* Guessing the range of the Vehicle on Map. If you can drive x kilometers with your Vehicle it's not feasible to
* project this x km Radius on Map. The roads to be taken are causing some overhead because they are not a straight
* line from Location A to B.
* I've taken some measurements to calculate the overhead factor based on Google Maps
* Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
* Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
* After measuring more distances you'll find out that the outcome is between 70% and 90%. So
*
* This depends also on the roads of a concrete route but this is only a guess without any Route Navigation behind
*
* In future it's foreseen to replace this with BMW RangeMap Service which isn't running at the moment.
*
* @param range
* @return mapping from air-line distance to "real road" distance
*/
public static double guessRangeRadius(double range) {
return range * 0.8;
}
public static State getMiles(QuantityType<Length> qtLength) {
if (qtLength.intValue() == -1) {
return UnDefType.UNDEF;
}
QuantityType<Length> qt = qtLength.toUnit(ImperialUnits.MILE);
if (qt != null) {
return qt;
} else {
LOGGER.debug("Cannot convert {} to miles", qt);
return UnDefType.UNDEF;
}
}
public static int getIndex(String fullString) {
int index = -1;
try {
index = Integer.parseInt(fullString);
} catch (NumberFormatException nfe) {
}
return index;
}
public static String transformLegacyStatus(@Nullable VehicleAttributesContainer vac) {
if (vac != null) {
if (vac.attributesMap != null && vac.vehicleMessages != null) {
VehicleAttributes attributesMap = vac.attributesMap;
VehicleMessages vehicleMessages = vac.vehicleMessages;
// create target objects
VehicleStatusContainer vsc = new VehicleStatusContainer();
VehicleStatus vs = new VehicleStatus();
vsc.vehicleStatus = vs;
vs.mileage = attributesMap.mileage;
vs.doorLockState = attributesMap.doorLockState;
vs.doorDriverFront = attributesMap.doorDriverFront;
vs.doorDriverRear = attributesMap.doorDriverRear;
vs.doorPassengerFront = attributesMap.doorPassengerFront;
vs.doorPassengerRear = attributesMap.doorPassengerRear;
vs.hood = attributesMap.hoodState;
vs.trunk = attributesMap.trunkState;
vs.windowDriverFront = attributesMap.winDriverFront;
vs.windowDriverRear = attributesMap.winDriverRear;
vs.windowPassengerFront = attributesMap.winPassengerFront;
vs.windowPassengerRear = attributesMap.winPassengerRear;
vs.sunroof = attributesMap.sunroofState;
vs.remainingFuel = attributesMap.remainingFuel;
vs.remainingRangeElectric = attributesMap.beRemainingRangeElectricKm;
vs.remainingRangeElectricMls = attributesMap.beRemainingRangeElectricMile;
vs.remainingRangeFuel = attributesMap.beRemainingRangeFuelKm;
vs.remainingRangeFuelMls = attributesMap.beRemainingRangeFuelMile;
vs.remainingFuel = attributesMap.remainingFuel;
vs.chargingLevelHv = attributesMap.chargingLevelHv;
vs.maxRangeElectric = attributesMap.beMaxRangeElectric;
vs.maxRangeElectricMls = attributesMap.beMaxRangeElectricMile;
vs.chargingStatus = attributesMap.chargingHVStatus;
vs.connectionStatus = attributesMap.connectorStatus;
vs.lastChargingEndReason = attributesMap.lastChargingEndReason;
vs.updateTime = attributesMap.updateTimeConverted;
vs.updateReason = attributesMap.lastUpdateReason;
Position p = new Position();
p.lat = attributesMap.gpsLat;
p.lon = attributesMap.gpsLon;
p.heading = attributesMap.heading;
vs.position = p;
final List<CCMMessage> ccml = new ArrayList<CCMMessage>();
if (vehicleMessages != null) {
if (vehicleMessages.ccmMessages != null) {
vehicleMessages.ccmMessages.forEach(entry -> {
CCMMessage ccmM = new CCMMessage();
ccmM.ccmDescriptionShort = entry.text;
ccmM.ccmDescriptionLong = Constants.HYPHEN;
ccmM.ccmMileage = entry.unitOfLengthRemaining;
ccml.add(ccmM);
});
}
}
vs.checkControlMessages = ccml;
final List<CBSMessage> cbsl = new ArrayList<CBSMessage>();
if (vehicleMessages != null) {
if (vehicleMessages.cbsMessages != null) {
vehicleMessages.cbsMessages.forEach(entry -> {
CBSMessage cbsm = new CBSMessage();
cbsm.cbsType = entry.text;
cbsm.cbsDescription = entry.description;
cbsm.cbsDueDate = entry.date;
cbsm.cbsRemainingMileage = entry.unitOfLengthRemaining;
cbsl.add(cbsm);
});
}
}
vs.cbsData = cbsl;
return Converter.getGson().toJson(vsc);
}
}
return Constants.EMPTY_JSON;
}
}

View File

@ -1,47 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link HTTPConstants} class contains fields mapping thing configuration parameters.
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class HTTPConstants {
public static final int HTTP_TIMEOUT_SEC = 10;
public static final String AUTH_HTTP_CLIENT_NAME = "AuthHttpClient";
public static final String CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
public static final String CONTENT_TYPE_JSON_ENCODED = "application/json";
public static final String KEEP_ALIVE = "Keep-Alive";
public static final String CLIENT_ID = "client_id";
public static final String RESPONSE_TYPE = "response_type";
public static final String TOKEN = "token";
public static final String CODE = "code";
public static final String REDIRECT_URI = "redirect_uri";
public static final String AUTHORIZATION = "authorization";
public static final String GRANT_TYPE = "grant_type";
public static final String SCOPE = "scope";
public static final String CREDENTIALS = "Credentials";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String CONTENT_LENGTH = "Content-Length";
public static final String ACCESS_TOKEN = "access_token";
public static final String TOKEN_TYPE = "token_type";
public static final String EXPIRES_IN = "expires_in";
public static final String CHUNKED = "chunked";
}

View File

@ -1,49 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ImageProperties} Properties of current Vehicle Image
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class ImageProperties {
public static final int RETRY_COUNTER = 5;
public int failCounter = 0;
public String viewport = Constants.EMPTY;
public int size = -1;
public ImageProperties(String viewport, int size) {
this.viewport = viewport;
this.size = size;
}
public ImageProperties() {
}
public void failed() {
failCounter++;
}
public boolean failLimitReached() {
return failCounter > RETRY_COUNTER;
}
@Override
public String toString() {
return viewport + size;
}
}

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.handler.RemoteServiceHandler.RemoteService;
import org.openhab.core.types.StateOption;
/**
* Helper class for Remote Service Commands
*
* @author Norbert Truchsess - Initial contribution
*/
@NonNullByDefault
public class RemoteServiceUtils {
private static final Map<String, RemoteService> COMMAND_SERVICES = Stream.of(RemoteService.values())
.collect(Collectors.toUnmodifiableMap(RemoteService::getCommand, service -> service));
private static final Set<RemoteService> ELECTRIC_SERVICES = EnumSet.of(RemoteService.CHARGE_NOW,
RemoteService.CHARGING_CONTROL);
public static Optional<RemoteService> getRemoteService(final String command) {
return Optional.ofNullable(COMMAND_SERVICES.get(command));
}
public static List<StateOption> getOptions(final boolean isElectric) {
return Stream.of(RemoteService.values())
.filter(service -> isElectric ? true : !ELECTRIC_SERVICES.contains(service))
.map(service -> new StateOption(service.getCommand(), service.getLabel()))
.collect(Collectors.toUnmodifiableList());
}
}

View File

@ -1,141 +0,0 @@
/**
* Copyright (c) 2010-2022 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.bmwconnecteddrive.internal.utils;
import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.*;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CBSMessage;
import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
/**
* The {@link VehicleStatusUtils} Data Transfer Object
*
* @author Bernd Weymann - Initial contribution
*/
@NonNullByDefault
public class VehicleStatusUtils {
public static String getNextServiceDate(VehicleStatus vStatus) {
if (vStatus.cbsData == null) {
return Constants.NULL_DATE;
}
if (vStatus.cbsData.isEmpty()) {
return Constants.NULL_DATE;
} else {
LocalDateTime farFuture = LocalDateTime.now().plusYears(100);
LocalDateTime serviceDate = farFuture;
for (int i = 0; i < vStatus.cbsData.size(); i++) {
CBSMessage entry = vStatus.cbsData.get(i);
if (entry.cbsDueDate != null) {
LocalDateTime d = LocalDateTime.parse(entry.cbsDueDate + Constants.UTC_APPENDIX);
if (d.isBefore(serviceDate)) {
serviceDate = d;
}
}
}
if (serviceDate.equals(farFuture)) {
return Constants.NULL_DATE;
} else {
return serviceDate.format(Converter.DATE_INPUT_PATTERN);
}
}
}
public static int getNextServiceMileage(VehicleStatus vStatus) {
if (vStatus.cbsData == null) {
return -1;
}
if (vStatus.cbsData.isEmpty()) {
return -1;
} else {
int serviceMileage = Integer.MAX_VALUE;
for (int i = 0; i < vStatus.cbsData.size(); i++) {
CBSMessage entry = vStatus.cbsData.get(i);
if (entry.cbsRemainingMileage != -1) {
if (entry.cbsRemainingMileage < serviceMileage) {
serviceMileage = entry.cbsRemainingMileage;
}
}
}
if (serviceMileage != Integer.MAX_VALUE) {
return serviceMileage;
} else {
return -1;
}
}
}
public static String checkControlActive(VehicleStatus vStatus) {
if (vStatus.checkControlMessages == null) {
return UNDEF;
}
if (vStatus.checkControlMessages.isEmpty()) {
return NOT_ACTIVE;
} else {
return ACTIVE;
}
}
public static String getUpdateTime(VehicleStatus vStatus) {
if (vStatus.internalDataTimeUTC != null) {
return vStatus.internalDataTimeUTC;
} else if (vStatus.updateTime != null) {
return vStatus.updateTime;
} else {
return Constants.NULL_DATE;
}
}
/**
* Check for certain Windows or Doors DTO object the "Closed" Status
* INVALID values will be ignored
*
* @param dto
* @return Closed if all "Closed", "Open" otherwise
*/
public static String checkClosed(Object dto) {
String overallState = Constants.UNDEF;
for (Field field : dto.getClass().getDeclaredFields()) {
try {
Object d = field.get(dto);
if (d != null) {
String state = d.toString();
// skip invalid entries - they don't apply to this Vehicle
if (!state.equalsIgnoreCase(INVALID)) {
if (state.equalsIgnoreCase(OPEN)) {
overallState = OPEN;
// stop searching for more open items - overall Doors / Windows are OPEN
break;
} else if (state.equalsIgnoreCase(INTERMEDIATE)) {
if (!overallState.equalsIgnoreCase(OPEN)) {
overallState = INTERMEDIATE;
// continue searching - maybe another Door / Window is OPEN
}
} else if (state.equalsIgnoreCase(CLOSED)) {
// at least one valid object needs to be found in order to reply "CLOSED"
if (overallState.equalsIgnoreCase(UNDEF)) {
overallState = CLOSED;
}
}
}
}
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
return Converter.toTitleCase(overallState);
}
}

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="bmwconnecteddrive" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>BMW ConnectedDrive</name>
<description>Provides access to your Vehicle Data via BMW Connected Drive Portal</description>
</binding:binding>

View File

@ -1,34 +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:bmwconnecteddrive:bridge">
<parameter name="userName" type="text" required="true">
<label>Username</label>
<description>BMW Connected Drive Username</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>BMW Connected Drive Password</description>
<context>password</context>
</parameter>
<parameter name="region" type="text" required="true">
<label>Region</label>
<description>Select Region in order to connect to the appropriate BMW Server</description>
<options>
<option value="NORTH_AMERICA">North America</option>
<option value="CHINA">China</option>
<option value="ROW">Rest of the World</option>
</options>
<default>ROW</default>
</parameter>
<parameter name="preferMyBmw" type="boolean" required="false">
<label>Prefer MyBMW API</label>
<description>Prefer *MyBMW* API instead of *BMW Connected Drive*</description>
<advanced>true</advanced>
<default>false</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -1,45 +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:bmwconnecteddrive:vehicle">
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number (VIN)</label>
<description>Unique VIN given by BMW</description>
</parameter>
<parameter name="refreshInterval" type="integer" min="1" unit="min" required="true">
<label>Refresh Interval</label>
<description>Data Refresh Rate for your Vehicle data</description>
<default>5</default>
</parameter>
<parameter name="units" type="text">
<label>Unit Selection</label>
<description>Units are selected via auto-detection but you can overrule</description>
<options>
<option value="AUTODETECT">Auto Detect</option>
<option value="METRIC">Metric</option>
<option value="IMPERIAL">Imperial</option>
</options>
<default>AUTODETECT</default>
</parameter>
<parameter name="imageSize" type="integer">
<label>Image Picture Size</label>
<description>Vehicle Image size for width and length</description>
<default>1024</default>
</parameter>
<parameter name="imageViewport" type="text">
<label>Image Viewport</label>
<description>Viewport for Vehicle Image</description>
<options>
<option value="FRONT">Front View</option>
<option value="REAR">Rear View</option>
<option value="SIDE">Side View</option>
<option value="DASHBOARD">Dashboard View</option>
<option value="DRIVERDOOR">Driver Door View</option>
</options>
<default>FRONT</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -1,253 +0,0 @@
# binding
binding.bmwconnecteddrive.name = BMW ConnectedDrive
binding.bmwconnecteddrive.description = Provides access to your Vehicle Data via BMW Connected Drive Portal
# thing types
thing-type.bmwconnecteddrive.account.label = BMW ConnectedDrive Account
thing-type.bmwconnecteddrive.account.description = Access to BMW ConnectedDrive Portal for a specific user
thing-type.bmwconnecteddrive.bev.label = Electric Vehicle
thing-type.bmwconnecteddrive.bev.description = Battery Electric Vehicle (BEV)
thing-type.bmwconnecteddrive.bev_rex.label = Electric Vehicle with REX
thing-type.bmwconnecteddrive.bev_rex.description = Battery Electric Vehicle with Range Extender (BEV_REX)
thing-type.bmwconnecteddrive.conv.label = Conventional Vehicle
thing-type.bmwconnecteddrive.conv.description = Conventional Fuel Vehicle (CONV)
thing-type.bmwconnecteddrive.phev.label = Plug-In-Hybrid Electric Vehicle
thing-type.bmwconnecteddrive.phev.description = Conventional Fuel Vehicle with supporting Electric Engine (PHEV)
# thing types config
thing-type.config.bmwconnecteddrive.bridge.password.label = Password
thing-type.config.bmwconnecteddrive.bridge.password.description = BMW Connected Drive Password
thing-type.config.bmwconnecteddrive.bridge.preferMyBmw.label = Prefer MyBMW API
thing-type.config.bmwconnecteddrive.bridge.preferMyBmw.description = Prefer *MyBMW* API instead of *BMW Connected Drive*
thing-type.config.bmwconnecteddrive.bridge.region.label = Region
thing-type.config.bmwconnecteddrive.bridge.region.description = Select Region in order to connect to the appropriate BMW Server
thing-type.config.bmwconnecteddrive.bridge.region.option.NORTH_AMERICA = North America
thing-type.config.bmwconnecteddrive.bridge.region.option.CHINA = China
thing-type.config.bmwconnecteddrive.bridge.region.option.ROW = Rest of the World
thing-type.config.bmwconnecteddrive.bridge.userName.label = Username
thing-type.config.bmwconnecteddrive.bridge.userName.description = BMW Connected Drive Username
thing-type.config.bmwconnecteddrive.vehicle.imageSize.label = Image Picture Size
thing-type.config.bmwconnecteddrive.vehicle.imageSize.description = Vehicle Image size for width and length
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.label = Image Viewport
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.description = Viewport for Vehicle Image
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.option.FRONT = Front View
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.option.REAR = Rear View
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.option.SIDE = Side View
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.option.DASHBOARD = Dashboard View
thing-type.config.bmwconnecteddrive.vehicle.imageViewport.option.DRIVERDOOR = Driver Door View
thing-type.config.bmwconnecteddrive.vehicle.refreshInterval.label = Refresh Interval
thing-type.config.bmwconnecteddrive.vehicle.refreshInterval.description = Data Refresh Rate for your Vehicle data
thing-type.config.bmwconnecteddrive.vehicle.units.label = Unit Selection
thing-type.config.bmwconnecteddrive.vehicle.units.description = Units are selected via auto-detection but you can overrule
thing-type.config.bmwconnecteddrive.vehicle.units.option.AUTODETECT = Auto Detect
thing-type.config.bmwconnecteddrive.vehicle.units.option.METRIC = Metric
thing-type.config.bmwconnecteddrive.vehicle.units.option.IMPERIAL = Imperial
thing-type.config.bmwconnecteddrive.vehicle.vin.label = Vehicle Identification Number (VIN)
thing-type.config.bmwconnecteddrive.vehicle.vin.description = Unique VIN given by BMW
# channel group types
channel-group-type.bmwconnecteddrive.charge-values.label = Electric Charging
channel-group-type.bmwconnecteddrive.charge-values.description = Charge Profiles of Vehicle
channel-group-type.bmwconnecteddrive.check-control-values.label = Check Control Messages
channel-group-type.bmwconnecteddrive.check-control-values.description = Show the current active CheckControl Messages
channel-group-type.bmwconnecteddrive.conv-range-values.label = Range Data
channel-group-type.bmwconnecteddrive.conv-range-values.description = Provides Mileage, remaining range and fuel level values
channel-group-type.bmwconnecteddrive.destination-values.label = Destination List
channel-group-type.bmwconnecteddrive.destination-values.description = Shows Your Destinations in a List
channel-group-type.bmwconnecteddrive.door-values.label = Detailed Door Status
channel-group-type.bmwconnecteddrive.door-values.description = Detailed Status of all Doors and Windows
channel-group-type.bmwconnecteddrive.ev-last-trip-values.label = Last Trip Statistics
channel-group-type.bmwconnecteddrive.ev-last-trip-values.description = EV Consumption Values and Distances for the Last Trip
channel-group-type.bmwconnecteddrive.ev-lifetime-values.label = Lifetime Statistics
channel-group-type.bmwconnecteddrive.ev-lifetime-values.description = Consumption Values and Distances over Lifetime
channel-group-type.bmwconnecteddrive.ev-range-values.label = Electric Range Data
channel-group-type.bmwconnecteddrive.ev-range-values.description = Provides Mileage, remaining range and charge level values
channel-group-type.bmwconnecteddrive.ev-vehicle-status.label = Vehicle Status
channel-group-type.bmwconnecteddrive.ev-vehicle-status.description = Provides Status of Doors, Windows, Lock State, Service and Check Control Messages
channel-group-type.bmwconnecteddrive.hybrid-last-trip-values.label = Last Trip Statistics
channel-group-type.bmwconnecteddrive.hybrid-last-trip-values.description = Hybrid Consumption Values and Distances for the Last Trip
channel-group-type.bmwconnecteddrive.hybrid-lifetime-values.label = Lifetime Statistics
channel-group-type.bmwconnecteddrive.hybrid-lifetime-values.description = Consumption Values and Distances over Lifetime
channel-group-type.bmwconnecteddrive.hybrid-range-values.label = Hybrid Range Data
channel-group-type.bmwconnecteddrive.hybrid-range-values.description = Provides Mileage, remaining range and fuel and charge level values
channel-group-type.bmwconnecteddrive.image-values.label = Vehicle Image
channel-group-type.bmwconnecteddrive.image-values.description = Provides an Image of your Vehicle
channel-group-type.bmwconnecteddrive.location-values.label = Vehicle Location
channel-group-type.bmwconnecteddrive.location-values.description = Coordinates and Heading of the Vehcile
channel-group-type.bmwconnecteddrive.remote-services.label = Remote Services
channel-group-type.bmwconnecteddrive.remote-services.description = Services can be executed via BMW Server like Door lock/unlock, Air Conditioning and more
channel-group-type.bmwconnecteddrive.service-values.label = Vehicle Services
channel-group-type.bmwconnecteddrive.service-values.description = All future Service schedules
channel-group-type.bmwconnecteddrive.vehicle-status.label = Vehicle Status
channel-group-type.bmwconnecteddrive.vehicle-status.description = Provides Status of Doors, Windows, Lock State, Service and Check Control Messages
# channel types
channel-type.bmwconnecteddrive.average-combined-consumption-channel.label = Avg. Combined Consumption
channel-type.bmwconnecteddrive.average-combined-consumption-channel.description = Average combined consumption in liter per 100 km/mi
channel-type.bmwconnecteddrive.average-combined-consumption-channel.label = Avg. Combined Consumption
channel-type.bmwconnecteddrive.average-combined-consumption-channel.description = Average combined consumption in liter per 100 km/mi
channel-type.bmwconnecteddrive.average-consumption-channel.label = Avg. Power Consumption
channel-type.bmwconnecteddrive.average-consumption-channel.description = Average Combined Consumption electric power consumption per 100 km/mi
channel-type.bmwconnecteddrive.average-consumption-channel.label = Avg. Power Consumption
channel-type.bmwconnecteddrive.average-consumption-channel.description = Average electric power consumption per 100 km/mi
channel-type.bmwconnecteddrive.average-recuperation-channel.label = Avg. Combined Consumption Recuperation
channel-type.bmwconnecteddrive.average-recuperation-channel.description = Average electric recuperation per 100 km/mi
channel-type.bmwconnecteddrive.average-recuperation-channel.label = Avg. Recuperation
channel-type.bmwconnecteddrive.average-recuperation-channel.description = Average electric recuperation per 100 km/mi
channel-type.bmwconnecteddrive.charging-remaining-channel.label = Remaining Charging Time
channel-type.bmwconnecteddrive.charging-status-channel.label = Charging Status
channel-type.bmwconnecteddrive.check-control-channel.label = Check Control
channel-type.bmwconnecteddrive.checkcontrol-details-channel.label = CheckControl Details
channel-type.bmwconnecteddrive.checkcontrol-mileage-channel.label = Mileage Occurrence
channel-type.bmwconnecteddrive.checkcontrol-name-channel.label = CheckControl Description
channel-type.bmwconnecteddrive.destination-gps-channel.label = GPS Coordinates
channel-type.bmwconnecteddrive.destination-name-channel.label = Name
channel-type.bmwconnecteddrive.distance-channel.label = Last Trip Distance
channel-type.bmwconnecteddrive.distance-since-charging-channel.label = Distance since Charge
channel-type.bmwconnecteddrive.distance-since-charging-channel.description = Total distance driven since last charging
channel-type.bmwconnecteddrive.doors-channel.label = Overall Door Status
channel-type.bmwconnecteddrive.driver-front-channel.label = Driver Door
channel-type.bmwconnecteddrive.driver-rear-channel.label = Driver Door Rear
channel-type.bmwconnecteddrive.gps-channel.label = GPS Coordinates
channel-type.bmwconnecteddrive.heading-channel.label = Heading Angle
channel-type.bmwconnecteddrive.hood-channel.label = Hood
channel-type.bmwconnecteddrive.image-size-channel.label = Image Size
channel-type.bmwconnecteddrive.image-view-channel.label = Image Viewport
channel-type.bmwconnecteddrive.image-view-channel.command.option.FRONT = Front View
channel-type.bmwconnecteddrive.image-view-channel.command.option.REAR = Rear View
channel-type.bmwconnecteddrive.image-view-channel.command.option.SIDE = Side View
channel-type.bmwconnecteddrive.image-view-channel.command.option.DASHBOARD = Dashboard View
channel-type.bmwconnecteddrive.image-view-channel.command.option.DRIVERDOOR = Driver Door View
channel-type.bmwconnecteddrive.last-update-channel.label = Last Status Timestamp
channel-type.bmwconnecteddrive.last-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
channel-type.bmwconnecteddrive.last-update-reason-channel.label = Last Status Timestamp Reason
channel-type.bmwconnecteddrive.lock-channel.label = Doors Locked
channel-type.bmwconnecteddrive.mileage-channel.label = Total Distance Driven
channel-type.bmwconnecteddrive.next-service-date-channel.label = Next Service Date
channel-type.bmwconnecteddrive.next-service-date-channel.state.pattern = %1$tb %1$tY
channel-type.bmwconnecteddrive.next-service-mileage-channel.label = Mileage Till Next Service
channel-type.bmwconnecteddrive.override-departure-channel.label = OT Departure Time
channel-type.bmwconnecteddrive.override-departure-channel.description = Departure time for override timer
channel-type.bmwconnecteddrive.override-departure-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.override-enabled-channel.label = OT Enabled
channel-type.bmwconnecteddrive.override-enabled-channel.description = Override timer enabled
channel-type.bmwconnecteddrive.passenger-front-channel.label = Passenger Door
channel-type.bmwconnecteddrive.passenger-rear-channel.label = Passenger Door Rear
channel-type.bmwconnecteddrive.plug-connection-channel.label = Plug Connection Status
channel-type.bmwconnecteddrive.png-channel.label = Rendered Vehicle Image
channel-type.bmwconnecteddrive.profile-climate-channel.label = A/C at Departure Time
channel-type.bmwconnecteddrive.profile-mode-channel.label = Charge Mode
channel-type.bmwconnecteddrive.profile-mode-channel.description = Mode for selecting immediate or delyed charging
channel-type.bmwconnecteddrive.profile-mode-channel.command.option.IMMEDIATE_CHARGING = Immediate Charging
channel-type.bmwconnecteddrive.profile-mode-channel.command.option.DELAYED_CHARGING = Prefer Charging in Charging Window
channel-type.bmwconnecteddrive.profile-prefs-channel.label = Charge Preferences
channel-type.bmwconnecteddrive.profile-prefs-channel.description = Preferences for delayed charging
channel-type.bmwconnecteddrive.profile-prefs-channel.command.option.NO_PRESELECTION = No Preference
channel-type.bmwconnecteddrive.profile-prefs-channel.command.option.CHARGING_WINDOW = Charging Window
channel-type.bmwconnecteddrive.range-electric-channel.label = Electric Range
channel-type.bmwconnecteddrive.range-electric-max-channel.label = Electric Range if Fully Charged
channel-type.bmwconnecteddrive.range-fuel-channel.label = Fuel Range
channel-type.bmwconnecteddrive.range-hybrid-channel.label = Hybrid Range
channel-type.bmwconnecteddrive.range-hybrid-max-channel.label = Hybrid Range if Fully Charged
channel-type.bmwconnecteddrive.range-radius-electric-channel.label = Electric Range Radius if Fully Charged
channel-type.bmwconnecteddrive.range-radius-electric-max-channel.label = Electric Range Radius
channel-type.bmwconnecteddrive.range-radius-fuel-channel.label = Fuel Range Radius
channel-type.bmwconnecteddrive.range-radius-hybrid-channel.label = Hybrid Range Radius
channel-type.bmwconnecteddrive.range-radius-hybrid-max-channel.label = Hybrid Range Radius if Fully Charged
channel-type.bmwconnecteddrive.remaining-fuel-channel.label = Remaining Fuel
channel-type.bmwconnecteddrive.remote-command-channel.label = Remote Command
channel-type.bmwconnecteddrive.remote-state-channel.label = Service Execution State
channel-type.bmwconnecteddrive.service-date-channel.label = Service Date
channel-type.bmwconnecteddrive.service-date-channel.state.pattern = %1$tb %1$tY
channel-type.bmwconnecteddrive.service-details-channel.label = Service Details
channel-type.bmwconnecteddrive.service-mileage-channel.label = Mileage till Service
channel-type.bmwconnecteddrive.service-name-channel.label = Service Name
channel-type.bmwconnecteddrive.single-longest-distance-channel.label = Longest 1-Charge Distance
channel-type.bmwconnecteddrive.soc-channel.label = Battery Charge Level
channel-type.bmwconnecteddrive.soc-max-channel.label = Max Battery Capacity
channel-type.bmwconnecteddrive.sunroof-channel.label = Sunroof
channel-type.bmwconnecteddrive.timer1-day-fri-channel.label = T1 Friday
channel-type.bmwconnecteddrive.timer1-day-fri-channel.description = Friday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-mon-channel.label = T1 Monday
channel-type.bmwconnecteddrive.timer1-day-mon-channel.description = Monday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-sat-channel.label = T1 Saturday
channel-type.bmwconnecteddrive.timer1-day-sat-channel.description = Saturday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-sun-channel.label = T1 Sunday
channel-type.bmwconnecteddrive.timer1-day-sun-channel.description = Sunday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-thu-channel.label = T1 Thursday
channel-type.bmwconnecteddrive.timer1-day-thu-channel.description = Thursday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-tue-channel.label = T1 Tuesday
channel-type.bmwconnecteddrive.timer1-day-tue-channel.description = Tuesday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-day-wed-channel.label = T1 Wednesday
channel-type.bmwconnecteddrive.timer1-day-wed-channel.description = Wednesday scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-days-channel.label = T1 Days
channel-type.bmwconnecteddrive.timer1-days-channel.description = Days scheduled for timer 1
channel-type.bmwconnecteddrive.timer1-departure-channel.label = T1 Departure Time
channel-type.bmwconnecteddrive.timer1-departure-channel.description = Departure time for regular schedule timer 1
channel-type.bmwconnecteddrive.timer1-departure-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.timer1-enabled-channel.label = T1 Enabled
channel-type.bmwconnecteddrive.timer1-enabled-channel.description = Timer 1 enabled
channel-type.bmwconnecteddrive.timer2-day-fri-channel.label = T2 Friday
channel-type.bmwconnecteddrive.timer2-day-fri-channel.description = Friday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-mon-channel.label = T2 Monday
channel-type.bmwconnecteddrive.timer2-day-mon-channel.description = Monday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-sat-channel.label = T2 Saturday
channel-type.bmwconnecteddrive.timer2-day-sat-channel.description = Saturday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-sun-channel.label = T2 Sunday
channel-type.bmwconnecteddrive.timer2-day-sun-channel.description = Sunday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-thu-channel.label = T2 Thursday
channel-type.bmwconnecteddrive.timer2-day-thu-channel.description = Thursday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-tue-channel.label = T2 Tuesday
channel-type.bmwconnecteddrive.timer2-day-tue-channel.description = Tuesday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-day-wed-channel.label = T2 Wednesday
channel-type.bmwconnecteddrive.timer2-day-wed-channel.description = Wednesday scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-days-channel.label = T2 Days
channel-type.bmwconnecteddrive.timer2-days-channel.description = Days scheduled for timer 2
channel-type.bmwconnecteddrive.timer2-departure-channel.label = T2 Departure Time
channel-type.bmwconnecteddrive.timer2-departure-channel.description = Departure time for regular schedule timer 2
channel-type.bmwconnecteddrive.timer2-departure-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.timer2-enabled-channel.label = T2 Enabled
channel-type.bmwconnecteddrive.timer2-enabled-channel.description = Timer 2 enabled
channel-type.bmwconnecteddrive.timer3-day-fri-channel.label = T3 Friday
channel-type.bmwconnecteddrive.timer3-day-fri-channel.description = Friday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-mon-channel.label = T3 Monday
channel-type.bmwconnecteddrive.timer3-day-mon-channel.description = Monday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-sat-channel.label = T3 Saturday
channel-type.bmwconnecteddrive.timer3-day-sat-channel.description = Saturday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-sun-channel.label = T3 Sunday
channel-type.bmwconnecteddrive.timer3-day-sun-channel.description = Sunday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-thu-channel.label = T3 Thursday
channel-type.bmwconnecteddrive.timer3-day-thu-channel.description = Thursday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-tue-channel.label = T3 Tuesday
channel-type.bmwconnecteddrive.timer3-day-tue-channel.description = Tuesday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-day-wed-channel.label = T3 Wednesday
channel-type.bmwconnecteddrive.timer3-day-wed-channel.description = Wednesday scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-days-channel.label = T3 Days
channel-type.bmwconnecteddrive.timer3-days-channel.description = Days scheduled for timer 3
channel-type.bmwconnecteddrive.timer3-departure-channel.label = T3 Departure Time
channel-type.bmwconnecteddrive.timer3-departure-channel.description = Departure time for regular schedule timer 3
channel-type.bmwconnecteddrive.timer3-departure-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.timer3-enabled-channel.label = T3 Enabled
channel-type.bmwconnecteddrive.timer3-enabled-channel.description = Timer 3 enabled
channel-type.bmwconnecteddrive.total-driven-distance-channel.label = Total Electric Distance
channel-type.bmwconnecteddrive.trip-date-time-channel.label = Date and Time
channel-type.bmwconnecteddrive.trip-date-time-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
channel-type.bmwconnecteddrive.trip-duration-channel.label = Last Trip Duration
channel-type.bmwconnecteddrive.trunk-channel.label = Trunk
channel-type.bmwconnecteddrive.window-driver-front-channel.label = Driver Window
channel-type.bmwconnecteddrive.window-driver-rear-channel.label = Driver Rear Window
channel-type.bmwconnecteddrive.window-end-channel.label = Window End Time
channel-type.bmwconnecteddrive.window-end-channel.description = End time of charging window
channel-type.bmwconnecteddrive.window-end-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.window-passenger-front-channel.label = Passenger Window
channel-type.bmwconnecteddrive.window-passenger-rear-channel.label = Passenger Rear Window
channel-type.bmwconnecteddrive.window-rear-channel.label = Rear Window
channel-type.bmwconnecteddrive.window-start-channel.label = Window Start Time
channel-type.bmwconnecteddrive.window-start-channel.description = Start time of charging window
channel-type.bmwconnecteddrive.window-start-channel.state.pattern = %1$tH:%1$tM
channel-type.bmwconnecteddrive.windows-channel.label = Overall Window Status

View File

@ -1,278 +0,0 @@
# Binding
binding.bmwconnecteddrive.name = BMW ConnectedDrive
binding.bmwconnecteddrive.description = Zeigt die Fahrzeugdaten über das BMW ConnectedDrive Portal
# bridge types
thing-type.bmwconnecteddrive.account.label = BMW ConnectedDrive Benutzerkonto
thing-type.bmwconnecteddrive.account.description = Zugriff auf das BMW ConnectedDrive Portal für einen Benutzer
thing-type.config.bmwconnecteddrive.account.userName.label = Benutzername
thing-type.config.bmwconnecteddrive.account.userName.description = Benutzername für das ConnectedDrive Portal
thing-type.config.bmwconnecteddrive.account.password.label = Passwort
thing-type.config.bmwconnecteddrive.account.password.description = Passwort für das ConnectedDrive Portal
thing-type.config.bmwconnecteddrive.account.region.label = Region
thing-type.config.bmwconnecteddrive.account.region.description = Auswahl Ihrer Region zur Verbindung mit dem korrekten BMW Server
thing-type.config.bmwconnecteddrive.account.region.option.NORTH_AMERICA = Nordamerika
thing-type.config.bmwconnecteddrive.account.region.option.CHINA = China
thing-type.config.bmwconnecteddrive.account.region.option.ROW = Rest der Welt
thing-type.config.bmwconnecteddrive.account.preferMyBmw.label = Benutze MyBMW API
thing-type.config.bmwconnecteddrive.account.preferMyBmw.description = Benutzung des MyBMW API anstelle der BMW ConnectedDrive API
# thing types
thing-type.bmwconnecteddrive.bev_rex.label = Elektrofahrzeug mit REX
thing-type.bmwconnecteddrive.bev_rex.description = Elektrofahrzeug mit Range Extender (bev_rex)
thing-type.config.bmwconnecteddrive.bev_rex.vin.label = Fahrzeug Identifikationsnummer (VIN)
thing-type.config.bmwconnecteddrive.bev_rex.vin.description = VIN des Fahrzeugs
thing-type.config.bmwconnecteddrive.bev_rex.refreshInterval.label = Datenaktualisierung in Minuten
thing-type.config.bmwconnecteddrive.bev_rex.refreshInterval.description = Rate der Datenaktualisierung Ihres Fahrzeugs
thing-type.config.bmwconnecteddrive.bev_rex.units.label = Einheiten
thing-type.config.bmwconnecteddrive.bev_rex.units.description = Automatische oder direkte Auswahl der Einheiten
thing-type.config.bmwconnecteddrive.bev_rex.units.option.AUTODETECT = Automatische Auswahl
thing-type.config.bmwconnecteddrive.bev_rex.units.option.IMPERIAL = Angloamerikanisches System
thing-type.config.bmwconnecteddrive.bev_rex.units.option.METRIC = Metrisches System
thing-type.config.bmwconnecteddrive.bev_rex.imageSize.label = Bildgröße
thing-type.config.bmwconnecteddrive.bev_rex.imageSize.description = Bildgröße des Fahrzeugs für Länge und Breite
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.label = Bild Ansicht
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.description = Ansicht des Fahrzeugs
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.option.FRONT = Vorderansicht
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.option.REAR = Rückansicht
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.option.SIDE = Seitenansicht
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.option.DASHBOARD = Innenansicht Armaturen
thing-type.config.bmwconnecteddrive.bev_rex.imageViewport.option.DRIVERDOOR = Seitenansicht Fahrertür
thing-type.bmwconnecteddrive.bev.label = Elektrofahrzeug
thing-type.bmwconnecteddrive.bev.description = Batterieelektrisches Fahrzeug (bev)
thing-type.config.bmwconnecteddrive.bev.vin.label = Fahrzeug Identifikationsnummer (VIN)
thing-type.config.bmwconnecteddrive.bev.vin.description = VIN des Fahrzeugs
thing-type.config.bmwconnecteddrive.bev.refreshInterval.label = Datenaktualisierung in Minuten
thing-type.config.bmwconnecteddrive.bev.refreshInterval.description = Rate der Datenaktualisierung Ihres Fahrzeugs
thing-type.config.bmwconnecteddrive.bev.units.label = Einheiten
thing-type.config.bmwconnecteddrive.bev.units.description = Automatische oder direkte Auswahl der Einheiten
thing-type.config.bmwconnecteddrive.bev.units.option.AUTODETECT = Automatische Auswahl
thing-type.config.bmwconnecteddrive.bev.units.option.IMPERIAL = Angloamerikanisches System
thing-type.config.bmwconnecteddrive.bev.units.option.METRIC = Metrisches System
thing-type.config.bmwconnecteddrive.bev.imageSize.label = Bildgröße
thing-type.config.bmwconnecteddrive.bev.imageSize.description = Bildgröße des Fahrzeugs für Länge und Breite
thing-type.config.bmwconnecteddrive.bev.imageViewport.label = Bild Ansicht
thing-type.config.bmwconnecteddrive.bev.imageViewport.description = Ansicht des Fahrzeugs
thing-type.config.bmwconnecteddrive.bev.imageViewport.option.FRONT = Vorderansicht
thing-type.config.bmwconnecteddrive.bev.imageViewport.option.REAR = Rückansicht
thing-type.config.bmwconnecteddrive.bev.imageViewport.option.SIDE = Seitenansicht
thing-type.config.bmwconnecteddrive.bev.imageViewport.option.DASHBOARD = Innenansicht Armaturen
thing-type.config.bmwconnecteddrive.bev.imageViewport.option.DRIVERDOOR = Seitenansicht Fahrertür
thing-type.bmwconnecteddrive.phev.label = Plug-in-Hybrid Elektrofahrzeug
thing-type.bmwconnecteddrive.phev.description = Konventionelles Fahrzeug mit Elektromotor (phev)
thing-type.config.bmwconnecteddrive.phev.vin.label = Fahrzeug Identifikationsnummer (VIN)
thing-type.config.bmwconnecteddrive.phev.vin.description = VIN des Fahrzeugs
thing-type.config.bmwconnecteddrive.phev.refreshInterval.label = Datenaktualisierung in Minuten
thing-type.config.bmwconnecteddrive.phev.refreshInterval.description = Rate der Datenaktualisierung Ihres Fahrzeugs
thing-type.config.bmwconnecteddrive.phev.units.label = Einheiten
thing-type.config.bmwconnecteddrive.phev.units.description = Automatische oder direkte Auswahl der Einheiten
thing-type.config.bmwconnecteddrive.phev.units.option.AUTODETECT = Automatische Auswahl
thing-type.config.bmwconnecteddrive.phev.units.option.IMPERIAL = Angloamerikanisches System
thing-type.config.bmwconnecteddrive.phev.units.option.METRIC = Metrisches System
thing-type.config.bmwconnecteddrive.phev.imageSize.label = Bildgröße
thing-type.config.bmwconnecteddrive.phev.imageSize.description = Bildgröße des Fahrzeugs für Länge und Breite
thing-type.config.bmwconnecteddrive.phev.imageViewport.label = Bild Ansicht
thing-type.config.bmwconnecteddrive.phev.imageViewport.description = Ansicht des Fahrzeugs
thing-type.config.bmwconnecteddrive.phev.imageViewport.option.FRONT = Vorderansicht
thing-type.config.bmwconnecteddrive.phev.imageViewport.option.REAR = Rückansicht
thing-type.config.bmwconnecteddrive.phev.imageViewport.option.SIDE = Seitenansicht
thing-type.config.bmwconnecteddrive.phev.imageViewport.option.DASHBOARD = Innenansicht Armaturen
thing-type.config.bmwconnecteddrive.phev.imageViewport.option.DRIVERDOOR = Seitenansicht Fahrertür
thing-type.bmwconnecteddrive.conv.label = Konventionelles Fahrzeug
thing-type.bmwconnecteddrive.conv.description = Konventionelles Benzin/Diesel Fahrzeug (conv)
thing-type.config.bmwconnecteddrive.conv.vin.label = Fahrzeug Identifikationsnummer (VIN)
thing-type.config.bmwconnecteddrive.conv.vin.description = VIN des Fahrzeugs
thing-type.config.bmwconnecteddrive.conv.refreshInterval.label = Datenaktualisierung in Minuten
thing-type.config.bmwconnecteddrive.conv.refreshInterval.description = Rate der Datenaktualisierung Ihres Fahrzeugs
thing-type.config.bmwconnecteddrive.conv.units.label = Einheiten
thing-type.config.bmwconnecteddrive.conv.units.description = Automatische oder direkte Auswahl der Einheiten
thing-type.config.bmwconnecteddrive.conv.units.option.AUTODETECT = Automatische Auswahl
thing-type.config.bmwconnecteddrive.conv.units.option.IMPERIAL = Angloamerikanisches System
thing-type.config.bmwconnecteddrive.conv.units.option.METRIC = Metrisches System
thing-type.config.bmwconnecteddrive.conv.imageSize.label = Bildgröße
thing-type.config.bmwconnecteddrive.conv.imageSize.description = Bildgröße des Fahrzeugs für Länge und Breite
thing-type.config.bmwconnecteddrive.conv.imageViewport.label = Bild Ansicht
thing-type.config.bmwconnecteddrive.conv.imageViewport.description = Ansicht des Fahrzeugs
thing-type.config.bmwconnecteddrive.conv.imageViewport.option.FRONT = Vorderansicht
thing-type.config.bmwconnecteddrive.conv.imageViewport.option.REAR = Rückansicht
thing-type.config.bmwconnecteddrive.conv.imageViewport.option.SIDE = Seitenansicht
thing-type.config.bmwconnecteddrive.conv.imageViewport.option.DASHBOARD = Innenansicht Armaturen
thing-type.config.bmwconnecteddrive.conv.imageViewport.option.DRIVERDOOR = Seitenansicht Fahrertür
# Channel Groups
channel-group-type.bmwconnecteddrive.charge-values.label = Elektrisches Laden
channel-group-type.bmwconnecteddrive.charge-values.description = Ladezustand und Ladeprofile des Fahrzeugs
channel-group-type.bmwconnecteddrive.ev-lifetime-values.label = Gesamtlaufzeit Statistik
channel-group-type.bmwconnecteddrive.ev-lifetime-values.description = Verbrauchswerte und zurückgelegte Strecken über die Fahrzeug-Gesamtlaufzeit
channel-group-type.bmwconnecteddrive.hybrid-lifetime-values.label = Gesamtlaufzeit Statistik
channel-group-type.bmwconnecteddrive.hybrid-lifetime-values.description = Verbrauchswerte und zurückgelegte Strecken über die Fahrzeug-Gesamtlaufzeit
channel-group-type.bmwconnecteddrive.ev-last-trip-values.label = Statistik der letzten Fahrt
channel-group-type.bmwconnecteddrive.ev-last-trip-values.description = Verbrauchswerte und zurück gelegte Strecke der letzten Fahrt
channel-group-type.bmwconnecteddrive.hybrid-last-trip-values.label = Statistik der letzten Fahrt
channel-group-type.bmwconnecteddrive.hybrid-last-trip-values.description = Verbrauchswerte und zurück gelegte Strecke der letzten Fahrt
channel-group-type.bmwconnecteddrive.ev-range-values.label = Elektrische Reichweite
channel-group-type.bmwconnecteddrive.ev-range-values.description = Tachostand, Reichweiten und Ladestand des Fahrzeugs
channel-group-type.bmwconnecteddrive.check-control-values.label = Warnungen
channel-group-type.bmwconnecteddrive.check-control-values.description = Aktuelle Warungen des Fahrzeugs
channel-group-type.bmwconnecteddrive.service-values.label = Wartung
channel-group-type.bmwconnecteddrive.service-values.description = Zukünftige Wartungstermine des Fahrzeugs
channel-group-type.bmwconnecteddrive.conv-range-values.label = Reichweite
channel-group-type.bmwconnecteddrive.conv-range-values.description = Tachostand, Reichweite und Tankfüllung des Fahrzeugs
channel-group-type.bmwconnecteddrive.hybrid-range-values.label = Hybride Reichweite
channel-group-type.bmwconnecteddrive.hybrid-range-values.description = Tachostand, Reichweite, Ladezustand und Tankfüllung des Fahrzeugs
channel-group-type.bmwconnecteddrive.image-values.label = Fahrzeug Bild
channel-group-type.bmwconnecteddrive.image-values.description = Bild des Fahrzeug basierend auf der Ansicht in der Konfiguration
channel-group-type.bmwconnecteddrive.remote-services.label = Fahrzeug Fernsteuerung
channel-group-type.bmwconnecteddrive.remote-services.description = Fernsteuerung des Fahrzeugs über den BMW Server wie Türen schließen / öffnen, Klimasteuerung und mehr
channel-group-type.bmwconnecteddrive.vehicle-status.label = Fahrzeug Zustand
channel-group-type.bmwconnecteddrive.vehicle-status.description = Zustand des Fahrzeugs über Türen, Fenster, abgeschlossen, anstehende Wartung und aktive Warnungen
channel-group-type.bmwconnecteddrive.ev-vehicle-status.label = Fahrzeug Zustand
channel-group-type.bmwconnecteddrive.ev-vehicle-status.description = Zustand des Fahrzeugs über Türen, Fenster, abgeschlossen, anstehende Wartung und aktive Warnungen
channel-group-type.bmwconnecteddrive.location-values.label = Fahrzeug Standort
channel-group-type.bmwconnecteddrive.location-values.description = Koordinaten und Ausrichtung des Fahrzeugs
channel-group-type.bmwconnecteddrive.destination-values.label = Ziele
channel-group-type.bmwconnecteddrive.destination-values.description = Zeigt die gespeicherten Ziele des Fahrzeugs
channel-group-type.bmwconnecteddrive.troubleshoot-control.label = Fehlerbehebung
channel-group-type.bmwconnecteddrive.troubleshoot-control.description = Generiert Daten zur Fehlerbehebung eines Problems
channel-group-type.bmwconnecteddrive.door-values.label = Details aller Türen
channel-group-type.bmwconnecteddrive.door-values.description = Zeigt die Details der Türen und Fenster des Fahrzeugs
# Channel Types
channel-type.bmwconnecteddrive.doors-channel.label = Gesamtzustand der Türen
channel-type.bmwconnecteddrive.windows-channel.label = Gesamtzustand der Fenster
channel-type.bmwconnecteddrive.lock-channel.label = Fahrzeug Abgeschlossen
channel-type.bmwconnecteddrive.next-service-date-channel.label = Nächster Service Termin
channel-type.bmwconnecteddrive.next-service-mileage-channel.label = Nächster Service in Kilometern
channel-type.bmwconnecteddrive.check-control-channel.label = Warnung Aktiv
channel-type.bmwconnecteddrive.charging-status-channel.label = Ladezustand
channel-type.bmwconnecteddrive.plug-connection-channel.label = Ladestecker
channel-type.bmwconnecteddrive.charging-remaining-channel.label = Verbleibende Ladezeit
channel-type.bmwconnecteddrive.last-update-channel.label = Letzte Aktualisierung
channel-type.bmwconnecteddrive.last-update-reason-channel.label = Grund der letzten Aktualisierung
channel-type.bmwconnecteddrive.driver-front-channel.label = Fahrertür
channel-type.bmwconnecteddrive.driver-rear-channel.label = Fahrertür Hinten
channel-type.bmwconnecteddrive.passenger-front-channel.label = Beifahrertür
channel-type.bmwconnecteddrive.passenger-rear-channel.label = Beifahrertür Hinten
channel-type.bmwconnecteddrive.hood-channel.label = Frontklappe
channel-type.bmwconnecteddrive.trunk-channel.label = Heckklappe
channel-type.bmwconnecteddrive.window-driver-front-channel.label = Fahrertür Fenster
channel-type.bmwconnecteddrive.window-driver-rear-channel.label = Fahrertür Hinten Fenster
channel-type.bmwconnecteddrive.window-passenger-front-channel.label = Beifahrertür Fenster
channel-type.bmwconnecteddrive.window-passenger-rear-channel.label = Beifahrertür Hinten Fenster
channel-type.bmwconnecteddrive.window-rear-channel.label = Heckfenster
channel-type.bmwconnecteddrive.sunroof-channel.label = Schiebedach
channel-type.bmwconnecteddrive.mileage-channel.label = Tachostand
channel-type.bmwconnecteddrive.range-hybrid-channel.label = Hybride Reichweite
channel-type.bmwconnecteddrive.range-hybrid-max-channel.label = Hybride Reichweite bei voller Ladung
channel-type.bmwconnecteddrive.range-electric-channel.label = Elektrische Reichweite
channel-type.bmwconnecteddrive.range-electric-max-channel.label = Elektrische Reichweite bei voller Ladung
channel-type.bmwconnecteddrive.soc-channel.label = Batterie Ladestand
channel-type.bmwconnecteddrive.soc-max-channel.label = Maximale Batteriekapazität
channel-type.bmwconnecteddrive.range-fuel-channel.label = Verbrenner Reichweite
channel-type.bmwconnecteddrive.remaining-fuel-channel.label = Tankstand
channel-type.bmwconnecteddrive.range-radius-electric-channel.label = Elektrischer Reichweiten-Radius
channel-type.bmwconnecteddrive.range-radius-electric-max-channel.label = Elektrischer Reichweiten-Radius bei voller Ladung
channel-type.bmwconnecteddrive.range-radius-fuel-channel.label = Verbrenner Reichweiten-Radius
channel-type.bmwconnecteddrive.range-radius-hybrid-channel.label = Hybrider Reichweiten-Radius
channel-type.bmwconnecteddrive.range-radius-hybrid-max-channel.label = Hybrider Reichweiten-Radius bei voller Ladung
channel-type.bmwconnecteddrive.service-name-channel.label = Service
channel-type.bmwconnecteddrive.service-details-channel.label = Service Details
channel-type.bmwconnecteddrive.service-date-channel.label = Service Termin
channel-type.bmwconnecteddrive.service-mileage-channel.label = Service in Kilometern
channel-type.bmwconnecteddrive.checkcontrol-name-channel.label = Warnung
channel-type.bmwconnecteddrive.checkcontrol-details-channel.label = Warnung Details
channel-type.bmwconnecteddrive.checkcontrol-mileage-channel.label = Warnung bei Kilometer
channel-type.bmwconnecteddrive.profile-climate-channel.label = Klimatisierung bei Abfahrt
channel-type.bmwconnecteddrive.profile-mode-channel.label = Ladeprofil
channel-type.bmwconnecteddrive.profile-mode-channel.option.IMMEDIATE_CHARGING = Sofortiges Laden
channel-type.bmwconnecteddrive.profile-mode-channel.option.DELAYED_CHARGING = Laden im Zeitfenster
channel-type.bmwconnecteddrive.profile-prefs-channel.label = Ladeprofil Präferenz
channel-type.bmwconnecteddrive.profile-prefs-channel.option.NO_PRESELECTION = Keine Präferenz
channel-type.bmwconnecteddrive.profile-prefs-channel.option.Charging\ Window = Zeitfenster
channel-type.bmwconnecteddrive.window-start-channel.label = Ladefenster Startzeit
channel-type.bmwconnecteddrive.window-start-hour-channel.label = Ladefenster Startzeit Stunde
channel-type.bmwconnecteddrive.window-start-minute-channel.label = Ladefenster Startzeit Minute
channel-type.bmwconnecteddrive.window-end-channel.label = Ladefenster Endzeit
channel-type.bmwconnecteddrive.window-end-hour-channel.label = Ladefenster Endzeit Stunde
channel-type.bmwconnecteddrive.window-end-minute-channel.label = Ladefenster Endzeit Minute
channel-type.bmwconnecteddrive.timer1-enabled-channel.label = Zeitprofil 1 - Aktiviert
channel-type.bmwconnecteddrive.timer1-departure-channel.label = Zeitprofil 1 - Abfahrtszeit
channel-type.bmwconnecteddrive.timer1-departure-hour-channel.label = Zeitprofil 1 - Abfahrtszeit Stunde
channel-type.bmwconnecteddrive.timer1-departure-minute-channel.label = Zeitprofil 1 - Abfahrtszeit Minute
channel-type.bmwconnecteddrive.timer1-days-channel.label = Zeitprofil 1 - Tage
channel-type.bmwconnecteddrive.timer1-day-mon-channel.label = Zeitprofil 1 - Montag
channel-type.bmwconnecteddrive.timer1-day-tue-channel.label = Zeitprofil 1 - Dienstag
channel-type.bmwconnecteddrive.timer1-day-wed-channel.label = Zeitprofil 1 - Mittwoch
channel-type.bmwconnecteddrive.timer1-day-thu-channel.label = Zeitprofil 1 - Donnerstag
channel-type.bmwconnecteddrive.timer1-day-fri-channel.label = Zeitprofil 1 - Freitag
channel-type.bmwconnecteddrive.timer1-day-sat-channel.label = Zeitprofil 1 - Samstag
channel-type.bmwconnecteddrive.timer1-day-sun-channel.label = Zeitprofil 1 - Sonntag
channel-type.bmwconnecteddrive.timer2-enabled-channel.label = Zeitprofil 2 - Aktiviert
channel-type.bmwconnecteddrive.timer2-departure-channel.label = Zeitprofil 2 - Abfahrtszeit
channel-type.bmwconnecteddrive.timer2-departure-hour-channel.label = Zeitprofil 2 - Abfahrtszeit Stunde
channel-type.bmwconnecteddrive.timer2-departure-minute-channel.label = Zeitprofil 2 - Abfahrtszeit Minute
channel-type.bmwconnecteddrive.timer2-days-channel.label = Zeitprofil 2 - Tage
channel-type.bmwconnecteddrive.timer2-day-mon-channel.label = Zeitprofil 2 - Montag
channel-type.bmwconnecteddrive.timer2-day-tue-channel.label = Zeitprofil 2 - Dienstag
channel-type.bmwconnecteddrive.timer2-day-wed-channel.label = Zeitprofil 2 - Mittwoch
channel-type.bmwconnecteddrive.timer2-day-thu-channel.label = Zeitprofil 2 - Donnerstag
channel-type.bmwconnecteddrive.timer2-day-fri-channel.label = Zeitprofil 2 - Freitag
channel-type.bmwconnecteddrive.timer2-day-sat-channel.label = Zeitprofil 2 - Samstag
channel-type.bmwconnecteddrive.timer2-day-sun-channel.label = Zeitprofil 2 - Sonnatg
channel-type.bmwconnecteddrive.timer3-enabled-channel.label = Zeitprofil 3 - Aktiviert
channel-type.bmwconnecteddrive.timer3-departure-channel.label = Zeitprofil 3 - Abfahrtszeit
channel-type.bmwconnecteddrive.timer3-departure-hour-channel.label = Zeitprofil 3 - Abfahrtszeit Stunde
channel-type.bmwconnecteddrive.timer3-departure-minute-channel.label = Zeitprofil 3 - Abfahrtszeit Minute
channel-type.bmwconnecteddrive.timer3-days-channel.label = Zeitprofil 3 - Tage
channel-type.bmwconnecteddrive.timer3-day-mon-channel.label = Zeitprofil 3 - Montag
channel-type.bmwconnecteddrive.timer3-day-tue-channel.label = Zeitprofil 3 - Dienstag
channel-type.bmwconnecteddrive.timer3-day-wed-channel.label = Zeitprofil 3 - Mittwoch
channel-type.bmwconnecteddrive.timer3-day-thu-channel.label = Zeitprofil 3 - Donnerstag
channel-type.bmwconnecteddrive.timer3-day-fri-channel.label = Zeitprofil 3 - Freitag
channel-type.bmwconnecteddrive.timer3-day-sat-channel.label = Zeitprofil 3 - Samstag
channel-type.bmwconnecteddrive.timer3-day-sun-channel.label = Zeitprofil 3 - Sonntag
channel-type.bmwconnecteddrive.override-departure-channel.label = Einmaliges Zeitprofil - Abfahrtszeit
channel-type.bmwconnecteddrive.override-departure-hour-channel.label = Einmaliges Zeitprofil - Abfahrtszeit Stunde
channel-type.bmwconnecteddrive.override-departure-minute-channel.label = Einmaliges Zeitprofil - Abfahrtszeit Minute
channel-type.bmwconnecteddrive.override-enabled-channel.label = Einmaliges Zeitprofil - Aktiviert
channel-type.bmwconnecteddrive.destination-name-channel.label = Zieladresse
channel-type.bmwconnecteddrive.destination-gps-channel.label = Zielkoordinaten
channel-type.bmwconnecteddrive.gps-channel.label = Koordinaten
channel-type.bmwconnecteddrive.heading-channel.label = Ausrichtung
channel-type.bmwconnecteddrive.trip-date-time-channel.label = Datum
channel-type.bmwconnecteddrive.trip-duration-channel.label = Dauer
channel-type.bmwconnecteddrive.distance-channel.label = Distanz
channel-type.bmwconnecteddrive.distance-since-charging-channel.label = Strecke seit Ladung
channel-type.bmwconnecteddrive.average-consumption-channel.label = Elektrischer Verbrauch
channel-type.bmwconnecteddrive.average-consumption-channel.description = Elektrischer Durchnittsverbaruch über 100 km/mi
channel-type.bmwconnecteddrive.average-combined-consumption-channel.label = Kombinierter Verbrauch
channel-type.bmwconnecteddrive.average-combined-consumption-channel.description = Kombinierter Durchnittsverbaruch in Liter über 100 km/mi
channel-type.bmwconnecteddrive.average-recuperation-channel.label = Rekuperation Durchschnitt
channel-type.bmwconnecteddrive.average-recuperation-channel.description = Durchschnittliche Rekuperation über 100 km/mi
channel-type.bmwconnecteddrive.total-driven-distance-channel.label = Elektrisch gefahrene Distanz
channel-type.bmwconnecteddrive.single-longest-distance-channel.label = Längste Fahrt mit einer Ladung
channel-type.bmwconnecteddrive.remote-command-channel.label = Kommando Auswahl
channel-type.bmwconnecteddrive.remote-command-channel.option.light = Lichthupe Ausführen
channel-type.bmwconnecteddrive.remote-command-channel.option.finder = Fahrzeug Lokalisieren
channel-type.bmwconnecteddrive.remote-command-channel.option.lock = Fahrzeug Abschließen
channel-type.bmwconnecteddrive.remote-command-channel.option.unlock = Fahrzug Aufschließen
channel-type.bmwconnecteddrive.remote-command-channel.option.horn = Hupe Aktivieren
channel-type.bmwconnecteddrive.remote-command-channel.option.climate = Klimatisierung Ausführen
channel-type.bmwconnecteddrive.remote-state-channel.label = Ausführungszustand
channel-type.bmwconnecteddrive.png-channel.label = Fahrzeug Bild
channel-type.bmwconnecteddrive.image-view-channel.label = Fahrzeug Ansicht
channel-type.bmwconnecteddrive.image-size-channel.label = Fahrzeug Bildgröße

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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>BMW ConnectedDrive Account</label>
<description>Access to BMW ConnectedDrive Portal for a specific user</description>
<config-description-ref uri="thing-type:bmwconnecteddrive:bridge"/>
</bridge-type>
</thing:thing-descriptions>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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-values">
<label>Electric Charging</label>
<description>Charge Profiles of Vehicle</description>
<channels>
<channel id="profile-climate" typeId="profile-climate-channel"/>
<channel id="profile-mode" typeId="profile-mode-channel"/>
<channel id="profile-prefs" typeId="profile-prefs-channel"/>
<channel id="window-start" typeId="window-start-channel"/>
<channel id="window-end" typeId="window-end-channel"/>
<channel id="timer1-departure" typeId="timer1-departure-channel"/>
<channel id="timer1-days" typeId="timer1-days-channel"/>
<channel id="timer1-day-mon" typeId="timer1-day-mon-channel"/>
<channel id="timer1-day-tue" typeId="timer1-day-tue-channel"/>
<channel id="timer1-day-wed" typeId="timer1-day-wed-channel"/>
<channel id="timer1-day-thu" typeId="timer1-day-thu-channel"/>
<channel id="timer1-day-fri" typeId="timer1-day-fri-channel"/>
<channel id="timer1-day-sat" typeId="timer1-day-sat-channel"/>
<channel id="timer1-day-sun" typeId="timer1-day-sun-channel"/>
<channel id="timer1-enabled" typeId="timer1-enabled-channel"/>
<channel id="timer2-departure" typeId="timer2-departure-channel"/>
<channel id="timer2-days" typeId="timer2-days-channel"/>
<channel id="timer2-day-mon" typeId="timer2-day-mon-channel"/>
<channel id="timer2-day-tue" typeId="timer2-day-tue-channel"/>
<channel id="timer2-day-wed" typeId="timer2-day-wed-channel"/>
<channel id="timer2-day-thu" typeId="timer2-day-thu-channel"/>
<channel id="timer2-day-fri" typeId="timer2-day-fri-channel"/>
<channel id="timer2-day-sat" typeId="timer2-day-sat-channel"/>
<channel id="timer2-day-sun" typeId="timer2-day-sun-channel"/>
<channel id="timer2-enabled" typeId="timer2-enabled-channel"/>
<channel id="timer3-departure" typeId="timer3-departure-channel"/>
<channel id="timer3-days" typeId="timer3-days-channel"/>
<channel id="timer3-day-mon" typeId="timer3-day-mon-channel"/>
<channel id="timer3-day-tue" typeId="timer3-day-tue-channel"/>
<channel id="timer3-day-wed" typeId="timer3-day-wed-channel"/>
<channel id="timer3-day-thu" typeId="timer3-day-thu-channel"/>
<channel id="timer3-day-fri" typeId="timer3-day-fri-channel"/>
<channel id="timer3-day-sat" typeId="timer3-day-sat-channel"/>
<channel id="timer3-day-sun" typeId="timer3-day-sun-channel"/>
<channel id="timer3-enabled" typeId="timer3-enabled-channel"/>
<channel id="override-departure" typeId="override-departure-channel"/>
<channel id="override-enabled" typeId="override-enabled-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,208 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="profile-climate-channel">
<item-type>Switch</item-type>
<label>A/C at Departure Time</label>
</channel-type>
<channel-type id="profile-mode-channel">
<item-type>String</item-type>
<label>Charge Mode</label>
<description>Mode for selecting immediate or delyed charging</description>
<command>
<options>
<option value="IMMEDIATE_CHARGING">Immediate Charging</option>
<option value="DELAYED_CHARGING">Prefer Charging in Charging Window</option>
</options>
</command>
</channel-type>
<channel-type id="profile-prefs-channel">
<item-type>String</item-type>
<label>Charge Preferences</label>
<description>Preferences for delayed charging</description>
<command>
<options>
<option value="NO_PRESELECTION">No Preference</option>
<option value="CHARGING_WINDOW">Charging Window</option>
</options>
</command>
</channel-type>
<channel-type id="window-start-channel">
<item-type>DateTime</item-type>
<label>Window Start Time</label>
<description>Start time of charging window</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="window-end-channel">
<item-type>DateTime</item-type>
<label>Window End Time</label>
<description>End time of charging window</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="timer1-departure-channel">
<item-type>DateTime</item-type>
<label>T1 Departure Time</label>
<description>Departure time for regular schedule timer 1</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="timer1-days-channel">
<item-type>String</item-type>
<label>T1 Days</label>
<description>Days scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-mon-channel">
<item-type>Switch</item-type>
<label>T1 Monday</label>
<description>Monday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-tue-channel">
<item-type>Switch</item-type>
<label>T1 Tuesday</label>
<description>Tuesday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-wed-channel">
<item-type>Switch</item-type>
<label>T1 Wednesday</label>
<description>Wednesday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-thu-channel">
<item-type>Switch</item-type>
<label>T1 Thursday</label>
<description>Thursday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-fri-channel">
<item-type>Switch</item-type>
<label>T1 Friday</label>
<description>Friday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-sat-channel">
<item-type>Switch</item-type>
<label>T1 Saturday</label>
<description>Saturday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-day-sun-channel">
<item-type>Switch</item-type>
<label>T1 Sunday</label>
<description>Sunday scheduled for timer 1</description>
</channel-type>
<channel-type id="timer1-enabled-channel">
<item-type>Switch</item-type>
<label>T1 Enabled</label>
<description>Timer 1 enabled</description>
</channel-type>
<channel-type id="timer2-departure-channel">
<item-type>DateTime</item-type>
<label>T2 Departure Time</label>
<description>Departure time for regular schedule timer 2</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="timer2-days-channel">
<item-type>String</item-type>
<label>T2 Days</label>
<description>Days scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-mon-channel">
<item-type>Switch</item-type>
<label>T2 Monday</label>
<description>Monday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-tue-channel">
<item-type>Switch</item-type>
<label>T2 Tuesday</label>
<description>Tuesday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-wed-channel">
<item-type>Switch</item-type>
<label>T2 Wednesday</label>
<description>Wednesday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-thu-channel">
<item-type>Switch</item-type>
<label>T2 Thursday</label>
<description>Thursday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-fri-channel">
<item-type>Switch</item-type>
<label>T2 Friday</label>
<description>Friday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-sat-channel">
<item-type>Switch</item-type>
<label>T2 Saturday</label>
<description>Saturday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-day-sun-channel">
<item-type>Switch</item-type>
<label>T2 Sunday</label>
<description>Sunday scheduled for timer 2</description>
</channel-type>
<channel-type id="timer2-enabled-channel">
<item-type>Switch</item-type>
<label>T2 Enabled</label>
<description>Timer 2 enabled</description>
</channel-type>
<channel-type id="timer3-departure-channel">
<item-type>DateTime</item-type>
<label>T3 Departure Time</label>
<description>Departure time for regular schedule timer 3</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="timer3-days-channel">
<item-type>String</item-type>
<label>T3 Days</label>
<description>Days scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-mon-channel">
<item-type>Switch</item-type>
<label>T3 Monday</label>
<description>Monday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-tue-channel">
<item-type>Switch</item-type>
<label>T3 Tuesday</label>
<description>Tuesday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-wed-channel">
<item-type>Switch</item-type>
<label>T3 Wednesday</label>
<description>Wednesday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-thu-channel">
<item-type>Switch</item-type>
<label>T3 Thursday</label>
<description>Thursday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-fri-channel">
<item-type>Switch</item-type>
<label>T3 Friday</label>
<description>Friday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-sat-channel">
<item-type>Switch</item-type>
<label>T3 Saturday</label>
<description>Saturday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-day-sun-channel">
<item-type>Switch</item-type>
<label>T3 Sunday</label>
<description>Sunday scheduled for timer 3</description>
</channel-type>
<channel-type id="timer3-enabled-channel">
<item-type>Switch</item-type>
<label>T3 Enabled</label>
<description>Timer 3 enabled</description>
</channel-type>
<channel-type id="override-departure-channel">
<item-type>DateTime</item-type>
<label>OT Departure Time</label>
<description>Departure time for override timer</description>
<state pattern="%1$tH:%1$tM" readOnly="false"/>
</channel-type>
<channel-type id="override-enabled-channel">
<item-type>Switch</item-type>
<label>OT Enabled</label>
<description>Override timer enabled</description>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="checkcontrol-name-channel">
<item-type>String</item-type>
<label>CheckControl Description</label>
</channel-type>
<channel-type id="checkcontrol-details-channel">
<item-type>String</item-type>
<label>CheckControl Details</label>
</channel-type>
<channel-type id="checkcontrol-mileage-channel">
<item-type>Number:Length</item-type>
<label>Mileage Occurrence</label>
<state pattern="%d %unit%"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="check-control-values">
<label>Check Control Messages</label>
<description>Show the current active CheckControl Messages</description>
<channels>
<channel id="name" typeId="checkcontrol-name-channel"/>
<channel id="details" typeId="checkcontrol-details-channel"/>
<channel id="mileage" typeId="checkcontrol-mileage-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-group-type id="conv-range-values">
<label>Range Data</label>
<description>Provides Mileage, remaining range and fuel level values</description>
<channels>
<channel id="mileage" typeId="mileage-channel"/>
<channel id="fuel" typeId="range-fuel-channel"/>
<channel id="remaining-fuel" typeId="remaining-fuel-channel"/>
<channel id="radius-fuel" typeId="range-radius-fuel-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="destination-name-channel">
<item-type>String</item-type>
<label>Name</label>
</channel-type>
<channel-type id="destination-gps-channel">
<item-type>Location</item-type>
<label>GPS Coordinates</label>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="destination-values">
<label>Destination List</label>
<description>Shows Your Destinations in a List</description>
<channels>
<channel id="name" typeId="destination-name-channel"/>
<channel id="gps" typeId="destination-gps-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="driver-front-channel">
<item-type>String</item-type>
<label>Driver Door</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="driver-rear-channel">
<item-type>String</item-type>
<label>Driver Door Rear</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="passenger-front-channel">
<item-type>String</item-type>
<label>Passenger Door</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="passenger-rear-channel">
<item-type>String</item-type>
<label>Passenger Door Rear</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="hood-channel">
<item-type>String</item-type>
<label>Hood</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="trunk-channel">
<item-type>String</item-type>
<label>Trunk</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="window-driver-front-channel">
<item-type>String</item-type>
<label>Driver Window</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="window-driver-rear-channel">
<item-type>String</item-type>
<label>Driver Rear Window</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="window-passenger-front-channel">
<item-type>String</item-type>
<label>Passenger Window</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="window-passenger-rear-channel">
<item-type>String</item-type>
<label>Passenger Rear Window</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="window-rear-channel">
<item-type>String</item-type>
<label>Rear Window</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="sunroof-channel">
<item-type>String</item-type>
<label>Sunroof</label>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="door-values">
<label>Detailed Door Status</label>
<description>Detailed Status of all Doors and Windows</description>
<channels>
<channel id="driver-front" typeId="driver-front-channel"/>
<channel id="driver-rear" typeId="driver-rear-channel"/>
<channel id="passenger-front" typeId="passenger-front-channel"/>
<channel id="passenger-rear" typeId="passenger-rear-channel"/>
<channel id="hood" typeId="hood-channel"/>
<channel id="trunk" typeId="trunk-channel"/>
<channel id="win-driver-front" typeId="window-driver-front-channel"/>
<channel id="win-driver-rear" typeId="window-driver-rear-channel"/>
<channel id="win-passenger-front" typeId="window-passenger-front-channel"/>
<channel id="win-passenger-rear" typeId="window-passenger-rear-channel"/>
<channel id="win-rear" typeId="window-rear-channel"/>
<channel id="sunroof" typeId="sunroof-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="ev-last-trip-values">
<label>Last Trip Statistics</label>
<description>EV Consumption Values and Distances for the Last Trip</description>
<channels>
<channel id="date" typeId="trip-date-time-channel"/>
<channel id="duration" typeId="trip-duration-channel"/>
<channel id="distance" typeId="distance-channel"/>
<channel id="distance-since-charging" typeId="distance-since-charging-channel"/>
<channel id="avg-consumption" typeId="average-consumption-channel"/>
<channel id="avg-recuperation" typeId="average-recuperation-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="ev-lifetime-values">
<label>Lifetime Statistics</label>
<description>Consumption Values and Distances over Lifetime</description>
<channels>
<channel id="avg-consumption" typeId="average-consumption-channel"/>
<channel id="avg-recuperation" typeId="average-recuperation-channel"/>
<channel id="total-driven-distance" typeId="total-driven-distance-channel"/>
<channel id="single-longest-distance" typeId="single-longest-distance-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="ev-range-values">
<label>Electric Range Data</label>
<description>Provides Mileage, remaining range and charge level values</description>
<channels>
<channel id="mileage" typeId="mileage-channel"/>
<channel id="electric" typeId="range-electric-channel"/>
<channel id="radius-electric" typeId="range-radius-electric-channel"/>
<channel id="electric-max" typeId="range-electric-max-channel"/>
<channel id="radius-electric-max" typeId="range-radius-electric-max-channel"/>
<channel id="soc" typeId="soc-channel"/>
<channel id="soc-max" typeId="soc-max-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="ev-vehicle-status">
<label>Vehicle Status</label>
<description>Provides Status of Doors, Windows, Lock State, Service and Check Control Messages</description>
<channels>
<channel id="doors" typeId="doors-channel"/>
<channel id="windows" typeId="windows-channel"/>
<channel id="lock" typeId="lock-channel"/>
<channel id="service-date" typeId="next-service-date-channel"/>
<channel id="service-mileage" typeId="next-service-mileage-channel"/>
<channel id="check-control" typeId="check-control-channel"/>
<channel id="plug-connection" typeId="plug-connection-channel"/>
<channel id="charge" typeId="charging-status-channel"/>
<channel id="remaining" typeId="charging-remaining-channel"/>
<channel id="last-update" typeId="last-update-channel"/>
<channel id="last-update-reason" typeId="last-update-reason-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="hybrid-last-trip-values">
<label>Last Trip Statistics</label>
<description>Hybrid Consumption Values and Distances for the Last Trip</description>
<channels>
<channel id="date" typeId="trip-date-time-channel"/>
<channel id="duration" typeId="trip-duration-channel"/>
<channel id="distance" typeId="distance-channel"/>
<channel id="distance-since-charging" typeId="distance-since-charging-channel"/>
<channel id="avg-consumption" typeId="average-consumption-channel"/>
<channel id="avg-combined-consumption" typeId="average-combined-consumption-channel"/>
<channel id="avg-recuperation" typeId="average-recuperation-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="hybrid-lifetime-values">
<label>Lifetime Statistics</label>
<description>Consumption Values and Distances over Lifetime</description>
<channels>
<channel id="avg-consumption" typeId="average-consumption-channel"/>
<channel id="avg-combined-consumption" typeId="average-combined-consumption-channel"/>
<channel id="avg-recuperation" typeId="average-recuperation-channel"/>
<channel id="total-driven-distance" typeId="total-driven-distance-channel"/>
<channel id="single-longest-distance" typeId="single-longest-distance-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="hybrid-range-values">
<label>Hybrid Range Data</label>
<description>Provides Mileage, remaining range and fuel and charge level values</description>
<channels>
<channel id="mileage" typeId="mileage-channel"/>
<channel id="hybrid" typeId="range-hybrid-channel"/>
<channel id="hybrid-max" typeId="range-hybrid-max-channel"/>
<channel id="electric" typeId="range-electric-channel"/>
<channel id="radius-electric" typeId="range-radius-electric-channel"/>
<channel id="electric-max" typeId="range-electric-max-channel"/>
<channel id="radius-electric-max" typeId="range-radius-electric-max-channel"/>
<channel id="fuel" typeId="range-fuel-channel"/>
<channel id="remaining-fuel" typeId="remaining-fuel-channel"/>
<channel id="radius-hybrid" typeId="range-radius-hybrid-channel"/>
<channel id="radius-hybrid-max" typeId="range-radius-hybrid-max-channel"/>
<channel id="soc" typeId="soc-channel"/>
<channel id="soc-max" typeId="soc-max-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="image-values">
<label>Vehicle Image</label>
<description>Provides an Image of your Vehicle</description>
<channels>
<channel id="png" typeId="png-channel"/>
<channel id="size" typeId="image-size-channel"/>
<channel id="view" typeId="image-view-channel"/>
</channels>
</channel-group-type>
</thing:thing-descriptions>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="png-channel">
<item-type>Image</item-type>
<label>Rendered Vehicle Image</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="image-view-channel">
<item-type>String</item-type>
<label>Image Viewport</label>
<command>
<options>
<option value="FRONT">Front View</option>
<option value="REAR">Rear View</option>
<option value="SIDE">Side View</option>
<option value="DASHBOARD">Dashboard View</option>
<option value="DRIVERDOOR">Driver Door View</option>
</options>
</command>
</channel-type>
<channel-type id="image-size-channel">
<item-type>Number</item-type>
<label>Image Size</label>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="trip-date-time-channel">
<item-type>DateTime</item-type>
<label>Date and Time</label>
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
</channel-type>
<channel-type id="trip-duration-channel">
<item-type>Number:Time</item-type>
<label>Last Trip Duration</label>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="distance-channel">
<item-type>Number:Length</item-type>
<label>Last Trip Distance</label>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="distance-since-charging-channel">
<item-type>Number:Length</item-type>
<label>Distance since Charge</label>
<description>Total distance driven since last charging</description>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-consumption-channel">
<item-type>Number:Energy</item-type>
<label>Avg. Power Consumption</label>
<description>Average electric power consumption per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-combined-consumption-channel">
<item-type>Number:Volume</item-type>
<label>Avg. Combined Consumption</label>
<description>Average combined consumption in liter per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-recuperation-channel">
<item-type>Number:Energy</item-type>
<label>Avg. Recuperation</label>
<description>Average electric recuperation per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="bmwconnecteddrive"
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="total-driven-distance-channel">
<item-type>Number:Length</item-type>
<label>Total Electric Distance</label>
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="single-longest-distance-channel">
<item-type>Number:Length</item-type>
<label>Longest 1-Charge Distance</label>
<state pattern="%.0f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-consumption-channel">
<item-type>Number:Energy</item-type>
<label>Avg. Power Consumption</label>
<description>Average Combined Consumption electric power consumption per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-recuperation-channel">
<item-type>Number:Energy</item-type>
<label>Avg. Combined Consumption Recuperation</label>
<description>Average electric recuperation per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<channel-type id="average-combined-consumption-channel">
<item-type>Number:Volume</item-type>
<label>Avg. Combined Consumption</label>
<description>Average combined consumption in liter per 100 km/mi</description>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

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