[mercedesme] Initial contribution (#13044)
Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
@ -906,6 +906,11 @@
|
||||
<artifactId>org.openhab.binding.melcloud</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.mercedesme</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.meteoalerte</artifactId>
|
||||
|
13
bundles/org.openhab.binding.mercedesme/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
503
bundles/org.openhab.binding.mercedesme/README.md
Normal file
@ -0,0 +1,503 @@
|
||||
# MercedesMe Binding
|
||||
|
||||
This binding provides similar access to your Mercedes Benz vehicle like the Smartphone App _Mercedes Me_.
|
||||
For this you need a Mercedes developer account to get data from your vehicles.
|
||||
Setup requires some, time so follow [the steps of bridge configuration](#bridge-configuration).
|
||||
|
||||
If you face some problems during setup or runtime please have a look into the [Troubleshooting section](#troubleshooting)
|
||||
|
||||
## Supported Things
|
||||
|
||||
| Type | ID | Description |
|
||||
|-----------------|---------------|-------------------------------------------------|
|
||||
| Bridge | `account` | Connect your Mercedes Me account |
|
||||
| Thing | `combustion` | Conventional fuel vehicle |
|
||||
| Thing | `hybrid` | Fuel vehicle with supporting electric engine |
|
||||
| Thing | `bev` | Battery electric vehicle |
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
Bridge needs configuration in order to connect properly to your Mercedes Me Account.
|
||||
|
||||
### Pre-Conditions
|
||||
|
||||
- **each bridge shall have its own Mercedes Benz Client ID!**
|
||||
Don't create several `account` bridges with the same client id! If this is not the case the tokens won't be stored properly and the authorization is jeopardized!
|
||||
- **each bridge shall have its own port.**
|
||||
It's absolutely necessary to assign a different port for each `account` bridge. If this is not the case the tokens won't be stored properly and the authorization is jeopardized!
|
||||
|
||||
### Bridge Setup
|
||||
|
||||
Perform the following steps to obtain the configuration data and perform the authorization flow.
|
||||
|
||||
1. Go to [Mercedes Developer Page](https://developer.mercedes-benz.com/). Login with your Mercedes Me credentials.
|
||||
2. Create a project in the [console tab](https://developer.mercedes-benz.com/console)
|
||||
- _Project Name:_ unique name e.g. **openHAB Mercedes Me binding** plus **Your bridge ID**
|
||||
- _Purpose URL:_ use link towards [this binding description](https://www.openhab.org/addons/bindings/mercedesme/)
|
||||
- _Business Purpose:_ e.g. **Private usage in openHAB Smarthome system**
|
||||
3. After project is created subscribe [to these Mercedes Benz APIs](https://developer.mercedes-benz.com/products?vt=cars&vt=vans&vt=smart&p=BYOCAR) with _Add Products_ button
|
||||
4. For all Products perform the same steps
|
||||
- Select product
|
||||
- Choose _Get For Free_
|
||||
- Choose _BYOCAR_ (Build Your Own Car)
|
||||
- Button _Confirm_
|
||||
5. Select the following products
|
||||
- Vehicle Status
|
||||
- Vehicle Lock Status
|
||||
- Pay as you drive insurance
|
||||
- Electric Vehicle Status
|
||||
- Fuel Status
|
||||
6. Optional: Subscribe also to _Vehicle images_. Select the _Basic Trial_ version. The images will be stored so the API is used just a few times.
|
||||
7. Press _Subscribe_ button. Your project should have [these product subscriptions](#mb-product-subscriptions)
|
||||
8. Generate the [project credentials](#mb-credentials)
|
||||
9. Open in new browser tab your openHAB page. Add a new Thing _Mercedes Me Account_
|
||||
10. Copy paste _Client ID_ , _Client Secret_ and _API Key_ from the Mercedes tab into the openHAB configuration
|
||||
11. Check if the registered Mercedes products _excluding Vehicle Images_ are matching exactly with the openHab configuration switches
|
||||
12. Create Thing!
|
||||
13. The fresh created [account has one property](#openhab-configuration) `callbackUrl`. Copy it and paste it in a new browser tab
|
||||
14. A [simple HTML page is shown including a link towards the Authorization flow](#callback-page) - **don't click yet**. If page isn't shown please adapt IP and port in openHAB configuration with Advanced Options activated
|
||||
15. The copied URL needs to be added in your [Mercedes project credentials](#mb-credentials) from 8
|
||||
16. Now click onto the link from 14. You'll be asked one time if you [grant access](#mb-access-request) towards the API. Click OK and authorization is done!
|
||||
|
||||
Some supporting screenshots for the setup
|
||||
|
||||
### MB Credentials
|
||||
|
||||
<img src="./doc/MBDeveloper-Credentials.png" width="500" height="280"/>
|
||||
|
||||
### MB Product Subscriptions
|
||||
|
||||
<img src="./doc/MBDeveloper-Subscriptions.png" width="500" height="300"/>
|
||||
|
||||
### openHAB Configuration
|
||||
|
||||
<img src="./doc/MercedesMeConfiguration.png" width="400" height="500"/>
|
||||
|
||||
### MB Access Request
|
||||
|
||||
<img src="./doc/MBAccessRequest.png" width="500" height="220"/>
|
||||
|
||||
### Callback page
|
||||
|
||||
<img src="./doc/CallbackUrl_Page.png" width="500" height="350"/>
|
||||
|
||||
|
||||
### Bridge Configuration Parameters
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|-----------------|---------|---------------------------------------|-------------|----------|----------|
|
||||
| clientId | text | Mercedes Benz Developer Client ID | N/A | yes | no |
|
||||
| clientSecret | text | Mercedes Benz Developer Client Secret | N/A | yes | no |
|
||||
| imageApiKey | text | Mercedes Benz Developer Image API Key | N/A | no | no |
|
||||
| odoScope | boolean | PayAsYourDrive Insurance | true | yes | no |
|
||||
| vehicleScope | boolean | Vehicle Status | true | yes | no |
|
||||
| lockScope | boolean | Lock status of doors and trunk | true | yes | no |
|
||||
| fuelScope | boolean | Fuel Status | true | yes | no |
|
||||
| evScope | boolean | Electric Vehicle Status | true | yes | no |
|
||||
| callbackIp | text | IP address of your openHAB server | auto detect | no | yes |
|
||||
| callbackPort | integer | **Unique** port number | auto detect | no | yes |
|
||||
|
||||
The `callbackPort` needs to be unique for all created Mercedes Me account things. Otherwise token exchange will be corrupted.
|
||||
Set the advanced options by yourself if you know your IP and Port, otherwise give auto detect a try.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
For vehicle images Mercedes Benz Developer offers only a trial version with limited calls.
|
||||
Check in **beforehand** if your vehicle has some restrictions or even if it's supported at all.
|
||||
Visit [Vehicle Image Details](https://developer.mercedes-benz.com/products/vehicle_images/details) in order to check your vehicle capabilities.
|
||||
Visit [Image Settings](https://developer.mercedes-benz.com/products/vehicle_images/docs#_default_image_settings) to get more information about
|
||||
For example the EQA doesn't provide `night` images with `background`.
|
||||
If your configuration is set this way the API calls are wasted!
|
||||
|
||||
<img src="./doc/ImageRestrictions.png" width="800" height="36"/>
|
||||
|
||||
See also [image channel section](#image) for further advise.
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|-----------------|---------|-----------------------------------------------------|---------|----------|----------|
|
||||
| vin | text | Vehicle identification number | N/A | yes | no |
|
||||
| refreshInterval | integer | Refresh interval in minutes | 5 | yes | no |
|
||||
| background | boolean | Vehicle images provided with or without background | false | no | yes |
|
||||
| night | boolean | Vehicle images in night conditions | false | no | yes |
|
||||
| cropped | boolean | Vehicle images in 4:3 instead of 16:9 | false | no | yes |
|
||||
| roofOpen | boolean | Vehicle images with open roof (only Cabriolet) | false | no | yes |
|
||||
| format | text | Vehicle images format (webp or png) | webp | no | yes |
|
||||
|
||||
For all vehicles you're free to give the tank / battery capacity.
|
||||
Giving these values in configuration the open fuel / charge capacities are reported in the [range](#range) channels.
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced | combustion | bev | hybrid |
|
||||
|-----------------|---------|-----------------------------------------------------|---------|----------|----------|------------|-----|--------|
|
||||
| batteryCapacity | decimal | Battery Capacity | N/A | no | no | | X | X |
|
||||
| fuelCapacity | decimal | Fuel Capacity | N/A | no | no | X | | X |
|
||||
|
||||
## Channels
|
||||
|
||||
Channels are separated in groups:
|
||||
|
||||
| Channel Group ID | Description |
|
||||
|----------------------------------|---------------------------------------------------|
|
||||
| [range](#range) | Provides mileage, range and charge / fuel levels |
|
||||
| [doors](#doors) | Details of all doors |
|
||||
| [windows](#windows) | Current position of windows |
|
||||
| [lights](#lights) | Interior lights and main light switch |
|
||||
| [lock](#lock) | Overall lock state of vehicle |
|
||||
| [location](#location) | Heading of the vehicle |
|
||||
| [image](#image) | Images of your vehicle |
|
||||
|
||||
### Range
|
||||
|
||||
Group name: `range`
|
||||
All channels `read-only`
|
||||
|
||||
| Channel | Type | Description | bev | hybrid | combustion |
|
||||
|------------------|----------------------|------------------------------| ----|--------|------------|
|
||||
| mileage | Number:Length | Total mileage | X | X | X |
|
||||
| soc | Number:Dimensionless | Battery state of charge | X | X | |
|
||||
| charged | Number:Energy | Charged Battery Energy | X | X | |
|
||||
| uncharged | Number:Energy | Uncharged Battery Energy | X | X | |
|
||||
| soc | Number:Dimensionless | Battery state of charge | X | X | |
|
||||
| range-electric | Number:Length | Electric range | X | X | |
|
||||
| radius-electric | Number:Length | Electric radius for map | X | X | |
|
||||
| fuel-level | Number:Dimensionless | Fuel level in percent | | X | X |
|
||||
| fuel-remain | Number:Volume | Reamaining Fuel | | X | X |
|
||||
| fuel-open | Number:Volume | Open Fuel Capacity | | X | X |
|
||||
| range-fuel | Number:Length | Fuel range | | X | X |
|
||||
| radius-fuel | Number:Length | Fuel radius for map | | X | X |
|
||||
| range-hybrid | Number:Length | Hybrid range | | X | |
|
||||
| radius-hybrid | Number:Length | Hybrid radius for map | | X | |
|
||||
| last-update | DateTime | Last range update | X | X | X |
|
||||
|
||||
Channels with `radius` are just giving a _guess_ which radius can be reached in a map display.
|
||||
|
||||
### Doors
|
||||
|
||||
Group name: `doors`
|
||||
All channels `read-only`
|
||||
|
||||
| Channel | Type | Description |
|
||||
|------------------|----------------------|------------------------------|
|
||||
| driver-front | Contact | Driver door |
|
||||
| driver-rear | Contact | Driver door reat |
|
||||
| passenger-front | Contact | Passenger door |
|
||||
| passenger-rear | Contact | Passenger door rear |
|
||||
| deck-lid | Contact | Deck lid |
|
||||
| sunroof | Number | Sun roof (only Cabriolet) |
|
||||
| rooftop | Number | Roof top |
|
||||
| last-update | DateTime | Last doors update |
|
||||
|
||||
Mapping table `sunroof`
|
||||
|
||||
| Number | Mapping |
|
||||
|-----------------|---------------------|
|
||||
| 0 | Closed |
|
||||
| 1 | Open |
|
||||
| 2 | Open Lifting |
|
||||
| 3 | Running |
|
||||
| 4 | Closing |
|
||||
| 5 | Opening |
|
||||
| 6 | Closing |
|
||||
|
||||
Mapping table `rootop`
|
||||
|
||||
| Number | Mapping |
|
||||
|-----------------|---------------------|
|
||||
| 0 | Unlocked |
|
||||
| 1 | Open and locked |
|
||||
| 2 | Closed and locked |
|
||||
|
||||
### Windows
|
||||
|
||||
Group name: `windows`
|
||||
All channels `readonly`
|
||||
|
||||
| Channel | Type | Description |
|
||||
|------------------|----------------------|------------------------------|
|
||||
| driver-front | Number | Driver window |
|
||||
| driver-rear | Number | Driver window rear |
|
||||
| passenger-front | Number | Passenger window |
|
||||
| passenger-rear | Number | Passenger window rear |
|
||||
| last-update | DateTime | Last windows update |
|
||||
|
||||
Mapping table for all windows
|
||||
|
||||
| Number | Mapping |
|
||||
|-----------------|---------------------|
|
||||
| 0 | Intermediate |
|
||||
| 1 | Open |
|
||||
| 2 | Closed |
|
||||
| 3 | Airing |
|
||||
| 4 | Intermediate |
|
||||
| 5 | Running |
|
||||
|
||||
### Lights
|
||||
|
||||
Group name: `lights`
|
||||
All channels `read-only`
|
||||
|
||||
| Channel | Type | Description |
|
||||
|------------------|----------------------|------------------------------|
|
||||
| interior-front | Switch | Interior light front |
|
||||
| interior-rear | Switch | Interior light rear |
|
||||
| reading-left | Switch | Reading light left |
|
||||
| reading-right | Switch | Reading light right |
|
||||
| light-switch | Number | Main light switch |
|
||||
| last-update | DateTime | Last lights update |
|
||||
|
||||
Mapping table `light-switch`
|
||||
|
||||
| Number | Mapping |
|
||||
|-----------------|---------------------|
|
||||
| 0 | Auto |
|
||||
| 1 | Headlight |
|
||||
| 2 | Sidelight Left |
|
||||
| 3 | Sidelight Right |
|
||||
| 4 | Parking Light |
|
||||
|
||||
### Lock
|
||||
|
||||
Group name: `lock`
|
||||
All channels `read-only`
|
||||
|
||||
| Channel | Type | Description |
|
||||
|------------------|----------------------|------------------------------|
|
||||
| doors | Number | Lock status all doors |
|
||||
| deck-lid | Switch | Deck lid lock |
|
||||
| flap | Switch | Flap lock |
|
||||
| last-update | DateTime | Last lock update |
|
||||
|
||||
Mapping table `doors`
|
||||
|
||||
| Number | Mapping |
|
||||
|-----------------|---------------------|
|
||||
| 0 | Unlocked |
|
||||
| 1 | Locked Internal |
|
||||
| 2 | Locked External |
|
||||
| 3 | Unlocked Selective |
|
||||
|
||||
### Location
|
||||
|
||||
Group name: `location`
|
||||
All channels `readonly`
|
||||
|
||||
| Channel | Type | Description |
|
||||
|------------------|----------------------|------------------------------|
|
||||
| heading | Number:Angle | Vehicle heading |
|
||||
| last-update | DateTime | Last location update |
|
||||
|
||||
### Image
|
||||
|
||||
Provides exterior and interior images for your specific vehicle.
|
||||
Group name: `image`
|
||||
|
||||
| Channel | Type | Description | Write |
|
||||
|------------------|----------------------|------------------------------|-------|
|
||||
| image-data | Raw | Vehicle image | |
|
||||
| image-view | text | Vehicle image viewpoint | X |
|
||||
| clear-cache | Switch | Remove all stored images | X |
|
||||
|
||||
**If** the `imageApiKey` in [Bridge Configuration Parameters](#bridge-configuration-parameters) is set the vehicle thing will try to get images.
|
||||
Pay attention to the [Advanced Image Configuration Properties](#thing-configuration) before requesting new images.
|
||||
Sending commands towards the `image-view` channel will change the image.
|
||||
The `image-view` is providing options to select the available images for your specific vehicle.
|
||||
Images are stored in `jsondb` so if you requested all images the Mercedes Benz Image API will not be called anymore which is good because you have a restricted amount of calls!
|
||||
If you're not satisfied e.g. you want a background you need to
|
||||
|
||||
1. change the [Advanced Image Configuration Properties](#thing-configuration)
|
||||
2. Switch `clear-cache` channel item to `ON` to clear all images
|
||||
3. request them via `image-view`
|
||||
|
||||
### Image View Options
|
||||
|
||||
You can access the options either in a rule via `YOUR_IMAGE_VIEW_ITEM.getStateDescription().getOptions()` or in UI in widget configuration as _Action: Command options_ and as _Action Item: YOUR_IMAGE_VIEW_ITEM_
|
||||
|
||||
<img src="./doc/ImageView-CommandOptions.png" width="400" height="350"/>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authorization fails
|
||||
|
||||
The configuration of openHAB account thing and the Mercedes Developer project need an extract match regarding
|
||||
|
||||
- MB project credentials vs. `clientId` `clientSecret` and `callbackUrl`
|
||||
- MB project subscription of products vs. `scope`
|
||||
|
||||
If you follow the [bridge configuration steps](#bridge-configuration) both will match.
|
||||
Otherwise you'll receive some error message when clicking the link after opening the `callbackUrl` in your browser
|
||||
|
||||
Most common errors:
|
||||
|
||||
- redirect URL doesn't match: Double check if `callbackUrl` is really saved correctly in your Mercedes Benz Developer project
|
||||
- scope failure: the requested scope doesn't match with the subscribed products.
|
||||
- Check [openHab configuration switches](#openhab-configuration)
|
||||
- apply changes if necessary and don't forget to save
|
||||
- after these steps refresh the `callbackUrl` in [your browser](#callback-page) to apply these changes
|
||||
- try a new authorization clicking the link
|
||||
|
||||
### Receive no data
|
||||
|
||||
Especially after setting the frist Mercedes Benz Developer Project you'll receive no data.
|
||||
It seems that the API isn't _filled_ yet.
|
||||
|
||||
**Pre-Condition**
|
||||
|
||||
- The Mercedes Me bridge is online = authorization is fine
|
||||
- The Mercedes Me thing is online = API calls are fine
|
||||
|
||||
**Solution**
|
||||
|
||||
- Reduce `refreshInterval` to 1 minute
|
||||
- Go to your vehicle, open doors and windows, turn on lights, drive a bit ...
|
||||
- wait until values are providing the right states
|
||||
|
||||
### Images
|
||||
|
||||
Testing the whole image settings is hard due to the restricted call number towards the Image API.
|
||||
|
||||
My personal experience during limited testing
|
||||
|
||||
| Test |Tested | OK | Not OK | Comment |
|
||||
|------------------|-------|-----|---------|---------------------------------------------------------|
|
||||
| `format` webp | Yes | X | | |
|
||||
| `format` png | Yes | | X | Internal Server Error 500 on Mercedes Server side |
|
||||
| `format` jpeg | No | | | Not tested due to missing transparency in jpeg format |
|
||||
| all options off | Yes | X | | |
|
||||
| `background` | Yes | X | | |
|
||||
| `night` | No | | | Not support by my vehicle |
|
||||
| `roofOpen` | No | | | Not support by my vehicle |
|
||||
| `cropped` | No | | | Not desired from my side |
|
||||
|
||||
## Storage
|
||||
|
||||
Data is stored in directory `%USER_DATA%/jsondb` for handling tokens and vehicle images.
|
||||
|
||||
* _StorageHandler.For.OAuthClientService.json_ - token is stored with key `clientId` which is provided by `account` [Brige Configuration Parameters](#bridge-configuration-parameters)
|
||||
* _mercedesme_%VEHICLE_VIN%.json_ - images are stored per vehicle. File name contains `vin` configured by [vehicle Thing Configuration](#thing-configuration)
|
||||
|
||||
With this data the binding is able to operate without new authorization towards Mercedes each startup and reduces the restricted calls towards image API.
|
||||
Also these files are properly stored in your [backup](https://community.openhab.org/t/docs-on-how-to-backup-openhab/100182) e.g. if you perform `openhab-cli backup`
|
||||
|
||||
|
||||
## Full example
|
||||
|
||||
The example is based on a battery electric vehicle.
|
||||
Exchange configuration parameters in the Things section
|
||||
|
||||
Bridge
|
||||
|
||||
* 4711 - your desired bridge id
|
||||
* YOUR_CLIENT_ID - Client ID of the Mercedes Developer project
|
||||
* YOUR_CLIENT_SECRET - Client Secret of the Mercedes Developer project
|
||||
* YOUR_API_KEY - Image API Key of the Mercedes Developer project
|
||||
* YOUR_OPENHAB_SERVER_IP - IP address of your openHAB server
|
||||
* 8090 - a **unique** port number - each bridge in your openHAB installation needs to have different port number!
|
||||
|
||||
Thing
|
||||
|
||||
* eqa - your desired vehicle thing id
|
||||
* VEHICLE_VIN - your Vehicle Identification Number
|
||||
|
||||
### Things file
|
||||
|
||||
```
|
||||
Bridge mercedesme:account:4711 "MercedesMe John Doe" [ clientId="YOUR_CLIENT_ID", clientSecret="YOUR_CLIENT_SECRET", imageApiKey="YOUR_API_KEY", callbackIp="YOUR_OPENHAB_SERVER_IP", callbackPort=8092, odoScope=true, vehicleScope=true, lockScope=true, fuelScope=true, evScope=true] {
|
||||
Thing bev eqa "Mercedes EQA" [ vin="VEHICLE_VIN", refreshInterval=5, background=false, night=false, cropped=false, roofOpen=false, format="webp"]
|
||||
}
|
||||
```
|
||||
|
||||
### Items file
|
||||
|
||||
```
|
||||
Number:Length EQA_Mileage "Odometer [%d %unit%]" {channel="mercedesme:bev:4711:eqa:range#mileage" }
|
||||
Number:Length EQA_Range "Range [%d %unit%]" {channel="mercedesme:bev:4711:eqa:range#range-electric"}
|
||||
Number:Length EQA_RangeRadius "Range Radius [%d %unit%]" {channel="mercedesme:bev:4711:eqa:range#radius-electric"}
|
||||
Number:Dimensionless EQA_BatterySoc "Battery Charge [%.1f %%]" {channel="mercedesme:bev:4711:eqa:range#soc"}
|
||||
|
||||
Contact EQA_DriverDoor "Driver Door [%s]" {channel="mercedesme:bev:4711:eqa:doors#driver-front" }
|
||||
Contact EQA_DriverDoorRear "Driver Door Rear [%s]" {channel="mercedesme:bev:4711:eqa:doors#driver-rear" }
|
||||
Contact EQA_PassengerDoor "Passenger Door [%s]" {channel="mercedesme:bev:4711:eqa:doors#passenger-front" }
|
||||
Contact EQA_PassengerDoorRear "Passenger Door Rear [%s]" {channel="mercedesme:bev:4711:eqa:doors#passenger-rear" }
|
||||
Number EQA_Trunk "Trunk [%s]" {channel="mercedesme:bev:4711:eqa:doors#deck-lid" }
|
||||
Number EQA_Rooftop "Rooftop [%s]" {channel="mercedesme:bev:4711:eqa:doors#rooftop" }
|
||||
Number EQA_Sunroof "Sunroof [%s]" {channel="mercedesme:bev:4711:eqa:doors#sunroof" }
|
||||
|
||||
Number EQA_DoorLock "Door Lock [%s]" {channel="mercedesme:bev:4711:eqa:lock#doors" }
|
||||
Switch EQA_TrunkLock "Trunk Lock [%s]" {channel="mercedesme:bev:4711:eqa:lock#deck-lid" }
|
||||
Switch EQA_FlapLock "Charge Flap Lock [%s]" {channel="mercedesme:bev:4711:eqa:lock#flap" }
|
||||
|
||||
Number EQA_DriverWindow "Driver Window [%s]" {channel="mercedesme:bev:4711:eqa:windows#driver-front" }
|
||||
Number EQA_DriverWindowRear "Driver Window Rear [%s]" {channel="mercedesme:bev:4711:eqa:windows#driver-rear" }
|
||||
Number EQA_PassengerWindow "Passenger Window [%s]" {channel="mercedesme:bev:4711:eqa:windows#passenger-front" }
|
||||
Number EQA_PassengerWindowRear "Passenger Window Rear [%s]" {channel="mercedesme:bev:4711:eqa:windows#passenger-rear" }
|
||||
|
||||
Number:Angle EQA_Heading "Heading [%.1f %unit%]" {channel="mercedesme:bev:4711:eqa:location#heading" }
|
||||
|
||||
Image EQA_Image "Image" {channel="mercedesme:bev:4711:eqa:image#image-data" }
|
||||
String EQA_ImageViewport "Image Viewport [%s]" {channel="mercedesme:bev:4711:eqa:image#image-view" }
|
||||
Switch EQA_ClearCache "Clear Cache [%s]" {channel="mercedesme:bev:4711:eqa:image#clear-cache" }
|
||||
|
||||
Switch EQA_InteriorFront "Interior Front Light [%s]" {channel="mercedesme:bev:4711:eqa:lights#interior-front" }
|
||||
Switch EQA_InteriorRear "Interior Rear Light [%s]" {channel="mercedesme:bev:4711:eqa:lights#interior-rear" }
|
||||
Switch EQA_ReadingLeft "Reading Light Left [%s]" {channel="mercedesme:bev:4711:eqa:lights#reading-left" }
|
||||
Switch EQA_ReadingRight "Reading Light Right [%s]" {channel="mercedesme:bev:4711:eqa:lights#reading-right" }
|
||||
Number EQA_LightSwitch "Main Light Switch [%s]" {channel="mercedesme:bev:4711:eqa:lights#light-switch" }
|
||||
```
|
||||
|
||||
### Sitemap
|
||||
|
||||
```
|
||||
sitemap MB label="Mercedes Benz EQA" {
|
||||
Frame label="EQA Image" {
|
||||
Image item=EQA_Image
|
||||
|
||||
}
|
||||
Frame label="Range" {
|
||||
Text item=EQA_Mileage
|
||||
Text item=EQA_Range
|
||||
Text item=EQA_RangeRadius
|
||||
Text item=EQA_BatterySoc
|
||||
}
|
||||
|
||||
Frame label="Door Details" {
|
||||
Text item=EQA_DriverDoor
|
||||
Text item=EQA_DriverDoorRear
|
||||
Text item=EQA_PassengerDoor
|
||||
Text item=EQA_PassengerDoorRear
|
||||
Text item=EQA_Trunk
|
||||
Text item=EQA_Rooftop
|
||||
Text item=EQA_Sunroof
|
||||
Text item=EQA_DoorLock
|
||||
Text item=EQA_TrunkLock
|
||||
Text item=EQA_FlapLock
|
||||
}
|
||||
|
||||
Frame label="Windows" {
|
||||
Text item=EQA_DriverWindow
|
||||
Text item=EQA_DriverWindowRear
|
||||
Text item=EQA_PassengerWindow
|
||||
Text item=EQA_PassengerWindowRear
|
||||
}
|
||||
|
||||
Frame label="Location" {
|
||||
Text item=EQA_Heading
|
||||
}
|
||||
|
||||
Frame label="Lights" {
|
||||
Text item=EQA_InteriorFront
|
||||
Text item=EQA_InteriorRear
|
||||
Text item=EQA_ReadingLeft
|
||||
Text item=EQA_ReadingRight
|
||||
Text item=EQA_LightSwitch
|
||||
}
|
||||
|
||||
Frame label="Image Properties" {
|
||||
Selection item=EQA_ImageViewport
|
||||
Switch item=EQA_ClearCache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Mercedes Benz Developer
|
||||
|
||||
Visit [Mercedes Benz Developer](https://developer.mercedes-benz.com/) to gain more deep information.
|
BIN
bundles/org.openhab.binding.mercedesme/doc/CallbackUrl_Page.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
bundles/org.openhab.binding.mercedesme/doc/ImageRestrictions.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 85 KiB |
BIN
bundles/org.openhab.binding.mercedesme/doc/MBAccessRequest.png
Normal file
After Width: | Height: | Size: 235 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 32 KiB |
27
bundles/org.openhab.binding.mercedesme/pom.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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.4.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<!-- version needs to match with other projects like org.openhab.io.openhabcloud.pom.xml -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20180813</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<artifactId>org.openhab.binding.mercedesme</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: MercedesMe Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.mercedesme-${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/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-mercedesme" description="MercedesMe Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mercedesme/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.mercedesme.internal;
|
||||
|
||||
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.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link Constants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Constants {
|
||||
public static final String BINDING_ID = "mercedesme";
|
||||
|
||||
public static final String COMBUSTION = "combustion";
|
||||
public static final String HYBRID = "hybrid";
|
||||
public static final String BEV = "bev";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_COMB = new ThingTypeUID(BINDING_ID, COMBUSTION);
|
||||
public static final ThingTypeUID THING_TYPE_HYBRID = new ThingTypeUID(BINDING_ID, HYBRID);
|
||||
public static final ThingTypeUID THING_TYPE_BEV = new ThingTypeUID(BINDING_ID, BEV);
|
||||
|
||||
public static final String GROUP_RANGE = "range";
|
||||
public static final String GROUP_DOORS = "doors";
|
||||
public static final String GROUP_WINDOWS = "windows";
|
||||
public static final String GROUP_LOCK = "lock";
|
||||
public static final String GROUP_LIGHTS = "lights";
|
||||
public static final String GROUP_LOCATION = "location";
|
||||
public static final String GROUP_IMAGE = "image";
|
||||
|
||||
public static final String MB_AUTH_URL = "https://id.mercedes-benz.com/as/authorization.oauth2";
|
||||
public static final String MB_TOKEN_URL = "https://id.mercedes-benz.com/as/token.oauth2";
|
||||
public static final String CALLBACK_ENDPOINT = "/mb-callback";
|
||||
public static final String OAUTH_CLIENT_NAME = "#byocar";
|
||||
|
||||
// https://developer.mercedes-benz.com/products/electric_vehicle_status/docs
|
||||
public static final String SCOPE_EV = "mb:vehicle:mbdata:evstatus";
|
||||
// https://developer.mercedes-benz.com/products/fuel_status/docs
|
||||
public static final String SCOPE_FUEL = "mb:vehicle:mbdata:fuelstatus";
|
||||
// https://developer.mercedes-benz.com/products/pay_as_you_drive_insurance/docs
|
||||
public static final String SCOPE_ODO = "mb:vehicle:mbdata:payasyoudrive";
|
||||
// https://developer.mercedes-benz.com/products/vehicle_lock_status/docs
|
||||
public static final String SCOPE_LOCK = "mb:vehicle:mbdata:vehiclelock";
|
||||
// https://developer.mercedes-benz.com/products/vehicle_status/docs
|
||||
public static final String SCOPE_STATUS = "mb:vehicle:mbdata:vehiclestatus";
|
||||
public static final String SCOPE_OFFLINE = "offline_access";
|
||||
|
||||
public static final String BASE_URL = "https://api.mercedes-benz.com/vehicledata/v2";
|
||||
public static final String ODO_URL = BASE_URL + "/vehicles/%s/containers/payasyoudrive";
|
||||
public static final String STATUS_URL = BASE_URL + "/vehicles/%s/containers/vehiclestatus";
|
||||
public static final String LOCK_URL = BASE_URL + "/vehicles/%s/containers/vehiclelockstatus";
|
||||
public static final String FUEL_URL = BASE_URL + "/vehicles/%s/containers/fuelstatus";
|
||||
public static final String EV_URL = BASE_URL + "/vehicles/%s/containers/electricvehicle";
|
||||
|
||||
// https://developer.mercedes-benz.com/content-page/api_migration_guide
|
||||
public static final String IMAGE_BASE_URL = "https://api.mercedes-benz.com/vehicle_images/v2";
|
||||
public static final String IMAGE_EXTERIOR_RESOURCE_URL = IMAGE_BASE_URL + "/vehicles/%s";
|
||||
|
||||
public static final String STATUS_TEXT_PREFIX = "@text/mercedesme.";
|
||||
public static final String STATUS_AUTH_NEEDED = ".status.authorization-needed";
|
||||
public static final String STATUS_IP_MISSING = ".status.ip-missing";
|
||||
public static final String STATUS_PORT_MISSING = ".status.port-missing";
|
||||
public static final String STATUS_CLIENT_ID_MISSING = ".status.client-id-missing";
|
||||
public static final String STATUS_CLIENT_SECRET_MISSING = ".status.client-secret-missing";
|
||||
public static final String STATUS_SERVER_RESTART = ".status.server-restart";
|
||||
public static final String STATUS_BRIDGE_MISSING = ".status.bridge-missing";
|
||||
public static final String STATUS_BRIDGE_ATHORIZATION = ".status.bridge-authoriziation";
|
||||
|
||||
public static final String SPACE = " ";
|
||||
public static final String EMPTY = "";
|
||||
public static final String COLON = ":";
|
||||
public static final String NOT_SET = "not set";
|
||||
|
||||
public static final String CODE = "code";
|
||||
public static final String MIME_PREFIX = "image/";
|
||||
|
||||
public static final Unit<Length> KILOMETRE_UNIT = MetricPrefix.KILO(SIUnits.METRE);
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mercedesme.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of command options while leaving other state description fields as original.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicCommandDescriptionProvider.class, MercedesMeCommandOptionProvider.class })
|
||||
public class MercedesMeCommandOptionProvider extends BaseDynamicCommandDescriptionProvider {
|
||||
@Activate
|
||||
public MercedesMeCommandOptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 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.mercedesme.internal;
|
||||
|
||||
import static org.openhab.binding.mercedesme.internal.Constants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.WWWAuthenticationProtocolHandler;
|
||||
import org.openhab.binding.mercedesme.internal.handler.AccountHandler;
|
||||
import org.openhab.binding.mercedesme.internal.handler.VehicleHandler;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
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.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link MercedesMeHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.mercedesme", service = ThingHandlerFactory.class)
|
||||
public class MercedesMeHandlerFactory extends BaseThingHandlerFactory {
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BEV, THING_TYPE_COMB,
|
||||
THING_TYPE_HYBRID, THING_TYPE_ACCOUNT);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MercedesMeHandlerFactory.class);
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final HttpClient httpClient;
|
||||
private final MercedesMeCommandOptionProvider mmcop;
|
||||
private final MercedesMeStateOptionProvider mmsop;
|
||||
private final StorageService storageService;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Activate
|
||||
public MercedesMeHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory hcf,
|
||||
@Reference StorageService storageService, final @Reference MercedesMeCommandOptionProvider cop,
|
||||
final @Reference MercedesMeStateOptionProvider sop, final @Reference TimeZoneProvider tzp) {
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.storageService = storageService;
|
||||
mmcop = cop;
|
||||
mmsop = sop;
|
||||
timeZoneProvider = tzp;
|
||||
httpClient = hcf.createHttpClient(Constants.BINDING_ID);
|
||||
// https://github.com/jetty-project/jetty-reactive-httpclient/issues/33
|
||||
httpClient.getProtocolHandlers().remove(WWWAuthenticationProtocolHandler.NAME);
|
||||
try {
|
||||
httpClient.start();
|
||||
} catch (Exception e) {
|
||||
logger.warn("HTTP client not started: {} - no web access possible!", e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||
return new AccountHandler((Bridge) thing, httpClient, oAuthFactory);
|
||||
}
|
||||
return new VehicleHandler(thing, httpClient, thingTypeUID.getId(), storageService, mmcop, mmsop,
|
||||
timeZoneProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate(ComponentContext componentContext) {
|
||||
super.deactivate(componentContext);
|
||||
try {
|
||||
httpClient.stop();
|
||||
} catch (Exception e) {
|
||||
logger.debug("HTTP client not stopped: {}", e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mercedesme.internal;
|
||||
|
||||
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
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, MercedesMeStateOptionProvider.class })
|
||||
public class MercedesMeStateOptionProvider extends BaseDynamicStateDescriptionProvider {
|
||||
@Activate
|
||||
public MercedesMeStateOptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.config;
|
||||
|
||||
import static org.openhab.binding.mercedesme.internal.Constants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link AccountConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AccountConfiguration {
|
||||
|
||||
public String clientId = NOT_SET;
|
||||
public String clientSecret = NOT_SET;
|
||||
public String imageApiKey = NOT_SET;
|
||||
|
||||
// Advanced Parameters
|
||||
public String callbackIP = NOT_SET;
|
||||
public int callbackPort = -1;
|
||||
public boolean odoScope = true;
|
||||
public boolean vehicleScope = true;
|
||||
public boolean lockScope = true;
|
||||
public boolean fuelScope = true;
|
||||
public boolean evScope = true;
|
||||
|
||||
// https://developer.mercedes-benz.com/products/electric_vehicle_status/docs#_required_scopes
|
||||
public String getScope() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(SCOPE_OFFLINE);
|
||||
if (odoScope) {
|
||||
sb.append(SPACE).append(SCOPE_ODO);
|
||||
}
|
||||
if (vehicleScope) {
|
||||
sb.append(SPACE).append(SCOPE_STATUS);
|
||||
}
|
||||
if (lockScope) {
|
||||
sb.append(SPACE).append(SCOPE_LOCK);
|
||||
}
|
||||
if (fuelScope) {
|
||||
sb.append(SPACE).append(SCOPE_FUEL);
|
||||
}
|
||||
if (evScope) {
|
||||
sb.append(SPACE).append(SCOPE_EV);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ID " + clientId + ", Secret " + clientSecret + ", IP " + callbackIP + ", Port " + callbackPort
|
||||
+ ", scope " + getScope();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
|
||||
/**
|
||||
* The {@link VehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleConfiguration {
|
||||
|
||||
public String vin = Constants.NOT_SET;
|
||||
public int refreshInterval = 5;
|
||||
public float batteryCapacity = -1;
|
||||
public float fuelCapacity = -1;
|
||||
|
||||
// Advanced
|
||||
public boolean background = false;
|
||||
public boolean night = false;
|
||||
public boolean cropped = false;
|
||||
public boolean roofOpen = false;
|
||||
public String format = "webp";
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.handler;
|
||||
|
||||
import java.net.SocketException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
|
||||
import org.openhab.binding.mercedesme.internal.server.CallbackServer;
|
||||
import org.openhab.binding.mercedesme.internal.server.Utils;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AccountHandler} takes care of the valid authorization for the user account
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefreshListener {
|
||||
private final Logger logger = LoggerFactory.getLogger(AccountHandler.class);
|
||||
private final OAuthFactory oAuthFactory;
|
||||
private final HttpClient httpClient;
|
||||
private Optional<CallbackServer> server = Optional.empty();
|
||||
|
||||
Optional<AccountConfiguration> config = Optional.empty();
|
||||
|
||||
public AccountHandler(Bridge bridge, HttpClient hc, OAuthFactory oaf) {
|
||||
super(bridge);
|
||||
httpClient = hc;
|
||||
oAuthFactory = oaf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// no commands available
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = Optional.of(getConfigAs(AccountConfiguration.class));
|
||||
autodetectCallback();
|
||||
String configValidReason = configValid();
|
||||
if (!configValidReason.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configValidReason);
|
||||
} else {
|
||||
String callbackUrl = Utils.getCallbackAddress(config.get().callbackIP, config.get().callbackPort);
|
||||
thing.setProperty("callbackUrl", callbackUrl);
|
||||
server = Optional.of(new CallbackServer(this, httpClient, oAuthFactory, config.get(), callbackUrl));
|
||||
if (!server.get().start()) {
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
|
||||
+ Constants.STATUS_SERVER_RESTART;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, textKey);
|
||||
} else {
|
||||
// get fresh token
|
||||
this.getToken();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void autodetectCallback() {
|
||||
// if Callback IP and Callback Port are not set => autodetect these values
|
||||
config = Optional.of(getConfigAs(AccountConfiguration.class));
|
||||
Configuration updateConfig = super.editConfiguration();
|
||||
if (!updateConfig.containsKey("callbackPort")) {
|
||||
updateConfig.put("callbackPort", Utils.getFreePort());
|
||||
} else {
|
||||
Utils.addPort(config.get().callbackPort);
|
||||
}
|
||||
if (!updateConfig.containsKey("callbackIP")) {
|
||||
String ip;
|
||||
try {
|
||||
ip = Utils.getCallbackIP();
|
||||
updateConfig.put("callbackIP", ip);
|
||||
} catch (SocketException e) {
|
||||
logger.info("Cannot detect IP address {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
super.updateConfiguration(updateConfig);
|
||||
// get new config after update
|
||||
config = Optional.of(getConfigAs(AccountConfiguration.class));
|
||||
}
|
||||
|
||||
private String configValid() {
|
||||
config = Optional.of(getConfigAs(AccountConfiguration.class));
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId();
|
||||
if (config.get().callbackIP.equals(Constants.NOT_SET)) {
|
||||
return textKey + Constants.STATUS_IP_MISSING;
|
||||
} else if (config.get().callbackPort == -1) {
|
||||
return textKey + Constants.STATUS_PORT_MISSING;
|
||||
} else if (config.get().clientId.equals(Constants.NOT_SET)) {
|
||||
return textKey + Constants.STATUS_CLIENT_ID_MISSING;
|
||||
} else if (config.get().clientSecret.equals(Constants.NOT_SET)) {
|
||||
return textKey + Constants.STATUS_CLIENT_SECRET_MISSING;
|
||||
} else {
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!server.isEmpty()) {
|
||||
server.get().stop();
|
||||
Utils.removePort(config.get().callbackPort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* https://next.openhab.org/javadoc/latest/org/openhab/core/auth/client/oauth2/package-summary.html
|
||||
*/
|
||||
@Override
|
||||
public void onAccessTokenResponse(AccessTokenResponse tokenResponse) {
|
||||
if (!tokenResponse.getAccessToken().isEmpty()) {
|
||||
// token not empty - fine
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else if (server.isEmpty()) {
|
||||
// server not running - fix first
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
|
||||
+ Constants.STATUS_SERVER_RESTART;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, textKey);
|
||||
} else {
|
||||
// all failed - start manual authorization
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
|
||||
+ Constants.STATUS_AUTH_NEEDED;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
|
||||
textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]");
|
||||
}
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return server.get().getToken();
|
||||
}
|
||||
|
||||
public String getImageApiKey() {
|
||||
return config.get().imageApiKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(config.get().callbackPort);
|
||||
}
|
||||
}
|
@ -0,0 +1,579 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.handler;
|
||||
|
||||
import static org.openhab.binding.mercedesme.internal.Constants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.measure.quantity.Length;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.openhab.binding.mercedesme.internal.MercedesMeCommandOptionProvider;
|
||||
import org.openhab.binding.mercedesme.internal.MercedesMeStateOptionProvider;
|
||||
import org.openhab.binding.mercedesme.internal.config.VehicleConfiguration;
|
||||
import org.openhab.binding.mercedesme.internal.utils.ChannelStateMap;
|
||||
import org.openhab.binding.mercedesme.internal.utils.Mapper;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
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.unit.Units;
|
||||
import org.openhab.core.storage.Storage;
|
||||
import org.openhab.core.storage.StorageService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link VehicleHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleHandler extends BaseThingHandler {
|
||||
private static final String EXT_IMG_RES = "ExtImageResources_";
|
||||
private static final String INITIALIZE_COMMAND = "Initialze";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
|
||||
private final Map<String, Long> timeHash = new HashMap<String, Long>();
|
||||
private final MercedesMeCommandOptionProvider mmcop;
|
||||
private final MercedesMeStateOptionProvider mmsop;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final StorageService storageService;
|
||||
private final HttpClient httpClient;
|
||||
private final String uid;
|
||||
|
||||
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
|
||||
private Optional<AccountHandler> accountHandler = Optional.empty();
|
||||
private Optional<QuantityType<?>> rangeElectric = Optional.empty();
|
||||
private Optional<Storage<String>> imageStorage = Optional.empty();
|
||||
private Optional<VehicleConfiguration> config = Optional.empty();
|
||||
private Optional<QuantityType<?>> rangeFuel = Optional.empty();
|
||||
private Instant nextRefresh;
|
||||
private boolean online = false;
|
||||
|
||||
public VehicleHandler(Thing thing, HttpClient hc, String uid, StorageService storageService,
|
||||
MercedesMeCommandOptionProvider mmcop, MercedesMeStateOptionProvider mmsop, TimeZoneProvider tzp) {
|
||||
super(thing);
|
||||
httpClient = hc;
|
||||
this.uid = uid;
|
||||
this.mmcop = mmcop;
|
||||
this.mmsop = mmsop;
|
||||
timeZoneProvider = tzp;
|
||||
this.storageService = storageService;
|
||||
nextRefresh = Instant.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("Received {} {} {}", channelUID.getAsString(), command.toFullString(), channelUID.getId());
|
||||
if (command instanceof RefreshType) {
|
||||
/**
|
||||
* Refresh requested e.g. after adding new item
|
||||
* Adding several items will frequently raise RefreshType command. Calling API each time shall be avoided
|
||||
* API update is performed after 5 seconds for all items which should be sufficient for a frequent update
|
||||
*/
|
||||
if (Instant.now().isAfter(nextRefresh)) {
|
||||
nextRefresh = Instant.now().plus(Duration.ofSeconds(5));
|
||||
logger.trace("Refresh granted - next at {}", nextRefresh);
|
||||
scheduler.schedule(this::getData, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
} else if ("image-view".equals(channelUID.getIdWithoutGroup())) {
|
||||
if (imageStorage.isPresent()) {
|
||||
if (INITIALIZE_COMMAND.equals(command.toFullString())) {
|
||||
getImageResources();
|
||||
}
|
||||
String key = command.toFullString() + "_" + config.get().vin;
|
||||
String encodedImage = EMPTY;
|
||||
if (imageStorage.get().containsKey(key)) {
|
||||
encodedImage = imageStorage.get().get(key);
|
||||
logger.trace("Image {} found in storage", key);
|
||||
} else {
|
||||
logger.trace("Request Image {} ", key);
|
||||
encodedImage = getImage(command.toFullString());
|
||||
if (!encodedImage.isEmpty()) {
|
||||
imageStorage.get().put(key, encodedImage);
|
||||
}
|
||||
}
|
||||
if (encodedImage != null && !encodedImage.isEmpty()) {
|
||||
RawType image = new RawType(Base64.getDecoder().decode(encodedImage),
|
||||
MIME_PREFIX + config.get().format);
|
||||
updateState(new ChannelUID(thing.getUID(), GROUP_IMAGE, "image-data"), image);
|
||||
} else {
|
||||
logger.debug("Image {} is empty", key);
|
||||
}
|
||||
}
|
||||
} else if (channelUID.getIdWithoutGroup().equals("clear-cache") && command.equals(OnOffType.ON)) {
|
||||
List<String> removals = new ArrayList<String>();
|
||||
imageStorage.get().getKeys().forEach(entry -> {
|
||||
if (entry.contains("_" + config.get().vin)) {
|
||||
removals.add(entry);
|
||||
}
|
||||
});
|
||||
removals.forEach(entry -> {
|
||||
imageStorage.get().remove(entry);
|
||||
});
|
||||
updateState(new ChannelUID(thing.getUID(), GROUP_IMAGE, "clear-cache"), OnOffType.OFF);
|
||||
getImageResources();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = Optional.of(getConfigAs(VehicleConfiguration.class));
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null) {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
BridgeHandler handler = bridge.getHandler();
|
||||
if (handler != null) {
|
||||
accountHandler = Optional.of((AccountHandler) handler);
|
||||
startSchedule(config.get().refreshInterval);
|
||||
if (!config.get().vin.equals(NOT_SET)) {
|
||||
imageStorage = Optional.of(storageService.getStorage(BINDING_ID + "_" + config.get().vin));
|
||||
if (!imageStorage.get().containsKey(EXT_IMG_RES + config.get().vin)) {
|
||||
getImageResources();
|
||||
}
|
||||
setImageOtions();
|
||||
}
|
||||
updateState(new ChannelUID(thing.getUID(), GROUP_IMAGE, "clear-cache"), OnOffType.OFF);
|
||||
} else {
|
||||
throw new IllegalStateException("BridgeHandler is null");
|
||||
}
|
||||
} else {
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + "vehicle" + Constants.STATUS_BRIDGE_MISSING;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, textKey);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
public void getData() {
|
||||
if (accountHandler.isEmpty()) {
|
||||
logger.warn("AccountHandler not set");
|
||||
return;
|
||||
}
|
||||
String token = accountHandler.get().getToken();
|
||||
if (token.isEmpty()) {
|
||||
String textKey = Constants.STATUS_TEXT_PREFIX + "vehicle" + Constants.STATUS_BRIDGE_ATHORIZATION;
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, textKey);
|
||||
return;
|
||||
} else if (!online) { // only update if thing isn't already ONLINE
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
// Mileage for all cars
|
||||
String odoUrl = String.format(ODO_URL, config.get().vin);
|
||||
if (accountConfigAvailable()) {
|
||||
if (accountHandler.get().config.get().odoScope) {
|
||||
call(odoUrl);
|
||||
} else {
|
||||
logger.trace("{} Odo scope not activated", this.getThing().getLabel());
|
||||
}
|
||||
} else {
|
||||
logger.trace("{} Account not properly configured", this.getThing().getLabel());
|
||||
}
|
||||
|
||||
// Electric status for hybrid and electric
|
||||
if (uid.equals(BEV) || uid.equals(HYBRID)) {
|
||||
String evUrl = String.format(EV_URL, config.get().vin);
|
||||
if (accountConfigAvailable()) {
|
||||
if (accountHandler.get().config.get().evScope) {
|
||||
call(evUrl);
|
||||
} else {
|
||||
logger.trace("{} Electric Status scope not activated", this.getThing().getLabel());
|
||||
}
|
||||
} else {
|
||||
logger.trace("{} Account not properly configured", this.getThing().getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
// Fuel for hybrid and combustion
|
||||
if (uid.equals(COMBUSTION) || uid.equals(HYBRID)) {
|
||||
String fuelUrl = String.format(FUEL_URL, config.get().vin);
|
||||
if (accountConfigAvailable()) {
|
||||
if (accountHandler.get().config.get().fuelScope) {
|
||||
call(fuelUrl);
|
||||
} else {
|
||||
logger.trace("{} Fuel scope not activated", this.getThing().getLabel());
|
||||
}
|
||||
} else {
|
||||
logger.trace("{} Account not properly configured", this.getThing().getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
// Status and Lock for all
|
||||
String statusUrl = String.format(STATUS_URL, config.get().vin);
|
||||
if (accountConfigAvailable()) {
|
||||
if (accountHandler.get().config.get().vehicleScope) {
|
||||
call(statusUrl);
|
||||
} else {
|
||||
logger.trace("{} Vehicle Status scope not activated", this.getThing().getLabel());
|
||||
}
|
||||
} else {
|
||||
logger.trace("{} Account not properly configured", this.getThing().getLabel());
|
||||
}
|
||||
String lockUrl = String.format(LOCK_URL, config.get().vin);
|
||||
if (accountConfigAvailable()) {
|
||||
if (accountHandler.get().config.get().lockScope) {
|
||||
call(lockUrl);
|
||||
} else {
|
||||
logger.trace("{} Lock scope not activated", this.getThing().getLabel());
|
||||
}
|
||||
} else {
|
||||
logger.trace("{} Account not properly configured", this.getThing().getLabel());
|
||||
}
|
||||
|
||||
// Range radius for all types
|
||||
updateRadius();
|
||||
}
|
||||
|
||||
private boolean accountConfigAvailable() {
|
||||
if (accountHandler.isPresent()) {
|
||||
if (accountHandler.get().config.isPresent()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void getImageResources() {
|
||||
if (accountHandler.get().getImageApiKey().equals(NOT_SET)) {
|
||||
logger.debug("Image API key not set");
|
||||
return;
|
||||
}
|
||||
// add config parameters
|
||||
MultiMap<String> parameterMap = new MultiMap<String>();
|
||||
parameterMap.add("background", Boolean.toString(config.get().background));
|
||||
parameterMap.add("night", Boolean.toString(config.get().night));
|
||||
parameterMap.add("cropped", Boolean.toString(config.get().cropped));
|
||||
parameterMap.add("roofOpen", Boolean.toString(config.get().roofOpen));
|
||||
parameterMap.add("fileFormat", config.get().format);
|
||||
String params = UrlEncoded.encode(parameterMap, StandardCharsets.UTF_8, false);
|
||||
String url = String.format(IMAGE_EXTERIOR_RESOURCE_URL, config.get().vin) + "?" + params;
|
||||
logger.debug("Get Image resources {} {} ", accountHandler.get().getImageApiKey(), url);
|
||||
Request req = httpClient.newRequest(url);
|
||||
req.header("x-api-key", accountHandler.get().getImageApiKey());
|
||||
req.header(HttpHeader.ACCEPT, "application/json");
|
||||
try {
|
||||
ContentResponse cr = req.send();
|
||||
if (cr.getStatus() == 200) {
|
||||
imageStorage.get().put(EXT_IMG_RES + config.get().vin, cr.getContentAsString());
|
||||
setImageOtions();
|
||||
} else {
|
||||
logger.debug("Failed to get image resources {} {}", cr.getStatus(), cr.getContentAsString());
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Error getting image resources {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void setImageOtions() {
|
||||
List<String> entries = new ArrayList<String>();
|
||||
if (imageStorage.get().containsKey(EXT_IMG_RES + config.get().vin)) {
|
||||
String resources = imageStorage.get().get(EXT_IMG_RES + config.get().vin);
|
||||
JSONObject jo = new JSONObject(resources);
|
||||
jo.keySet().forEach(entry -> {
|
||||
entries.add(entry);
|
||||
});
|
||||
}
|
||||
Collections.sort(entries);
|
||||
List<CommandOption> commandOptions = new ArrayList<CommandOption>();
|
||||
List<StateOption> stateOptions = new ArrayList<StateOption>();
|
||||
entries.forEach(entry -> {
|
||||
CommandOption co = new CommandOption(entry, null);
|
||||
commandOptions.add(co);
|
||||
StateOption so = new StateOption(entry, null);
|
||||
stateOptions.add(so);
|
||||
});
|
||||
if (commandOptions.isEmpty()) {
|
||||
commandOptions.add(new CommandOption("Initilaze", null));
|
||||
stateOptions.add(new StateOption("Initilaze", null));
|
||||
}
|
||||
ChannelUID cuid = new ChannelUID(thing.getUID(), GROUP_IMAGE, "image-view");
|
||||
mmcop.setCommandOptions(cuid, commandOptions);
|
||||
mmsop.setStateOptions(cuid, stateOptions);
|
||||
}
|
||||
|
||||
private String getImage(String key) {
|
||||
if (accountHandler.get().getImageApiKey().equals(NOT_SET)) {
|
||||
logger.debug("Image API key not set");
|
||||
return EMPTY;
|
||||
}
|
||||
String imageId = EMPTY;
|
||||
if (imageStorage.get().containsKey(EXT_IMG_RES + config.get().vin)) {
|
||||
String resources = imageStorage.get().get(EXT_IMG_RES + config.get().vin);
|
||||
JSONObject jo = new JSONObject(resources);
|
||||
if (jo.has(key)) {
|
||||
imageId = jo.getString(key);
|
||||
}
|
||||
} else {
|
||||
getImageResources();
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
String url = IMAGE_BASE_URL + "/images/" + imageId;
|
||||
Request req = httpClient.newRequest(url);
|
||||
req.header("x-api-key", accountHandler.get().getImageApiKey());
|
||||
req.header(HttpHeader.ACCEPT, "*/*");
|
||||
ContentResponse cr;
|
||||
try {
|
||||
cr = req.send();
|
||||
byte[] response = cr.getContent();
|
||||
return Base64.getEncoder().encodeToString(response);
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Get Image {} error {}", url, e.getMessage());
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
private void call(String url) {
|
||||
String requestUrl = String.format(url, config.get().vin);
|
||||
// Calculate endpoint for debugging
|
||||
String[] endpoint = requestUrl.split("/");
|
||||
String finalEndpoint = endpoint[endpoint.length - 1];
|
||||
// debug prefix contains Thing label and call endpoint for propper debugging
|
||||
String debugPrefix = this.getThing().getLabel() + Constants.COLON + finalEndpoint;
|
||||
|
||||
Request req = httpClient.newRequest(requestUrl);
|
||||
req.header(HttpHeader.AUTHORIZATION, "Bearer " + accountHandler.get().getToken());
|
||||
try {
|
||||
ContentResponse cr = req.send();
|
||||
logger.trace("{} Response {} {}", debugPrefix, cr.getStatus(), cr.getContentAsString());
|
||||
if (cr.getStatus() == 200) {
|
||||
distributeContent(cr.getContentAsString().trim());
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.info("{} Error getting data {}", debugPrefix, e.getMessage());
|
||||
fallbackCall(requestUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback solution with Java11 classes
|
||||
* Performs try with Java11 HttpClient - https://zetcode.com/java/getpostrequest/ to identify Community problem
|
||||
* https://community.openhab.org/t/mercedes-me-binding/136852/21
|
||||
*
|
||||
* @param requestUrl
|
||||
*/
|
||||
private void fallbackCall(String requestUrl) {
|
||||
// Calculate endpoint for debugging
|
||||
String[] endpoint = requestUrl.split("/");
|
||||
String finalEndpoint = endpoint[endpoint.length - 1];
|
||||
// debug prefix contains Thing label and call endpoint for propper debugging
|
||||
String debugPrefix = this.getThing().getLabel() + Constants.COLON + finalEndpoint;
|
||||
|
||||
java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
|
||||
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(requestUrl))
|
||||
.header(HttpHeader.AUTHORIZATION.toString(), "Bearer " + accountHandler.get().getToken()).GET().build();
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
logger.debug("{} Fallback Response {} {}", debugPrefix, response.statusCode(), response.body());
|
||||
if (response.statusCode() == 200) {
|
||||
distributeContent(response.body().trim());
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.warn("{} Error getting data via fallback {}", debugPrefix, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void distributeContent(String json) {
|
||||
if (json.startsWith("[") && json.endsWith("]")) {
|
||||
JSONArray ja = new JSONArray(json);
|
||||
for (Iterator<Object> iterator = ja.iterator(); iterator.hasNext();) {
|
||||
JSONObject jo = (JSONObject) iterator.next();
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
if (csm.isValid()) {
|
||||
updateChannel(csm);
|
||||
|
||||
/**
|
||||
* handle some specific channels
|
||||
*/
|
||||
// store ChannelMap for range radius calculation
|
||||
String channel = csm.getChannel();
|
||||
if ("range-electric".equals(channel)) {
|
||||
rangeElectric = Optional.of((QuantityType<?>) csm.getState());
|
||||
} else if ("range-fuel".equals(channel)) {
|
||||
rangeFuel = Optional.of((QuantityType<?>) csm.getState());
|
||||
} else if ("soc".equals(channel)) {
|
||||
if (config.get().batteryCapacity > 0) {
|
||||
float socValue = ((QuantityType<?>) csm.getState()).floatValue();
|
||||
float batteryCapacity = config.get().batteryCapacity;
|
||||
float chargedValue = Math.round(socValue * 1000 * batteryCapacity / 1000) / (float) 100;
|
||||
ChannelStateMap charged = new ChannelStateMap("charged", GROUP_RANGE,
|
||||
QuantityType.valueOf(chargedValue, Units.KILOWATT_HOUR), csm.getTimestamp());
|
||||
updateChannel(charged);
|
||||
float unchargedValue = Math.round((100 - socValue) * 1000 * batteryCapacity / 1000)
|
||||
/ (float) 100;
|
||||
ChannelStateMap uncharged = new ChannelStateMap("uncharged", GROUP_RANGE,
|
||||
QuantityType.valueOf(unchargedValue, Units.KILOWATT_HOUR), csm.getTimestamp());
|
||||
updateChannel(uncharged);
|
||||
} else {
|
||||
logger.debug("No battery capacity given");
|
||||
}
|
||||
} else if ("fuel-level".equals(channel)) {
|
||||
if (config.get().fuelCapacity > 0) {
|
||||
float fuelLevelValue = ((QuantityType<?>) csm.getState()).floatValue();
|
||||
float fuelCapacity = config.get().fuelCapacity;
|
||||
float litersInTank = Math.round(fuelLevelValue * 1000 * fuelCapacity / 1000) / (float) 100;
|
||||
ChannelStateMap tankFilled = new ChannelStateMap("tank-remain", GROUP_RANGE,
|
||||
QuantityType.valueOf(litersInTank, Units.LITRE), csm.getTimestamp());
|
||||
updateChannel(tankFilled);
|
||||
float litersFree = Math.round((100 - fuelLevelValue) * 1000 * fuelCapacity / 1000)
|
||||
/ (float) 100;
|
||||
ChannelStateMap tankOpen = new ChannelStateMap("tank-open", GROUP_RANGE,
|
||||
QuantityType.valueOf(litersFree, Units.LITRE), csm.getTimestamp());
|
||||
updateChannel(tankOpen);
|
||||
} else {
|
||||
logger.debug("No fuel capacity given");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unable to deliver state for {}", jo);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("JSON Array expected but received {}", json);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRadius() {
|
||||
if (rangeElectric.isPresent()) {
|
||||
// update electric radius
|
||||
ChannelStateMap radiusElectric = new ChannelStateMap("radius-electric", GROUP_RANGE,
|
||||
guessRangeRadius(rangeElectric.get()), 0);
|
||||
updateChannel(radiusElectric);
|
||||
if (rangeFuel.isPresent()) {
|
||||
// update fuel & hybrid radius
|
||||
ChannelStateMap radiusFuel = new ChannelStateMap("radius-fuel", GROUP_RANGE,
|
||||
guessRangeRadius(rangeFuel.get()), 0);
|
||||
updateChannel(radiusFuel);
|
||||
int hybridKm = rangeElectric.get().intValue() + rangeFuel.get().intValue();
|
||||
QuantityType<Length> hybridRangeState = QuantityType.valueOf(hybridKm, KILOMETRE_UNIT);
|
||||
ChannelStateMap rangeHybrid = new ChannelStateMap("range-hybrid", GROUP_RANGE, hybridRangeState, 0);
|
||||
updateChannel(rangeHybrid);
|
||||
ChannelStateMap radiusHybrid = new ChannelStateMap("radius-hybrid", GROUP_RANGE,
|
||||
guessRangeRadius(hybridRangeState), 0);
|
||||
updateChannel(radiusHybrid);
|
||||
}
|
||||
} else if (rangeFuel.isPresent()) {
|
||||
// update fuel & hybrid radius
|
||||
ChannelStateMap radiusFuel = new ChannelStateMap("radius-fuel", GROUP_RANGE,
|
||||
guessRangeRadius(rangeFuel.get()), 0);
|
||||
updateChannel(radiusFuel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param range
|
||||
* @return mapping from air-line distance to "real road" distance
|
||||
*/
|
||||
public static State guessRangeRadius(QuantityType<?> s) {
|
||||
double radius = s.intValue() * 0.8;
|
||||
return QuantityType.valueOf(Math.round(radius), KILOMETRE_UNIT);
|
||||
}
|
||||
|
||||
protected void updateChannel(ChannelStateMap csm) {
|
||||
updateTime(csm.getGroup(), csm.getTimestamp());
|
||||
updateState(new ChannelUID(thing.getUID(), csm.getGroup(), csm.getChannel()), csm.getState());
|
||||
}
|
||||
|
||||
private void updateTime(String group, long timestamp) {
|
||||
boolean updateTime = false;
|
||||
Long l = timeHash.get(group);
|
||||
if (l != null) {
|
||||
if (l.longValue() < timestamp) {
|
||||
updateTime = true;
|
||||
}
|
||||
} else {
|
||||
updateTime = true;
|
||||
}
|
||||
if (updateTime) {
|
||||
timeHash.put(group, timestamp);
|
||||
DateTimeType dtt = new DateTimeType(Instant.ofEpochMilli(timestamp).atZone(timeZoneProvider.getTimeZone()));
|
||||
updateState(new ChannelUID(thing.getUID(), group, "last-update"), dtt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(ThingStatus ts, ThingStatusDetail tsd, @Nullable String details) {
|
||||
online = ts.equals(ThingStatus.ONLINE);
|
||||
super.updateStatus(ts, tsd, details);
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
|
||||
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthClientService;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthException;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CallbackServer} class defines an HTTP Server for authentication callbacks
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CallbackServer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CallbackServer.class);
|
||||
private static final Map<Integer, OAuthClientService> AUTH_MAP = new HashMap<Integer, OAuthClientService>();
|
||||
private static final Map<Integer, CallbackServer> SERVER_MAP = new HashMap<Integer, CallbackServer>();
|
||||
private static final AccessTokenResponse INVALID_ACCESS_TOKEN = new AccessTokenResponse();
|
||||
|
||||
private Optional<Server> server = Optional.empty();
|
||||
private AccessTokenRefreshListener listener;
|
||||
private AccountConfiguration config;
|
||||
private OAuthClientService oacs;
|
||||
private String callbackUrl;
|
||||
|
||||
public CallbackServer(AccessTokenRefreshListener l, HttpClient hc, OAuthFactory oAuthFactory,
|
||||
AccountConfiguration config, String callbackUrl) {
|
||||
oacs = oAuthFactory.createOAuthClientService(config.clientId, Constants.MB_TOKEN_URL, Constants.MB_AUTH_URL,
|
||||
config.clientId, config.clientSecret, config.getScope(), false);
|
||||
listener = l;
|
||||
AUTH_MAP.put(Integer.valueOf(config.callbackPort), oacs);
|
||||
SERVER_MAP.put(Integer.valueOf(config.callbackPort), this);
|
||||
this.config = config;
|
||||
this.callbackUrl = callbackUrl;
|
||||
INVALID_ACCESS_TOKEN.setAccessToken(Constants.EMPTY);
|
||||
}
|
||||
|
||||
public String getAuthorizationUrl() {
|
||||
try {
|
||||
return oacs.getAuthorizationUrl(callbackUrl, null, null);
|
||||
} catch (OAuthException e) {
|
||||
LOGGER.warn("Error creating Authorization URL {}", e.getMessage());
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return config.getScope();
|
||||
}
|
||||
|
||||
public boolean start() {
|
||||
LOGGER.debug("Start Callback Server for port {}", config.callbackPort);
|
||||
if (!server.isEmpty()) {
|
||||
LOGGER.debug("Callback server for port {} already started", config.callbackPort);
|
||||
return true;
|
||||
}
|
||||
server = Optional.of(new Server());
|
||||
ServerConnector connector = new ServerConnector(server.get());
|
||||
connector.setPort(config.callbackPort);
|
||||
server.get().setConnectors(new Connector[] { connector });
|
||||
ServletHandler servletHandler = new ServletHandler();
|
||||
server.get().setHandler(servletHandler);
|
||||
servletHandler.addServletWithMapping(CallbackServlet.class, Constants.CALLBACK_ENDPOINT);
|
||||
try {
|
||||
server.get().start();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
LOGGER.debug("Stop Callback Server");
|
||||
try {
|
||||
if (!server.isEmpty()) {
|
||||
server.get().stop();
|
||||
server = Optional.empty();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
AccessTokenResponse atr = null;
|
||||
try {
|
||||
/*
|
||||
* this will automatically trigger
|
||||
* - return last stored token if it's still valid
|
||||
* - refreshToken if current token is expired
|
||||
* - inform listeners if refresh delivered new token
|
||||
* - store new token in persistence
|
||||
*/
|
||||
atr = oacs.getAccessTokenResponse();
|
||||
} catch (OAuthException | IOException | OAuthResponseException e) {
|
||||
LOGGER.warn("Exception getting token {}", e.getMessage());
|
||||
}
|
||||
if (atr == null) {
|
||||
LOGGER.debug("Token empty - Manual Authorization needed at {}", callbackUrl);
|
||||
listener.onAccessTokenResponse(INVALID_ACCESS_TOKEN);
|
||||
return INVALID_ACCESS_TOKEN.getAccessToken();
|
||||
}
|
||||
listener.onAccessTokenResponse(atr);
|
||||
return atr.getAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static callback for Servlet calls
|
||||
*
|
||||
* @param port
|
||||
* @param code
|
||||
*/
|
||||
public static void callback(int port, String code) {
|
||||
LOGGER.trace("Callback from Servlet {} {}", port, code);
|
||||
try {
|
||||
OAuthClientService oacs = AUTH_MAP.get(port);
|
||||
LOGGER.trace("Get token from code {}", code);
|
||||
// get CallbackServer instance
|
||||
CallbackServer srv = SERVER_MAP.get(port);
|
||||
LOGGER.trace("Deliver token to {}", srv);
|
||||
if (srv != null && oacs != null) {
|
||||
// token stored and persisted inside oacs
|
||||
AccessTokenResponse atr = oacs.getAccessTokenResponseByAuthorizationCode(code, srv.callbackUrl);
|
||||
// inform listener - not done by oacs
|
||||
srv.listener.onAccessTokenResponse(atr);
|
||||
} else {
|
||||
LOGGER.warn("Either Callbackserver {} or Authorization Service {} not found", srv, oacs);
|
||||
}
|
||||
} catch (OAuthException | IOException | OAuthResponseException e) {
|
||||
LOGGER.warn("Exception getting token from code {} {}", code, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAuthorizationUrl(int port) {
|
||||
CallbackServer srv = SERVER_MAP.get(port);
|
||||
if (srv != null) {
|
||||
return srv.getAuthorizationUrl();
|
||||
} else {
|
||||
LOGGER.debug("No Callbackserver found for {}", port);
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getScope(int port) {
|
||||
CallbackServer srv = SERVER_MAP.get(port);
|
||||
if (srv != null) {
|
||||
return srv.getScope();
|
||||
} else {
|
||||
LOGGER.debug("No Callbackserver found for {}", port);
|
||||
return Constants.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link CallbackServlet} class provides authentication callback endpoint
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@NonNullByDefault
|
||||
public class CallbackServlet extends HttpServlet {
|
||||
private final Logger logger = LoggerFactory.getLogger(CallbackServlet.class);
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
String code = request.getParameter(Constants.CODE);
|
||||
if (code != null) {
|
||||
CallbackServer.callback(request.getLocalPort(), code);
|
||||
logger.trace("Code successfully extracted {}", request.getParameterMap());
|
||||
response.setContentType("application/json");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getWriter().println(request.getParameterMap());
|
||||
response.getWriter().println("{ \"status\": \"ok\"}");
|
||||
} else {
|
||||
response.setContentType("text/html");
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.getWriter().println("<HTML>");
|
||||
response.getWriter().println("<BODY>");
|
||||
response.getWriter().println("<B>Call Parameters</B>");
|
||||
response.getWriter().println("<BR>");
|
||||
response.getWriter().println(request.getParameterMap());
|
||||
response.getWriter().println("<BR><BR>");
|
||||
response.getWriter().println("<B>Configured scopes</B><BR>");
|
||||
String[] scopes = CallbackServer.getScope(request.getLocalPort()).split(Constants.SPACE);
|
||||
for (int i = 0; i < scopes.length; i++) {
|
||||
response.getWriter().println(scopes[i] + "<BR>");
|
||||
}
|
||||
response.getWriter().println("<BR><BR>");
|
||||
response.getWriter().println("<B>Get your access token for openHAB MercedesMe Binding</B>");
|
||||
response.getWriter().println("<BR>");
|
||||
response.getWriter().println("<a href=\"" + CallbackServer.getAuthorizationUrl(request.getLocalPort())
|
||||
+ "\">Start Authorization</a>");
|
||||
response.getWriter().println("</BODY>");
|
||||
response.getWriter().println("</HTML>");
|
||||
}
|
||||
logger.debug("Call from {}:{} parameters {}", request.getLocalAddr(), request.getLocalPort(),
|
||||
request.getParameterMap());
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.server;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Utils} class defines an HTTP Server for authentication callbacks
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Utils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
|
||||
private static final List<Integer> PORTS = new ArrayList<Integer>();
|
||||
private static int port = 8090;
|
||||
|
||||
/**
|
||||
* Get free port without other Thread interference
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static synchronized int getFreePort() {
|
||||
while (PORTS.contains(port)) {
|
||||
port++;
|
||||
}
|
||||
PORTS.add(port);
|
||||
return port;
|
||||
}
|
||||
|
||||
public static synchronized void addPort(int portNr) {
|
||||
if (PORTS.contains(portNr)) {
|
||||
LOGGER.warn("Port {} already occupied", portNr);
|
||||
}
|
||||
PORTS.add(portNr);
|
||||
}
|
||||
|
||||
public static synchronized void removePort(int portNr) {
|
||||
PORTS.remove(Integer.valueOf(portNr));
|
||||
}
|
||||
|
||||
public static String getCallbackIP() throws SocketException {
|
||||
// https://stackoverflow.com/questions/1062041/ip-address-not-obtained-in-java
|
||||
for (Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); ifaces
|
||||
.hasMoreElements();) {
|
||||
NetworkInterface iface = ifaces.nextElement();
|
||||
try {
|
||||
if (!iface.isLoopback()) {
|
||||
if (iface.isUp()) {
|
||||
for (Enumeration<InetAddress> addresses = iface.getInetAddresses(); addresses
|
||||
.hasMoreElements();) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
return address.getHostAddress();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException se) {
|
||||
// Calling one network interface failed - continue searching
|
||||
LOGGER.trace("Network {} failed {}", iface.getName(), se.getMessage());
|
||||
}
|
||||
}
|
||||
throw new SocketException("IP address not detected");
|
||||
}
|
||||
|
||||
public static String getCallbackAddress(String callbackIP, int callbackPort) {
|
||||
return "http://" + callbackIP + Constants.COLON + callbackPort + Constants.CALLBACK_ENDPOINT;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.utils;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link ChannelStateMap} holds the necessary values to update a channel state
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChannelStateMap {
|
||||
private String channel;
|
||||
private String group;
|
||||
private State state;
|
||||
private long timestamp;
|
||||
|
||||
public ChannelStateMap(String ch, String grp, State st, long ts) {
|
||||
channel = ch;
|
||||
group = grp;
|
||||
state = st;
|
||||
timestamp = ts;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return group + ":" + channel + " " + state;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return !channel.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
/**
|
||||
* 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.mercedesme.internal.utils;
|
||||
|
||||
import static org.openhab.binding.mercedesme.internal.Constants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.json.JSONObject;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link Mapper} maps a given Json Object towards a channel, group and state
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Mapper {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Mapper.class);
|
||||
|
||||
public static final ChannelStateMap INVALID_MAP = new ChannelStateMap(EMPTY, EMPTY, UnDefType.UNDEF, -1);
|
||||
public static final Map<String, String[]> CHANNELS = new HashMap<String, String[]>();
|
||||
public static final String TIMESTAMP = "timestamp";
|
||||
public static final String VALUE = "value";
|
||||
|
||||
public static ChannelStateMap getChannelStateMap(JSONObject jo) {
|
||||
if (CHANNELS.isEmpty()) {
|
||||
init();
|
||||
}
|
||||
Set<String> s = jo.keySet();
|
||||
if (s.size() == 1) {
|
||||
String id = s.toArray()[0].toString();
|
||||
String[] ch = CHANNELS.get(id);
|
||||
if (ch != null) {
|
||||
State state;
|
||||
switch (id) {
|
||||
// Kilometer values
|
||||
case "odo":
|
||||
case "rangeelectric":
|
||||
case "rangeliquid":
|
||||
state = getKilometers((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
// Percentages
|
||||
case "soc":
|
||||
case "tanklevelpercent":
|
||||
state = getPercentage((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
// Contacts
|
||||
case "decklidstatus":
|
||||
case "doorstatusfrontleft":
|
||||
case "doorstatusfrontright":
|
||||
case "doorstatusrearleft":
|
||||
case "doorstatusrearright":
|
||||
state = getContact((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
// Number Status
|
||||
case "lightswitchposition":
|
||||
case "rooftopstatus":
|
||||
case "sunroofstatus":
|
||||
case "windowstatusfrontleft":
|
||||
case "windowstatusfrontright":
|
||||
case "windowstatusrearleft":
|
||||
case "windowstatusrearright":
|
||||
case "doorlockstatusvehicle":
|
||||
state = getDecimal((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
// Switches
|
||||
case "interiorLightsFront":
|
||||
case "interiorLightsRear":
|
||||
case "readingLampFrontLeft":
|
||||
case "readingLampFrontRight":
|
||||
state = getOnOffType((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
case "doorlockstatusdecklid":
|
||||
case "doorlockstatusgas":
|
||||
state = getOnOffTypeLock((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
|
||||
// Angle
|
||||
case "positionHeading":
|
||||
state = getAngle((JSONObject) jo.get(id));
|
||||
return new ChannelStateMap(ch[0], ch[1], state, getTimestamp((JSONObject) jo.get(id)));
|
||||
default:
|
||||
LOGGER.trace("No mapping available for {}", id);
|
||||
}
|
||||
} else {
|
||||
LOGGER.trace("No mapping available for {}", id);
|
||||
}
|
||||
} else {
|
||||
LOGGER.debug("More than one key found {}", s);
|
||||
}
|
||||
return INVALID_MAP;
|
||||
}
|
||||
|
||||
private static long getTimestamp(JSONObject jo) {
|
||||
if (jo.has(TIMESTAMP)) {
|
||||
return jo.getLong(TIMESTAMP);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static State getOnOffType(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
boolean b = Boolean.valueOf(value);
|
||||
return OnOffType.from(b);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getOnOffTypeLock(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
boolean b = Boolean.valueOf(value);
|
||||
// Yes, false is locked and true unlocked
|
||||
// https://developer.mercedes-benz.com/products/vehicle_lock_status/specifications/vehicle_lock_status_api
|
||||
return OnOffType.from(!b);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getAngle(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
return QuantityType.valueOf(Double.valueOf(value), Units.DEGREE_ANGLE);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getDecimal(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
return DecimalType.valueOf(value);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getContact(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
boolean b = Boolean.valueOf(value);
|
||||
if (!b) {
|
||||
return OpenClosedType.CLOSED;
|
||||
} else {
|
||||
return OpenClosedType.OPEN;
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getKilometers(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
return QuantityType.valueOf(Integer.valueOf(value), KILOMETRE_UNIT);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
private static State getPercentage(JSONObject jo) {
|
||||
if (jo.has(VALUE)) {
|
||||
String value = jo.get(VALUE).toString();
|
||||
return QuantityType.valueOf(Integer.valueOf(value), Units.PERCENT);
|
||||
} else {
|
||||
LOGGER.warn("JSONObject contains no value {}", jo);
|
||||
return UnDefType.UNDEF;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping of json id towards channel group and id
|
||||
*/
|
||||
private static void init() {
|
||||
CHANNELS.put("odo", new String[] { "mileage", GROUP_RANGE });
|
||||
CHANNELS.put("rangeelectric", new String[] { "range-electric", GROUP_RANGE });
|
||||
CHANNELS.put("soc", new String[] { "soc", GROUP_RANGE });
|
||||
CHANNELS.put("rangeliquid", new String[] { "range-fuel", GROUP_RANGE });
|
||||
CHANNELS.put("tanklevelpercent", new String[] { "fuel-level", GROUP_RANGE });
|
||||
CHANNELS.put("decklidstatus", new String[] { "deck-lid", GROUP_DOORS });
|
||||
CHANNELS.put("doorstatusfrontleft", new String[] { "driver-front", GROUP_DOORS });
|
||||
CHANNELS.put("doorstatusfrontright", new String[] { "passenger-front", GROUP_DOORS });
|
||||
CHANNELS.put("doorstatusrearleft", new String[] { "driver-rear", GROUP_DOORS });
|
||||
CHANNELS.put("doorstatusrearright", new String[] { "passenger-rear", GROUP_DOORS });
|
||||
CHANNELS.put("interiorLightsFront", new String[] { "interior-front", GROUP_LIGHTS });
|
||||
CHANNELS.put("interiorLightsRear", new String[] { "interior-rear", GROUP_LIGHTS });
|
||||
CHANNELS.put("lightswitchposition", new String[] { "light-switch", GROUP_LIGHTS });
|
||||
CHANNELS.put("readingLampFrontLeft", new String[] { "reading-left", GROUP_LIGHTS });
|
||||
CHANNELS.put("readingLampFrontRight", new String[] { "reading-right", GROUP_LIGHTS });
|
||||
CHANNELS.put("rooftopstatus", new String[] { "rooftop", GROUP_DOORS });
|
||||
CHANNELS.put("sunroofstatus", new String[] { "sunroof", GROUP_DOORS });
|
||||
CHANNELS.put("windowstatusfrontleft", new String[] { "driver-front", GROUP_WINDOWS });
|
||||
CHANNELS.put("windowstatusfrontright", new String[] { "passenger-front", GROUP_WINDOWS });
|
||||
CHANNELS.put("windowstatusrearleft", new String[] { "driver-rear", GROUP_WINDOWS });
|
||||
CHANNELS.put("windowstatusrearright", new String[] { "passenger-rear", GROUP_WINDOWS });
|
||||
CHANNELS.put("doorlockstatusvehicle", new String[] { "doors", GROUP_LOCK });
|
||||
CHANNELS.put("doorlockstatusdecklid", new String[] { "deck-lid", GROUP_LOCK });
|
||||
CHANNELS.put("doorlockstatusgas", new String[] { "flap", GROUP_LOCK });
|
||||
CHANNELS.put("positionHeading", new String[] { "heading", GROUP_LOCATION });
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="mercedesme" 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>Mercedes Me Binding</name>
|
||||
<description>The binding provides access to your Mercedes developer account and vehicles</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,57 @@
|
||||
<?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:mercedesme:bev">
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>Vehicle Identification Number</label>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" min="1" unit="min" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Data refresh rate for vehicle data</description>
|
||||
<default>5</default>
|
||||
</parameter>
|
||||
<parameter name="batteryCapacity" type="decimal">
|
||||
<label>Battery Capacity</label>
|
||||
<description>Battery capacity in kWh of vehicle</description>
|
||||
</parameter>
|
||||
<!-- https://developer.mercedes-benz.com/products/vehicle_images/docs#_default_image_settings -->
|
||||
<parameter name="background" type="boolean">
|
||||
<label>Background Image</label>
|
||||
<description>Vehicle images provided with or without background</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="night" type="boolean">
|
||||
<label>Night Image</label>
|
||||
<description>Vehicle images in night conditions</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="cropped" type="boolean">
|
||||
<label>Cropped Image</label>
|
||||
<description>Vehicle images in 4:3 instead of 16:9</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="roofOpen" type="boolean">
|
||||
<label>Cabriolet Open Roof</label>
|
||||
<description>Vehicle images with open roof (only Cabriolet)</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="format" type="text">
|
||||
<label>Image Format</label>
|
||||
<description>Preferred Image Format</description>
|
||||
<default>webp</default>
|
||||
<advanced>true</advanced>
|
||||
<options>
|
||||
<option value="webp">webp</option>
|
||||
<option value="png">png</option>
|
||||
<option value="jpeg">jpeg</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,56 @@
|
||||
<?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:mercedesme:bridge">
|
||||
<parameter name="clientId" type="text" required="true">
|
||||
<label>MB Developer Client ID</label>
|
||||
<description>Mercedes Benz Developer Client ID</description>
|
||||
</parameter>
|
||||
<parameter name="clientSecret" type="text" required="true">
|
||||
<label>MB Developer Client Secret</label>
|
||||
<description>Mercedes Benz Developer Client Secret</description>
|
||||
</parameter>
|
||||
<parameter name="imageApiKey" type="text">
|
||||
<label>MB Developer Image API Key</label>
|
||||
<description>Mercedes Benz Developer Image API Key</description>
|
||||
</parameter>
|
||||
<parameter name="odoScope" type="boolean">
|
||||
<label>PayAsYourDrive Insurance</label>
|
||||
<description>Provides total Mileage</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="vehicleScope" type="boolean">
|
||||
<label>Vehicle Status</label>
|
||||
<description>Status of doors, windows lights</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="lockScope" type="boolean">
|
||||
<label>Vehicle Lock Status</label>
|
||||
<description>Lock status of doors and trunk</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="fuelScope" type="boolean">
|
||||
<label>Fuel Status</label>
|
||||
<description>Tank level and range</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="evScope" type="boolean">
|
||||
<label>Electric Vehicle Status</label>
|
||||
<description>Electric charge and range</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="callbackIP" type="text">
|
||||
<label>Callback IP Address</label>
|
||||
<description>IP address for openHAB callback URL</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="callbackPort" type="integer">
|
||||
<label>Callback Port Number</label>
|
||||
<description>Port Number for openHAB callback URL</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,57 @@
|
||||
<?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:mercedesme:conv">
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>Vehicle Identification Number</label>
|
||||
</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="fuelCapacity" type="decimal" min="0">
|
||||
<label>Fuel Capacity</label>
|
||||
<description>Fuel capacity in liters of vehicle</description>
|
||||
</parameter>
|
||||
<!-- https://developer.mercedes-benz.com/products/vehicle_images/docs#_default_image_settings -->
|
||||
<parameter name="background" type="boolean">
|
||||
<label>Background Image</label>
|
||||
<description>Vehicle images provided with or without background</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="night" type="boolean">
|
||||
<label>Night Image</label>
|
||||
<description>Vehicle images in night conditions</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="cropped" type="boolean">
|
||||
<label>Cropped Image</label>
|
||||
<description>Vehicle images in 4:3 instead of 16:9</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="roofOpen" type="boolean">
|
||||
<label>Cabriolet Open Roof</label>
|
||||
<description>Vehicle images with open roof (only Cabriolet)</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="format" type="text">
|
||||
<label>Image Format</label>
|
||||
<description>Preferred Image Format</description>
|
||||
<default>webp</default>
|
||||
<advanced>true</advanced>
|
||||
<options>
|
||||
<option value="webp">webp</option>
|
||||
<option value="png">png</option>
|
||||
<option value="jpeg">jpeg</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,61 @@
|
||||
<?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:mercedesme:hybrid">
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>Vehicle Identification Number</label>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" min="1" unit="min" required="true">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Data refresh rate for vehicle data</description>
|
||||
<default>5</default>
|
||||
</parameter>
|
||||
<parameter name="batteryCapacity" type="decimal">
|
||||
<label>Battery Capacity</label>
|
||||
<description>Battery capacity in kWh of vehicle</description>
|
||||
</parameter>
|
||||
<parameter name="fuelCapacity" type="decimal">
|
||||
<label>Fuel Capacity</label>
|
||||
<description>Fuel capacity in liters of vehicle</description>
|
||||
</parameter>
|
||||
<!-- https://developer.mercedes-benz.com/products/vehicle_images/docs#_default_image_settings -->
|
||||
<parameter name="background" type="boolean">
|
||||
<label>Background Image</label>
|
||||
<description>Vehicle images provided with or without background</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="night" type="boolean">
|
||||
<label>Night Image</label>
|
||||
<description>Vehicle images in night conditions</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="cropped" type="boolean">
|
||||
<label>Cropped Image</label>
|
||||
<description>Vehicle images in 4:3 instead of 16:9</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="roofOpen" type="boolean">
|
||||
<label>Cabriolet Open Roof</label>
|
||||
<description>Vehicle images with open roof (only Cabriolet)</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="format" type="text">
|
||||
<label>Image Format</label>
|
||||
<description>Preferred Image Format</description>
|
||||
<default>webp</default>
|
||||
<advanced>true</advanced>
|
||||
<options>
|
||||
<option value="webp">webp</option>
|
||||
<option value="png">png</option>
|
||||
<option value="jpeg">jpeg</option>
|
||||
</options>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,218 @@
|
||||
# binding
|
||||
|
||||
binding.mercedesme.name = Mercedes Me Binding
|
||||
binding.mercedesme.description = The binding provides access to your Mercedes developer account and vehicles
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.mercedesme.account.label = Mercedes Me Account
|
||||
thing-type.mercedesme.account.description = Mercedes Benz account data
|
||||
thing-type.mercedesme.bev.label = Mercedes Benz BEV
|
||||
thing-type.mercedesme.bev.description = Battery Electric Vehicle
|
||||
thing-type.mercedesme.combustion.label = Mercedes Benz
|
||||
thing-type.mercedesme.combustion.description = Conventional Fuel Vehicle
|
||||
thing-type.mercedesme.hybrid.label = Mercedes Benz Hybrid
|
||||
thing-type.mercedesme.hybrid.description = Conventional Fuel Vehicle with supporting Electric Engine
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.mercedesme.bev.background.label = Background Image
|
||||
thing-type.config.mercedesme.bev.background.description = Vehicle images provided with or without background
|
||||
thing-type.config.mercedesme.bev.batteryCapacity.label = Battery Capacity
|
||||
thing-type.config.mercedesme.bev.batteryCapacity.description = Battery capacity in kwh of vehicle
|
||||
thing-type.config.mercedesme.bev.cropped.label = Cropped Image
|
||||
thing-type.config.mercedesme.bev.cropped.description = Vehicle images in 4:3 instead of 16:9
|
||||
thing-type.config.mercedesme.bev.format.label = Image Format
|
||||
thing-type.config.mercedesme.bev.format.description = Preferred Image Format
|
||||
thing-type.config.mercedesme.bev.format.option.webp = webp
|
||||
thing-type.config.mercedesme.bev.format.option.png = png
|
||||
thing-type.config.mercedesme.bev.format.option.jpeg = jpeg
|
||||
thing-type.config.mercedesme.bev.night.label = Night Image
|
||||
thing-type.config.mercedesme.bev.night.description = Vehicle images in night conditions
|
||||
thing-type.config.mercedesme.bev.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.mercedesme.bev.refreshInterval.description = Data refresh rate for vehicle data
|
||||
thing-type.config.mercedesme.bev.roofOpen.label = Cabriolet Open Roof
|
||||
thing-type.config.mercedesme.bev.roofOpen.description = Vehicle images with open roof (only Cabriolet)
|
||||
thing-type.config.mercedesme.bev.vin.label = Vehicle Identification Number
|
||||
thing-type.config.mercedesme.bridge.callbackIP.label = Callback IP Address
|
||||
thing-type.config.mercedesme.bridge.callbackIP.description = IP address for openHAB callback URL
|
||||
thing-type.config.mercedesme.bridge.callbackPort.label = Callback Port Number
|
||||
thing-type.config.mercedesme.bridge.callbackPort.description = Port Number for openHAB callback URL
|
||||
thing-type.config.mercedesme.bridge.clientId.label = MB Developer Client ID
|
||||
thing-type.config.mercedesme.bridge.clientId.description = Mercedes Benz Developer Client ID
|
||||
thing-type.config.mercedesme.bridge.clientSecret.label = MB Developer Client Secret
|
||||
thing-type.config.mercedesme.bridge.clientSecret.description = Mercedes Benz Developer Client Secret
|
||||
thing-type.config.mercedesme.bridge.evScope.label = Electric Vehicle Status
|
||||
thing-type.config.mercedesme.bridge.evScope.description = Electric charge and range
|
||||
thing-type.config.mercedesme.bridge.fuelScope.label = Fuel Status
|
||||
thing-type.config.mercedesme.bridge.fuelScope.description = Tank level and range
|
||||
thing-type.config.mercedesme.bridge.imageApiKey.label = MB Developer Image API Key
|
||||
thing-type.config.mercedesme.bridge.imageApiKey.description = Mercedes Benz Developer Image API Key
|
||||
thing-type.config.mercedesme.bridge.lockScope.label = Vehicle Lock Status
|
||||
thing-type.config.mercedesme.bridge.lockScope.description = Lock status of doors and trunk
|
||||
thing-type.config.mercedesme.bridge.odoScope.label = PayAsYourDrive Insurance
|
||||
thing-type.config.mercedesme.bridge.odoScope.description = Provides total Mileage
|
||||
thing-type.config.mercedesme.bridge.vehicleScope.label = Vehicle Status
|
||||
thing-type.config.mercedesme.bridge.vehicleScope.description = Status of doors, windows lights
|
||||
thing-type.config.mercedesme.conv.background.label = Background Image
|
||||
thing-type.config.mercedesme.conv.background.description = Vehicle images provided with or without background
|
||||
thing-type.config.mercedesme.conv.cropped.label = Cropped Image
|
||||
thing-type.config.mercedesme.conv.cropped.description = Vehicle images in 4:3 instead of 16:9
|
||||
thing-type.config.mercedesme.conv.format.label = Image Format
|
||||
thing-type.config.mercedesme.conv.format.description = Preferred Image Format
|
||||
thing-type.config.mercedesme.conv.format.option.webp = webp
|
||||
thing-type.config.mercedesme.conv.format.option.png = png
|
||||
thing-type.config.mercedesme.conv.format.option.jpeg = jpeg
|
||||
thing-type.config.mercedesme.conv.fuelCapacity.label = Fuel Capacity
|
||||
thing-type.config.mercedesme.conv.fuelCapacity.description = Fuel capacity in liters of vehicle
|
||||
thing-type.config.mercedesme.conv.night.label = Night Image
|
||||
thing-type.config.mercedesme.conv.night.description = Vehicle images in night conditions
|
||||
thing-type.config.mercedesme.conv.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.mercedesme.conv.refreshInterval.description = Data refresh rate for your vehicle data
|
||||
thing-type.config.mercedesme.conv.roofOpen.label = Cabriolet Open Roof
|
||||
thing-type.config.mercedesme.conv.roofOpen.description = Vehicle images with open roof (only Cabriolet)
|
||||
thing-type.config.mercedesme.conv.vin.label = Vehicle Identification Number
|
||||
thing-type.config.mercedesme.hybrid.background.label = Background Image
|
||||
thing-type.config.mercedesme.hybrid.background.description = Vehicle images provided with or without background
|
||||
thing-type.config.mercedesme.hybrid.batteryCapacity.label = Battery Capacity
|
||||
thing-type.config.mercedesme.hybrid.batteryCapacity.description = Battery capacity in kwh of vehicle
|
||||
thing-type.config.mercedesme.hybrid.cropped.label = Cropped Image
|
||||
thing-type.config.mercedesme.hybrid.cropped.description = Vehicle images in 4:3 instead of 16:9
|
||||
thing-type.config.mercedesme.hybrid.format.label = Image Format
|
||||
thing-type.config.mercedesme.hybrid.format.description = Preferred Image Format
|
||||
thing-type.config.mercedesme.hybrid.format.option.webp = webp
|
||||
thing-type.config.mercedesme.hybrid.format.option.png = png
|
||||
thing-type.config.mercedesme.hybrid.format.option.jpeg = jpeg
|
||||
thing-type.config.mercedesme.hybrid.fuelCapacity.label = Fuel Capacity
|
||||
thing-type.config.mercedesme.hybrid.fuelCapacity.description = Fuel capacity in liters of vehicle
|
||||
thing-type.config.mercedesme.hybrid.night.label = Night Image
|
||||
thing-type.config.mercedesme.hybrid.night.description = Vehicle images in night conditions
|
||||
thing-type.config.mercedesme.hybrid.refreshInterval.label = Refresh Interval
|
||||
thing-type.config.mercedesme.hybrid.refreshInterval.description = Data refresh rate for vehicle data
|
||||
thing-type.config.mercedesme.hybrid.roofOpen.label = Cabriolet Open Roof
|
||||
thing-type.config.mercedesme.hybrid.roofOpen.description = Vehicle images with open roof (only Cabriolet)
|
||||
thing-type.config.mercedesme.hybrid.vin.label = Vehicle Identification Number
|
||||
|
||||
# channel group types
|
||||
|
||||
channel-group-type.mercedesme.door-values.label = Detailed Door Status
|
||||
channel-group-type.mercedesme.door-values.description = Detailed Status of all Doors and Windows
|
||||
channel-group-type.mercedesme.image-values.label = Vehicle Images
|
||||
channel-group-type.mercedesme.light-values.label = Light Status
|
||||
channel-group-type.mercedesme.light-values.description = Light Status of interior lights and main light switch
|
||||
channel-group-type.mercedesme.location-values.label = Vehicle Location
|
||||
channel-group-type.mercedesme.location-values.description = Heading of vehicle
|
||||
channel-group-type.mercedesme.lock-values.label = Lock Status
|
||||
channel-group-type.mercedesme.lock-values.description = Vehicle Lock Status
|
||||
channel-group-type.mercedesme.range-conv-values.label = Range and Fuel Data
|
||||
channel-group-type.mercedesme.range-conv-values.description = Provides Mileage, remaining range and fuel level values
|
||||
channel-group-type.mercedesme.range-ev-values.label = Range and Charge Data
|
||||
channel-group-type.mercedesme.range-ev-values.description = Provides Mileage, remaining range and charge level values
|
||||
channel-group-type.mercedesme.range-hybrid-values.label = Range, Charge / Fuel Data
|
||||
channel-group-type.mercedesme.range-hybrid-values.description = Provides mileage, remaining fuel and range data for hybrid vehicles
|
||||
channel-group-type.mercedesme.window-values.label = Detailed Window Status
|
||||
channel-group-type.mercedesme.window-values.description = Detailed Status Windows
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.mercedesme.charged-channel.label = Charged Battery Energy
|
||||
channel-type.mercedesme.clear-cache-channel.label = Remove All Stored Images
|
||||
channel-type.mercedesme.deck-lid-channel.label = Deck Lid
|
||||
channel-type.mercedesme.deck-lid-lock-channel.label = Deck Lid Lock
|
||||
channel-type.mercedesme.doors-lock-channel.label = Door Lock Status
|
||||
channel-type.mercedesme.doors-lock-channel.state.option.0 = Unlocked
|
||||
channel-type.mercedesme.doors-lock-channel.state.option.1 = Locked Internal
|
||||
channel-type.mercedesme.doors-lock-channel.state.option.2 = Locked External
|
||||
channel-type.mercedesme.doors-lock-channel.state.option.3 = Unlocked Selective
|
||||
channel-type.mercedesme.driver-front-channel.label = Driver Door
|
||||
channel-type.mercedesme.driver-rear-channel.label = Driver Door Rear
|
||||
channel-type.mercedesme.flap-lock-channel.label = Flap Lock
|
||||
channel-type.mercedesme.fuel-level-channel.label = Fuel Level
|
||||
channel-type.mercedesme.fuel-open-channel.label = Open Fuel Capacity
|
||||
channel-type.mercedesme.fuel-remain-channel.label = Remaining Fuel
|
||||
channel-type.mercedesme.heading-channel.label = Heading Angle
|
||||
channel-type.mercedesme.image-data-channel.label = Rendered Vehicle Image
|
||||
channel-type.mercedesme.image-view-channel.label = Image Viewport
|
||||
channel-type.mercedesme.interior-front-channel.label = Interior Light Front
|
||||
channel-type.mercedesme.interior-rear-channel.label = Interior Light Rear
|
||||
channel-type.mercedesme.last-doors-update-channel.label = Last Doors Update
|
||||
channel-type.mercedesme.last-doors-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.last-lights-update-channel.label = Last Light Update
|
||||
channel-type.mercedesme.last-lights-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.last-location-update-channel.label = Last Location Update
|
||||
channel-type.mercedesme.last-location-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.last-lock-update-channel.label = Last Lock Update
|
||||
channel-type.mercedesme.last-lock-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.last-range-update-channel.label = Last Range Update
|
||||
channel-type.mercedesme.last-range-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.last-windows-update-channel.label = Last Window Update
|
||||
channel-type.mercedesme.last-windows-update-channel.state.pattern = %1$tA, %1$td.%1$tm. %1$tH:%1$tM
|
||||
channel-type.mercedesme.light-switch-channel.label = Main Light Rotary
|
||||
channel-type.mercedesme.light-switch-channel.state.option.0 = Auto
|
||||
channel-type.mercedesme.light-switch-channel.state.option.1 = Headlight
|
||||
channel-type.mercedesme.light-switch-channel.state.option.2 = Sidelight Left
|
||||
channel-type.mercedesme.light-switch-channel.state.option.3 = Sidelight Right
|
||||
channel-type.mercedesme.light-switch-channel.state.option.4 = Parking Light
|
||||
channel-type.mercedesme.mileage-channel.label = Mileage
|
||||
channel-type.mercedesme.passenger-front-channel.label = Passenger Door
|
||||
channel-type.mercedesme.passenger-rear-channel.label = Passenger Door Rear
|
||||
channel-type.mercedesme.radius-electric-channel.label = Electric Radius
|
||||
channel-type.mercedesme.radius-fuel-channel.label = Fuel Radius
|
||||
channel-type.mercedesme.radius-hybrid-channel.label = Hybrid Radius
|
||||
channel-type.mercedesme.range-electric-channel.label = Electric Range
|
||||
channel-type.mercedesme.range-fuel-channel.label = Fuel Range
|
||||
channel-type.mercedesme.range-hybrid-channel.label = Hybrid Range
|
||||
channel-type.mercedesme.reading-left-channel.label = Reading Light Left
|
||||
channel-type.mercedesme.reading-right-channel.label = Reading Light Right
|
||||
channel-type.mercedesme.rooftop-channel.label = Roof top
|
||||
channel-type.mercedesme.rooftop-channel.state.option.0 = Unlocked
|
||||
channel-type.mercedesme.rooftop-channel.state.option.1 = Open and locked
|
||||
channel-type.mercedesme.rooftop-channel.state.option.2 = Closed and locked
|
||||
channel-type.mercedesme.soc-channel.label = Battery Charge Level
|
||||
channel-type.mercedesme.sunroof-channel.label = Sun Roof
|
||||
channel-type.mercedesme.sunroof-channel.state.option.0 = Closed
|
||||
channel-type.mercedesme.sunroof-channel.state.option.1 = Open
|
||||
channel-type.mercedesme.sunroof-channel.state.option.2 = Open Lifting
|
||||
channel-type.mercedesme.sunroof-channel.state.option.3 = Running
|
||||
channel-type.mercedesme.sunroof-channel.state.option.4 = Closing
|
||||
channel-type.mercedesme.sunroof-channel.state.option.5 = Opening
|
||||
channel-type.mercedesme.sunroof-channel.state.option.6 = Closing
|
||||
channel-type.mercedesme.uncharged-channel.label = Uncharged Battery Energy
|
||||
channel-type.mercedesme.window-driver-front-channel.label = Driver Window
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.0 = Intermediate
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.1 = Open
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.2 = Closed
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.3 = Airing
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.4 = Intermediate
|
||||
channel-type.mercedesme.window-driver-front-channel.state.option.5 = Running
|
||||
channel-type.mercedesme.window-driver-rear-channel.label = Driver Window Rear
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.0 = Intermediate
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.1 = Open
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.2 = Closed
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.3 = Airing
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.4 = Intermediate
|
||||
channel-type.mercedesme.window-driver-rear-channel.state.option.5 = Running
|
||||
channel-type.mercedesme.window-passenger-front-channel.label = Passenger Window
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.0 = Intermediate
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.1 = Open
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.2 = Closed
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.3 = Airing
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.4 = Intermediate
|
||||
channel-type.mercedesme.window-passenger-front-channel.state.option.5 = Running
|
||||
channel-type.mercedesme.window-passenger-rear-channel.label = Passenger Window Rear
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.0 = Intermediate
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.1 = Open
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.2 = Closed
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.3 = Airing
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.4 = Intermediate
|
||||
channel-type.mercedesme.window-passenger-rear-channel.state.option.5 = Running
|
||||
|
||||
# MercedesMe Things Status Details
|
||||
mercedesme.account.status.authorization-needed = Manual Authorization needed at {0}
|
||||
mercedesme.account.status.ip-missing = Callback IP missing
|
||||
mercedesme.account.status.port-missing = Callback Port missing
|
||||
mercedesme.account.status.client-id-missing = Client ID missing
|
||||
mercedesme.account.status.client-secret-missing = Client Secret missing
|
||||
mercedesme.account.status.server-restart = Disable and enable Bridge to restart Authorization Server
|
||||
mercedesme.vehicle.status.bridge-missing = Bridge not set
|
||||
mercedesme.vehicle.status.bridge-authoriziation = Check Bridge Authorization
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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>Mercedes Me Account</label>
|
||||
<description>Mercedes Benz account data</description>
|
||||
<config-description-ref uri="thing-type:mercedesme:bridge"/>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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>Contact</item-type>
|
||||
<label>Driver Door</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="driver-rear-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Driver Door Rear</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="passenger-front-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Passenger Door</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="passenger-rear-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Passenger Door Rear</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="deck-lid-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Deck Lid</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="rooftop-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Roof top</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Unlocked</option>
|
||||
<option value="1">Open and locked</option>
|
||||
<option value="2">Closed and locked</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="sunroof-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Sun Roof</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Closed</option>
|
||||
<option value="1">Open</option>
|
||||
<option value="2">Open Lifting</option>
|
||||
<option value="3">Running</option>
|
||||
<option value="4">Closing</option>
|
||||
<option value="5">Opening</option>
|
||||
<option value="6">Closing</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="last-doors-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Doors Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="deck-lid" typeId="deck-lid-channel"/>
|
||||
<channel id="sunroof" typeId="sunroof-channel"/>
|
||||
<channel id="rooftop" typeId="rooftop-channel"/>
|
||||
<channel id="last-update" typeId="last-doors-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="image-data-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>
|
||||
</channel-type>
|
||||
<channel-type id="clear-cache-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Remove All Stored Images</label>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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 Images</label>
|
||||
<channels>
|
||||
<channel id="image-data" typeId="image-data-channel"/>
|
||||
<channel id="image-view" typeId="image-view-channel"/>
|
||||
<channel id="clear-cache" typeId="clear-cache-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="interior-front-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Interior Light Front</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="interior-rear-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Interior Light Rear</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="reading-left-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Reading Light Left</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="reading-right-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Reading Light Right</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="light-switch-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Main Light Rotary</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Auto</option>
|
||||
<option value="1">Headlight</option>
|
||||
<option value="2">Sidelight Left</option>
|
||||
<option value="3">Sidelight Right</option>
|
||||
<option value="4">Parking Light</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="last-lights-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Light Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="light-values">
|
||||
<label>Light Status</label>
|
||||
<description>Light Status of interior lights and main light switch</description>
|
||||
<channels>
|
||||
<channel id="interior-front" typeId="interior-front-channel"/>
|
||||
<channel id="interior-rear" typeId="interior-rear-channel"/>
|
||||
<channel id="light-switch" typeId="light-switch-channel"/>
|
||||
<channel id="reading-left" typeId="reading-left-channel"/>
|
||||
<channel id="reading-right" typeId="reading-right-channel"/>
|
||||
<channel id="last-update" typeId="last-lights-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="location-values">
|
||||
<label>Vehicle Location</label>
|
||||
<description>Heading of vehicle</description>
|
||||
<channels>
|
||||
<channel id="heading" typeId="heading-channel"/>
|
||||
<channel id="last-update" typeId="last-location-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="heading-channel">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Heading Angle</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="last-location-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Location Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="doors-lock-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Door Lock Status</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Unlocked</option>
|
||||
<option value="1">Locked Internal</option>
|
||||
<option value="2">Locked External</option>
|
||||
<option value="3">Unlocked Selective</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="deck-lid-lock-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Deck Lid Lock</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="flap-lock-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Flap Lock</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="last-lock-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Lock Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="lock-values">
|
||||
<label>Lock Status</label>
|
||||
<description>Vehicle Lock Status</description>
|
||||
<channels>
|
||||
<channel id="doors" typeId="doors-lock-channel"/>
|
||||
<channel id="deck-lid" typeId="deck-lid-lock-channel"/>
|
||||
<channel id="flap" typeId="flap-lock-channel"/>
|
||||
<channel id="last-update" typeId="last-lock-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="mileage-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Mileage</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="range-electric-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Electric Range</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="radius-electric-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Electric Radius</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="soc-channel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Battery Charge Level</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="charged-channel">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Charged Battery Energy</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="uncharged-channel">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Uncharged Battery Energy</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="range-fuel-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Fuel Range</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="radius-fuel-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Fuel Radius</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fuel-level-channel">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Fuel Level</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fuel-remain-channel">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Remaining Fuel</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="fuel-open-channel">
|
||||
<item-type>Number:Volume</item-type>
|
||||
<label>Open Fuel Capacity</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="range-hybrid-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Hybrid Range</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="radius-hybrid-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Hybrid Radius</label>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="last-range-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Range Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="range-conv-values">
|
||||
<label>Range and Fuel Data</label>
|
||||
<description>Provides Mileage, remaining range and fuel level values</description>
|
||||
<channels>
|
||||
<channel id="mileage" typeId="mileage-channel"/>
|
||||
<channel id="range-fuel" typeId="range-fuel-channel"/>
|
||||
<channel id="radius-fuel" typeId="radius-fuel-channel"/>
|
||||
<channel id="fuel-level" typeId="fuel-level-channel"/>
|
||||
<channel id="fuel-remain" typeId="fuel-remain-channel"/>
|
||||
<channel id="fuel-open" typeId="fuel-open-channel"/>
|
||||
<channel id="last-update" typeId="last-range-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="range-ev-values">
|
||||
<label>Range and Charge Data</label>
|
||||
<description>Provides Mileage, remaining range and charge level values</description>
|
||||
<channels>
|
||||
<channel id="mileage" typeId="mileage-channel"/>
|
||||
<channel id="range-electric" typeId="range-electric-channel"/>
|
||||
<channel id="radius-electric" typeId="radius-electric-channel"/>
|
||||
<channel id="soc" typeId="soc-channel"/>
|
||||
<channel id="charged" typeId="charged-channel"/>
|
||||
<channel id="uncharged" typeId="uncharged-channel"/>
|
||||
<channel id="last-update" typeId="last-range-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="range-hybrid-values">
|
||||
<label>Range, Charge / Fuel Data</label>
|
||||
<description>Provides mileage, remaining fuel and range data for hybrid vehicles</description>
|
||||
<channels>
|
||||
<channel id="mileage" typeId="mileage-channel"/>
|
||||
<channel id="range-electric" typeId="range-electric-channel"/>
|
||||
<channel id="radius-electric" typeId="radius-electric-channel"/>
|
||||
<channel id="soc" typeId="soc-channel"/>
|
||||
<channel id="charged" typeId="charged-channel"/>
|
||||
<channel id="uncharged" typeId="uncharged-channel"/>
|
||||
<channel id="range-fuel" typeId="range-fuel-channel"/>
|
||||
<channel id="radius-fuel" typeId="radius-fuel-channel"/>
|
||||
<channel id="fuel-level" typeId="fuel-level-channel"/>
|
||||
<channel id="fuel-remain" typeId="fuel-remain-channel"/>
|
||||
<channel id="fuel-open" typeId="fuel-open-channel"/>
|
||||
<channel id="range-hybrid" typeId="range-hybrid-channel"/>
|
||||
<channel id="radius-hybrid" typeId="radius-fuel-channel"/>
|
||||
<channel id="last-update" typeId="last-range-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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">
|
||||
|
||||
<thing-type id="bev">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Mercedes Benz BEV</label>
|
||||
<description>Battery Electric Vehicle</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="range" typeId="range-ev-values"/>
|
||||
<channel-group id="doors" typeId="door-values"/>
|
||||
<channel-group id="windows" typeId="window-values"/>
|
||||
<channel-group id="lights" typeId="light-values"/>
|
||||
<channel-group id="lock" typeId="lock-values"/>
|
||||
<channel-group id="location" typeId="location-values"/>
|
||||
<channel-group id="image" typeId="image-values"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:mercedesme:bev"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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">
|
||||
|
||||
<thing-type id="combustion">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Mercedes Benz</label>
|
||||
<description>Conventional Fuel Vehicle</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="range" typeId="range-conv-values"/>
|
||||
<channel-group id="doors" typeId="door-values"/>
|
||||
<channel-group id="windows" typeId="window-values"/>
|
||||
<channel-group id="lights" typeId="light-values"/>
|
||||
<channel-group id="lock" typeId="lock-values"/>
|
||||
<channel-group id="location" typeId="location-values"/>
|
||||
<channel-group id="image" typeId="image-values"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:mercedesme:conv"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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">
|
||||
|
||||
<thing-type id="hybrid">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Mercedes Benz Hybrid</label>
|
||||
<description>Conventional Fuel Vehicle with supporting Electric Engine</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="range" typeId="range-hybrid-values"/>
|
||||
<channel-group id="doors" typeId="door-values"/>
|
||||
<channel-group id="windows" typeId="window-values"/>
|
||||
<channel-group id="lights" typeId="light-values"/>
|
||||
<channel-group id="lock" typeId="lock-values"/>
|
||||
<channel-group id="location" typeId="location-values"/>
|
||||
<channel-group id="image" typeId="image-values"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:mercedesme:hybrid"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="window-driver-front-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Driver Window</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Intermediate</option>
|
||||
<option value="1">Open</option>
|
||||
<option value="2">Closed</option>
|
||||
<option value="3">Airing</option>
|
||||
<option value="4">Intermediate</option>
|
||||
<option value="5">Running</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="window-driver-rear-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Driver Window Rear</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Intermediate</option>
|
||||
<option value="1">Open</option>
|
||||
<option value="2">Closed</option>
|
||||
<option value="3">Airing</option>
|
||||
<option value="4">Intermediate</option>
|
||||
<option value="5">Running</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="window-passenger-front-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Passenger Window</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Intermediate</option>
|
||||
<option value="1">Open</option>
|
||||
<option value="2">Closed</option>
|
||||
<option value="3">Airing</option>
|
||||
<option value="4">Intermediate</option>
|
||||
<option value="5">Running</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="window-passenger-rear-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Passenger Window Rear</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Intermediate</option>
|
||||
<option value="1">Open</option>
|
||||
<option value="2">Closed</option>
|
||||
<option value="3">Airing</option>
|
||||
<option value="4">Intermediate</option>
|
||||
<option value="5">Running</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="last-windows-update-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Window Update</label>
|
||||
<state pattern="%1$tA, %1$td.%1$tm. %1$tH:%1$tM" readOnly="true"/>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="mercedesme"
|
||||
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="window-values">
|
||||
<label>Detailed Window Status</label>
|
||||
<description>Detailed Status Windows</description>
|
||||
<channels>
|
||||
<channel id="driver-front" typeId="window-driver-front-channel"/>
|
||||
<channel id="driver-rear" typeId="window-driver-rear-channel"/>
|
||||
<channel id="passenger-front" typeId="window-passenger-front-channel"/>
|
||||
<channel id="passenger-rear" typeId="window-passenger-rear-channel"/>
|
||||
<channel id="last-update" typeId="last-windows-update-channel"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.mercedesme;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mercedesme.internal.Constants;
|
||||
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
|
||||
import org.openhab.binding.mercedesme.internal.server.Utils;
|
||||
|
||||
/**
|
||||
* The {@link ConfigurationTest} Test configuration settings
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ConfigurationTest {
|
||||
|
||||
@Test
|
||||
void testScope() {
|
||||
AccountConfiguration ac = new AccountConfiguration();
|
||||
assertEquals(
|
||||
"offline_access mb:vehicle:mbdata:payasyoudrive mb:vehicle:mbdata:vehiclestatus mb:vehicle:mbdata:vehiclelock mb:vehicle:mbdata:fuelstatus mb:vehicle:mbdata:evstatus",
|
||||
ac.getScope());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testApiUrlEndpoint() {
|
||||
String url = Constants.FUEL_URL;
|
||||
String[] endpoint = url.split("/");
|
||||
String finalEndpoint = endpoint[endpoint.length - 1];
|
||||
assertEquals("fuelstatus", finalEndpoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRound() {
|
||||
int socValue = 66;
|
||||
double batteryCapacity = 66.5;
|
||||
float chargedValue = Math.round(socValue * 1000 * (float) batteryCapacity / 1000) / (float) 100;
|
||||
assertEquals(43.89, chargedValue, 0.01);
|
||||
float unchargedValue = Math.round((100 - socValue) * 1000 * (float) batteryCapacity / 1000) / (float) 100;
|
||||
assertEquals(22.61, unchargedValue, 0.01);
|
||||
assertEquals(batteryCapacity, chargedValue + unchargedValue, 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackUrl() throws SocketException {
|
||||
String ip = Utils.getCallbackIP();
|
||||
try {
|
||||
assertTrue(InetAddress.getByName(ip).isReachable(10));
|
||||
} catch (IOException e) {
|
||||
assertTrue(false, "IP " + ip + " not reachable");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.mercedesme;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.util.MultiMap;
|
||||
import org.eclipse.jetty.util.UrlEncoded;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mercedesme.internal.config.VehicleConfiguration;
|
||||
|
||||
/**
|
||||
* The {@link ImageTest} Test Image conversions
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class ImageTest {
|
||||
|
||||
@Test
|
||||
public void testConfig() {
|
||||
Optional<VehicleConfiguration> config = Optional.of(new VehicleConfiguration());
|
||||
MultiMap<String> parameterMap = new MultiMap<String>();
|
||||
parameterMap.add("background", Boolean.toString(config.get().background));
|
||||
parameterMap.add("night", Boolean.toString(config.get().night));
|
||||
parameterMap.add("cropped", Boolean.toString(config.get().cropped));
|
||||
parameterMap.add("roofOpen", Boolean.toString(config.get().roofOpen));
|
||||
parameterMap.add("format", config.get().format);
|
||||
String params = UrlEncoded.encode(parameterMap, null, false);
|
||||
assertEquals("background=false&night=false&cropped=false&roofOpen=false&format=webp", params);
|
||||
|
||||
config.get().background = true;
|
||||
config.get().format = "png";
|
||||
config.get().cropped = true;
|
||||
parameterMap = new MultiMap<String>();
|
||||
parameterMap.add("background", Boolean.toString(config.get().background));
|
||||
parameterMap.add("night", Boolean.toString(config.get().night));
|
||||
parameterMap.add("cropped", Boolean.toString(config.get().cropped));
|
||||
parameterMap.add("roofOpen", Boolean.toString(config.get().roofOpen));
|
||||
parameterMap.add("format", config.get().format);
|
||||
params = UrlEncoded.encode(parameterMap, null, false);
|
||||
assertEquals("background=true&night=false&cropped=true&roofOpen=false&format=png", params);
|
||||
}
|
||||
}
|
@ -0,0 +1,250 @@
|
||||
/**
|
||||
* 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.mercedesme;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mercedesme.internal.utils.ChannelStateMap;
|
||||
import org.openhab.binding.mercedesme.internal.utils.Mapper;
|
||||
|
||||
/**
|
||||
* The {@link JsonTest} Test Json conversions
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class JsonTest {
|
||||
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);
|
||||
|
||||
@Test
|
||||
void testOdoMapper() throws Exception {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("range:mileage 4131 km");
|
||||
String content = Files.readString(Path.of("src/test/resources/odo.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
assertEquals(0, expectedResults.size(), "All content delivered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEVMapper() throws IOException {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("range:range-electric 325 km");
|
||||
expectedResults.add("range:soc 78 %");
|
||||
String content = Files.readString(Path.of("src/test/resources/evstatus.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
assertEquals(0, expectedResults.size(), "All content delivered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFuelMapper() throws IOException {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("range:range-fuel 1292 km");
|
||||
expectedResults.add("range:fuel-level 90 %");
|
||||
String content = Files.readString(Path.of("src/test/resources/fuel.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLockMapper() throws IOException {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("lock:doors 0");
|
||||
expectedResults.add("lock:deck-lid ON");
|
||||
expectedResults.add("lock:flap ON");
|
||||
expectedResults.add("location:heading 120 °");
|
||||
String content = Files.readString(Path.of("src/test/resources/lock.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStatusMapper() throws IOException {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("doors:deck-lid CLOSED");
|
||||
expectedResults.add("doors:driver-front CLOSED");
|
||||
expectedResults.add("doors:passenger-front CLOSED");
|
||||
expectedResults.add("doors:driver-rear CLOSED");
|
||||
expectedResults.add("doors:passenger-rear CLOSED");
|
||||
expectedResults.add("lights:interior-front OFF");
|
||||
expectedResults.add("lights:interior-rear OFF");
|
||||
expectedResults.add("lights:light-switch 0");
|
||||
expectedResults.add("lights:reading-left OFF");
|
||||
expectedResults.add("lights:reading-right OFF");
|
||||
expectedResults.add("doors:rooftop 0");
|
||||
expectedResults.add("doors:sunroof 0");
|
||||
expectedResults.add("windows:driver-front 0");
|
||||
expectedResults.add("windows:passenger-front 0");
|
||||
expectedResults.add("windows:driver-rear 0");
|
||||
expectedResults.add("windows:passenger-rear 0");
|
||||
|
||||
String content = Files.readString(Path.of("src/test/resources/status.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
assertEquals(0, expectedResults.size(), "All content delivered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEQALightsMapper() throws IOException {
|
||||
// real life example
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("doors:passenger-front OPEN");
|
||||
expectedResults.add("windows:driver-front 1");
|
||||
expectedResults.add("windows:driver-rear 1");
|
||||
expectedResults.add("windows:passenger-rear 1");
|
||||
expectedResults.add("windows:passenger-front 1");
|
||||
expectedResults.add("lights:light-switch 0");
|
||||
expectedResults.add("lights:reading-right ON");
|
||||
expectedResults.add("lights:reading-left ON");
|
||||
expectedResults.add("doors:driver-front CLOSED");
|
||||
expectedResults.add("doors:driver-rear CLOSED");
|
||||
|
||||
String content = Files.readString(Path.of("src/test/resources/eqa-light-sample.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
assertEquals(0, expectedResults.size(), "All content delivered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimeStamp() throws IOException {
|
||||
String content = Files.readString(Path.of("src/test/resources/eqa-light-sample.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
long lastTimestamp = 0;
|
||||
for (Iterator<Object> iterator = ja.iterator(); iterator.hasNext();) {
|
||||
JSONObject jo = (JSONObject) iterator.next();
|
||||
Set<String> s = jo.keySet();
|
||||
if (!s.isEmpty()) {
|
||||
String id = s.toArray()[0].toString();
|
||||
JSONObject val = jo.getJSONObject(id);
|
||||
if (val.has("timestamp")) {
|
||||
lastTimestamp = val.getLong("timestamp");
|
||||
}
|
||||
}
|
||||
}
|
||||
Date d = new Date(lastTimestamp);
|
||||
ZonedDateTime zdt = d.toInstant().atZone(ZoneId.of("Europe/Paris"));
|
||||
assertEquals("2022-06-19T16:46:31", zdt.format(DATE_INPUT_PATTERN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidData() throws IOException {
|
||||
String content = Files.readString(Path.of("src/test/resources/invalid-key.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertFalse(csm.isValid());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingTimestamp() throws IOException {
|
||||
List<String> expectedResults = new ArrayList<String>();
|
||||
expectedResults.add("range:mileage 4131 km");
|
||||
String content = Files.readString(Path.of("src/test/resources/invalid-timestamp.json"));
|
||||
JSONArray ja = new JSONArray(content);
|
||||
assertTrue(ja.length() > 0);
|
||||
ja.forEach(entry -> {
|
||||
JSONObject jo = (JSONObject) entry;
|
||||
ChannelStateMap csm = Mapper.getChannelStateMap(jo);
|
||||
assertNotNull(csm);
|
||||
assertTrue(expectedResults.contains(csm.toString()));
|
||||
assertEquals(-1, csm.getTimestamp());
|
||||
boolean removed = expectedResults.remove(csm.toString());
|
||||
if (!removed) {
|
||||
assertTrue(false, csm.toString() + " not removed");
|
||||
}
|
||||
});
|
||||
assertEquals(0, expectedResults.size(), "All content delivered");
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
[
|
||||
{
|
||||
"doorstatusfrontright": {
|
||||
"value": "true",
|
||||
"timestamp": 1655650113000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusfrontleft": {
|
||||
"value": "false",
|
||||
"timestamp": 1655650104000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusfrontleft": {
|
||||
"value": "1",
|
||||
"timestamp": 1655648946000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusrearleft": {
|
||||
"value": "1",
|
||||
"timestamp": 1655648959000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusrearright": {
|
||||
"value": "1",
|
||||
"timestamp": 1655648959000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusrearleft": {
|
||||
"value": "false",
|
||||
"timestamp": 1655498496000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusfrontright": {
|
||||
"value": "1",
|
||||
"timestamp": 1655648953000
|
||||
}
|
||||
},
|
||||
{
|
||||
"lightswitchposition": {
|
||||
"value": "0",
|
||||
"timestamp": 1655650824000
|
||||
}
|
||||
},
|
||||
{
|
||||
"readingLampFrontRight": {
|
||||
"value": "true",
|
||||
"timestamp": 1655649991000
|
||||
}
|
||||
},
|
||||
{
|
||||
"readingLampFrontLeft": {
|
||||
"value": "true",
|
||||
"timestamp": 1655649991000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"rangeelectric": {
|
||||
"value": "325",
|
||||
"timestamp": 1655401822000
|
||||
}
|
||||
},
|
||||
{
|
||||
"soc": {
|
||||
"value": "78",
|
||||
"timestamp": 1655399096000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,14 @@
|
||||
[
|
||||
{
|
||||
"tanklevelpercent": {
|
||||
"value": "90",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"rangeliquid": {
|
||||
"value": "1292",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"EXT000": "5jgA6wXiEiufaoAJcWWhQGALZUoltT2pnoXIFsCc6NBvSKz5wtbR3tfFFRzvg2aZWU9OeL75vJZpAQeh3jgzSDB1vXLyXEtErBzLP7WGVMu1Mvz5w6vf77fW_R_4vE57-FsZZQO8i4VCpAIvOhwTtV-2uvVw9qAtXQrAwpJz7r839JlvNf4uYyVFjiRjrE75_vDZA7bbkw33xDs2fSs5htgd39Zz3KfuKZvLmGW7qpCHRnr9lZixKCGydR8Hj4LM3XsEa0ebaCcdWOg4tuy0qN-YBH2-DrE7xQVso5iEiKIndBdA-ecF42jIYPdKk9x8gIKeSx-zcYFpwYXUZPtWo3vZQjqdiBVTxBVVK2JRYTOk9I0Qzp735Mqi5PtIYGrg2hIOH4F0CKuiknhc4XBabN22dAaxaUuf5juveQVD-h0WwV2xue6vQ8nvaTN2gyVpO4JXDmW9jE8kShKvDRZeh3JZKq8zGW4FfCP78rtyt8kc671d9PLcZBdi1_TgDMUBWldBP4xHrY_5aJvxn9nNji7E1A9Cw8tfmMyjuq0Wy3nsUHpctYXb0eXrrunj0-Uk_Aq2vx1PI1i8ko6-05uilE026reCDysoWY8Re2Ea1SRocp4P3B67Luqa__kgxqtdY3VdYtWuAuPrTESuHQZBEFa2EfNktvpZdXOYrvcqhGGbq76ybJXuQGB6ZwwiCi5ZFsq9ejYt9qcFYN_kxbpk6phLu-2yzqpcMEMMftFhZnY=",
|
||||
"EXT090": "oHtxoRJTvmza2fm4AFhd2_fFYmRFC-uuZeQP7B8uENz9cCHgN9jMXS0bWGprZhIuhM65tPtdUSsvjoVqYD-n_HcQrqUB_PQkvr61RAiHz5UOXZSW7_5QPv_0Tdt3g1NHQFs5zPdI1KffucgiuNlhLNaWgpzT5z0AcZba9o7IwxzF5WKOkT4HsQ0etc3FYXvfnXeHi71F0223JkRQtlXEu4aNHELonubumptt9YJM0qXbcttPs-eNFE8l2eHOsKvh34g6X4Z2LZAENmABjMXQMQhxyWGRr_N7mIoeQWo6AVQ01QavM_MSKZY6Bw3-WdDHr8pybwg7uee52NgrwMsN7_ufeEG3xkAeSXixMhNt4yutYwaWkdxSdGKp4UnINj493VL8_5XAF7nfeHRioXe3XWU6mfg-PGzJwywm3ll7qbp_8OGkws3u03xXKxINkgZWVjmAtOnmFm80XGTQL8HxsPspI2ItF1QQDyQhvYYrGe-saxRstxxr1HM2VkualA3hxlNkE5CSNfbm3B1fUSH25Ba-YBo4SluEsu5mQIOemT1NBqMMZeQBCiO_73PwgIoRZnjH3cRPl_oUO3jwQEPPqH-jXBdFdQ9mksVWrHBcAQrVPAY5QQYaXN0Io8Va0q11GR8RUj_avraROJ_dLlWUDJCU1DUTv677lifWhIxyDXHj4CfgartLyYAPsGWuXm7wLc41dOXuS-WsiJoJjwuK2S83oUfUVH56Hf6gwZhvK8E=",
|
||||
"EXT150": "FyyrR6D7K99MXXbCQ2Jt1N2GErxjQricdwDCJtzY5vW3TnqDr3JmEf5PUIy_yYnqscuiVLGTrBw1-9r6eR92uJncUPCkaNYHHIXt3p5ZfEu9TVcnURaydDH6GBB3yV9Cocv-8EeYbT2njjEe-n5qwhj44LfILKmMVC0lFzGRdixcAr1cu4chtnhvitIzOoCIG43BeTfWxjz4CwiIt3d4X05dS15qTWXFFlNCBc1xXGngdrqqO9USE3D8Hrq_0RuL_m4utANmLw2ud_rzlmpJFN7smkUJ62eKrpuJrn-aSrt3q9_OPewzC88-CpybvorcuVK9Oy_UZbU5aTFomUKH5DTEydJFBDeGLF6uZJSySKHWf395mmh1JBIxpghji4NhjONG8vlRKAZu9MbMQSWBiEJj2i1yWQQi9E8tYJN5g8uadP1SpSnIsM1FkDW8Esnu8bfqrYMQoobilCRkrmqaWCE4YDJcJRrf04oas12qqMZwLsRSvafSh7Cc6jIwggXxZ9u5d4wu9ov0oKfWMiBmvyt9Pb1kYxrt-nbL_pTkDOCAkBJw2AjiEFqcocMoCzx2uh1xQ8K9-DP4gGq4yKmY5EizbYMmYYhtorjKzmequ0BbKJ2YG-wIe65XA8i_dE5V-JeTL5CoSsRhoOBQK6hHtXk7PtgcdBV_0RAI85cnDSsoGNti2Nu3WT_S4PIstSSKrPpI9Yj6KeEV2EiM9a6flUkNo_QMwjKCuM3kCnvopjE=",
|
||||
"EXT180": "h2nEDkftnTN7t3b93Rv-_KnASjdCQMiVbFWDIH4UQPlmDw44dH54TqJg1KgQFqVIojWgPRHLvh9DBfQCHh3wTpk-b_UGrsv22uWrm4DwVgC9ZMPaYM8nWUGmVfN2Aigzp8nH5gWNmRP2rE3rEPRC7W6E3E_-U8CHSEZ9rvld04eYbZEb-zdrcLmo9DhgzX6ETMq3Rac2ehXSO5KipM0t2-X5LtPXmdf1NcH_EBCSBW1bSrRCJr6RvGjRXvPiDFXR7QGhEYKHzIW6o0smbRp8BnIXnL7LlRe4cDH_M4Wk3wYTAsi6ET5gXed82Ubf2qtl-aHLL3_Su5DfyGAuzEc6rjcIljpZrgTE1UfcBxYgob2kYGr8iHBltgNhs69iAe2V_e6mS3ogQE8Hc2BMe9T8aLylmKtainufJKfzCOnyuMUDnBDNe84oKRNsyJHT8pKHewMJVxW8mOsm3sPC_nTb-UsUN9jqXmCugPqYdPAYtEp5mOpBpsltUX7a6LMcJERX0bGRGXYuGSpaFXCeynlQyTCnGJk2tgt65f4c2MGxg3OLWNh5FCQMq3miC8S2tIN15h2rw0vXrTYrvPXJPBk6Kjw74c0xUWtdf9EQwR79g0JPnjEUIeeeJvbipXPvZi3TPCev0d1eE3lOq_3onIZI16Noqjm7WHvUPGgn5g8QxDRI9t4aZ1MSKxxYrKUJBlJrMV7GmGHuLl_jYihCeslaXR5qyirZxB0r4KFyLeXuyJ4=",
|
||||
"EXT270": "V1B7Vmu7mPFnDobcpvy5gVMAsnmi3sG_1AISG5nFaIhKw3h8NVwLYTAnptyiYtYaqcTzznk9jQROerdO0loInPHaBFN4lY59RoVQv7uA1ZMpQ4TUu_s7igbwgKEdcB55w3oei1ocLWcdn7QNvaxIqYl3ch5MAJlEOxhljGd5Hu4XHtmKe9IW7KZLqSwJKoI6M5HmiVY16UeqRcKJcKRaYoyaIq3jOJSUPfPJw22An2_oAp0aniq6Mydf3t-LNn8oDCPA5ztT9ivDhasZdWwk3dDF_IBEnwBc4X-UKGk04MRwnDkFnE-jmV5f4_k49VcuWLJEq2S5g-5lpZI55Et19lvGZv_tD1rsBMHcZ4beHwsUWrtykLSWRWt9XDVHk9cvgDAWeutKReMp3Rt3yN9gLMV-Puo9wscg51CuDvGXOsVO_V7vF2_1OEq5E6WJcDa9XqqphUrIhMs7l-s5gT16S4wYZ07BrhRWx1vCATJdP0Oo26QvE-tWthaPkcD1PUsePbx-dXrj-5IRsotGgoEzNRMmahkwNbc_SbILNVhCZXVMyo9h32n1Kdf-uNtF-KBb0aODmti2HV3qe52qILoKeO2XoIDMceNeVp_HjO6UL1np-tm4JOEqkF_as6VCGXfz2YE5cq8wIBfX9uKQwmPmtisK3tmfZKF6nuz4yxGo78cDJZdkE6xY8819ObqZyuFCDbONoxOkdA6KYUjjvhiPikDxzhFJXaq-tegNoDNWUJs=",
|
||||
"EXT330": "-VkMlhWJez5VxQgKzZjpqaXFNL5wjxxAs9TGLMQCkmfrYqHs5qONanQHnzU6Dz7QQwb8a51qk7ULc3dMHtUMvx4ILa-vKpQYGZ2uGenWFIS7lK5YWjHUvHu3uGwgTbAfzigiLq13opE9a5lauK0e4zmJ4aMbWkhIMxHjrak83zsmjFJa0IYhD0qD_f7oH8z-eCJ9tww4cy-p3Rd_pJCfGYi7j3Kz_Z9-gwFe7WFdKLowzzMpCNHr8hQxujtWzZZVQVrH4RSUAZEaN8sMm8uhkR3mE_21wFPFLLG2-Ui3bvObEALZ8ajMUdKJNu0etHGfUTYnHUAWxwesBDu5rqgSXDbeZiN6-4FDSkfTl2IkXOXrtFrW1YzmPvR5SBjed_QuNKrDCvSi9NBxP1vq4by6xJUaSCPyBJb9SfVBswfk6-_2g_d6LNLoT9Qz4Xn9RRHlACNyZe6tb-NNCCHYxY7nz88cDO0Kfb0qkEXyoOXWeDjCLvRXI-dCHCqNSq2D54P_A7e_xtqsoy4qjWp_4EKc7QRDX1j9BN7_CfUtt4MuKLMskP1MpBm07aZFLP6eHwVw8So5cZRDMB1zeQcfjFcHzoZ5nVBvVEPT3UkPfKdFAPkfuf3p233QbJiTGTnbsI05xdQQHtEWbrHc7sf446DsT_oH6w9ej0KwQ41if7jzz5uMIsGTgtOfyGz23k-IUsJQH-T237GegQKNXG0_SetgL8gn0OADUkdkaGbvAAxqMjg=",
|
||||
"INT1": "Kr29P2pI2LE9y2Y7S7kSD-EzhXs6sOfP-HVFAqoNbaIIb34CaauXwOlTz7NjjJ9xBg88y73aR-RGcIg_aqSqmwZvViIrs5esGXOJOvXROoWpodYsYK65XfmSD3o6PtkPXtTFEtfj_3OEgtmvqXuHLB17VXhPWB-GV5O4U4z8Wki7aZTzQi3Xh8ry9Or3GT7JD6z5JWGDFH3wHDkzNuTzuCR8SocQSDBlkH8uSTtl-ZuSQZczYyLuJcqff86niQZbaXEQGArqYifqW-hs3f0Xjm3-Pqvt8sMQcpDZsNLTOzz5GgExE5BDlMMewVlGHK6SkVmA5DDcXSpr0EQjoWjUET5rLDXNiNUMwk_gCJLFBE3qOP-dNFOYnPtyGCHOAt1fWHrLV-iDXBoyRm5nl7fvxGglECyEB1DVLDvOkd882z32G5OFy2oRMpq3qY1sccPE2L8i_lViNtKUJqRuwygkfEFrygQTaoFhqaGfnAp64a8PDsYMpBlHEl4-Y_m0l-7FhPQiYoYJBZDrKG5h37O9RZ8Jttt0jdzhtgHPnTiyz9LEdtBNMgqGr9QFYjSVnGSE3dAHpNuo6J4VeNTEGViVBj0Imqy5-s9N0VR2aziC4kqhQ2M4mRniu2ad6PycqJEWwP4YzlIKt6Pox9goDEhCpY8d9fuQv5ppj0butxtBDC_nw4eyWRlctlH8Lx-pfwjwQb08H3syFM6eCXCtHKGilsyeN2E7HZ3mq1LoprwZHUo=",
|
||||
"INT2": "JFFG7zz5OlAgoUkJUUPXu9h29bBg03yCTOCCtGzQPWCW3cqS453QCVSGd_K-TwXuRYnG54ZLHRPdJ4HQjMTDUZv6ExZck9td5r27H5cpO6wpV7hofz4bW5Klj-9kKOcyF8wHTEwPnoDv1lMVSEL6mcVEXFB0CnzngMs-njRnEXdvhgMrprEoSu1hSeikP5kUaFViOBDAuv9-6V6cfpZehoMQhjBmq6WMTNjhDS-fGsZH0Z2jqu7Oe8yaoydD3tJXZ1KY8GRpf048piD9bv-URJPR5531f4t5b6t6l9sivHDW0wgDoa0K-aF5mRKj6k8Psuu_Krsvm4EAJHZCXyZ-ixg8i58X8TODy70HXS1wRGrLVOBqGYsTpMEWxLOl7YLrn3VUCVN8yYavIDNdxU9nr6o5dA187IqStMEUS7NDUxSWcwmFmpeYDImh4or95wa7cEk1-7w74HAyVg8sC8-VHfwgKPLA2QrSVTp-F9lOu4CmStzf7C7SU3mkcb6XmyHQRAavjiCzAI8eeFMgT8ma_8B62gS1XKmppEQwddz5bZ2f8eKSc4q5WKoq9scxur42d-7JjsSzuenPwVAd9MSqWa2q5qnVMTbWtuLlO9ipLXXGTTYacAQJjZ9dOEdJIIh8MsSteJ6tpas3bAoX15IfvLGuObJFs2vjQ1h4AonjTquPIdo1JAhjActBgNCurWElyBpJ3dprvQ_y9qXgt12kKM0SvWV1zky_p8x53UPcHHk=",
|
||||
"INT3": "utnpbmDAfakTIjB-oSkx7eFoRjXxfxpbexmHUi2RPFCs5A3w8wtH0EM6cThnwEFHulWqSmXFc6v_hd3dyLNsKDAaIUHFktCoE3m5fX2ORgUi4tdXSrx2T8H0xpf0cfFZcg_aS9SK0fgtj2NYaWjoafEI6aLc79XNoiIpTyBONJi0QU6IjlLCA13a47QjVIkrQtPjIntm1EKY34Hyoq8QErZsZQG_f8JEXQMUfM-oOAY0U8gj0outMTtWRAnfdrgaSWhVyIWloUJkXP6cWhZsypKp7e8bm-poc4nE4R-tniMWegY7BLlvtJeSjs71txBaDomr9xqi9lhmX6ENkvGPAHpBbrTOmsMLvEJu6nQR7P08XYalXpiJQjrIQJ77hE499jOIdgSoBjuy6JKlaLc4ejpHeGNXYCPUBJM5KVLITLYpLOjgZFzbhzI8qr4GdjxcfW82PAxhlTDywjDH-eMzbXnVZ-YaLrEocNZ8i88R1bLnv90lUrwzgn7n3zUcTeiRILfkApNZRh_K9ClGSu612LzGv7E-_zN1JdOt4UCcfSSkJLMppbeyIXjE6tDsxjgUyRr0Xj1azvHtEDfFYKj76x9doyk8Xm6_sKF4z9rPrgEmo9u6zr4dhb6fmhTnBjdhWBFjQ1Zpe7P7IfaXeDFrWSQBt7jdADWzB5L8cjaMnULm295uSo1k_ka1FQtFg4NtHiZ2G_zNLINh4DBjZdo5hmqBW7tl4gnNWaUf8YO7J1A=",
|
||||
"INT4": "kJ4tgroRxuaYe0vF9xMe-nI4NUy4Eg1NyAr_E-WxJKlt5jZuoOcWYijc7cQEOiY3xlRPLTnfN-l9nf4UWO9rN4uMIGH9tuEql1g2ATLxff1ML3tCiexoxn45l-LQRH-PL2bL8jsQ3ByiHxLdzMHUzAarWj76rZk4mDCM9tRAE-hQsUZaaGMbO_hL3BCu6J-mI9hU3em4RuFwNTzCfBxgOJShU_PAU3tt-xROhld5Re3cyZzQZfQI17xDJKpTdZmnZCeabv7BIjh6qEapxhCHi0Ue6_EX3DPld1Faj438FptfBTn8UXW9Es6tkM1BGHw8npbHtGVqI1g7DdWuYUb5qeAtUzkEnFpxIl-67sKctYGBWvGkRnZrpu33jwaorW7xInwSymTg0isVCDQCfTEsa8zszj-bD4UMF2jeWr_pL-g4Vk_Ns0vMDFhsZGJTLO9Vv5DNf0WYOMWBISHXgW9z_d0UzCwlJpojqqO1DNhEt1aRRSb_zIBhdfAUHmJy35KmA0tPiDNb1K6uEVVcKSQBD2G9BfrvGXq-o_E3JAUzO2-u4ues0eWseOa0dQQIasjMZ0ZNAakfxCwg7dHp9Lq0c_0fti37OAk69Cyi0EMThCTYr8Yij_sidT9sts-YeQgTniI2LFsMnDnGkWB7wubmxUvG9qOq5ZmonBArkOt8xHWT9Xy5xq0hQj6Ba6Dgcy8qrcCdq8HoIneUaHH_c7pkRW2V-MKHQ6gr9iopAtP1Ifw=",
|
||||
"BET1": "hismr3VKvRu_fQ25jJjIjjBKs-nhdOYsbtMA7Sgc0-P99yjGnVvOq8nJBGFHcjCLHGb6lTcLyA4eVSflnKER2r6R35PqRznX63s1bDGZuuyPexBCTVZSh1ubtI3ImQuO5mRb9wFAyeUcG53JnZEXQmOnCdmLW8seIugeSrsj5-HC_Z7vOT-KTmcONFP2q2tbKqDTziLVXEJ0dy2EdgvSk8l-sn1QsgodDVe-FCaAp35jlswp8faJuyTv0j7luc5dRP7xvSVXX08uTD9AWHooVCbsGSJqwVRsjY0bFJMcvKqzCCE5xlyBVVmkbphD3aEsBqMVsG4wL2_W_pEgdbmI06qHgJK0sotwj0iEbGVBOYOuBp4uAQdadKYbQL2OkJaqXkDWJTy0MDUSRBbZ6qH3KCyVRfUFcxlFUxqw9I7l5G35vnDMCpoJyj6wyzyq4oFTKgDXkn6zMTDD7lgpYWW1zRDtCTecU6nkmsUQnwe-9EXBfcePlRtJasG40ykj5x4MRJQoDiX9F8VBDnquaR_8K5HOL3de8ypEl7q22bKkKv-pHEPO8hH5lZynfQ86RHyHhDGxXuNuCEX-ekqK7K8PGTF8gO_fIGMpp_Z5aqWSHQgu7Ge1R-FTtfKZ5L_E-RjnR9QWFqEIC2_hNiZoQBdSTC8XUMmnpltTBQJOngEBhmgGepekZmuGnP3xmmvVbK1VijHtgB6PCtIVaTIzkwpVo1o4aj25zQG_3VSqgT8Ufnw="
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"wrong": {
|
||||
"value": "4131",
|
||||
"timestamp": 1655399236000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"odo": {
|
||||
"value": "4131"
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"doorlockstatusvehicle": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorlockstatusdecklid": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorlockstatusgas": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"positionHeading": {
|
||||
"value": "120",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"odo": {
|
||||
"value": "4131",
|
||||
"timestamp": 1655399236000
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"name": "decklidstatus",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/decklidstatus"
|
||||
},
|
||||
{
|
||||
"name": "doorstatusfrontleft",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/doorstatusfrontleft"
|
||||
},
|
||||
{
|
||||
"name": "doorstatusfrontright",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/doorstatusfrontright"
|
||||
},
|
||||
{
|
||||
"name": "doorstatusrearleft",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/doorstatusrearleft"
|
||||
},
|
||||
{
|
||||
"name": "doorstatusrearright",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/doorstatusrearright"
|
||||
},
|
||||
{
|
||||
"name": "interiorLightsFront",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/interiorLightsFront"
|
||||
},
|
||||
{
|
||||
"name": "interiorLightsRear",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/interiorLightsRear"
|
||||
},
|
||||
{
|
||||
"name": "lightswitchposition",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/lightswitchposition"
|
||||
},
|
||||
{
|
||||
"name": "readingLampFrontLeft",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/readingLampFrontLeft"
|
||||
},
|
||||
{
|
||||
"name": "readingLampFrontRight",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/readingLampFrontRight"
|
||||
},
|
||||
{
|
||||
"name": "rooftopstatus",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/rooftopstatus"
|
||||
},
|
||||
{
|
||||
"name": "sunroofstatus",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/sunroofstatus"
|
||||
},
|
||||
{
|
||||
"name": "windowstatusfrontleft",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/windowstatusfrontleft"
|
||||
},
|
||||
{
|
||||
"name": "windowstatusfrontright",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/windowstatusfrontright"
|
||||
},
|
||||
{
|
||||
"name": "windowstatusrearleft",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/windowstatusrearleft"
|
||||
},
|
||||
{
|
||||
"name": "windowstatusrearright",
|
||||
"version": "1.0",
|
||||
"href": "/vehicles/WDB111111ZZZ22222/resources/windowstatusrearright"
|
||||
}
|
||||
]
|
@ -0,0 +1,98 @@
|
||||
[
|
||||
{
|
||||
"decklidstatus": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusfrontleft": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusfrontright": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusrearleft": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"doorstatusrearright": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"interiorLightsFront": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"interiorLightsRear": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"lightswitchposition": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"readingLampFrontLeft": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"readingLampFrontRight": {
|
||||
"value": "false",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"rooftopstatus": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"sunroofstatus": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusfrontleft": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusfrontright": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusrearleft": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
},
|
||||
{
|
||||
"windowstatusrearright": {
|
||||
"value": "0",
|
||||
"timestamp": 1541080800000
|
||||
}
|
||||
}
|
||||
]
|
@ -215,6 +215,7 @@
|
||||
<module>org.openhab.binding.mcp23017</module>
|
||||
<module>org.openhab.binding.mecmeter</module>
|
||||
<module>org.openhab.binding.melcloud</module>
|
||||
<module>org.openhab.binding.mercedesme</module>
|
||||
<module>org.openhab.binding.meteoalerte</module>
|
||||
<module>org.openhab.binding.meteoblue</module>
|
||||
<module>org.openhab.binding.meteostick</module>
|
||||
|