mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[saicismart] Initial contribution (#15894)
* [saicismart] initial binding creation Signed-off-by: Markus Heberling <markus@heberling.net> Signed-off-by: dougculnane <doug@culnane.net>
This commit is contained in:
parent
229c2b7032
commit
652845fee5
@ -306,6 +306,7 @@
|
||||
/bundles/org.openhab.binding.rotel/ @lolodomo
|
||||
/bundles/org.openhab.binding.russound/ @openhab/add-ons-maintainers
|
||||
/bundles/org.openhab.binding.sagercaster/ @clinique
|
||||
/bundles/org.openhab.binding.saicismart/ @tisoft @dougculnane
|
||||
/bundles/org.openhab.binding.samsungtv/ @paulianttila
|
||||
/bundles/org.openhab.binding.satel/ @druciak
|
||||
/bundles/org.openhab.binding.semsportal/ @itb3
|
||||
|
@ -1516,6 +1516,11 @@
|
||||
<artifactId>org.openhab.binding.sagercaster</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.saicismart</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.samsungtv</artifactId>
|
||||
|
13
bundles/org.openhab.binding.saicismart/NOTICE
Normal file
13
bundles/org.openhab.binding.saicismart/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
|
131
bundles/org.openhab.binding.saicismart/README.md
Normal file
131
bundles/org.openhab.binding.saicismart/README.md
Normal file
@ -0,0 +1,131 @@
|
||||
# SAICiSMART Binding
|
||||
|
||||
OpenHAB binding to the SAIC-API used by MG cars (MG4, MG5 EV, MG ZSV...)
|
||||
|
||||
It enables iSMART users to get battery status and other data from their cars.
|
||||
They can also pre-heat their cars by turning ON the AC.
|
||||
|
||||
Based on the work done here: https://github.com/SAIC-iSmart-API
|
||||
|
||||
## Supported Things
|
||||
|
||||
European iSMART accounts and vehicles.
|
||||
|
||||
- `account`: Bridge representing an iSMART Account
|
||||
- `vehicle`: Thing representing an iSMART MG Car
|
||||
|
||||
|
||||
## Discovery
|
||||
|
||||
Vehicle discovery is implemented.
|
||||
Once an account has been configured it can be scanned for vehicles.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### `account` iSMART Account Configuration
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|----------|---------|-----------------------------|---------|----------|----------|
|
||||
| username | text | iSMART username | N/A | yes | no |
|
||||
| password | text | iSMART password | N/A | yes | no |
|
||||
|
||||
### `vehicle` An iSMART MG Car
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|---------------|------|--------------------------------------|---------|----------|----------|
|
||||
| vin | text | Vehicle identification number (VIN) | N/A | yes | no |
|
||||
| abrpUserToken | text | User token for A Better Routeplanner | N/A | no | no |
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel | Type | Read/Write | Description | Advanced |
|
||||
|----------------------------|--------------------------|------------|-----------------------------------------------------|----------|
|
||||
| odometer | Number:Length | R | Total distance driven | no |
|
||||
| range-electric | Number:Length | R | Electric range | no |
|
||||
| soc | Number | R | State of the battery in % | no |
|
||||
| power | Number:Power | R | Power usage | no |
|
||||
| charging | Switch | R | Charging | no |
|
||||
| engine | Switch | R | Engine state | no |
|
||||
| speed | Number:Speed | R | Vehicle speed | no |
|
||||
| location | Location | R | The actual position of the vehicle | no |
|
||||
| heading | Number:Angle | R | The compass heading of the car, (0-360 degrees) | no |
|
||||
| auxiliary-battery-voltage | Number:ElectricPotential | R | Auxiliary battery voltage | no |
|
||||
| tyre-pressure-front-left | Number:Pressure | R | Pressure front left | no |
|
||||
| tyre-pressure-front-right | Number:Pressure | R | Pressure front right | no |
|
||||
| tyre-pressure-rear-left | Number:Pressure | R | Pressure rear left | no |
|
||||
| tyre-pressure-rear-right | Number:Pressure | R | Pressure rear right | no |
|
||||
| interior-temperature | Number:Temperature | R | Interior temperature | no |
|
||||
| exterior-temperature | Number:Temperature | R | Exterior temperature | no |
|
||||
| door-driver | Contact | R | Driver door open state | no |
|
||||
| door-passenger | Contact | R | Passenger door open state | no |
|
||||
| door-rear-left | Contact | R | Rear left door open state | no |
|
||||
| door-rear-right | Contact | R | Rear right door open state | no |
|
||||
| window-driver | Contact | R | Driver window open state | no |
|
||||
| window-passenger | Contact | R | Passenger window open state | no |
|
||||
| window-rear-left | Contact | R | Rear left window open state | no |
|
||||
| window-rear-right | Contact | R | Rear right window open state | no |
|
||||
| window-sun-roof | Contact | R | Sun roof open state | no |
|
||||
| last-activity | DateTime | R | Last time the engine was on or the car was charging | no |
|
||||
| last-position-update | DateTime | R | Last time the Position data was updated | no |
|
||||
| last-charge-state-update | DateTime | R | Last time the Charge State data was updated | no |
|
||||
| remote-ac-status | Number | R | Status of the A/C | no |
|
||||
| switch-ac | Switch | R/W | Control the A/C remotely | no |
|
||||
| force-refresh | Switch | R/W | Force an immediate refresh of the car data | yes |
|
||||
| last-alarm-message-date | DateTime | R | Last time an alarm message was sent | no |
|
||||
| last-alarm-message-content | String | R | Vehicle message | no |
|
||||
|
||||
# Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```java
|
||||
Bridge saicismart:account:myaccount "My iSMART Account" [ username="MyEmail@domian.com", password="MyPassword" ] {
|
||||
Thing vehicle mymg5 "MG5" [ vin="XXXXXXXXXXXXXXXXX", abrpUserToken="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ]
|
||||
}
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```java
|
||||
Number MG5_Total_Distance_Driven "MG5 Total Distance Driven" ["Length"] {channel="saicismart:vehicle:myaccount:mymg5:odometer"}
|
||||
Number MG5_Electric_Range "MG5 Electric Range" ["Length"] {channel="saicismart:vehicle:myaccount:mymg5:range-electric"}
|
||||
Number MG5_Battery_Level "MG5 Battery Level" ["Energy"] {channel="saicismart:vehicle:myaccount:mymg5:soc"}
|
||||
Number MG5_Power_Usage "MG5 Power Usage" ["Power"] {channel="saicismart:vehicle:myaccount:mymg5:power"}
|
||||
Switch MG5_Charging "MG5 Charging" {channel="saicismart:vehicle:myaccount:mymg5:charging"}
|
||||
Switch MG5_Engine_State "MG5 Engine State" {channel="saicismart:vehicle:myaccount:mymg5:engine"}
|
||||
Number MG5_Speed "MG5 Speed" ["Speed"] {channel="saicismart:vehicle:myaccount:mymg5:speed"}
|
||||
Location MG5_Location "MG5 Location" {channel="saicismart:vehicle:myaccount:mymg5:location"}
|
||||
Number MG5_Heading "MG5 Heading" ["Angle"] {channel="saicismart:vehicle:myaccount:mymg5:heading"}
|
||||
Number MG5_Auxiliary_Battery_Voltage "MG5 Auxiliary Battery Voltage" ["ElectricPotential"] {channel="saicismart:vehicle:myaccount:mymg5:auxiliary-battery-voltage"}
|
||||
Number MG5_Pressure_Front_Left "MG5 Pressure Front Left" ["Pressure"] {channel="saicismart:vehicle:myaccount:mymg5:tyre-pressure-front-left"}
|
||||
Number MG5_Pressure_Front_Right "MG5 Pressure Front Right ["Pressure"] {channel="saicismart:vehicle:myaccount:mymg5:tyre-pressure-front-right"}
|
||||
Number MG5_Pressure_Rear_Left "MG5 Pressure Rear Left" ["Pressure"] {channel="saicismart:vehicle:myaccount:mymg5:tyre-pressure-rear-left"}
|
||||
Number MG5_Pressure_Rear_Right "MG5 Pressure Rear Right" ["Pressure"] {channel="saicismart:vehicle:myaccount:mymg5:tyre-pressure-rear-right"}
|
||||
Number MG5_Interior_Temperature "MG5 Interior Temperature" ["Temperature"] {channel="saicismart:vehicle:myaccount:mymg5:interior-temperature"}
|
||||
Number MG5_Exterior_Temperature "MG5 Exterior Temperature" ["Temperature"] {channel="saicismart:vehicle:myaccount:mymg5:exterior-temperature"}
|
||||
Contact MG5_Driver_Door "MG5 Driver Door" {channel="saicismart:vehicle:myaccount:mymg5:door-driver"}
|
||||
Contact MG5_Passenger_Door "MG5 Passenger Door" {channel="saicismart:vehicle:myaccount:mymg5:door-passenger"}
|
||||
Contact MG5_Rear_Left_Door "MG5 Rear Left Door" {channel="saicismart:vehicle:myaccount:mymg5:door-rear-left"}
|
||||
Contact MG5_Rear_Right_Door "MG5 Rear Right Door" {channel="saicismart:vehicle:myaccount:mymg5:door-rear-right"}
|
||||
Contact MG5_Driver_Window "MG5 Driver Window" {channel="saicismart:vehicle:myaccount:mymg5:window-driver"}
|
||||
Contact MG5_Passenger_Window "MG5 Passenger Window" {channel="saicismart:vehicle:myaccount:mymg5:window-passenger"}
|
||||
Contact MG5_Rear_Left_Window "MG5 Rear Left Window" {channel="saicismart:vehicle:myaccount:mymg5:window-rear-left"}
|
||||
Contact MG5_Rear_Right_Window "MG5 Rear Right Window" {channel="saicismart:vehicle:myaccount:mymg5:window-rear-right"}
|
||||
Contact MG5_Sun_Roof "MG5 Sun Roof" {channel="saicismart:vehicle:myaccount:mymg5:window-sun-roof"}
|
||||
DateTime MG5_Last_Car_Activity "MG5 Last Car Activity" {channel="saicismart:vehicle:myaccount:mymg5:last-activity"}
|
||||
DateTime MG5_Last_Position_Timestamp "MG5 Last Position Timestamp" {channel="saicismart:vehicle:myaccount:mymg5:last-position-update"}
|
||||
DateTime MG5_Last_Charge_State_Timestamp "MG5 Last Charge State Timestamp" {channel="saicismart:vehicle:myaccount:mymg5:last-charge-state-update"}
|
||||
Number MG5_Remote_AC "MG5 Remote A/C" {channel="saicismart:vehicle:myaccount:mymg5:remote-ac-status"}
|
||||
Switch MG5_Switch_AC "MG5 Switch A/C" {channel="saicismart:vehicle:myaccount:mymg5:switch-ac"}
|
||||
Switch MG5_Force_Refresh "MG5 Force Refresh" {channel="saicismart:vehicle:myaccount:mymg5:force-refresh"}
|
||||
DateTime MG5_Last_Alarm_Message_Timestamp "MG5 Last Alarm Message Timestamp" {channel="saicismart:vehicle:myaccount:mymg5:last-alarm-message-date"}
|
||||
String MG5_Vehicle_Message "MG5 Vehicle Message" {channel="saicismart:vehicle:myaccount:mymg5:last-alarm-message-content"}
|
||||
```
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
The advanced channel "force refresh" if used regularly will drain the 12v car battery and you will be unable to start it!
|
||||
|
||||
Only European iSMART accounts and vehicles are supported. API host configuration and testing for other markets is required.
|
52
bundles/org.openhab.binding.saicismart/pom.xml
Normal file
52
bundles/org.openhab.binding.saicismart/pom.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?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>4.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.saicismart</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: SAICiSMART Binding</name>
|
||||
<properties>
|
||||
<bnd.importpackage>org.brotli.dec;resolution:=optional,org.conscrypt;resolution:=optional</bnd.importpackage>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.saic-ismart-api</groupId>
|
||||
<artifactId>saic-ismart-client</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.saic-ismart-api</groupId>
|
||||
<artifactId>saic-ismart-api</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<version>5.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<artifactId>httpcore5</artifactId>
|
||||
<version>5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
||||
<artifactId>httpcore5-h2</artifactId>
|
||||
<version>5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.heberling.binarynotes</groupId>
|
||||
<artifactId>binarynotes</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.saicismart-${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-saicismart" description="SAICiSMART Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.saicismart/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.API_ENDPOINT_V30;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_POWER;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_SOC;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.bn.coders.IASN1PreparedElement;
|
||||
import org.eclipse.jdt.annotation.DefaultLocation;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.saicismart.internal.exceptions.ChargingStatusAPIException;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import net.heberling.ismart.asn1.v3_0.Message;
|
||||
import net.heberling.ismart.asn1.v3_0.MessageCoder;
|
||||
import net.heberling.ismart.asn1.v3_0.entity.OTA_ChrgMangDataResp;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault({ DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD,
|
||||
DefaultLocation.TYPE_BOUND })
|
||||
class ChargeStateUpdater implements Callable<OTA_ChrgMangDataResp> {
|
||||
private final Logger logger = LoggerFactory.getLogger(ChargeStateUpdater.class);
|
||||
|
||||
private final SAICiSMARTHandler saiCiSMARTHandler;
|
||||
|
||||
public ChargeStateUpdater(SAICiSMARTHandler saiCiSMARTHandler) {
|
||||
this.saiCiSMARTHandler = saiCiSMARTHandler;
|
||||
}
|
||||
|
||||
public OTA_ChrgMangDataResp call() throws URISyntaxException, ExecutionException, InterruptedException,
|
||||
TimeoutException, ChargingStatusAPIException {
|
||||
MessageCoder<IASN1PreparedElement> chargingStatusRequestmessageCoder = new MessageCoder<>(
|
||||
IASN1PreparedElement.class);
|
||||
Message<IASN1PreparedElement> chargingStatusMessage = chargingStatusRequestmessageCoder.initializeMessage(
|
||||
saiCiSMARTHandler.getBridgeHandler().getUid(), saiCiSMARTHandler.getBridgeHandler().getToken(),
|
||||
saiCiSMARTHandler.config.vin, "516", 768, 5, null);
|
||||
|
||||
String chargingStatusRequestMessage = chargingStatusRequestmessageCoder.encodeRequest(chargingStatusMessage);
|
||||
|
||||
String chargingStatusResponse = saiCiSMARTHandler.getBridgeHandler().sendRequest(chargingStatusRequestMessage,
|
||||
API_ENDPOINT_V30);
|
||||
|
||||
Message<OTA_ChrgMangDataResp> chargingStatusResponseMessage = new MessageCoder<>(OTA_ChrgMangDataResp.class)
|
||||
.decodeResponse(chargingStatusResponse);
|
||||
|
||||
// we get an eventId back...
|
||||
chargingStatusMessage.getBody().setEventID(chargingStatusResponseMessage.getBody().getEventID());
|
||||
// ... use that to request the data again, until we have it
|
||||
while (chargingStatusResponseMessage.getApplicationData() == null) {
|
||||
if (chargingStatusResponseMessage.getBody().getResult() != 0
|
||||
|| chargingStatusResponseMessage.getBody().isErrorMessagePresent()) {
|
||||
if (chargingStatusResponseMessage.getBody().getResult() == 2) {
|
||||
saiCiSMARTHandler.getBridgeHandler().relogin();
|
||||
}
|
||||
throw new ChargingStatusAPIException(chargingStatusResponseMessage.getBody());
|
||||
}
|
||||
|
||||
chargingStatusMessage.getBody().setUid(saiCiSMARTHandler.getBridgeHandler().getUid());
|
||||
chargingStatusMessage.getBody().setToken(saiCiSMARTHandler.getBridgeHandler().getToken());
|
||||
|
||||
chargingStatusRequestMessage = chargingStatusRequestmessageCoder.encodeRequest(chargingStatusMessage);
|
||||
|
||||
chargingStatusResponse = saiCiSMARTHandler.getBridgeHandler().sendRequest(chargingStatusRequestMessage,
|
||||
API_ENDPOINT_V30);
|
||||
|
||||
chargingStatusResponseMessage = new MessageCoder<>(OTA_ChrgMangDataResp.class)
|
||||
.decodeResponse(chargingStatusResponse);
|
||||
}
|
||||
saiCiSMARTHandler.updateState(CHANNEL_SOC,
|
||||
new DecimalType(chargingStatusResponseMessage.getApplicationData().getBmsPackSOCDsp() / 10.d));
|
||||
logger.debug("Got message: {}",
|
||||
new GsonBuilder().setPrettyPrinting().create().toJson(chargingStatusResponseMessage));
|
||||
|
||||
Double power = (chargingStatusResponseMessage.getApplicationData().getBmsPackCrnt() * 0.05d - 1000.0d)
|
||||
* ((double) chargingStatusResponseMessage.getApplicationData().getBmsPackVol() * 0.25d);
|
||||
|
||||
saiCiSMARTHandler.updateState(CHANNEL_POWER, new QuantityType<>(power.intValue(), Units.WATT));
|
||||
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_LAST_CHARGE_STATE_UPDATE,
|
||||
new DateTimeType(ZonedDateTime.now(saiCiSMARTHandler.getTimeZone())));
|
||||
|
||||
saiCiSMARTHandler.updateStatus(ThingStatus.ONLINE);
|
||||
return chargingStatusResponseMessage.getApplicationData();
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SAICiSMARTBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "saicismart";
|
||||
|
||||
/**
|
||||
* Interval in seconds between polls of API.
|
||||
*/
|
||||
public static final int REFRESH_INTERVAL = 10;
|
||||
|
||||
/**
|
||||
* Active polling period in minutes
|
||||
*/
|
||||
public static final int POLLING_ACTIVE_MINS = 10;
|
||||
|
||||
/**
|
||||
* URL of the SAIC API Host.
|
||||
*/
|
||||
private static final String API_HOST_URL = "https://tap-eu.soimt.com";
|
||||
|
||||
/**
|
||||
* https://github.com/SAIC-iSmart-API/documentation?tab=readme-ov-file#api-v11
|
||||
*/
|
||||
public static final String API_ENDPOINT_V11 = API_HOST_URL + "/TAP.Web/ota.mp";
|
||||
|
||||
/**
|
||||
* https://github.com/SAIC-iSmart-API/documentation?tab=readme-ov-file#api-v21
|
||||
*/
|
||||
public static final String API_ENDPOINT_V21 = API_HOST_URL + "/TAP.Web/ota.mpv21";
|
||||
|
||||
/**
|
||||
* https://github.com/SAIC-iSmart-API/documentation?tab=readme-ov-file#api-v30
|
||||
*/
|
||||
public static final String API_ENDPOINT_V30 = API_HOST_URL + "/TAP.Web/ota.mpv30";
|
||||
|
||||
public static final String ABRP_API_KEY = "8cfc314b-03cd-4efe-ab7d-4431cd8f2e2d";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||
public static final ThingTypeUID THING_TYPE_VEHICLE = new ThingTypeUID(BINDING_ID, "vehicle");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_ODOMETER = "odometer";
|
||||
public static final String CHANNEL_RANGE_ELECTRIC = "range-electric";
|
||||
public static final String CHANNEL_SOC = "soc";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_ENGINE = "engine";
|
||||
public static final String CHANNEL_CHARGING = "charging";
|
||||
public static final String CHANNEL_TYRE_PRESSURE_FRONT_LEFT = "tyre-pressure-front-left";
|
||||
public static final String CHANNEL_TYRE_PRESSURE_FRONT_RIGHT = "tyre-pressure-front-right";
|
||||
public static final String CHANNEL_TYRE_PRESSURE_REAR_LEFT = "tyre-pressure-rear-left";
|
||||
public static final String CHANNEL_TYRE_PRESSURE_REAR_RIGHT = "tyre-pressure-rear-right";
|
||||
public static final String CHANNEL_INTERIOR_TEMPERATURE = "interior-temperature";
|
||||
public static final String CHANNEL_EXTERIOR_TEMPERATURE = "exterior-temperature";
|
||||
public static final String CHANNEL_SPEED = "speed";
|
||||
public static final String CHANNEL_LOCATION = "location";
|
||||
public static final String CHANNEL_HEADING = "heading";
|
||||
public static final String CHANNEL_AUXILIARY_BATTERY_VOLTAGE = "auxiliary-battery-voltage";
|
||||
public static final String CHANNEL_DOOR_DRIVER = "door-driver";
|
||||
public static final String CHANNEL_DOOR_PASSENGER = "door-passenger";
|
||||
public static final String CHANNEL_DOOR_REAR_LEFT = "door-rear-left";
|
||||
public static final String CHANNEL_DOOR_REAR_RIGHT = "door-rear-right";
|
||||
public static final String CHANNEL_WINDOW_DRIVER = "window-driver";
|
||||
public static final String CHANNEL_WINDOW_PASSENGER = "window-passenger";
|
||||
public static final String CHANNEL_WINDOW_REAR_LEFT = "window-rear-left";
|
||||
public static final String CHANNEL_WINDOW_REAR_RIGHT = "window-rear-right";
|
||||
public static final String CHANNEL_WINDOW_SUN_ROOF = "window-sun-roof";
|
||||
public static final String CHANNEL_LAST_ACTIVITY = "last-activity";
|
||||
public static final String CHANNEL_FORCE_REFRESH = "force-refresh";
|
||||
public static final String CHANNEL_REMOTE_AC_STATUS = "remote-ac-status";
|
||||
public static final String CHANNEL_SWITCH_AC = "switch-ac";
|
||||
public static final String CHANNEL_LAST_POSITION_UPDATE = "last-position-update";
|
||||
public static final String CHANNEL_LAST_CHARGE_STATE_UPDATE = "last-charge-state-update";
|
||||
public static final String CHANNEL_ALARM_MESSAGE_DATE = "last-alarm-message-date";
|
||||
public static final String CHANNEL_ALARM_MESSAGE_CONTENT = "last-alarm-message-content";
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SAICiSMARTBridgeConfiguration {
|
||||
|
||||
public String username = "";
|
||||
public String password = "";
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.API_ENDPOINT_V11;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.bn.coders.IASN1PreparedElement;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import net.heberling.ismart.asn1.v1_1.Message;
|
||||
import net.heberling.ismart.asn1.v1_1.MessageCoder;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.AlarmSwitch;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.AlarmSwitchReq;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.MP_AlarmSettingType;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.MP_UserLoggingInReq;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.MP_UserLoggingInResp;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.MessageListReq;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.MessageListResp;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.StartEndNumber;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.VinInfo;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SAICiSMARTBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SAICiSMARTBridgeHandler.class);
|
||||
|
||||
private @Nullable SAICiSMARTBridgeConfiguration config;
|
||||
|
||||
private @Nullable String uid;
|
||||
|
||||
private @Nullable String token;
|
||||
|
||||
private @Nullable Collection<VinInfo> vinList;
|
||||
private HttpClient httpClient;
|
||||
private @Nullable Future<?> pollingJob;
|
||||
|
||||
public SAICiSMARTBridgeHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// no commands available
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(SAICiSMARTBridgeConfiguration.class);
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// Validate configuration
|
||||
if (this.config.username.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/thing-type.config.saicismart.bridge.username.required");
|
||||
return;
|
||||
}
|
||||
if (this.config.password.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/thing-type.config.saicismart.bridge.password.required");
|
||||
return;
|
||||
}
|
||||
if (this.config.username.length() > 50) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/thing-type.config.saicismart.bridge.username.toolong");
|
||||
return;
|
||||
}
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::updateStatus, 1,
|
||||
SAICiSMARTBindingConstants.REFRESH_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
if (uid == null || token == null) {
|
||||
login();
|
||||
} else {
|
||||
registerForMessages();
|
||||
}
|
||||
}
|
||||
|
||||
private void login() {
|
||||
MessageCoder<MP_UserLoggingInReq> mpUserLoggingInRequestMessageCoder = new MessageCoder<>(
|
||||
MP_UserLoggingInReq.class);
|
||||
|
||||
MP_UserLoggingInReq mpUserLoggingInReq = new MP_UserLoggingInReq();
|
||||
mpUserLoggingInReq.setPassword(config.password);
|
||||
Message<MP_UserLoggingInReq> loginRequestMessage = mpUserLoggingInRequestMessageCoder.initializeMessage(
|
||||
StringUtils.padLeft("#" + config.username, 50, "0"), null, null, "501", 513, 1, mpUserLoggingInReq);
|
||||
|
||||
String loginRequest = mpUserLoggingInRequestMessageCoder.encodeRequest(loginRequestMessage);
|
||||
|
||||
try {
|
||||
String loginResponse = sendRequest(loginRequest, API_ENDPOINT_V11);
|
||||
|
||||
Message<MP_UserLoggingInResp> loginResponseMessage = new MessageCoder<>(MP_UserLoggingInResp.class)
|
||||
.decodeResponse(loginResponse);
|
||||
|
||||
logger.trace("Got message: {}",
|
||||
new GsonBuilder().setPrettyPrinting().create().toJson(loginResponseMessage));
|
||||
|
||||
uid = loginResponseMessage.getBody().getUid();
|
||||
token = loginResponseMessage.getApplicationData().getToken();
|
||||
vinList = loginResponseMessage.getApplicationData().getVinList();
|
||||
|
||||
// register for all known alarm types (not all might be actually delivered)
|
||||
for (MP_AlarmSettingType.EnumType type : MP_AlarmSettingType.EnumType.values()) {
|
||||
registerAlarmMessage(loginResponseMessage.getBody().getUid(),
|
||||
loginResponseMessage.getApplicationData().getToken(), type);
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (TimeoutException | URISyntaxException | ExecutionException | InterruptedException
|
||||
| NoSuchAlgorithmException | IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerForMessages() {
|
||||
MessageCoder<MessageListReq> messageListReqMessageCoder = new MessageCoder<>(MessageListReq.class);
|
||||
Message<MessageListReq> messageListRequestMessage = messageListReqMessageCoder.initializeMessage(uid, token,
|
||||
null, "531", 513, 1, new MessageListReq());
|
||||
|
||||
messageListRequestMessage.getHeader().setProtocolVersion(18);
|
||||
|
||||
// We currently assume that the newest message is the first.
|
||||
messageListRequestMessage.getApplicationData().setStartEndNumber(new StartEndNumber());
|
||||
messageListRequestMessage.getApplicationData().getStartEndNumber().setStartNumber(1L);
|
||||
messageListRequestMessage.getApplicationData().getStartEndNumber().setEndNumber(5L);
|
||||
messageListRequestMessage.getApplicationData().setMessageGroup("ALARM");
|
||||
|
||||
String messageListRequest = messageListReqMessageCoder.encodeRequest(messageListRequestMessage);
|
||||
|
||||
try {
|
||||
String messageListResponse = sendRequest(messageListRequest, API_ENDPOINT_V11);
|
||||
|
||||
Message<MessageListResp> messageListResponseMessage = new MessageCoder<>(MessageListResp.class)
|
||||
.decodeResponse(messageListResponse);
|
||||
|
||||
logger.trace("Got message: {}",
|
||||
new GsonBuilder().setPrettyPrinting().create().toJson(messageListResponseMessage));
|
||||
|
||||
if (messageListResponseMessage.getApplicationData() != null
|
||||
&& messageListResponseMessage.getApplicationData().getMessages() != null) {
|
||||
for (net.heberling.ismart.asn1.v1_1.entity.Message message : messageListResponseMessage
|
||||
.getApplicationData().getMessages()) {
|
||||
if (message.isVinPresent()) {
|
||||
String vin = message.getVin();
|
||||
getThing().getThings().stream().filter(t -> t.getUID().getId().equals(vin))
|
||||
.map(Thing::getHandler).filter(Objects::nonNull)
|
||||
.filter(SAICiSMARTHandler.class::isInstance).map(SAICiSMARTHandler.class::cast)
|
||||
.forEach(t -> t.handleMessage(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (TimeoutException | URISyntaxException | ExecutionException | InterruptedException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void registerAlarmMessage(String uid, String token, MP_AlarmSettingType.EnumType type)
|
||||
throws NoSuchAlgorithmException, IOException, URISyntaxException, ExecutionException, InterruptedException,
|
||||
TimeoutException {
|
||||
MessageCoder<AlarmSwitchReq> alarmSwitchReqMessageCoder = new MessageCoder<>(AlarmSwitchReq.class);
|
||||
|
||||
AlarmSwitchReq alarmSwitchReq = new AlarmSwitchReq();
|
||||
alarmSwitchReq
|
||||
.setAlarmSwitchList(Stream.of(type).map(v -> createAlarmSwitch(v, true)).collect(Collectors.toList()));
|
||||
alarmSwitchReq.setPin(hashMD5("123456"));
|
||||
|
||||
Message<AlarmSwitchReq> alarmSwitchMessage = alarmSwitchReqMessageCoder.initializeMessage(uid, token, null,
|
||||
"521", 513, 1, alarmSwitchReq);
|
||||
String alarmSwitchRequest = alarmSwitchReqMessageCoder.encodeRequest(alarmSwitchMessage);
|
||||
String alarmSwitchResponse = sendRequest(alarmSwitchRequest, API_ENDPOINT_V11);
|
||||
final MessageCoder<IASN1PreparedElement> alarmSwitchResMessageCoder = new MessageCoder<>(
|
||||
IASN1PreparedElement.class);
|
||||
Message<IASN1PreparedElement> alarmSwitchResponseMessage = alarmSwitchResMessageCoder
|
||||
.decodeResponse(alarmSwitchResponse);
|
||||
|
||||
logger.trace("Got message: {}",
|
||||
new GsonBuilder().setPrettyPrinting().create().toJson(alarmSwitchResponseMessage));
|
||||
|
||||
if (alarmSwitchResponseMessage.getBody().getErrorMessage() != null) {
|
||||
logger.debug("Could not register for {} messages: {}", type,
|
||||
new String(alarmSwitchResponseMessage.getBody().getErrorMessage(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
logger.debug("Registered for {} messages", type);
|
||||
}
|
||||
}
|
||||
|
||||
private static AlarmSwitch createAlarmSwitch(MP_AlarmSettingType.EnumType type, boolean enabled) {
|
||||
AlarmSwitch alarmSwitch = new AlarmSwitch();
|
||||
MP_AlarmSettingType alarmSettingType = new MP_AlarmSettingType();
|
||||
alarmSettingType.setValue(type);
|
||||
alarmSettingType.setIntegerForm(type.ordinal());
|
||||
alarmSwitch.setAlarmSettingType(alarmSettingType);
|
||||
alarmSwitch.setAlarmSwitch(enabled);
|
||||
alarmSwitch.setFunctionSwitch(enabled);
|
||||
return alarmSwitch;
|
||||
}
|
||||
|
||||
public String hashMD5(String password) throws NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(password.getBytes());
|
||||
byte[] digest = md.digest();
|
||||
return DatatypeConverter.printHexBinary(digest).toUpperCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(VehicleDiscovery.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public Collection<VinInfo> getVinList() {
|
||||
return Optional.ofNullable(vinList).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
public String sendRequest(String request, String endpoint)
|
||||
throws URISyntaxException, ExecutionException, InterruptedException, TimeoutException {
|
||||
return httpClient.POST(new URI(endpoint)).content(new StringContentProvider(request), "text/html").send()
|
||||
.getContentAsString();
|
||||
}
|
||||
|
||||
public void relogin() {
|
||||
uid = null;
|
||||
token = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Future<?> job = pollingJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.ABRP_API_KEY;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.API_ENDPOINT_V21;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_FORCE_REFRESH;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_LAST_ACTIVITY;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_SWITCH_AC;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.StringType;
|
||||
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.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import net.heberling.ismart.abrp.ABRP;
|
||||
import net.heberling.ismart.asn1.v1_1.entity.Message;
|
||||
import net.heberling.ismart.asn1.v2_1.MessageCoder;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.OTA_RVCReq;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.OTA_RVCStatus25857;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.OTA_RVMVehicleStatusResp25857;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.RvcReqParam;
|
||||
import net.heberling.ismart.asn1.v3_0.entity.OTA_ChrgMangDataResp;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SAICiSMARTHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SAICiSMARTHandler.class);
|
||||
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Nullable
|
||||
SAICiSMARTVehicleConfiguration config;
|
||||
private @Nullable Future<?> pollingJob;
|
||||
private ZonedDateTime lastAlarmMessage;
|
||||
private ZonedDateTime lastCarActivity;
|
||||
|
||||
/**
|
||||
* If the binding is initialized, treat the car as active (lastCarActivity = now) to get some first data.
|
||||
*
|
||||
* @param httpClientFactory
|
||||
* @param timeZoneProvider
|
||||
* @param thing
|
||||
*/
|
||||
public SAICiSMARTHandler(TimeZoneProvider timeZoneProvider, Thing thing) {
|
||||
super(thing);
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
lastAlarmMessage = ZonedDateTime.now(getTimeZone());
|
||||
lastCarActivity = ZonedDateTime.now(getTimeZone());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(SAICiSMARTBindingConstants.CHANNEL_FORCE_REFRESH) && command == OnOffType.ON) {
|
||||
// reset channel to off
|
||||
updateState(CHANNEL_FORCE_REFRESH, OnOffType.from(false));
|
||||
// update internal activity date, to query the car for about a minute
|
||||
notifyCarActivity(ZonedDateTime.now(getTimeZone()).minus(SAICiSMARTBindingConstants.POLLING_ACTIVE_MINS - 1,
|
||||
ChronoUnit.MINUTES), true);
|
||||
} else if (channelUID.getId().equals(CHANNEL_SWITCH_AC) && command == OnOffType.ON) {
|
||||
// reset channel to off
|
||||
updateState(CHANNEL_SWITCH_AC, OnOffType.ON);
|
||||
// enable air conditioning
|
||||
try {
|
||||
sendACCommand((byte) 5, (byte) 8);
|
||||
} catch (URISyntaxException | ExecutionException | TimeoutException | InterruptedException e) {
|
||||
logger.warn("A/C On Command failed", e);
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SWITCH_AC) && command == OnOffType.OFF) {
|
||||
// reset channel to off
|
||||
updateState(CHANNEL_SWITCH_AC, OnOffType.OFF);
|
||||
// disable air conditioning
|
||||
try {
|
||||
sendACCommand((byte) 0, (byte) 0);
|
||||
} catch (URISyntaxException | ExecutionException | TimeoutException | InterruptedException e) {
|
||||
logger.warn("A/C Off Command failed", e);
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_LAST_ACTIVITY)
|
||||
&& command instanceof DateTimeType commnadAsDateTimeType) {
|
||||
// update internal activity date from external date
|
||||
notifyCarActivity(commnadAsDateTimeType.getZonedDateTime(), true);
|
||||
}
|
||||
}
|
||||
|
||||
protected @Nullable SAICiSMARTBridgeHandler getBridgeHandler() {
|
||||
return (SAICiSMARTBridgeHandler) super.getBridge().getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(SAICiSMARTVehicleConfiguration.class);
|
||||
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
updateThing(thingBuilder.build());
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// Validate configuration
|
||||
if (this.config.vin.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/thing-type.config.saicismart.vehicle.vin.required");
|
||||
return;
|
||||
}
|
||||
|
||||
// just started, make sure we start querying
|
||||
notifyCarActivity(ZonedDateTime.now(getTimeZone()), true);
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::updateStatus, 2,
|
||||
SAICiSMARTBindingConstants.REFRESH_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
if (lastCarActivity.isAfter(
|
||||
ZonedDateTime.now().minus(SAICiSMARTBindingConstants.POLLING_ACTIVE_MINS, ChronoUnit.MINUTES))) {
|
||||
if (this.getBridgeHandler().getUid() != null && this.getBridgeHandler().getToken() != null) {
|
||||
try {
|
||||
OTA_RVMVehicleStatusResp25857 otaRvmVehicleStatusResp25857 = new VehicleStateUpdater(this).call();
|
||||
OTA_ChrgMangDataResp otaChrgMangDataResp = new ChargeStateUpdater(this).call();
|
||||
|
||||
if (config.abrpUserToken != null && config.abrpUserToken.length() > 0) {
|
||||
String execute = ABRP.updateAbrp(ABRP_API_KEY, config.abrpUserToken,
|
||||
otaRvmVehicleStatusResp25857, otaChrgMangDataResp);
|
||||
|
||||
logger.debug("ABRP: {}", execute);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Could not refresh car data.", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/addon.saicismart.error.refresh.car.data");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendACCommand(byte command, byte temperature)
|
||||
throws URISyntaxException, ExecutionException, InterruptedException, TimeoutException {
|
||||
MessageCoder<OTA_RVCReq> otaRvcReqMessageCoder = new MessageCoder<>(OTA_RVCReq.class);
|
||||
|
||||
// we send a command end expect the car to wake up
|
||||
notifyCarActivity(ZonedDateTime.now(getTimeZone()), false);
|
||||
|
||||
OTA_RVCReq req = new OTA_RVCReq();
|
||||
req.setRvcReqType(new byte[] { 6 });
|
||||
List<RvcReqParam> params = new ArrayList<>();
|
||||
req.setRvcParams(params);
|
||||
RvcReqParam param = new RvcReqParam();
|
||||
param.setParamId(19);
|
||||
param.setParamValue(new byte[] { command });
|
||||
params.add(param);
|
||||
param = new RvcReqParam();
|
||||
param.setParamId(20);
|
||||
param.setParamValue(new byte[] { temperature });
|
||||
params.add(param);
|
||||
param = new RvcReqParam();
|
||||
param.setParamId(255);
|
||||
param.setParamValue(new byte[] { 0 });
|
||||
params.add(param);
|
||||
|
||||
net.heberling.ismart.asn1.v2_1.Message<OTA_RVCReq> enableACRequest = otaRvcReqMessageCoder.initializeMessage(
|
||||
getBridgeHandler().getUid(), getBridgeHandler().getToken(), config.vin, "510", 25857, 1, req);
|
||||
|
||||
String enableACRequestMessage = otaRvcReqMessageCoder.encodeRequest(enableACRequest);
|
||||
|
||||
String enableACResponseMessage = getBridgeHandler().sendRequest(enableACRequestMessage, API_ENDPOINT_V21);
|
||||
|
||||
net.heberling.ismart.asn1.v2_1.Message<OTA_RVCStatus25857> enableACResponse = new net.heberling.ismart.asn1.v2_1.MessageCoder<>(
|
||||
OTA_RVCStatus25857.class).decodeResponse(enableACResponseMessage);
|
||||
|
||||
// ... use that to request the data again, until we have it
|
||||
while (enableACResponse.getApplicationData() == null) {
|
||||
if (enableACResponse.getBody().isErrorMessagePresent()) {
|
||||
if (enableACResponse.getBody().getResult() == 2) {
|
||||
getBridgeHandler().relogin();
|
||||
}
|
||||
throw new TimeoutException(new String(enableACResponse.getBody().getErrorMessage()));
|
||||
}
|
||||
|
||||
if (enableACResponse.getBody().getResult() == 0) {
|
||||
// we get an eventId back...
|
||||
enableACRequest.getBody().setEventID(enableACResponse.getBody().getEventID());
|
||||
} else {
|
||||
// try a fresh eventId
|
||||
enableACRequest.getBody().setEventID(0);
|
||||
}
|
||||
|
||||
enableACRequestMessage = otaRvcReqMessageCoder.encodeRequest(enableACRequest);
|
||||
|
||||
enableACResponseMessage = getBridgeHandler().sendRequest(enableACRequestMessage, API_ENDPOINT_V21);
|
||||
|
||||
enableACResponse = new net.heberling.ismart.asn1.v2_1.MessageCoder<>(OTA_RVCStatus25857.class)
|
||||
.decodeResponse(enableACResponseMessage);
|
||||
}
|
||||
|
||||
logger.trace("Got A/C message: {}", new GsonBuilder().setPrettyPrinting().create().toJson(enableACResponse));
|
||||
}
|
||||
|
||||
public void notifyCarActivity(ZonedDateTime now, boolean force) {
|
||||
// if the car activity changed, notify the channel
|
||||
if (force || lastCarActivity.isBefore(now)) {
|
||||
lastCarActivity = now;
|
||||
updateState(CHANNEL_LAST_ACTIVITY, new DateTimeType(lastCarActivity));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Future<?> job = pollingJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(String channelID, State state) {
|
||||
super.updateState(channelID, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(ThingStatus status) {
|
||||
super.updateStatus(status);
|
||||
}
|
||||
|
||||
public void handleMessage(Message message) {
|
||||
ZonedDateTime time = ZonedDateTime.ofInstant(Instant.ofEpochSecond(message.getMessageTime().getSeconds()),
|
||||
getTimeZone());
|
||||
|
||||
if (time.isAfter(lastAlarmMessage)) {
|
||||
lastAlarmMessage = time;
|
||||
updateState(SAICiSMARTBindingConstants.CHANNEL_ALARM_MESSAGE_CONTENT,
|
||||
new StringType(new String(message.getContent(), StandardCharsets.UTF_8)));
|
||||
updateState(SAICiSMARTBindingConstants.CHANNEL_ALARM_MESSAGE_DATE, new DateTimeType(time));
|
||||
}
|
||||
|
||||
notifyCarActivity(time, false);
|
||||
}
|
||||
|
||||
public ZoneId getTimeZone() {
|
||||
return timeZoneProvider.getTimeZone();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.saicismart", service = ThingHandlerFactory.class)
|
||||
public class SAICiSMARTHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_VEHICLE);
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public SAICiSMARTHandlerFactory(final @Reference TranslationProvider translationProvider,
|
||||
final @Reference LocaleProvider localeProvider, final @Reference HttpClientFactory httpClientFactory,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@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 SAICiSMARTBridgeHandler((Bridge) thing, httpClient);
|
||||
} else if (THING_TYPE_VEHICLE.equals(thingTypeUID)) {
|
||||
return new SAICiSMARTHandler(timeZoneProvider, thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link SAICiSMARTVehicleConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SAICiSMARTVehicleConfiguration {
|
||||
|
||||
public String vin = "";
|
||||
|
||||
@Nullable
|
||||
public String abrpUserToken;
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.THING_TYPE_VEHICLE;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
|
||||
import net.heberling.ismart.asn1.v1_1.entity.VinInfo;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
|
||||
|
||||
private @Nullable SAICiSMARTBridgeHandler handler;
|
||||
private static final String PROPERTY_VIN = "vin";
|
||||
|
||||
public VehicleDiscovery() throws IllegalArgumentException {
|
||||
super(Set.of(THING_TYPE_VEHICLE), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
Collection<VinInfo> vinList = handler.getVinList();
|
||||
for (VinInfo vinInfo : vinList) {
|
||||
ThingTypeUID type = THING_TYPE_VEHICLE;
|
||||
ThingUID thingUID = new ThingUID(type, handler.getThing().getUID(), vinInfo.getVin());
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
|
||||
.withLabel(new String(vinInfo.getBrandName()) + " " + new String(vinInfo.getModelName()))
|
||||
.withBridge(handler.getThing().getUID()).withProperty(PROPERTY_VIN, vinInfo.getVin())
|
||||
.withRepresentationProperty(PROPERTY_VIN).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
this.handler = (SAICiSMARTBridgeHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate(@Nullable Map<String, Object> configProperties) {
|
||||
super.activate(configProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal;
|
||||
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.API_ENDPOINT_V21;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_AUXILIARY_BATTERY_VOLTAGE;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_CHARGING;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_ENGINE;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_HEADING;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_LOCATION;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_ODOMETER;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_RANGE_ELECTRIC;
|
||||
import static org.openhab.binding.saicismart.internal.SAICiSMARTBindingConstants.CHANNEL_SPEED;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.saicismart.internal.exceptions.VehicleStatusAPIException;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import net.heberling.ismart.asn1.v2_1.Message;
|
||||
import net.heberling.ismart.asn1.v2_1.MessageCoder;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.OTA_RVMVehicleStatusReq;
|
||||
import net.heberling.ismart.asn1.v2_1.entity.OTA_RVMVehicleStatusResp25857;
|
||||
|
||||
/**
|
||||
* @author Markus Heberling - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class VehicleStateUpdater implements Callable<OTA_RVMVehicleStatusResp25857> {
|
||||
private final Logger logger = LoggerFactory.getLogger(VehicleStateUpdater.class);
|
||||
|
||||
private final SAICiSMARTHandler saiCiSMARTHandler;
|
||||
|
||||
public VehicleStateUpdater(SAICiSMARTHandler saiCiSMARTHandler) {
|
||||
this.saiCiSMARTHandler = saiCiSMARTHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OTA_RVMVehicleStatusResp25857 call() throws URISyntaxException, ExecutionException, InterruptedException,
|
||||
TimeoutException, VehicleStatusAPIException {
|
||||
MessageCoder<OTA_RVMVehicleStatusReq> otaRvmVehicleStatusRequstMessageCoder = new MessageCoder<>(
|
||||
OTA_RVMVehicleStatusReq.class);
|
||||
|
||||
OTA_RVMVehicleStatusReq otaRvmVehicleStatusReq = new OTA_RVMVehicleStatusReq();
|
||||
otaRvmVehicleStatusReq.setVehStatusReqType(1);
|
||||
|
||||
Message<OTA_RVMVehicleStatusReq> chargingStatusMessage = otaRvmVehicleStatusRequstMessageCoder
|
||||
.initializeMessage(saiCiSMARTHandler.getBridgeHandler().getUid(),
|
||||
saiCiSMARTHandler.getBridgeHandler().getToken(), saiCiSMARTHandler.config.vin, "511", 25857, 1,
|
||||
otaRvmVehicleStatusReq);
|
||||
|
||||
String chargingStatusRequestMessage = otaRvmVehicleStatusRequstMessageCoder
|
||||
.encodeRequest(chargingStatusMessage);
|
||||
|
||||
String chargingStatusResponse = saiCiSMARTHandler.getBridgeHandler().sendRequest(chargingStatusRequestMessage,
|
||||
API_ENDPOINT_V21);
|
||||
|
||||
Message<OTA_RVMVehicleStatusResp25857> chargingStatusResponseMessage = new MessageCoder<>(
|
||||
OTA_RVMVehicleStatusResp25857.class).decodeResponse(chargingStatusResponse);
|
||||
|
||||
// we get an eventId back...
|
||||
chargingStatusMessage.getBody().setEventID(chargingStatusResponseMessage.getBody().getEventID());
|
||||
// ... use that to request the data again, until we have it
|
||||
while (chargingStatusResponseMessage.getApplicationData() == null) {
|
||||
if (chargingStatusResponseMessage.getBody().getResult() != 0
|
||||
|| chargingStatusResponseMessage.getBody().isErrorMessagePresent()) {
|
||||
if (chargingStatusResponseMessage.getBody().getResult() == 2) {
|
||||
saiCiSMARTHandler.getBridgeHandler().relogin();
|
||||
}
|
||||
throw new VehicleStatusAPIException(chargingStatusResponseMessage.getBody());
|
||||
}
|
||||
|
||||
chargingStatusMessage.getBody().setUid(saiCiSMARTHandler.getBridgeHandler().getUid());
|
||||
chargingStatusMessage.getBody().setToken(saiCiSMARTHandler.getBridgeHandler().getToken());
|
||||
|
||||
chargingStatusRequestMessage = otaRvmVehicleStatusRequstMessageCoder.encodeRequest(chargingStatusMessage);
|
||||
|
||||
chargingStatusResponse = saiCiSMARTHandler.getBridgeHandler().sendRequest(chargingStatusRequestMessage,
|
||||
API_ENDPOINT_V21);
|
||||
|
||||
chargingStatusResponseMessage = new MessageCoder<>(OTA_RVMVehicleStatusResp25857.class)
|
||||
.decodeResponse(chargingStatusResponse);
|
||||
}
|
||||
|
||||
logger.trace("Got message: {}",
|
||||
new GsonBuilder().setPrettyPrinting().create().toJson(chargingStatusResponseMessage));
|
||||
|
||||
boolean engineRunning = chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus()
|
||||
.getEngineStatus() == 1;
|
||||
boolean isCharging = chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus()
|
||||
.isExtendedData2Present()
|
||||
&& chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getExtendedData2() >= 1;
|
||||
saiCiSMARTHandler.updateState(CHANNEL_ENGINE, OnOffType.from(engineRunning));
|
||||
saiCiSMARTHandler.updateState(CHANNEL_CHARGING, OnOffType.from(isCharging));
|
||||
|
||||
saiCiSMARTHandler.updateState(CHANNEL_AUXILIARY_BATTERY_VOLTAGE, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getBatteryVoltage() / 10.d,
|
||||
Units.VOLT));
|
||||
|
||||
saiCiSMARTHandler.updateState(CHANNEL_SPEED, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getGpsPosition().getWayPoint().getSpeed() / 10.d,
|
||||
SIUnits.KILOMETRE_PER_HOUR));
|
||||
saiCiSMARTHandler.updateState(CHANNEL_HEADING,
|
||||
new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getGpsPosition().getWayPoint().getHeading(),
|
||||
Units.DEGREE_ANGLE));
|
||||
saiCiSMARTHandler.updateState(CHANNEL_LOCATION,
|
||||
new PointType(
|
||||
new DecimalType(chargingStatusResponseMessage.getApplicationData().getGpsPosition()
|
||||
.getWayPoint().getPosition().getLatitude() / 1000000d),
|
||||
new DecimalType(chargingStatusResponseMessage.getApplicationData().getGpsPosition()
|
||||
.getWayPoint().getPosition().getLongitude() / 1000000d),
|
||||
new DecimalType(chargingStatusResponseMessage.getApplicationData().getGpsPosition()
|
||||
.getWayPoint().getPosition().getAltitude())));
|
||||
|
||||
saiCiSMARTHandler.updateState(CHANNEL_ODOMETER,
|
||||
new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getMileage() / 10.d,
|
||||
MetricPrefix.KILO(SIUnits.METRE)));
|
||||
saiCiSMARTHandler.updateState(CHANNEL_RANGE_ELECTRIC, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getFuelRangeElec() / 10.d,
|
||||
MetricPrefix.KILO(SIUnits.METRE)));
|
||||
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_TYRE_PRESSURE_FRONT_LEFT, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getFrontLeftTyrePressure()
|
||||
* 4 / 100.d,
|
||||
Units.BAR));
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_TYRE_PRESSURE_FRONT_RIGHT, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getFrontRrightTyrePressure()
|
||||
* 4 / 100.d,
|
||||
Units.BAR));
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_TYRE_PRESSURE_REAR_LEFT, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearLeftTyrePressure() * 4
|
||||
/ 100.d,
|
||||
Units.BAR));
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_TYRE_PRESSURE_REAR_RIGHT, new QuantityType<>(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearRightTyrePressure()
|
||||
* 4 / 100.d,
|
||||
Units.BAR));
|
||||
|
||||
Integer interiorTemperature = chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus()
|
||||
.getInteriorTemperature();
|
||||
if (interiorTemperature > -128) {
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_INTERIOR_TEMPERATURE,
|
||||
new QuantityType<>(interiorTemperature, SIUnits.CELSIUS));
|
||||
}
|
||||
Integer exteriorTemperature = chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus()
|
||||
.getExteriorTemperature();
|
||||
if (exteriorTemperature > -128) {
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_EXTERIOR_TEMPERATURE,
|
||||
new QuantityType<>(exteriorTemperature, SIUnits.CELSIUS));
|
||||
}
|
||||
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_DOOR_DRIVER,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getDriverDoor()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_DOOR_PASSENGER,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getPassengerDoor()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_DOOR_REAR_LEFT,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearLeftDoor()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_DOOR_REAR_RIGHT,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearRightDoor()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_WINDOW_DRIVER,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getDriverWindow()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_WINDOW_PASSENGER,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getPassengerWindow()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_WINDOW_REAR_LEFT,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearLeftWindow()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_WINDOW_REAR_RIGHT,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRearRightWindow()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_WINDOW_SUN_ROOF,
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getSunroofStatus()
|
||||
? OpenClosedType.OPEN
|
||||
: OpenClosedType.CLOSED);
|
||||
|
||||
boolean acActive = chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus()
|
||||
.getRemoteClimateStatus() > 0;
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_SWITCH_AC, OnOffType.from(acActive));
|
||||
saiCiSMARTHandler.updateState(SAICiSMARTBindingConstants.CHANNEL_REMOTE_AC_STATUS, new DecimalType(
|
||||
chargingStatusResponseMessage.getApplicationData().getBasicVehicleStatus().getRemoteClimateStatus()));
|
||||
|
||||
saiCiSMARTHandler
|
||||
.updateState(SAICiSMARTBindingConstants.CHANNEL_LAST_POSITION_UPDATE,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(
|
||||
Instant.ofEpochSecond(chargingStatusResponseMessage.getApplicationData()
|
||||
.getGpsPosition().getTimestamp4Short().getSeconds()),
|
||||
saiCiSMARTHandler.getTimeZone())));
|
||||
|
||||
if (isCharging || acActive || engineRunning) {
|
||||
// update activity date
|
||||
saiCiSMARTHandler.notifyCarActivity(ZonedDateTime.now(saiCiSMARTHandler.getTimeZone()), true);
|
||||
}
|
||||
|
||||
saiCiSMARTHandler.updateStatus(ThingStatus.ONLINE);
|
||||
return chargingStatusResponseMessage.getApplicationData();
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import net.heberling.ismart.asn1.v3_0.MP_DispatcherBody;
|
||||
|
||||
/**
|
||||
* @author Doug Culnane - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargingStatusAPIException extends Exception {
|
||||
|
||||
public ChargingStatusAPIException(MP_DispatcherBody body) {
|
||||
super("[" + body.getResult() + "] " + new String(body.getErrorMessage()));
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.saicismart.internal.exceptions;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import net.heberling.ismart.asn1.v2_1.MP_DispatcherBody;
|
||||
|
||||
/**
|
||||
* @author Doug Culnane - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class VehicleStatusAPIException extends Exception {
|
||||
|
||||
public VehicleStatusAPIException(MP_DispatcherBody body) {
|
||||
super("[" + body.getResult() + "] " + new String(body.getErrorMessage()));
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="saicismart" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
<type>binding</type>
|
||||
<name>SAICiSMART Binding</name>
|
||||
<description>This is the binding for SAIC (MG) iSMART Cars.</description>
|
||||
<connection>cloud</connection>
|
||||
</addon:addon>
|
@ -0,0 +1,18 @@
|
||||
<?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:saicismart:bridge">
|
||||
<parameter name="username" type="text" required="true">
|
||||
<label>Username</label>
|
||||
<description>iSMART Username</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<label>Password</label>
|
||||
<description>iSMART Password</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,17 @@
|
||||
<?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:saicismart:vehicle">
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>VIN</label>
|
||||
<description>Unique Vehicle Identification Number (VIN) given by SAIC</description>
|
||||
</parameter>
|
||||
<parameter name="abrpUserToken" type="text" required="false">
|
||||
<label>ABRP User Token</label>
|
||||
<description>User Token for A Better Routeplanner.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="saicismart"
|
||||
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>iSMART Account</label>
|
||||
<description>Your iSMART account data</description>
|
||||
<config-description-ref uri="thing-type:saicismart:bridge"/>
|
||||
</bridge-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="saicismart"
|
||||
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">
|
||||
|
||||
<!-- Sample Thing Type -->
|
||||
<thing-type id="vehicle">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="account"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>SAIC Car</label>
|
||||
<description>iSMART enabled car</description>
|
||||
<category>Car</category>
|
||||
|
||||
<channels>
|
||||
<channel id="odometer" typeId="odometer-channel"/>
|
||||
<channel id="range-electric" typeId="range-electric-channel"/>
|
||||
<channel id="soc" typeId="system.battery-level"/>
|
||||
<channel id="power" typeId="power-channel"/>
|
||||
<channel id="charging" typeId="charging-channel"/>
|
||||
<channel id="engine" typeId="engine-channel"/>
|
||||
<channel id="speed" typeId="speed-channel"/>
|
||||
<channel id="location" typeId="location-channel"/>
|
||||
<channel id="heading" typeId="heading-channel"/>
|
||||
<channel id="auxiliary-battery-voltage" typeId="auxiliary-battery-voltage-channel"/>
|
||||
<channel id="tyre-pressure-front-left" typeId="tyre-pressure-channel">
|
||||
<label>Pressure Front Left</label>
|
||||
</channel>
|
||||
<channel id="tyre-pressure-front-right" typeId="tyre-pressure-channel">
|
||||
<label>Pressure Front Right</label>
|
||||
</channel>
|
||||
<channel id="tyre-pressure-rear-left" typeId="tyre-pressure-channel">
|
||||
<label>Pressure Rear Left</label>
|
||||
</channel>
|
||||
<channel id="tyre-pressure-rear-right" typeId="tyre-pressure-channel">
|
||||
<label>Pressure Rear Right</label>
|
||||
</channel>
|
||||
<channel id="interior-temperature" typeId="temperature-channel">
|
||||
<label>Interior Temperature</label>
|
||||
</channel>
|
||||
<channel id="exterior-temperature" typeId="temperature-channel">
|
||||
<label>Exterior Temperature</label>
|
||||
</channel>
|
||||
<channel id="door-driver" typeId="door-channel">
|
||||
<label>Driver Door</label>
|
||||
</channel>
|
||||
<channel id="door-passenger" typeId="door-channel">
|
||||
<label>Passenger Door</label>
|
||||
</channel>
|
||||
<channel id="door-rear-left" typeId="door-channel">
|
||||
<label>Rear Left Door</label>
|
||||
</channel>
|
||||
<channel id="door-rear-right" typeId="door-channel">
|
||||
<label>Rear Right Door</label>
|
||||
</channel>
|
||||
<channel id="window-driver" typeId="window-channel">
|
||||
<label>Driver Window</label>
|
||||
</channel>
|
||||
<channel id="window-passenger" typeId="window-channel">
|
||||
<label>Passenger Window</label>
|
||||
</channel>
|
||||
<channel id="window-rear-left" typeId="window-channel">
|
||||
<label>Rear Left Window</label>
|
||||
</channel>
|
||||
<channel id="window-rear-right" typeId="window-channel">
|
||||
<label>Rear Right Window</label>
|
||||
</channel>
|
||||
<channel id="window-sun-roof" typeId="window-channel">
|
||||
<label>Sun Roof</label>
|
||||
</channel>
|
||||
<channel id="last-activity" typeId="timestamp-channel">
|
||||
<label>Last Car Activity</label>
|
||||
<description>Last time either the engine was on or the car was charging</description>
|
||||
</channel>
|
||||
<channel id="last-position-update" typeId="timestamp-channel">
|
||||
<label>Last Position Timestamp</label>
|
||||
<description>Last time the Position data was updated</description>
|
||||
</channel>
|
||||
<channel id="last-charge-state-update" typeId="timestamp-channel">
|
||||
<label>Last Charge State Timestamp</label>
|
||||
<description>Last time the Charge State data was updated</description>
|
||||
</channel>
|
||||
<channel id="remote-ac-status" typeId="remote-ac-status-channel"/>
|
||||
<channel id="switch-ac" typeId="switch-ac-channel"/>
|
||||
<channel id="force-refresh" typeId="force-refresh-channel"/>
|
||||
<channel id="last-alarm-message-date" typeId="timestamp-channel">
|
||||
<label>Last Alarm Message Timestamp</label>
|
||||
<description>Last time an alarm message was sent</description>
|
||||
</channel>
|
||||
<channel id="last-alarm-message-content" typeId="alarm-message-channel"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>vin</representation-property>
|
||||
|
||||
<config-description-ref uri="thing-type:saicismart:vehicle"/>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="odometer-channel">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Total Distance Driven</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="power-channel">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power Usage</label>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Power</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="charging-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Charging</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="engine-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Engine State</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="tyre-pressure-channel">
|
||||
<item-type>Number:Pressure</item-type>
|
||||
<label>Pressure</label>
|
||||
<category>Pressure</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Pressure</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="temperature-channel">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="speed-channel">
|
||||
<item-type>Number:Speed</item-type>
|
||||
<label>Speed</label>
|
||||
<description>Vehicle speed</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="location-channel">
|
||||
<item-type>Location</item-type>
|
||||
<label>Location</label>
|
||||
<description>The actual position of the vehicle</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="heading-channel">
|
||||
<item-type>Number:Angle</item-type>
|
||||
<label>Heading</label>
|
||||
<description>Indicates the (compass) heading of the car, in 0-360 degrees</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="auxiliary-battery-voltage-channel">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Auxiliary Battery Voltage</label>
|
||||
<description>Voltage (V) of the auxiliary battery</description>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f V" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="door-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Door</label>
|
||||
<description>Indicates if the door is opened</description>
|
||||
<category>door</category>
|
||||
<tags>
|
||||
<tag>OpenState</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="window-channel">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Window</label>
|
||||
<description>Indicates if the window is opened</description>
|
||||
<category>window</category>
|
||||
<tags>
|
||||
<tag>OpenState</tag>
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="timestamp-channel">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Timestamp</label>
|
||||
<description>The time of the event</description>
|
||||
<state readOnly="true" pattern="%1$tF %1$tR"/>
|
||||
</channel-type>
|
||||
<channel-type id="remote-ac-status-channel">
|
||||
<item-type>Number</item-type>
|
||||
<label>Remote A/C</label>
|
||||
<description>Status of remote A/C</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Off</option>
|
||||
<option value="5">On</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="switch-ac-channel">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch A/C</label>
|
||||
<description>Control the A/C remotely</description>
|
||||
</channel-type>
|
||||
<channel-type id="force-refresh-channel" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Force Refresh</label>
|
||||
<description>Force an immediate refresh of the car data</description>
|
||||
</channel-type>
|
||||
<channel-type id="alarm-message-channel">
|
||||
<item-type>String</item-type>
|
||||
<label>Vehicle Message</label>
|
||||
<description>Vehicle Message</description>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -338,6 +338,7 @@
|
||||
<module>org.openhab.binding.rotel</module>
|
||||
<module>org.openhab.binding.russound</module>
|
||||
<module>org.openhab.binding.sagercaster</module>
|
||||
<module>org.openhab.binding.saicismart</module>
|
||||
<module>org.openhab.binding.samsungtv</module>
|
||||
<module>org.openhab.binding.satel</module>
|
||||
<module>org.openhab.binding.semsportal</module>
|
||||
|
Loading…
Reference in New Issue
Block a user