mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[Easee] Initial contribution (#12954)
* initial binding version Signed-off-by: Alexander Friese <af944580@gmail.com>
This commit is contained in:
parent
dce6dc9f79
commit
1f1de41bd3
@ -376,6 +376,11 @@
|
||||
<artifactId>org.openhab.binding.dwdunwetter</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.easee</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.ecobee</artifactId>
|
||||
|
13
bundles/org.openhab.binding.easee/NOTICE
Normal file
13
bundles/org.openhab.binding.easee/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
|
178
bundles/org.openhab.binding.easee/README.md
Normal file
178
bundles/org.openhab.binding.easee/README.md
Normal file
@ -0,0 +1,178 @@
|
||||
# Easee Binding
|
||||
|
||||
The Easee binding can be used to retrieve data from the Easee Cloud API and also to control your wallbox via the Cloud API.
|
||||
This allows you to dynamically adjust the charge current for your car depending on production of your solar plant.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding provides three thing types:
|
||||
|
||||
| Thing/Bridge | Thing Type | Description |
|
||||
|---------------------|---------------------|-----------------------------------------------------------------------------------------------|
|
||||
| bridge | site | cloud connection to a site within an Easee account |
|
||||
| thing | charger | the physical charger which is connected to a circuit within the given site |
|
||||
| thing | mastercharger | like the "normal" charger but with additional capability to control the circuit |
|
||||
|
||||
|
||||
Basically any Easee wallbox that supports the Cloud API should automatically be supported by this binding.
|
||||
|
||||
## Discovery
|
||||
|
||||
Auto-discovery is supported and will discover all circuits and chargers assigned to a given site.
|
||||
|
||||
## Bridge Configuration
|
||||
|
||||
The following configuration parameters are available for the binding/bridge:
|
||||
|
||||
| Configuration Parameter | Required | Description |
|
||||
|-------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| username | yes | The username to login at Easee Cloud service. This should be an e-mail address or phone number. |
|
||||
| passord | yes | Your password to login at Easee Cloud service. |
|
||||
| siteId | yes | The ID of the site containing the wallbox(es) and circuit(s) that should be integrated into openHAB. The ID of your site can be found via the sites overview (https://easee.cloud/sites). You just need to click one of the sites listed there, the id will be part of the URL which is then opened. It will be a number with typically 6 digits. |
|
||||
| dataPollingInterval | no | Interval (seconds) in which live data values are retrieved from the Easee Cloud API. (default = 120) |
|
||||
|
||||
## Thing configuration
|
||||
|
||||
It is recommended to use auto discovery which does not require further configuration.
|
||||
If manual configuration is preferred you need to specify configuration as below.
|
||||
|
||||
### Charger
|
||||
|
||||
| Configuration Parameter | Required | Description |
|
||||
|-------------------------|----------|------------------------------------------------------------------------------------------------------------------------|
|
||||
| id | yes | The id of the charger that will be represented by this thing. |
|
||||
|
||||
### Mastercharger
|
||||
|
||||
| Configuration Parameter | Required | Description |
|
||||
|-------------------------|----------|------------------------------------------------------------------------------------------------------------------------|
|
||||
| id | yes | The id of the charger that will be represented by this thing. |
|
||||
| circuitId | yes | The id of the circuit that is controlled by this charger. |
|
||||
|
||||
## Channels
|
||||
|
||||
The binding only supports a subset of the available endpoints provided by the Easee Cloud API.
|
||||
The tables below show all available channels and which of them are writable.
|
||||
The settings that start with "dynamic" can be changed frequently, the others are written to flash and thus should not be changed too often as this could result in damage of your flash.
|
||||
|
||||
### Charger Channels
|
||||
|
||||
| Channel | Item Type | Writable | Description | Allowed Values (write access) |
|
||||
|---------------------------------------------|--------------------------|----------|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| state#smartCharging | Switch | no | | |
|
||||
| state#cableLocked | Switch | no | | |
|
||||
| state#chargerOpMode | Number | no | | |
|
||||
| state#totalPower | Number:Power | no | current session total power (all phases) | |
|
||||
| state#sessionEnergy | Number:Energy | no | current session | |
|
||||
| state#dynamicCircuitCurrentP1 | Number:ElectricCurrent | no | | |
|
||||
| state#dynamicCircuitCurrentP2 | Number:ElectricCurrent | no | | |
|
||||
| state#dynamicCircuitCurrentP3 | Number:ElectricCurrent | no | | |
|
||||
| state#latestPulse | DateTime | no | | |
|
||||
| state#chargerFirmware | Number | no | | |
|
||||
| state#latestFirmware | Number | no | | |
|
||||
| state#voltage | Number:ElectricPotential | no | | |
|
||||
| state#outputCurrent | Number:ElectricCurrent | no | | |
|
||||
| state#isOnline | Switch | no | | |
|
||||
| state#dynamicChargerCurrent | Number:ElectricCurrent | yes | | 0, 6-32 |
|
||||
| state#reasonForNoCurrent | Number | no | | |
|
||||
| state#lifetimeEnergy | Number:Energy | no | | |
|
||||
| state#errorCode | Number | no | | |
|
||||
| state#fatalErrorCode | Number | no | | |
|
||||
| config#lockCablePermanently | Switch | yes | | true/false |
|
||||
| config#authorizationRequired | Switch | yes | | true/false |
|
||||
| config#limitToSinglePhaseCharging | Switch | yes | | true/false |
|
||||
| config#phaseMode | Number | yes | 1=1phase, 2=auto, 3=3phase | 1-3 |
|
||||
| config#maxChargerCurrent | Number:ElectricCurrent | no | write access not yet implemented | |
|
||||
| commands#genericCommand | String | yes | Generic Endpoint to send commands | reboot, update_firmware, poll_all, smart_charging, start_charging, stop_charging, pause_charging, resume_charging, toggle_charging, override_schedule |
|
||||
| commands#startStop | Switch | yes | Start/Stop Charing, only works with authorization | |
|
||||
| commands#pauseResume | Switch | yes | Pause/Resume Charing | |
|
||||
| latestSession#sessionEnergy | Number:Energy | no | latest (already ended) session | |
|
||||
| latestSession#sessionStart | DateTime | no | | |
|
||||
| latestSession#sessionEnd | DateTime | no | | |
|
||||
|
||||
### Master Charger Channels
|
||||
|
||||
The Master Charger is like the "normal" charger but has some extra channels to control the circuit. These additional channels are listed in the table below.
|
||||
|
||||
| Channel | Item Type | Writable | Description | Allowed Values (write access) |
|
||||
|---------------------------------------------|--------------------------|----------|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| dynamicCurrent#phase1 | Number:ElectricCurrent | no | | |
|
||||
| dynamicCurrent#phase2 | Number:ElectricCurrent | no | | |
|
||||
| dynamicCurrent#phase3 | Number:ElectricCurrent | no | | |
|
||||
| dynamicCurrent#dynamicCurrents | String | yes | read/write only for all phases. | <value phase1>;<value phase2>;<value phase3> valid values for each phase are 0, 6-32 |
|
||||
| settings#maxCircuitCurrentP1 | Number:ElectricCurrent | no | | |
|
||||
| settings#maxCircuitCurrentP2 | Number:ElectricCurrent | no | | |
|
||||
| settings#maxCircuitCurrentP3 | Number:ElectricCurrent | no | | |
|
||||
| settings#maxCurrents | String | yes | read/write only for all phases. | <value phase1>;<value phase2>;<value phase3> valid values for each phase are 0, 6-32 |
|
||||
| settings#offlineMaxCircuitCurrentP1 | Number:ElectricCurrent | no | | |
|
||||
| settings#offlineMaxCircuitCurrentP2 | Number:ElectricCurrent | no | | |
|
||||
| settings#offlineMaxCircuitCurrentP3 | Number:ElectricCurrent | no | | |
|
||||
| settings#offlineMaxCurrents | String | yes | read/write only for all phases. | <value phase1>;<value phase2>;<value phase3> valid values for each phase are 0, 6-32 |
|
||||
| settings#enableIdleCurrent | Switch | yes | | true/false |
|
||||
| settings#allowOfflineMaxCircuitCurrent | Switch | no | | |
|
||||
|
||||
## Full Example
|
||||
|
||||
### Thing
|
||||
|
||||
#### Minimum configuration
|
||||
|
||||
```
|
||||
Bridge easee:site:mysite1 [ username="abc@def.net", password="secret", siteId="123456" ]
|
||||
```
|
||||
|
||||
#### Manual configuration with two chargers, pollingInterval set to 60 seconds.
|
||||
|
||||
```
|
||||
Bridge easee:site:mysite1 [ username="abc@def.net", password="secret", siteId="471111", dataPollingInterval=60 ] {
|
||||
Thing mastercharger myCharger1 [ id="EHXXXXX1", circuitId="1234567" ]
|
||||
Thing charger myCharger2 [ id="EHXXXXX2" ]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Items
|
||||
|
||||
```
|
||||
Number:ElectricCurrent Easee_Circuit_Phase1 "Phase 1" { channel="easee:mastercharger:mysite1:myCharger1:dynamicCurrent#phase1" }
|
||||
Number:ElectricCurrent Easee_Circuit_Phase2 "Phase 2" { channel="easee:mastercharger:mysite1:myCharger1:dynamicCurrent#phase2" }
|
||||
Number:ElectricCurrent Easee_Circuit_Phase3 "Phase 3" { channel="easee:mastercharger:mysite1:myCharger1:dynamicCurrent#phase3" }
|
||||
String Easee_Circuit_Dynamic_Phases "Dynamic Power [MAP(easeePhases.map):%s]" { channel="easee:mastercharger:mysite1:myCharger1:dynamicCurrent#setDynamicCurrents" }
|
||||
Switch Easee_Charger_Start_Stop "Start / Stop" { channel="easee:mastercharger:mysite1:myCharger1:commands#startStop" }
|
||||
```
|
||||
|
||||
### Sitemap
|
||||
|
||||
```
|
||||
Switch item=Easee_Circuit_Dynamic_Phases mappings=["0;0;0"="0.00 kW", "6;0;0"="1.44 kW", "7;0;0"="1.68 kW", "8;0;0"="1.92 kW", "9;0;0"="2.16 kW", "10;0;0"="2.40 kW", "16;0;0"="3.72 kW", "16;16;16"="11.1 kW"] icon="energy"
|
||||
```
|
||||
|
||||
### Mapping
|
||||
|
||||
easeePhases.map will make the phase setting more readable.
|
||||
|
||||
```
|
||||
0;0;0=0.00 kW
|
||||
6;0;0=1.44 kW
|
||||
7;0;0=1.68 kW
|
||||
8;0;0=1.92 kW
|
||||
9;0;0=2.16 kW
|
||||
10;0;0=2.40 kW
|
||||
11;0;0=2.64 kW
|
||||
12;0;0=2.88 kW
|
||||
13;0;0=3.12 kW
|
||||
14;0;0=3.36 kW
|
||||
15;0;0=3.60 kW
|
||||
16;0;0=3.72 kW
|
||||
6;6;6=4.32 kW
|
||||
7;7;7=5.04 kW
|
||||
8;8;8=5.76 kW
|
||||
9;9;9=6.48 kW
|
||||
10;10;10=7.20 kW
|
||||
11;11;11=7.92 kW
|
||||
12;12;12=8.64 kW
|
||||
13;13;13=9.36 kW
|
||||
14;14;14=10.1 kW
|
||||
15;15;15=10.8 kW
|
||||
16;16;16=11.1 kW
|
||||
```
|
17
bundles/org.openhab.binding.easee/pom.xml
Normal file
17
bundles/org.openhab.binding.easee/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
|
||||
<artifactId>org.openhab.binding.easee</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Easee Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.easee-${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-easee" description="Easee Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.easee/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.easee.internal;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* trait class which contains useful helper methods. Thus, the interface can be implemented and methods are available
|
||||
* within the class.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AtomicReferenceTrait {
|
||||
|
||||
/**
|
||||
* this should usually not called directly. use updateJobReference or cancelJobReference instead
|
||||
*
|
||||
* @param job job to cancel.
|
||||
*/
|
||||
default void cancelJob(@Nullable Future<?> job) {
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a job reference with a new job. the old job will be cancelled if there is one.
|
||||
*
|
||||
* @param jobReference reference to be updated
|
||||
* @param newJob job to be assigned
|
||||
*/
|
||||
default void updateJobReference(AtomicReference<@Nullable Future<?>> jobReference, Future<?> newJob) {
|
||||
cancelJob(jobReference.getAndSet(newJob));
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a job reference to null and cancels any existing job which might be assigned to the reference.
|
||||
*
|
||||
* @param jobReference to be updated to null.
|
||||
*/
|
||||
default void cancelJobReference(AtomicReference<@Nullable Future<?>> jobReference) {
|
||||
cancelJob(jobReference.getAndSet(null));
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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.easee.internal;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link EaseeBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Alexander Friese - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "easee";
|
||||
|
||||
// List of main device types
|
||||
public static final String DEVICE_SITE = "site";
|
||||
public static final String DEVICE_MASTER_CHARGER = "mastercharger";
|
||||
public static final String DEVICE_CHARGER = "charger";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_SITE = new ThingTypeUID(BINDING_ID, DEVICE_SITE);
|
||||
public static final ThingTypeUID THING_TYPE_MASTER_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_MASTER_CHARGER);
|
||||
public static final ThingTypeUID THING_TYPE_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_CHARGER);
|
||||
|
||||
// List of all channel groups
|
||||
public static final String CHANNEL_GROUP_NONE = "";
|
||||
public static final String CHANNEL_GROUP_SITE_INFO = "info";
|
||||
public static final String CHANNEL_GROUP_CHARGER = "charger";
|
||||
public static final String CHANNEL_GROUP_CHARGER_STATE = "state";
|
||||
public static final String CHANNEL_GROUP_CHARGER_CONFIG = "config";
|
||||
public static final String CHANNEL_GROUP_CHARGER_COMMANDS = "commands";
|
||||
public static final String CHANNEL_GROUP_CHARGER_LATEST_SESSION = "latestSession";
|
||||
public static final String CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT = "dynamicCurrent";
|
||||
public static final String CHANNEL_GROUP_CIRCUIT_SETTINGS = "settings";
|
||||
|
||||
// Channel types
|
||||
public static final String CHANNEL_TYPE_SWITCH = "Switch";
|
||||
public static final String CHANNEL_TYPE_VOLT = "Number:ElectricPotential";
|
||||
public static final String CHANNEL_TYPE_AMPERE = "Number:ElectricCurrent";
|
||||
public static final String CHANNEL_TYPE_KWH = "Number:Energy";
|
||||
public static final String CHANNEL_TYPE_KW = "Number:Power";
|
||||
public static final String CHANNEL_TYPE_DATE = "DateTime";
|
||||
public static final String CHANNEL_TYPE_STRING = "String";
|
||||
public static final String CHANNEL_TYPE_NUMBER = "Number";
|
||||
|
||||
public static final String CHANNEL_TYPEPREFIX_RW = "rw";
|
||||
|
||||
public static final String CHANNEL_TYPENAME_INTEGER = "type-integer";
|
||||
|
||||
// Channels with specific handling
|
||||
public static final String CHANNEL_CHARGER_OP_MODE = "chargerOpMode";
|
||||
public static final String CHANNEL_CHARGER_DYNAMIC_CURRENT = "dynamicChargerCurrent";
|
||||
public static final String CHANNEL_CHARGER_REASON_FOR_NO_CURRENT = "reasonForNoCurrent";
|
||||
public static final String CHANNEL_CHARGER_START_STOP = "startStop";
|
||||
public static final String CHANNEL_CHARGER_PAUSE_RESUME = "pauseResume";
|
||||
public static final String CHANNEL_CIRCUIT_DYNAMIC_CURRENTS = "dynamicCurrents";
|
||||
public static final String CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE1 = "phase1";
|
||||
public static final String CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE2 = "phase2";
|
||||
public static final String CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE3 = "phase3";
|
||||
public static final String CHANNEL_CIRCUIT_MAX_CURRENTS = "maxCurrents";
|
||||
public static final String CHANNEL_CIRCUIT_MAX_CURRENT_PHASE1 = "maxCircuitCurrentP1";
|
||||
public static final String CHANNEL_CIRCUIT_MAX_CURRENT_PHASE2 = "maxCircuitCurrentP2";
|
||||
public static final String CHANNEL_CIRCUIT_MAX_CURRENT_PHASE3 = "maxCircuitCurrentP3";
|
||||
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENTS = "offlineMaxCurrents";
|
||||
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE1 = "offlineMaxCircuitCurrentP1";
|
||||
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE2 = "offlineMaxCircuitCurrentP2";
|
||||
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE3 = "offlineMaxCircuitCurrentP3";
|
||||
|
||||
// JSON Keys
|
||||
public static final String JSON_KEY_GENERIC_ID = "id";
|
||||
public static final String JSON_KEY_GENERIC_NAME = "name";
|
||||
public static final String JSON_KEY_CIRCUIT_NAME = "panelName";
|
||||
public static final String JSON_KEY_CIRCUIT_ID = "circuitId";
|
||||
public static final String JSON_KEY_CIRCUITS = "circuits";
|
||||
public static final String JSON_KEY_CHARGERS = "chargers";
|
||||
public static final String JSON_KEY_BACK_PLATE = "backPlate";
|
||||
public static final String JSON_KEY_MASTER_BACK_PLATE = "masterBackPlate";
|
||||
public static final String JSON_KEY_MASTER_BACK_PLATE_ID = "masterBackPlateId";
|
||||
public static final String JSON_KEY_ONLINE = "isOnline";
|
||||
public static final String JSON_KEY_SITE_KEY = "siteKey";
|
||||
public static final String JSON_KEY_ERROR_TITLE = "title";
|
||||
public static final String JSON_KEY_AUTH_ACCESS_TOKEN = "accessToken";
|
||||
public static final String JSON_KEY_AUTH_REFRESH_TOKEN = "refreshToken";
|
||||
public static final String JSON_KEY_AUTH_EXPIRES_IN = "expiresIn";
|
||||
|
||||
// Write Commands
|
||||
public static final String COMMAND_CHANGE_CONFIGURATION = "ChangeConfiguration";
|
||||
public static final String COMMAND_SEND_COMMAND = "SendCommand";
|
||||
public static final String COMMAND_SEND_COMMAND_START_STOP = "SendCommandStartStop";
|
||||
public static final String COMMAND_SET_CIRCUIT_SETTINGS = "SetCircuitSettings";
|
||||
public static final String COMMAND_SET_DYNAMIC_CIRCUIT_CURRENTS = "SetDynamicCircuitCurrents";
|
||||
public static final String COMMAND_SET_MAX_CIRCUIT_CURRENTS = "SetMaxCircuitCurrents";
|
||||
public static final String COMMAND_SET_OFFLINE_MAX_CIRCUIT_CURRENTS = "SetOfflineMaxCircuitCurrents";
|
||||
|
||||
// Command Values
|
||||
public static final String CMD_VAL_START_CHARGING = "start_charging";
|
||||
public static final String CMD_VAL_STOP_CHARGING = "stop_charging";
|
||||
public static final String CMD_VAL_PAUSE_CHARGING = "pause_charging";
|
||||
public static final String CMD_VAL_RESUME_CHARGING = "resume_charging";
|
||||
|
||||
// web request constants
|
||||
public static final long WEB_REQUEST_INITIAL_DELAY = 30;
|
||||
public static final long WEB_REQUEST_INTERVAL = 5;
|
||||
public static final int WEB_REQUEST_QUEUE_MAX_SIZE = 20;
|
||||
public static final int WEB_REQUEST_TOKEN_EXPIRY_BUFFER_MINUTES = 5;
|
||||
public static final int WEB_REQUEST_TOKEN_MAX_AGE_MINUTES = 60;
|
||||
public static final String WEB_REQUEST_BEARER_TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
// URLs
|
||||
public static final String LOGIN_URL = "https://api.easee.cloud/api/accounts/login";
|
||||
public static final String REFRESH_TOKEN_URL = "https://api.easee.cloud/api/accounts/refresh_token";
|
||||
public static final String GET_SITE_URL = "https://api.easee.cloud/api/sites/{siteId}";
|
||||
public static final String CHARGER_URL = "https://api.easee.cloud/api/chargers/{id}";
|
||||
public static final String STATE_URL = "https://api.easee.cloud/api/chargers/{id}/state";
|
||||
public static final String GET_CONFIGURATION_URL = "https://api.easee.cloud/api/chargers/{id}/config";
|
||||
public static final String CHANGE_CONFIGURATION_URL = "https://api.easee.cloud/api/chargers/{id}/settings";
|
||||
public static final String COMMANDS_URL = "https://api.easee.cloud/api/chargers/{id}/commands/{command}";
|
||||
public static final String LATEST_CHARGING_SESSION_URL = "https://api.easee.cloud/api/chargers/{id}/sessions/latest";
|
||||
public static final String DYNAMIC_CIRCUIT_CURRENT_URL = "https://api.easee.cloud/api/sites/{siteId}/circuits/{circuitId}/dynamicCurrent";
|
||||
public static final String CIRCUIT_SETTINGS_URL = "https://api.easee.cloud/api/sites/{siteId}/circuits/{circuitId}/settings";
|
||||
|
||||
// Status Keys
|
||||
public static final String STATUS_TOKEN_VALIDATED = "@text/status.token.validated";
|
||||
public static final String STATUS_WAITING_FOR_BRIDGE = "@text/status.waiting.for.bridge";
|
||||
public static final String STATUS_WAITING_FOR_LOGIN = "@text/status.waiting.for.login";
|
||||
public static final String STATUS_NO_VALID_DATA = "@text/status.no.valid.data";
|
||||
public static final String STATUS_NO_CONNECTION = "@text/status.no.connection";
|
||||
|
||||
// other
|
||||
public static final long POLLING_INITIAL_DELAY = 1;
|
||||
|
||||
public static final String GENERIC_YES = "Yes";
|
||||
public static final String GENERIC_NO = "No";
|
||||
public static final int CHARGER_OP_STATE_WAITING = 2;
|
||||
public static final int CHARGER_OP_STATE_CHARGING = 3;
|
||||
public static final int CHARGER_DYNAMIC_CURRENT_PAUSE = 0;
|
||||
public static final int CHARGER_REASON_FOR_NO_CURRENT_PAUSED = 52;
|
||||
|
||||
public static final String THING_CONFIG_ID = "id";
|
||||
public static final String THING_CONFIG_SITE_ID = "siteId";
|
||||
public static final String THING_CONFIG_CIRCUIT_ID = "circuitId";
|
||||
public static final String THING_CONFIG_CIRCUIT_NAME = "circuitName";
|
||||
public static final String THING_CONFIG_IS_MASTER = "isMaster";
|
||||
public static final String THING_CONFIG_BACK_PLATE_ID = "backPlateId";
|
||||
public static final String THING_CONFIG_MASTER_BACK_PLATE_ID = "masterBackPlateId";
|
||||
|
||||
public static final Instant OUTDATED_DATE = Instant.MIN;
|
||||
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String PARAMETER_NAME_WRITE_COMMAND = "writeCommand";
|
||||
public static final String PARAMETER_NAME_VALIDATION_REGEXP = "validationExpression";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SITE,
|
||||
THING_TYPE_MASTER_CHARGER, THING_TYPE_CHARGER);
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.easee.internal;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeChargerHandler;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeMasterChargerHandler;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
|
||||
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;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link EaseeHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Alexander Friese - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.easee", service = ThingHandlerFactory.class)
|
||||
public class EaseeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EaseeHandlerFactory.class);
|
||||
|
||||
/**
|
||||
* the shared http client
|
||||
*/
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public EaseeHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@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_SITE.equals(thingTypeUID)) {
|
||||
return new EaseeSiteHandler((Bridge) thing, httpClient);
|
||||
} else if (THING_TYPE_MASTER_CHARGER.equals(thingTypeUID)) {
|
||||
return new EaseeMasterChargerHandler(thing);
|
||||
} else if (THING_TYPE_CHARGER.equals(thingTypeUID)) {
|
||||
return new EaseeChargerHandler(thing);
|
||||
} else {
|
||||
logger.warn("Unsupported Thing-Type: {}", thingTypeUID.getAsString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/**
|
||||
* 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.easee.internal;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.easee.internal.model.ConfigurationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonNull;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* some helper methods.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Utils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
/**
|
||||
* only static methods no instance needed
|
||||
*/
|
||||
private Utils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* parses a date string in easee format to ZonedDateTime which is used by Openhab.
|
||||
*
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
public static ZonedDateTime parseDate(String date) throws DateTimeParseException {
|
||||
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX");
|
||||
LOGGER.trace("parsing: {}", date);
|
||||
ZonedDateTime zdt = ZonedDateTime.parse(date, formatter);
|
||||
return zdt;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a date in a readable format
|
||||
*
|
||||
* @param date
|
||||
* @return
|
||||
*/
|
||||
public static String formatDate(Instant date) {
|
||||
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
.withZone(ZoneId.systemDefault());
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* get element as JsonObject.
|
||||
*
|
||||
* @param jsonObject
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static @Nullable JsonObject getAsJsonObject(@Nullable JsonObject jsonObject, String key) {
|
||||
JsonElement element = jsonObject == null ? null : jsonObject.get(key);
|
||||
return (element instanceof JsonObject) ? element.getAsJsonObject() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get element as String.
|
||||
*
|
||||
* @param jsonObject
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static @Nullable String getAsString(@Nullable JsonObject jsonObject, String key) {
|
||||
JsonElement element = jsonObject == null ? null : jsonObject.get(key);
|
||||
String text = null;
|
||||
if (element != null) {
|
||||
if (element instanceof JsonPrimitive) {
|
||||
text = element.getAsString();
|
||||
} else if (element instanceof JsonObject || element instanceof JsonArray) {
|
||||
text = element.toString();
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* get element as int.
|
||||
*
|
||||
* @param jsonObject
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static int getAsInt(@Nullable JsonObject jsonObject, String key) {
|
||||
JsonElement element = jsonObject == null ? null : jsonObject.get(key);
|
||||
return (element instanceof JsonPrimitive) ? element.getAsInt() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* get element as boolean.
|
||||
*
|
||||
* @param jsonObject
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static @Nullable Boolean getAsBool(@Nullable JsonObject jsonObject, String key) {
|
||||
JsonElement json = jsonObject == null ? null : jsonObject.get(key);
|
||||
return (json == null || json instanceof JsonNull) ? null : json.getAsBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieves typeID of a channel.
|
||||
*
|
||||
* @param channel
|
||||
* @return typeID or empty string if typeUID is null.
|
||||
*/
|
||||
public static String getChannelTypeId(Channel channel) {
|
||||
ChannelTypeUID typeUID = channel.getChannelTypeUID();
|
||||
if (typeUID == null) {
|
||||
return "";
|
||||
}
|
||||
return typeUID.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieves the validation expression which is assigned to this channel, fallback to a public static, if no
|
||||
* validation
|
||||
* is
|
||||
* defined.
|
||||
*
|
||||
* @param channel
|
||||
* @return the validation expression
|
||||
*/
|
||||
public static String getValidationExpression(Channel channel) {
|
||||
String expr = getPropertyOrParameter(channel, PARAMETER_NAME_VALIDATION_REGEXP);
|
||||
if (expr == null) {
|
||||
throw new ConfigurationException(
|
||||
"channel (" + channel.getUID().getId() + ") does not have a validation expression configured");
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieves the write API url suffix which is assigned to this channel.
|
||||
*
|
||||
* @param channel
|
||||
* @return the url suffix
|
||||
*/
|
||||
public static String getWriteCommand(Channel channel) {
|
||||
String command = getPropertyOrParameter(channel, PARAMETER_NAME_WRITE_COMMAND);
|
||||
if (command == null) {
|
||||
throw new ConfigurationException(
|
||||
"channel (" + channel.getUID().getId() + ") does not have a write command configured");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* internal utiliy method which returns a property (if found) or a config parameter (if found) otherwise null
|
||||
*
|
||||
* @param channel
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
public static @Nullable String getPropertyOrParameter(Channel channel, String name) {
|
||||
String value = channel.getProperties().get(name);
|
||||
// also eclipse says this cannot be null, it definitely can!
|
||||
if (value == null || value.isEmpty()) {
|
||||
Object obj = channel.getConfiguration().get(name);
|
||||
value = obj == null ? null : obj.toString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
/**
|
||||
* 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.easee.internal.command;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.WEB_REQUEST_BEARER_TOKEN_PREFIX;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpStatus.Code;
|
||||
import org.openhab.binding.easee.internal.EaseeBindingConstants;
|
||||
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.GenericResponseTransformer;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.ToNumberPolicy;
|
||||
|
||||
/**
|
||||
* base class for all commands. common logic should be implemented here
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractCommand extends BufferingResponseListener implements EaseeCommand {
|
||||
|
||||
public static enum RetryOnFailure {
|
||||
YES,
|
||||
NO;
|
||||
}
|
||||
|
||||
public static enum ProcessFailureResponse {
|
||||
YES,
|
||||
NO;
|
||||
}
|
||||
|
||||
/**
|
||||
* logger
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractCommand.class);
|
||||
|
||||
/**
|
||||
* the configuration
|
||||
*/
|
||||
protected final EaseeThingHandler handler;
|
||||
|
||||
/**
|
||||
* JSON deserializer
|
||||
*/
|
||||
protected final Gson gson;
|
||||
|
||||
/**
|
||||
* status code of fulfilled request
|
||||
*/
|
||||
private final CommunicationStatus communicationStatus;
|
||||
|
||||
/**
|
||||
* generic transformer which just transfers all values in a plain map.
|
||||
*/
|
||||
private final GenericResponseTransformer transformer;
|
||||
|
||||
/**
|
||||
* retry counter.
|
||||
*/
|
||||
private int retries = 0;
|
||||
|
||||
/**
|
||||
* retry active
|
||||
*/
|
||||
private final RetryOnFailure retryOnFailure;
|
||||
|
||||
/**
|
||||
* process error response, e.g. set handler offline on error
|
||||
*/
|
||||
private final ProcessFailureResponse processFailureResponse;
|
||||
|
||||
/**
|
||||
* allows further processing of the json result data, if set.
|
||||
*/
|
||||
private List<JsonResultProcessor> resultProcessors;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*/
|
||||
public AbstractCommand(EaseeThingHandler handler, RetryOnFailure retryOnFailure,
|
||||
ProcessFailureResponse processFailureResponse) {
|
||||
this.gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create();
|
||||
this.communicationStatus = new CommunicationStatus();
|
||||
this.resultProcessors = new ArrayList<>();
|
||||
this.transformer = new GenericResponseTransformer(handler);
|
||||
this.handler = handler;
|
||||
this.processFailureResponse = processFailureResponse;
|
||||
this.retryOnFailure = retryOnFailure;
|
||||
}
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*/
|
||||
public AbstractCommand(EaseeThingHandler handler, RetryOnFailure retryOnFailure,
|
||||
ProcessFailureResponse processFailureResponse, JsonResultProcessor resultProcessor) {
|
||||
this(handler, retryOnFailure, processFailureResponse);
|
||||
this.resultProcessors.add(resultProcessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log request success
|
||||
*/
|
||||
@Override
|
||||
public final void onSuccess(@Nullable Response response) {
|
||||
super.onSuccess(response);
|
||||
if (response != null) {
|
||||
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
|
||||
logger.debug("HTTP response {}", response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log request failure
|
||||
*/
|
||||
@Override
|
||||
public final void onFailure(@Nullable Response response, @Nullable Throwable failure) {
|
||||
super.onFailure(response, failure);
|
||||
if (failure != null) {
|
||||
logger.info("Request failed: {}", failure.toString());
|
||||
communicationStatus.setError((Exception) failure);
|
||||
if (failure instanceof SocketTimeoutException || failure instanceof TimeoutException) {
|
||||
communicationStatus.setHttpCode(Code.REQUEST_TIMEOUT);
|
||||
} else if (failure instanceof UnknownHostException) {
|
||||
communicationStatus.setHttpCode(Code.BAD_GATEWAY);
|
||||
} else {
|
||||
communicationStatus.setHttpCode(Code.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
logger.info("Request failed");
|
||||
}
|
||||
if (response != null && response.getStatus() > 0) {
|
||||
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* just for logging of content
|
||||
*/
|
||||
@Override
|
||||
public void onContent(@Nullable Response response, @Nullable ByteBuffer content) {
|
||||
super.onContent(response, content);
|
||||
logger.debug("received content, length: {}", getContentAsString().length());
|
||||
}
|
||||
|
||||
/**
|
||||
* default handling of successful requests.
|
||||
*/
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
logger.debug("JSON String: {}", json);
|
||||
switch (getCommunicationStatus().getHttpCode()) {
|
||||
case OK:
|
||||
case ACCEPTED:
|
||||
onCompleteCodeOk(json);
|
||||
break;
|
||||
default:
|
||||
onCompleteCodeDefault(json);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handling of result in case of HTTP response OK.
|
||||
*
|
||||
* @param json
|
||||
*/
|
||||
protected void onCompleteCodeOk(@Nullable String json) {
|
||||
JsonObject jsonObject = transform(json);
|
||||
if (jsonObject != null) {
|
||||
logger.debug("success");
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
|
||||
processResult(jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handling of result in default case, this means error handling of http codes where no specific handling applies.
|
||||
*
|
||||
* @param json
|
||||
*/
|
||||
protected void onCompleteCodeDefault(@Nullable String json) {
|
||||
JsonObject jsonObject = transform(json);
|
||||
if (jsonObject == null) {
|
||||
jsonObject = new JsonObject();
|
||||
}
|
||||
if (processFailureResponse == ProcessFailureResponse.YES) {
|
||||
processResult(jsonObject);
|
||||
} else {
|
||||
logger.info("command failed, url: {} - code: {} - result: {}", getURL(),
|
||||
getCommunicationStatus().getHttpCode(), jsonObject.get(EaseeBindingConstants.JSON_KEY_ERROR_TITLE));
|
||||
}
|
||||
|
||||
if (retryOnFailure == RetryOnFailure.YES && retries++ < MAX_RETRIES) {
|
||||
handler.enqueueCommand(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* error safe json transformer.
|
||||
*
|
||||
* @param json
|
||||
* @return
|
||||
*/
|
||||
private @Nullable JsonObject transform(@Nullable String json) {
|
||||
if (json != null) {
|
||||
try {
|
||||
return gson.fromJson(json, JsonObject.class);
|
||||
} catch (Exception ex) {
|
||||
logger.debug("JSON could not be parsed: {}\nError: {}", json, ex.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* preparation of the request. will call a hook (prepareRequest) that has to be implemented in the subclass to add
|
||||
* content to the request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Override
|
||||
public void performAction(HttpClient asyncclient, String accessToken) throws ValidationException {
|
||||
Request request = asyncclient.newRequest(getURL()).timeout(handler.getBridgeConfiguration().getAsyncTimeout(),
|
||||
TimeUnit.SECONDS);
|
||||
|
||||
// we want to send and receive json only, so explicitely set this!
|
||||
request.header(HttpHeader.ACCEPT, "application/json");
|
||||
request.header(HttpHeader.CONTENT_TYPE, "application/json");
|
||||
|
||||
// this should be the default for Easee Cloud API
|
||||
request.followRedirects(false);
|
||||
|
||||
// add authentication data for every request. Handling this here makes it obsolete to implement for each and
|
||||
// every command
|
||||
if (!accessToken.isBlank()) {
|
||||
request.header(HttpHeader.AUTHORIZATION, WEB_REQUEST_BEARER_TOKEN_PREFIX + accessToken);
|
||||
}
|
||||
|
||||
prepareRequest(request).send(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return returns Http Status Code
|
||||
*/
|
||||
public CommunicationStatus getCommunicationStatus() {
|
||||
return communicationStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls the registered resultPRocessors.
|
||||
*
|
||||
* @param jsonObject
|
||||
*/
|
||||
protected final void processResult(JsonObject jsonObject) {
|
||||
for (JsonResultProcessor processor : resultProcessors) {
|
||||
try {
|
||||
processor.processResult(getCommunicationStatus(), jsonObject);
|
||||
} catch (Exception ex) {
|
||||
// this should not happen
|
||||
logger.warn("Exception caught: {}", ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* concrete implementation has to prepare the requests with additional parameters, etc
|
||||
*
|
||||
* @param requestToPrepare the request to prepare
|
||||
* @return prepared Request object
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected abstract Request prepareRequest(Request requestToPrepare) throws ValidationException;
|
||||
|
||||
/**
|
||||
* concrete implementation has to provide the channel group.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract String getChannelGroup();
|
||||
|
||||
/**
|
||||
* concrete implementation has to provide the URL
|
||||
*
|
||||
* @return Url
|
||||
*/
|
||||
protected abstract String getURL();
|
||||
|
||||
@Override
|
||||
public void registerResultProcessor(JsonResultProcessor resultProcessor) {
|
||||
this.resultProcessors.add(resultProcessor);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.easee.internal.command;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.CHANNEL_GROUP_NONE;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* base class for all write commands. common logic should be implemented here
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractWriteCommand extends AbstractCommand {
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractWriteCommand.class);
|
||||
|
||||
protected final Channel channel;
|
||||
protected Command command;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
public AbstractWriteCommand(EaseeThingHandler handler, Channel channel, Command command,
|
||||
RetryOnFailure retryOnFailure, ProcessFailureResponse processFailureResponse) {
|
||||
super(handler, retryOnFailure, processFailureResponse);
|
||||
this.channel = channel;
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method for write commands that extracts value from command.
|
||||
*
|
||||
* @return value as String without unit.
|
||||
*/
|
||||
protected String getCommandValue() {
|
||||
if (command instanceof QuantityType<?>) {
|
||||
// this is necessary because we must not send the unit to the backend
|
||||
return String.valueOf(((QuantityType<?>) command).doubleValue());
|
||||
} else if (command instanceof OnOffType) {
|
||||
// this is necessary because we must send booleans and not ON/OFF to the backend
|
||||
return String.valueOf(command.equals(OnOffType.ON));
|
||||
} else {
|
||||
return command.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* helper that transforms channelId + commandvalue in a JSON string that can be added as content to a POST request.
|
||||
*
|
||||
* @return converted JSON string
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected String getJsonContent() throws ValidationException {
|
||||
Map<String, String> content = new HashMap<String, String>(1);
|
||||
content.put(channel.getUID().getIdWithoutGroup(), getCommandValue());
|
||||
|
||||
return gson.toJson(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) throws ValidationException {
|
||||
String channelId = channel.getUID().getIdWithoutGroup();
|
||||
String expr = Utils.getValidationExpression(channel);
|
||||
String value = getCommandValue();
|
||||
|
||||
// quantity types are transformed to double and thus we might have decimals which could cause validation error.
|
||||
// So we will shorten here in case no decimals are needed.
|
||||
if (value.endsWith(".0")) {
|
||||
value = value.substring(0, value.length() - 2);
|
||||
}
|
||||
|
||||
if (value.matches(expr)) {
|
||||
return prepareWriteRequest(requestToPrepare);
|
||||
} else {
|
||||
logger.info("channel '{}' does not allow value '{}' - validation rule '{}'", channelId, value, expr);
|
||||
throw new ValidationException("channel (" + channelId + ") could not be updated due to a validation error");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
// this is a pure write command, thus no channel group needed.
|
||||
return CHANNEL_GROUP_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* concrete implementation has to prepare the write requests with additional parameters, etc
|
||||
*
|
||||
* @param requestToPrepare the request to prepare
|
||||
* @return prepared Request object
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected abstract Request prepareWriteRequest(Request requestToPrepare) throws ValidationException;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Response.CompleteListener;
|
||||
import org.eclipse.jetty.client.api.Response.ContentListener;
|
||||
import org.eclipse.jetty.client.api.Response.FailureListener;
|
||||
import org.eclipse.jetty.client.api.Response.SuccessListener;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
|
||||
/**
|
||||
* public interface for all commands
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EaseeCommand extends SuccessListener, FailureListener, ContentListener, CompleteListener {
|
||||
|
||||
static final int MAX_RETRIES = 5;
|
||||
|
||||
/**
|
||||
* this method is to be called by the UplinkWebinterface class
|
||||
*
|
||||
* @param asyncclient
|
||||
* @throws ValidationException
|
||||
*/
|
||||
void performAction(HttpClient asyncclient, String token) throws ValidationException;
|
||||
|
||||
/**
|
||||
* sets a result processor for json result data
|
||||
*
|
||||
* @param resultProcessor
|
||||
*/
|
||||
public void registerResultProcessor(JsonResultProcessor resultProcessor);
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* functional interface that is intended to provide a function for surther result processing of json data retrieved by a
|
||||
* command.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@FunctionalInterface
|
||||
public interface JsonResultProcessor {
|
||||
|
||||
void processResult(CommunicationStatus status, JsonObject jsonObject);
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.easee.internal.command.account;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* implements the login to the webinterface
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Login extends AbstractCommand {
|
||||
|
||||
class LoginData {
|
||||
final String userName;
|
||||
final String password;
|
||||
|
||||
public LoginData(String userName, String password) {
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
private final LoginData loginData;
|
||||
|
||||
public Login(EaseeBridgeHandler handler) {
|
||||
// flags do not matter as "onComplete" is overwritten in this class.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.NO);
|
||||
loginData = new LoginData(handler.getBridgeConfiguration().getUsername(),
|
||||
handler.getBridgeConfiguration().getPassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
StringContentProvider cp = new StringContentProvider(gson.toJson(loginData));
|
||||
requestToPrepare.content(cp);
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return LOGIN_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
JsonObject jsonObject = gson.fromJson(json, JsonObject.class);
|
||||
|
||||
if (jsonObject != null) {
|
||||
processResult(jsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_NONE;
|
||||
}
|
||||
}
|
@ -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.easee.internal.command.account;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.REFRESH_TOKEN_URL;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;
|
||||
|
||||
/**
|
||||
* implements the refresh of the access token.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RefreshToken extends Login {
|
||||
|
||||
class RefreshData {
|
||||
final String accessToken;
|
||||
final String refreshToken;
|
||||
|
||||
public RefreshData(String accessToken, String refreshToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
|
||||
private final RefreshData refreshData;
|
||||
|
||||
public RefreshToken(EaseeBridgeHandler handler, String accessToken, String refreshToken) {
|
||||
super(handler);
|
||||
refreshData = new RefreshData(accessToken, refreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
StringContentProvider cp = new StringContentProvider(gson.toJson(refreshData));
|
||||
requestToPrepare.content(cp);
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return REFRESH_TOKEN_URL;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.CHANGE_CONFIGURATION_URL;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractWriteCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the settings api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChangeConfiguration extends AbstractWriteCommand {
|
||||
private final String url;
|
||||
|
||||
public ChangeConfiguration(EaseeThingHandler handler, String chargerId, Channel channel, Command command) {
|
||||
super(handler, channel, command, RetryOnFailure.YES, ProcessFailureResponse.YES);
|
||||
this.url = CHANGE_CONFIGURATION_URL.replaceAll("\\{id\\}", chargerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareWriteRequest(Request requestToPrepare) throws ValidationException {
|
||||
StringContentProvider cp = new StringContentProvider(getJsonContent());
|
||||
requestToPrepare.content(cp);
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the charger api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Charger extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public Charger(EaseeThingHandler handler, String chargerId, JsonResultProcessor resultProcessor) {
|
||||
// retry does not make much sense as it is a polling command, command should always succeed therefore update
|
||||
// handler on failure.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
|
||||
this.url = CHARGER_URL.replaceAll("\\{id\\}", chargerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CHARGER;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the state api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ChargerState extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public ChargerState(EaseeThingHandler handler, String chargerId) {
|
||||
// retry does not make much sense as it is a polling command, command should always succeed therefore update
|
||||
// handler on failure.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES);
|
||||
this.url = STATE_URL.replaceAll("\\{id\\}", chargerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CHARGER_STATE;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the get configuration api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetConfiguration extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public GetConfiguration(EaseeThingHandler handler, String chargerId) {
|
||||
// retry does not make much sense as it is a polling command, command should always succeed therefore update
|
||||
// handler on failure.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES);
|
||||
this.url = GET_CONFIGURATION_URL.replaceAll("\\{id\\}", chargerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CHARGER_CONFIG;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the latest charging session api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LatestChargingSession extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public LatestChargingSession(EaseeThingHandler handler, String chargerId) {
|
||||
// retry does not make much sense as it is a polling command, command might fail if no charging sessions are
|
||||
// available, therefore just ignore failure.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.NO);
|
||||
this.url = LATEST_CHARGING_SESSION_URL.replaceAll("\\{id\\}", chargerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CHARGER_LATEST_SESSION;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.COMMANDS_URL;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractWriteCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the command api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SendCommand extends AbstractWriteCommand {
|
||||
String url = COMMANDS_URL;
|
||||
|
||||
/**
|
||||
* general constructor.
|
||||
*
|
||||
* @param handler the ThingHandler which is responsible for the channel
|
||||
* @param chargerId Id of the charger which is adressed by this command
|
||||
* @param channel the channel that triggered this command
|
||||
* @param command the command to be send
|
||||
*/
|
||||
public SendCommand(EaseeThingHandler handler, String chargerId, Channel channel, Command command) {
|
||||
this(handler, channel, command);
|
||||
this.url = COMMANDS_URL.replaceAll("\\{id\\}", chargerId).replaceAll("\\{command\\}", getCommandValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* this constructor should only be used by other command implementations that inherit this class.
|
||||
*
|
||||
* @param handler the ThingHandler which is responsible for the channel
|
||||
* @param channel the channel that triggered this command
|
||||
* @param command the command to be send
|
||||
*/
|
||||
SendCommand(EaseeThingHandler handler, Channel channel, Command command) {
|
||||
super(handler, channel, command, RetryOnFailure.YES, ProcessFailureResponse.YES);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareWriteRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the command api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SendCommandPauseResume extends SendCommand {
|
||||
|
||||
public SendCommandPauseResume(EaseeThingHandler handler, String chargerId, Channel channel, Command command) {
|
||||
super(handler, channel, command);
|
||||
String value;
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
value = CMD_VAL_PAUSE_CHARGING;
|
||||
} else {
|
||||
value = CMD_VAL_RESUME_CHARGING;
|
||||
}
|
||||
this.url = COMMANDS_URL.replaceAll("\\{id\\}", chargerId).replaceAll("\\{command\\}", value);
|
||||
}
|
||||
}
|
@ -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.easee.internal.command.charger;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the command api call of the charger.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SendCommandStartStop extends SendCommand {
|
||||
|
||||
public SendCommandStartStop(EaseeThingHandler handler, String chargerId, Channel channel, Command command) {
|
||||
super(handler, channel, command);
|
||||
String value;
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
value = CMD_VAL_START_CHARGING;
|
||||
} else {
|
||||
value = CMD_VAL_STOP_CHARGING;
|
||||
}
|
||||
this.url = COMMANDS_URL.replaceAll("\\{id\\}", chargerId).replaceAll("\\{command\\}", value);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.command.circuit;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the settings api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CircuitSettings extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public CircuitSettings(EaseeThingHandler handler, String circuitId) {
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES);
|
||||
String siteId = handler.getBridgeConfiguration().getSiteId();
|
||||
this.url = CIRCUIT_SETTINGS_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{circuitId\\}", circuitId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CIRCUIT_SETTINGS;
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.easee.internal.command.circuit;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
|
||||
/**
|
||||
* implements the dynamicCurrent api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DynamicCircuitCurrent extends AbstractCommand {
|
||||
private final String url;
|
||||
|
||||
public DynamicCircuitCurrent(EaseeThingHandler handler, String circuitId) {
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES);
|
||||
String siteId = handler.getBridgeConfiguration().getSiteId();
|
||||
this.url = DYNAMIC_CIRCUIT_CURRENT_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{circuitId\\}",
|
||||
circuitId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.easee.internal.command.circuit;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.CIRCUIT_SETTINGS_URL;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractWriteCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the settings api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetCircuitSettings extends AbstractWriteCommand {
|
||||
private final String url;
|
||||
|
||||
public SetCircuitSettings(EaseeThingHandler handler, Channel channel, Command command, String circuitId) {
|
||||
super(handler, channel, command, RetryOnFailure.YES, ProcessFailureResponse.YES);
|
||||
String siteId = handler.getBridgeConfiguration().getSiteId();
|
||||
this.url = CIRCUIT_SETTINGS_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{circuitId\\}", circuitId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareWriteRequest(Request requestToPrepare) throws ValidationException {
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
StringContentProvider cp = new StringContentProvider(getJsonContent());
|
||||
requestToPrepare.content(cp);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.easee.internal.command.circuit;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.DYNAMIC_CIRCUIT_CURRENT_URL;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractWriteCommand;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the dynamicCurrent api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetDynamicCircuitCurrents extends AbstractWriteCommand {
|
||||
private static final String PHASE1 = "phase1";
|
||||
private static final String PHASE2 = "phase2";
|
||||
private static final String PHASE3 = "phase3";
|
||||
private final String url;
|
||||
|
||||
public SetDynamicCircuitCurrents(EaseeThingHandler handler, Channel channel, Command command, String circuitId) {
|
||||
super(handler, channel, command, RetryOnFailure.YES, ProcessFailureResponse.YES);
|
||||
String siteId = handler.getBridgeConfiguration().getSiteId();
|
||||
this.url = DYNAMIC_CIRCUIT_CURRENT_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{circuitId\\}",
|
||||
circuitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* helper that transforms channelId + commandvalue in a JSON string that can be added as content to a POST request.
|
||||
*
|
||||
* @return converted JSON string
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Override
|
||||
protected String getJsonContent() throws ValidationException {
|
||||
Map<String, String> content = new HashMap<String, String>(3);
|
||||
String rawCommand = getCommandValue();
|
||||
String[] tokens = rawCommand.split(";");
|
||||
if (tokens.length == 3) {
|
||||
content.put(PHASE1, tokens[0]);
|
||||
content.put(PHASE2, tokens[1]);
|
||||
content.put(PHASE3, tokens[2]);
|
||||
} else {
|
||||
throw new ValidationException(
|
||||
"malformed command string, expected: '<phase1>;<phase2>;<phase3>', actual: " + rawCommand);
|
||||
}
|
||||
return gson.toJson(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareWriteRequest(Request requestToPrepare) throws ValidationException {
|
||||
requestToPrepare.method(HttpMethod.POST);
|
||||
StringContentProvider cp = new StringContentProvider(getJsonContent());
|
||||
requestToPrepare.content(cp);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return url;
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.easee.internal.command.circuit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the settings api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetMaxCircuitCurrents extends SetCircuitSettings {
|
||||
private static final String PHASE1 = "maxCircuitCurrentP1";
|
||||
private static final String PHASE2 = "maxCircuitCurrentP2";
|
||||
private static final String PHASE3 = "maxCircuitCurrentP3";
|
||||
|
||||
public SetMaxCircuitCurrents(EaseeThingHandler handler, Channel channel, Command command, String circuitId) {
|
||||
super(handler, channel, command, circuitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* helper that transforms channelId + commandvalue in a JSON string that can be added as content to a POST request.
|
||||
*
|
||||
* @return converted JSON string
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Override
|
||||
protected String getJsonContent() throws ValidationException {
|
||||
Map<String, String> content = new HashMap<String, String>(3);
|
||||
String rawCommand = getCommandValue();
|
||||
String[] tokens = rawCommand.split(";");
|
||||
if (tokens.length == 3) {
|
||||
content.put(PHASE1, tokens[0]);
|
||||
content.put(PHASE2, tokens[1]);
|
||||
content.put(PHASE3, tokens[2]);
|
||||
} else {
|
||||
throw new ValidationException(
|
||||
"malformed command string, expected: '<phase1>;<phase2>;<phase3>', actual: " + rawCommand);
|
||||
}
|
||||
return gson.toJson(content);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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.easee.internal.command.circuit;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* implements the settings api call of the circuit.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SetOfflineMaxCircuitCurrents extends SetCircuitSettings {
|
||||
private static final String PHASE1 = "offlineMaxCircuitCurrentP1";
|
||||
private static final String PHASE2 = "offlineMaxCircuitCurrentP2";
|
||||
private static final String PHASE3 = "offlineMaxCircuitCurrentP3";
|
||||
|
||||
public SetOfflineMaxCircuitCurrents(EaseeThingHandler handler, Channel channel, Command command, String circuitId) {
|
||||
super(handler, channel, command, circuitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* helper that transforms channelId + commandvalue in a JSON string that can be added as content to a POST request.
|
||||
*
|
||||
* @return converted JSON string
|
||||
* @throws ValidationException
|
||||
*/
|
||||
@Override
|
||||
protected String getJsonContent() throws ValidationException {
|
||||
Map<String, String> content = new HashMap<String, String>(3);
|
||||
String rawCommand = getCommandValue();
|
||||
String[] tokens = rawCommand.split(";");
|
||||
if (tokens.length == 3) {
|
||||
content.put(PHASE1, tokens[0]);
|
||||
content.put(PHASE2, tokens[1]);
|
||||
content.put(PHASE3, tokens[2]);
|
||||
} else {
|
||||
throw new ValidationException(
|
||||
"malformed command string, expected: '<phase1>;<phase2>;<phase3>', actual: " + rawCommand);
|
||||
}
|
||||
return gson.toJson(content);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.easee.internal.command.site;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.easee.internal.command.AbstractCommand;
|
||||
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;
|
||||
|
||||
/**
|
||||
* implements the get sites api call of the site.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GetSite extends AbstractCommand {
|
||||
|
||||
public GetSite(EaseeBridgeHandler handler, JsonResultProcessor resultProcessor) {
|
||||
// retry does not make much sense as it is a polling command, command should always succeed therefore update
|
||||
// handler on failure.
|
||||
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
String url = GET_SITE_URL;
|
||||
url = url.replaceAll("\\{siteId\\}", handler.getBridgeConfiguration().getSiteId());
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getChannelGroup() {
|
||||
return CHANNEL_GROUP_SITE_INFO;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.easee.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link EaseeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Alexander Friese - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeConfiguration {
|
||||
|
||||
private String username = "";
|
||||
private String password = "";
|
||||
private String siteId = "";
|
||||
|
||||
private Integer asyncTimeout = 120;
|
||||
private Integer syncTimeout = 120;
|
||||
private Integer dataPollingInterval = 120;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getSiteId() {
|
||||
return siteId;
|
||||
}
|
||||
|
||||
public void setSiteId(String siteId) {
|
||||
this.siteId = siteId;
|
||||
}
|
||||
|
||||
public Integer getAsyncTimeout() {
|
||||
return asyncTimeout;
|
||||
}
|
||||
|
||||
public void setAsyncTimeout(Integer asyncTimeout) {
|
||||
this.asyncTimeout = asyncTimeout;
|
||||
}
|
||||
|
||||
public Integer getSyncTimeout() {
|
||||
return syncTimeout;
|
||||
}
|
||||
|
||||
public void setSyncTimeout(Integer syncTimeout) {
|
||||
this.syncTimeout = syncTimeout;
|
||||
}
|
||||
|
||||
public Integer getDataPollingInterval() {
|
||||
return dataPollingInterval;
|
||||
}
|
||||
|
||||
public void setDataPollingInterval(Integer dataPollingInterval) {
|
||||
this.dataPollingInterval = dataPollingInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("EaseeConfiguration [username=").append(username).append(", password=").append(password)
|
||||
.append(", siteId=").append(siteId).append(", asyncTimeout=").append(asyncTimeout)
|
||||
.append(", syncTimeout=").append(syncTimeout).append(", dataPollingInterval=")
|
||||
.append(dataPollingInterval).append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.easee.internal.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpStatus.Code;
|
||||
|
||||
/**
|
||||
* this class contains the HTTP status of the communication and an optional exception that might occoured during
|
||||
* communication
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationStatus {
|
||||
|
||||
private @Nullable Code httpCode;
|
||||
private @Nullable Exception error;
|
||||
|
||||
public final Code getHttpCode() {
|
||||
Code code = httpCode;
|
||||
return code == null ? Code.INTERNAL_SERVER_ERROR : code;
|
||||
}
|
||||
|
||||
public final void setHttpCode(Code httpCode) {
|
||||
this.httpCode = httpCode;
|
||||
}
|
||||
|
||||
public final @Nullable Exception getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public final void setError(Exception error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public final String getMessage() {
|
||||
Exception err = error;
|
||||
String errMsg = err == null ? null : err.getMessage();
|
||||
String msg = getHttpCode().getMessage();
|
||||
if (errMsg != null && !errMsg.isEmpty()) {
|
||||
return errMsg;
|
||||
} else if (msg != null && !msg.isEmpty()) {
|
||||
return msg;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
@ -0,0 +1,346 @@
|
||||
/**
|
||||
* 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.easee.internal.connector;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.openhab.binding.easee.internal.AtomicReferenceTrait;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.EaseeCommand;
|
||||
import org.openhab.binding.easee.internal.command.account.Login;
|
||||
import org.openhab.binding.easee.internal.command.account.RefreshToken;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;
|
||||
import org.openhab.binding.easee.internal.handler.StatusHandler;
|
||||
import org.openhab.binding.easee.internal.model.ValidationException;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The connector is responsible for communication with the Easee Cloud API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WebInterface implements AtomicReferenceTrait {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WebInterface.class);
|
||||
|
||||
/**
|
||||
* bridge handler
|
||||
*/
|
||||
private final EaseeBridgeHandler handler;
|
||||
|
||||
/**
|
||||
* handler for updating bridge status
|
||||
*/
|
||||
private final StatusHandler statusHandler;
|
||||
|
||||
/**
|
||||
* holds authentication status
|
||||
*/
|
||||
private boolean authenticated = false;
|
||||
|
||||
/**
|
||||
* access token returned by login, needed to authenticate all requests send to API.
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* refresh token returned by login, needed for refreshing the access token.
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* expiry of the access token.
|
||||
*/
|
||||
private Instant tokenExpiry;
|
||||
|
||||
/**
|
||||
* last refresh of the access token.
|
||||
*/
|
||||
private Instant tokenRefreshDate;
|
||||
|
||||
/**
|
||||
* HTTP client for asynchronous calls
|
||||
*/
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* the scheduler which periodically sends web requests to the Easee Cloud API. Should be initiated with the thing's
|
||||
* existing scheduler instance.
|
||||
*/
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* request executor
|
||||
*/
|
||||
private final WebRequestExecutor requestExecutor;
|
||||
|
||||
/**
|
||||
* periodic request executor job
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> requestExecutorJobReference;
|
||||
|
||||
/**
|
||||
* this class is responsible for executing periodic web requests. This ensures that only one request is executed
|
||||
* at the same time and there will be a guaranteed minimum delay between subsequent requests.
|
||||
*
|
||||
* @author afriese - initial contribution
|
||||
*/
|
||||
private class WebRequestExecutor implements Runnable {
|
||||
|
||||
/**
|
||||
* queue which holds the commands to execute
|
||||
*/
|
||||
private final Queue<EaseeCommand> commandQueue;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
WebRequestExecutor() {
|
||||
this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
|
||||
}
|
||||
|
||||
private void processAuthenticationResult(CommunicationStatus status, JsonObject jsonObject) {
|
||||
String msg = Utils.getAsString(jsonObject, JSON_KEY_ERROR_TITLE);
|
||||
if (msg == null || msg.isBlank()) {
|
||||
msg = status.getMessage();
|
||||
}
|
||||
|
||||
switch (status.getHttpCode()) {
|
||||
case BAD_REQUEST:
|
||||
statusHandler.updateStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
|
||||
setAuthenticated(false);
|
||||
break;
|
||||
case OK:
|
||||
String accessToken = Utils.getAsString(jsonObject, JSON_KEY_AUTH_ACCESS_TOKEN);
|
||||
String refreshToken = Utils.getAsString(jsonObject, JSON_KEY_AUTH_REFRESH_TOKEN);
|
||||
int expiresInSeconds = Utils.getAsInt(jsonObject, JSON_KEY_AUTH_EXPIRES_IN);
|
||||
if (accessToken != null && refreshToken != null && expiresInSeconds != 0) {
|
||||
WebInterface.this.accessToken = accessToken;
|
||||
WebInterface.this.refreshToken = refreshToken;
|
||||
tokenRefreshDate = Instant.now();
|
||||
tokenExpiry = tokenRefreshDate.plusSeconds(expiresInSeconds);
|
||||
|
||||
logger.debug("access token refreshed: {}, expiry: {}", Utils.formatDate(tokenRefreshDate),
|
||||
Utils.formatDate(tokenExpiry));
|
||||
|
||||
statusHandler.updateStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE,
|
||||
STATUS_TOKEN_VALIDATED);
|
||||
setAuthenticated(true);
|
||||
handler.startDiscovery();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
statusHandler.updateStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, msg);
|
||||
setAuthenticated(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* puts a command into the queue
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
void enqueue(EaseeCommand command) {
|
||||
try {
|
||||
commandQueue.add(command);
|
||||
} catch (IllegalStateException ex) {
|
||||
if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
|
||||
logger.debug(
|
||||
"Could not add command to command queue because queue is already full. Maybe Easee Cloud is down?");
|
||||
} else {
|
||||
logger.warn("Could not add command to queue - IllegalStateException");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the web request
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("run queued commands, queue size is {}", commandQueue.size());
|
||||
if (!isAuthenticated()) {
|
||||
authenticate();
|
||||
} else {
|
||||
refreshAccessToken();
|
||||
|
||||
if (isAuthenticated() && !commandQueue.isEmpty()) {
|
||||
try {
|
||||
executeCommand();
|
||||
} catch (Exception ex) {
|
||||
logger.warn("command execution ended with exception:", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticates with the Easee Cloud interface.
|
||||
*/
|
||||
private synchronized void authenticate() {
|
||||
setAuthenticated(false);
|
||||
EaseeCommand loginCommand = new Login(handler);
|
||||
loginCommand.registerResultProcessor(this::processAuthenticationResult);
|
||||
try {
|
||||
loginCommand.performAction(httpClient, accessToken);
|
||||
} catch (ValidationException e) {
|
||||
// this cannot happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* periodically refreshed the access token.
|
||||
*/
|
||||
private synchronized void refreshAccessToken() {
|
||||
Instant now = Instant.now();
|
||||
|
||||
if (now.isAfter(tokenExpiry.minus(WEB_REQUEST_TOKEN_EXPIRY_BUFFER_MINUTES, ChronoUnit.MINUTES))
|
||||
|| now.isAfter(tokenRefreshDate.plus(WEB_REQUEST_TOKEN_MAX_AGE_MINUTES, ChronoUnit.MINUTES))) {
|
||||
logger.debug("access token needs to be refreshed, last refresh: {}, expiry: {}",
|
||||
Utils.formatDate(tokenRefreshDate), Utils.formatDate(tokenExpiry));
|
||||
|
||||
EaseeCommand refreshCommand = new RefreshToken(handler, accessToken, refreshToken);
|
||||
refreshCommand.registerResultProcessor(this::processAuthenticationResult);
|
||||
try {
|
||||
refreshCommand.performAction(httpClient, accessToken);
|
||||
} catch (ValidationException e) {
|
||||
// this cannot happen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the next command in the queue. requires authenticated session.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private void executeCommand() throws ValidationException {
|
||||
EaseeCommand command = commandQueue.poll();
|
||||
if (command != null) {
|
||||
command.registerResultProcessor(this::processExecutionResult);
|
||||
command.performAction(httpClient, accessToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void processExecutionResult(CommunicationStatus status, JsonObject jsonObject) {
|
||||
String msg = Utils.getAsString(jsonObject, JSON_KEY_ERROR_TITLE);
|
||||
if (msg == null || msg.isBlank()) {
|
||||
msg = status.getMessage();
|
||||
}
|
||||
|
||||
switch (status.getHttpCode()) {
|
||||
case OK:
|
||||
case ACCEPTED:
|
||||
// no action needed as the thing is already online.
|
||||
break;
|
||||
default:
|
||||
statusHandler.updateStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, msg);
|
||||
setAuthenticated(false);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to set up interface
|
||||
*
|
||||
* @param config Bridge configuration
|
||||
*/
|
||||
public WebInterface(ScheduledExecutorService scheduler, EaseeBridgeHandler handler, HttpClient httpClient,
|
||||
StatusHandler statusHandler) {
|
||||
this.handler = handler;
|
||||
this.statusHandler = statusHandler;
|
||||
this.scheduler = scheduler;
|
||||
this.httpClient = httpClient;
|
||||
this.tokenExpiry = OUTDATED_DATE;
|
||||
this.tokenRefreshDate = OUTDATED_DATE;
|
||||
this.accessToken = "";
|
||||
this.refreshToken = "";
|
||||
this.requestExecutor = new WebRequestExecutor();
|
||||
this.requestExecutorJobReference = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
setAuthenticated(false);
|
||||
updateJobReference(requestExecutorJobReference, scheduler.scheduleWithFixedDelay(requestExecutor,
|
||||
WEB_REQUEST_INITIAL_DELAY, WEB_REQUEST_INTERVAL, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* queues any command for execution
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
public void enqueueCommand(EaseeCommand command) {
|
||||
requestExecutor.enqueue(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* will be called by the ThingHandler to abort periodic jobs.
|
||||
*/
|
||||
public void dispose() {
|
||||
logger.debug("Webinterface disposed.");
|
||||
cancelJobReference(requestExecutorJobReference);
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns authentication status.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the authentication status, also resets token data.
|
||||
*
|
||||
* @param authenticated
|
||||
*/
|
||||
private void setAuthenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
if (!authenticated) {
|
||||
this.tokenExpiry = OUTDATED_DATE;
|
||||
this.accessToken = "";
|
||||
this.refreshToken = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current access token.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* 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.easee.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.easee.internal.EaseeBindingConstants;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.site.GetSite;
|
||||
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
|
||||
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* this class will handle discovery of wallboxes and circuits within the site configured.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeSiteDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EaseeSiteDiscoveryService.class);
|
||||
private @NonNullByDefault({}) EaseeSiteHandler bridgeHandler;
|
||||
|
||||
public EaseeSiteDiscoveryService() throws IllegalArgumentException {
|
||||
super(EaseeBindingConstants.SUPPORTED_THING_TYPES_UIDS, 300, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
bridgeHandler.enqueueCommand(new GetSite(bridgeHandler, this::processSiteDiscoveryResult));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof EaseeSiteHandler) {
|
||||
this.bridgeHandler = (EaseeSiteHandler) handler;
|
||||
this.bridgeHandler.setDiscoveryService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
// method is defined in both implemented interface and inherited class, thus we must define a behaviour here.
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* callback that handles json result data to provide discovery result.
|
||||
*
|
||||
* @param site
|
||||
*/
|
||||
private void processSiteDiscoveryResult(CommunicationStatus status, JsonObject site) {
|
||||
logger.debug("processDiscoveryResult {}", site);
|
||||
|
||||
JsonArray circuits = site.getAsJsonArray(JSON_KEY_CIRCUITS);
|
||||
if (circuits == null) {
|
||||
logger.info("Site discovery failed, no circuits found.");
|
||||
} else {
|
||||
circuits.forEach(this::handleCircuitDiscovery);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles each circuit discovery result.
|
||||
*
|
||||
* @param circuit
|
||||
*/
|
||||
private void handleCircuitDiscovery(JsonElement json) {
|
||||
logger.debug("handleCircuitDiscovery {}", json);
|
||||
|
||||
JsonObject circuit = json.getAsJsonObject();
|
||||
final String circuitId = Utils.getAsString(circuit, JSON_KEY_GENERIC_ID);
|
||||
final String circuitName = Utils.getAsString(circuit, JSON_KEY_CIRCUIT_NAME);
|
||||
|
||||
if (circuitId != null) {
|
||||
final String circuitLabel = circuitName != null ? circuitName : circuitId;
|
||||
|
||||
// handle contained chargers
|
||||
JsonArray chargers = circuit.getAsJsonArray(JSON_KEY_CHARGERS);
|
||||
if (chargers == null) {
|
||||
logger.info("Site discovery failed, no chargers found.");
|
||||
} else {
|
||||
chargers.forEach(charger -> handleChargerDiscovery(charger, circuitId, circuitLabel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles each charger discovery result.
|
||||
*
|
||||
* @param charger
|
||||
*/
|
||||
private void handleChargerDiscovery(JsonElement json, String circuitId, String circuitLabel) {
|
||||
logger.debug("handleChargerDiscovery {}", json);
|
||||
|
||||
JsonObject charger = json.getAsJsonObject();
|
||||
String chargerId = Utils.getAsString(charger, JSON_KEY_GENERIC_ID);
|
||||
String backPlateId = Utils.getAsString(charger.getAsJsonObject(JSON_KEY_BACK_PLATE), JSON_KEY_GENERIC_ID);
|
||||
String masterBackPlateId = Utils.getAsString(charger.getAsJsonObject(JSON_KEY_BACK_PLATE),
|
||||
JSON_KEY_MASTER_BACK_PLATE_ID);
|
||||
String chargerName = Utils.getAsString(charger, JSON_KEY_GENERIC_NAME);
|
||||
|
||||
if (chargerId != null && backPlateId != null && masterBackPlateId != null) {
|
||||
DiscoveryResultBuilder builder;
|
||||
|
||||
if (backPlateId.equals(masterBackPlateId)) {
|
||||
builder = initDiscoveryResultBuilder(DEVICE_MASTER_CHARGER, chargerId, chargerName)
|
||||
.withProperty(THING_CONFIG_IS_MASTER, GENERIC_YES);
|
||||
} else {
|
||||
builder = initDiscoveryResultBuilder(DEVICE_CHARGER, chargerId, chargerName)
|
||||
.withProperty(THING_CONFIG_IS_MASTER, GENERIC_NO);
|
||||
}
|
||||
builder.withProperty(THING_CONFIG_CIRCUIT_ID, circuitId);
|
||||
builder.withProperty(THING_CONFIG_CIRCUIT_NAME, circuitLabel);
|
||||
builder.withProperty(THING_CONFIG_BACK_PLATE_ID, backPlateId);
|
||||
builder.withProperty(THING_CONFIG_MASTER_BACK_PLATE_ID, masterBackPlateId);
|
||||
thingDiscovered(builder.build());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sends discovery notification to the framework.
|
||||
*
|
||||
* @param deviceType
|
||||
* @param deviceId
|
||||
* @param deviceName
|
||||
*/
|
||||
private DiscoveryResultBuilder initDiscoveryResultBuilder(String deviceType, String deviceId,
|
||||
@Nullable String deviceName) {
|
||||
ThingUID bridgeUID = bridgeHandler.getThing().getUID();
|
||||
ThingTypeUID typeUid = new ThingTypeUID(BINDING_ID, deviceType);
|
||||
|
||||
ThingUID thingUID = new ThingUID(typeUid, bridgeUID, deviceId);
|
||||
String label = deviceName != null ? deviceName : deviceId;
|
||||
|
||||
return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID).withLabel(label)
|
||||
.withProperty(THING_CONFIG_ID, deviceId).withRepresentationProperty(THING_CONFIG_ID);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
/**
|
||||
* this interface provides all methods which deal with channels
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelProvider {
|
||||
|
||||
@Nullable
|
||||
Channel getChannel(String groupId, String channelId);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.binding.BridgeHandler;
|
||||
|
||||
/**
|
||||
* public interface of the {@link EaseeBridgeHandler}
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EaseeBridgeHandler extends BridgeHandler, EaseeThingHandler {
|
||||
|
||||
/**
|
||||
* starts discovery of wallboxes and circuits
|
||||
*/
|
||||
void startDiscovery();
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
/**
|
||||
* 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.easee.internal.handler;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.easee.internal.AtomicReferenceTrait;
|
||||
import org.openhab.binding.easee.internal.EaseeBindingConstants;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.EaseeCommand;
|
||||
import org.openhab.binding.easee.internal.command.charger.ChangeConfiguration;
|
||||
import org.openhab.binding.easee.internal.command.charger.Charger;
|
||||
import org.openhab.binding.easee.internal.command.charger.ChargerState;
|
||||
import org.openhab.binding.easee.internal.command.charger.GetConfiguration;
|
||||
import org.openhab.binding.easee.internal.command.charger.LatestChargingSession;
|
||||
import org.openhab.binding.easee.internal.command.charger.SendCommand;
|
||||
import org.openhab.binding.easee.internal.command.charger.SendCommandStartStop;
|
||||
import org.openhab.binding.easee.internal.config.EaseeConfiguration;
|
||||
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EaseeChargerHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeChargerHandler extends BaseThingHandler implements EaseeThingHandler, AtomicReferenceTrait {
|
||||
private final Logger logger = LoggerFactory.getLogger(EaseeChargerHandler.class);
|
||||
|
||||
/**
|
||||
* Schedule for polling live data
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> dataPollingJobReference;
|
||||
|
||||
public EaseeChargerHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.dataPollingJobReference = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("About to initialize Charger");
|
||||
String chargerId = getConfig().get(EaseeBindingConstants.THING_CONFIG_ID).toString();
|
||||
logger.debug("Easee Charger initialized with id: {}", chargerId);
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, STATUS_WAITING_FOR_BRIDGE);
|
||||
startPolling();
|
||||
|
||||
enqueueCommand(new Charger(this, chargerId, this::updateProperties));
|
||||
}
|
||||
|
||||
private void updateProperties(CommunicationStatus status, JsonObject charger) {
|
||||
Map<String, String> properties = editProperties();
|
||||
|
||||
String backPlateId = Utils.getAsString(charger.getAsJsonObject(JSON_KEY_BACK_PLATE), JSON_KEY_GENERIC_ID);
|
||||
String masterBackPlateId = Utils.getAsString(charger.getAsJsonObject(JSON_KEY_BACK_PLATE),
|
||||
JSON_KEY_MASTER_BACK_PLATE_ID);
|
||||
if (backPlateId != null && masterBackPlateId != null) {
|
||||
if (backPlateId.equals(masterBackPlateId)) {
|
||||
properties.put(THING_CONFIG_IS_MASTER, GENERIC_YES);
|
||||
} else {
|
||||
properties.put(THING_CONFIG_IS_MASTER, GENERIC_NO);
|
||||
}
|
||||
properties.put(THING_CONFIG_BACK_PLATE_ID, backPlateId);
|
||||
properties.put(THING_CONFIG_MASTER_BACK_PLATE_ID, masterBackPlateId);
|
||||
}
|
||||
String chargerName = Utils.getAsString(charger, JSON_KEY_GENERIC_NAME);
|
||||
if (chargerName != null) {
|
||||
properties.put(JSON_KEY_GENERIC_NAME, chargerName);
|
||||
}
|
||||
String circuitId = Utils.getAsString(charger.getAsJsonObject(JSON_KEY_BACK_PLATE), JSON_KEY_CIRCUIT_ID);
|
||||
if (circuitId != null) {
|
||||
properties.put(JSON_KEY_CIRCUIT_ID, circuitId);
|
||||
}
|
||||
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the polling.
|
||||
*/
|
||||
private void startPolling() {
|
||||
updateJobReference(dataPollingJobReference, scheduler.scheduleWithFixedDelay(this::pollingRun,
|
||||
POLLING_INITIAL_DELAY, getBridgeConfiguration().getDataPollingInterval(), TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the Easee Cloud API one time.
|
||||
*/
|
||||
void pollingRun() {
|
||||
String chargerId = getConfig().get(EaseeBindingConstants.THING_CONFIG_ID).toString();
|
||||
logger.debug("polling charger data for {}", chargerId);
|
||||
|
||||
ChargerState state = new ChargerState(this, chargerId);
|
||||
state.registerResultProcessor(this::updateStatusInfo);
|
||||
enqueueCommand(state);
|
||||
|
||||
// proceed if charger is online
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
enqueueCommand(new GetConfiguration(this, chargerId));
|
||||
enqueueCommand(new LatestChargingSession(this, chargerId));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates status depending on online information received from the API.
|
||||
*
|
||||
* @param status
|
||||
* @param jsonObject
|
||||
*/
|
||||
private void updateStatusInfo(CommunicationStatus status, JsonObject jsonObject) {
|
||||
Boolean isOnline = Utils.getAsBool(jsonObject, JSON_KEY_ONLINE);
|
||||
|
||||
if (isOnline == null) {
|
||||
super.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, STATUS_NO_VALID_DATA);
|
||||
} else if (isOnline) {
|
||||
super.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
|
||||
} else {
|
||||
super.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, STATUS_NO_CONNECTION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the thing.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Handler disposed.");
|
||||
cancelJobReference(dataPollingJobReference);
|
||||
}
|
||||
|
||||
/**
|
||||
* will update all channels provided in the map
|
||||
*/
|
||||
@Override
|
||||
public void updateChannelStatus(Map<Channel, State> values) {
|
||||
logger.debug("Handling charger channel update.");
|
||||
|
||||
for (Channel channel : values.keySet()) {
|
||||
if (getThing().getChannels().contains(channel)) {
|
||||
State value = values.get(channel);
|
||||
if (value != null) {
|
||||
logger.debug("Channel is to be updated: {}: {}", channel.getUID().getAsString(), value);
|
||||
updateState(channel.getUID(), value);
|
||||
} else {
|
||||
logger.debug("Value is null or not provided by Easee Cloud (channel: {})",
|
||||
channel.getUID().getAsString());
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Could not identify channel: {} for model {}", channel.getUID().getAsString(),
|
||||
getThing().getThingTypeUID().getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueCommand(EaseeCommand command) {
|
||||
EaseeBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler != null) {
|
||||
bridgeHandler.enqueueCommand(command);
|
||||
} else {
|
||||
// this should not happen
|
||||
logger.warn("no bridge handler found");
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable EaseeBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
return bridge == null ? null : ((EaseeBridgeHandler) bridge.getHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public EaseeConfiguration getBridgeConfiguration() {
|
||||
EaseeBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
return bridgeHandler == null ? new EaseeConfiguration() : bridgeHandler.getBridgeConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EaseeCommand buildEaseeCommand(Command command, Channel channel) {
|
||||
String chargerId = getConfig().get(EaseeBindingConstants.THING_CONFIG_ID).toString();
|
||||
|
||||
switch (Utils.getWriteCommand(channel)) {
|
||||
case COMMAND_CHANGE_CONFIGURATION:
|
||||
return new ChangeConfiguration(this, chargerId, channel, command);
|
||||
case COMMAND_SEND_COMMAND:
|
||||
return new SendCommand(this, chargerId, channel, command);
|
||||
case COMMAND_SEND_COMMAND_START_STOP:
|
||||
return new SendCommandStartStop(this, chargerId, channel, command);
|
||||
default:
|
||||
// this should not happen
|
||||
logger.error("write command '{}' not found for channel '{}'", command.toString(),
|
||||
channel.getUID().getIdWithoutGroup());
|
||||
throw new UnsupportedOperationException(
|
||||
"write command not found for channel: " + channel.getUID().getIdWithoutGroup());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.easee.internal.handler;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.EaseeBindingConstants;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.EaseeCommand;
|
||||
import org.openhab.binding.easee.internal.command.circuit.CircuitSettings;
|
||||
import org.openhab.binding.easee.internal.command.circuit.DynamicCircuitCurrent;
|
||||
import org.openhab.binding.easee.internal.command.circuit.SetCircuitSettings;
|
||||
import org.openhab.binding.easee.internal.command.circuit.SetDynamicCircuitCurrents;
|
||||
import org.openhab.binding.easee.internal.command.circuit.SetMaxCircuitCurrents;
|
||||
import org.openhab.binding.easee.internal.command.circuit.SetOfflineMaxCircuitCurrents;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link EaseeMasterChargerHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeMasterChargerHandler extends EaseeChargerHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(EaseeMasterChargerHandler.class);
|
||||
|
||||
public EaseeMasterChargerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the Easee Cloud API one time.
|
||||
*/
|
||||
@Override
|
||||
void pollingRun() {
|
||||
super.pollingRun();
|
||||
|
||||
// proceed if charger is online, otherwise circuit data is not in sync.
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
String circuitId = getConfig().get(EaseeBindingConstants.THING_CONFIG_CIRCUIT_ID).toString();
|
||||
logger.debug("polling circuit data for {}", circuitId);
|
||||
|
||||
enqueueCommand(new DynamicCircuitCurrent(this, circuitId));
|
||||
enqueueCommand(new CircuitSettings(this, circuitId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EaseeCommand buildEaseeCommand(Command command, Channel channel) {
|
||||
String circuitId = getConfig().get(EaseeBindingConstants.THING_CONFIG_CIRCUIT_ID).toString();
|
||||
|
||||
switch (Utils.getWriteCommand(channel)) {
|
||||
case COMMAND_SET_CIRCUIT_SETTINGS:
|
||||
return new SetCircuitSettings(this, channel, command, circuitId);
|
||||
case COMMAND_SET_DYNAMIC_CIRCUIT_CURRENTS:
|
||||
return new SetDynamicCircuitCurrents(this, channel, command, circuitId);
|
||||
case COMMAND_SET_MAX_CIRCUIT_CURRENTS:
|
||||
return new SetMaxCircuitCurrents(this, channel, command, circuitId);
|
||||
case COMMAND_SET_OFFLINE_MAX_CIRCUIT_CURRENTS:
|
||||
return new SetOfflineMaxCircuitCurrents(this, channel, command, circuitId);
|
||||
default:
|
||||
return super.buildEaseeCommand(command, channel);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.easee.internal.handler;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.EaseeCommand;
|
||||
import org.openhab.binding.easee.internal.command.site.GetSite;
|
||||
import org.openhab.binding.easee.internal.config.EaseeConfiguration;
|
||||
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
|
||||
import org.openhab.binding.easee.internal.connector.WebInterface;
|
||||
import org.openhab.binding.easee.internal.discovery.EaseeSiteDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EaseeSiteHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EaseeSiteHandler extends BaseBridgeHandler implements EaseeBridgeHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(EaseeSiteHandler.class);
|
||||
|
||||
private @Nullable DiscoveryService discoveryService;
|
||||
|
||||
/**
|
||||
* Interface object for querying the Easee web interface
|
||||
*/
|
||||
private WebInterface webInterface;
|
||||
|
||||
public EaseeSiteHandler(Bridge bridge, HttpClient httpClient) {
|
||||
super(bridge);
|
||||
this.webInterface = new WebInterface(scheduler, this, httpClient, super::updateStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("About to initialize Easee Site");
|
||||
EaseeConfiguration config = getBridgeConfiguration();
|
||||
logger.debug("Easee Site initialized with configuration: {}", config.toString());
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, STATUS_WAITING_FOR_LOGIN);
|
||||
webInterface.start();
|
||||
|
||||
enqueueCommand(new GetSite(this, this::updateProperties));
|
||||
}
|
||||
|
||||
private void updateProperties(CommunicationStatus status, JsonObject jsonObject) {
|
||||
Map<String, String> properties = editProperties();
|
||||
String name = Utils.getAsString(jsonObject, JSON_KEY_GENERIC_NAME);
|
||||
if (name != null) {
|
||||
properties.put(JSON_KEY_GENERIC_NAME, name);
|
||||
}
|
||||
String siteKey = Utils.getAsString(jsonObject, JSON_KEY_SITE_KEY);
|
||||
if (siteKey != null) {
|
||||
properties.put(JSON_KEY_SITE_KEY, siteKey);
|
||||
}
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the bridge.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Handler disposed.");
|
||||
webInterface.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EaseeConfiguration getBridgeConfiguration() {
|
||||
return this.getConfigAs(EaseeConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(EaseeSiteDiscoveryService.class);
|
||||
}
|
||||
|
||||
public void setDiscoveryService(EaseeSiteDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDiscovery() {
|
||||
DiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.startScan(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueCommand(EaseeCommand command) {
|
||||
webInterface.enqueueCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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.easee.internal.handler;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.CHANNEL_TYPEPREFIX_RW;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.command.EaseeCommand;
|
||||
import org.openhab.binding.easee.internal.config.EaseeConfiguration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* public interface of the {@link EaseeThingHandler}. provides some default implementations which can be used by both
|
||||
* BridgeHandlers and ThingHandlers.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EaseeThingHandler extends ThingHandler, ChannelProvider {
|
||||
|
||||
/**
|
||||
* just to avoid usage of static loggers.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Logger getLogger();
|
||||
|
||||
/**
|
||||
* method which updates the channels. needs to be implemented by thing types which have channels.
|
||||
*
|
||||
* @param values key-value list where key is the channel
|
||||
*/
|
||||
default void updateChannelStatus(Map<Channel, State> values) {
|
||||
getLogger().debug("updateChannelStatus not implemented/supported by this thing type");
|
||||
}
|
||||
|
||||
/**
|
||||
* return the bridge's configuration
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
EaseeConfiguration getBridgeConfiguration();
|
||||
|
||||
/**
|
||||
* passes a new command o the command queue
|
||||
*
|
||||
* @param command to be queued/executed
|
||||
*/
|
||||
void enqueueCommand(EaseeCommand command);
|
||||
|
||||
/**
|
||||
* default implementation to handle commands
|
||||
*
|
||||
* @param channelUID
|
||||
* @param command
|
||||
*/
|
||||
@Override
|
||||
default void handleCommand(ChannelUID channelUID, Command command) {
|
||||
getLogger().debug("command for {}: {}", channelUID, command);
|
||||
|
||||
if (command instanceof RefreshType) {
|
||||
return;
|
||||
}
|
||||
|
||||
String group = channelUID.getGroupId();
|
||||
group = group == null ? "" : group;
|
||||
Channel channel = getChannel(group, channelUID.getIdWithoutGroup());
|
||||
if (channel == null) {
|
||||
// this should not happen
|
||||
getLogger().warn("channel not found: {}", channelUID);
|
||||
return;
|
||||
}
|
||||
|
||||
String channelType = Utils.getChannelTypeId(channel);
|
||||
if (!channelType.startsWith(CHANNEL_TYPEPREFIX_RW)) {
|
||||
getLogger().info("channel '{}' does not support write access - value to set '{}'",
|
||||
channelUID.getIdWithoutGroup(), command);
|
||||
throw new UnsupportedOperationException(
|
||||
"channel (" + channelUID.getIdWithoutGroup() + ") does not support write access");
|
||||
}
|
||||
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
getLogger().debug("Thing is not online, thus no commands will be handled");
|
||||
return;
|
||||
}
|
||||
enqueueCommand(buildEaseeCommand(command, channel));
|
||||
}
|
||||
|
||||
/**
|
||||
* builds the EaseeCommand which can be send to the webinterface.
|
||||
*
|
||||
* @param command the openhab raw command received from the framework
|
||||
* @param channel the channel which belongs to the command.
|
||||
* @return
|
||||
*/
|
||||
default EaseeCommand buildEaseeCommand(Command command, Channel channel) {
|
||||
throw new UnsupportedOperationException("buildEaseeCommand not implemented/supported by this thing type");
|
||||
}
|
||||
|
||||
/**
|
||||
* determines the channel for a given groupId and channelId.
|
||||
*
|
||||
* @param groupId groupId of the channel
|
||||
* @param channelId channelId of the channel
|
||||
*/
|
||||
@Override
|
||||
default @Nullable Channel getChannel(String groupId, String channelId) {
|
||||
ThingUID thingUID = this.getThing().getUID();
|
||||
ChannelGroupUID channelGroupUID = new ChannelGroupUID(thingUID, groupId);
|
||||
Channel channel = getThing().getChannel(new ChannelUID(channelGroupUID, channelId));
|
||||
return channel;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.easee.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
|
||||
/**
|
||||
* functional interface to provide an function to update status of a thing or bridge.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@NonNullByDefault
|
||||
public interface StatusHandler {
|
||||
/**
|
||||
* Called from WebInterface#authenticate() to update
|
||||
* the thing status because updateStatus is protected.
|
||||
*
|
||||
* @param status Thing status
|
||||
* @param statusDetail Thing status detail
|
||||
* @param description Thing status description
|
||||
*/
|
||||
void updateStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, String description);
|
||||
}
|
@ -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.easee.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* exception whichs is used to state a validation error
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationException extends RuntimeException {
|
||||
private static final long serialVersionUID = 5736865225953884234L;
|
||||
|
||||
public ConfigurationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ConfigurationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ConfigurationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.easee.internal.model;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.handler.ChannelProvider;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* transforms the http response into the openhab datamodel (instances of State).
|
||||
* This class is used to handle special cases which cannot be mapped by the generic transformer.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class CustomResponseTransformer {
|
||||
private final ChannelProvider channelProvider;
|
||||
|
||||
CustomResponseTransformer(ChannelProvider channelProvider) {
|
||||
this.channelProvider = channelProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* allows additional updates of special/composite channels.
|
||||
*
|
||||
* @param triggerChannel the channel which triggers the additional update
|
||||
* @param value updated value of the triggering channel
|
||||
* @param rawData raw json data provided by the API
|
||||
*/
|
||||
Map<Channel, State> transform(Channel triggerChannel, String value, JsonObject rawData) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
|
||||
switch (triggerChannel.getUID().getId()) {
|
||||
case CHANNEL_GROUP_CHARGER_STATE + "#" + CHANNEL_CHARGER_OP_MODE:
|
||||
updateChargerStartStop(result, value, rawData);
|
||||
break;
|
||||
case CHANNEL_GROUP_CHARGER_STATE + "#" + CHANNEL_CHARGER_DYNAMIC_CURRENT:
|
||||
updateChargerPauseResume(result, value);
|
||||
break;
|
||||
case CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT + "#" + CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE1:
|
||||
updateCompositePhaseChannel(result, rawData, CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT,
|
||||
CHANNEL_CIRCUIT_DYNAMIC_CURRENTS, CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE1,
|
||||
CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE2, CHANNEL_CIRCUIT_DYNAMIC_CURRENT_PHASE3);
|
||||
break;
|
||||
case CHANNEL_GROUP_CIRCUIT_SETTINGS + "#" + CHANNEL_CIRCUIT_MAX_CURRENT_PHASE1:
|
||||
updateCompositePhaseChannel(result, rawData, CHANNEL_GROUP_CIRCUIT_SETTINGS,
|
||||
CHANNEL_CIRCUIT_MAX_CURRENTS, CHANNEL_CIRCUIT_MAX_CURRENT_PHASE1,
|
||||
CHANNEL_CIRCUIT_MAX_CURRENT_PHASE2, CHANNEL_CIRCUIT_MAX_CURRENT_PHASE3);
|
||||
break;
|
||||
case CHANNEL_GROUP_CIRCUIT_SETTINGS + "#" + CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE1:
|
||||
updateCompositePhaseChannel(result, rawData, CHANNEL_GROUP_CIRCUIT_SETTINGS,
|
||||
CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENTS, CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE1,
|
||||
CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE2, CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE3);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void updateChargerStartStop(Map<Channel, State> result, String value, JsonObject rawData) {
|
||||
Channel channel = channelProvider.getChannel(CHANNEL_GROUP_CHARGER_COMMANDS, CHANNEL_CHARGER_START_STOP);
|
||||
if (channel != null) {
|
||||
int val = Integer.valueOf(value);
|
||||
// state >= 3 will mean charging, ready to charge or charging finished
|
||||
boolean charging = val >= CHARGER_OP_STATE_CHARGING;
|
||||
|
||||
String rfnc = Utils.getAsString(rawData, CHANNEL_CHARGER_REASON_FOR_NO_CURRENT);
|
||||
int reasonForNoCurrent = Integer.valueOf(rfnc == null ? "-1" : rfnc);
|
||||
boolean paused = (val == CHARGER_OP_STATE_WAITING
|
||||
&& reasonForNoCurrent == CHARGER_REASON_FOR_NO_CURRENT_PAUSED);
|
||||
|
||||
result.put(channel, OnOffType.from(charging || paused));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChargerPauseResume(Map<Channel, State> result, String value) {
|
||||
Channel channel = channelProvider.getChannel(CHANNEL_GROUP_CHARGER_COMMANDS, CHANNEL_CHARGER_PAUSE_RESUME);
|
||||
if (channel != null) {
|
||||
int val = Integer.valueOf(value);
|
||||
// value == 0 will mean paused
|
||||
boolean paused = val == CHARGER_DYNAMIC_CURRENT_PAUSE;
|
||||
|
||||
result.put(channel, OnOffType.from(paused));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCompositePhaseChannel(Map<Channel, State> result, JsonObject rawData, final String group,
|
||||
final String targetChannel, final String channelPhase1, final String channelPhase2,
|
||||
final String channelPhase3) {
|
||||
Channel channel = channelProvider.getChannel(group, targetChannel);
|
||||
String phase1 = Utils.getAsString(rawData, channelPhase1);
|
||||
String phase2 = Utils.getAsString(rawData, channelPhase2);
|
||||
String phase3 = Utils.getAsString(rawData, channelPhase3);
|
||||
if (channel != null && phase1 != null && phase2 != null && phase3 != null) {
|
||||
phase1 = phase1.replaceAll("\\.0", "");
|
||||
phase2 = phase2.replaceAll("\\.0", "");
|
||||
phase3 = phase3.replaceAll("\\.0", "");
|
||||
result.put(channel, new StringType(phase1 + ";" + phase2 + ";" + phase3));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* 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.easee.internal.model;
|
||||
|
||||
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
|
||||
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.MetricPrefix;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.easee.internal.Utils;
|
||||
import org.openhab.binding.easee.internal.handler.ChannelProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* transforms the http response into the openhab datamodel (instances of State)
|
||||
* this is a generic trnasformer which tries to map json fields 1:1 to channels.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GenericResponseTransformer {
|
||||
private final Logger logger = LoggerFactory.getLogger(GenericResponseTransformer.class);
|
||||
private final ChannelProvider channelProvider;
|
||||
private final CustomResponseTransformer customResponseTransformer;
|
||||
|
||||
public GenericResponseTransformer(ChannelProvider channelProvider) {
|
||||
this.channelProvider = channelProvider;
|
||||
this.customResponseTransformer = new CustomResponseTransformer(channelProvider);
|
||||
}
|
||||
|
||||
public Map<Channel, State> transform(JsonObject jsonData, String group) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
|
||||
for (String channelId : jsonData.keySet()) {
|
||||
String value = Utils.getAsString(jsonData, channelId);
|
||||
|
||||
Channel channel = channelProvider.getChannel(group, channelId);
|
||||
if (channel == null) {
|
||||
// As we have a generic response mapper it ould happen that a subset of key/values in the response
|
||||
// cannot be mapped to openhab channels.
|
||||
logger.debug("Channel not found: {}#{}", group, channelId);
|
||||
} else {
|
||||
logger.debug("mapping value '{}' to channel {}", value, channel.getUID().getId());
|
||||
String channelType = channel.getAcceptedItemType();
|
||||
|
||||
if (value == null || channelType == null) {
|
||||
result.put(channel, UnDefType.NULL);
|
||||
} else {
|
||||
try {
|
||||
switch (channelType) {
|
||||
case CHANNEL_TYPE_SWITCH:
|
||||
result.put(channel, OnOffType.from(Boolean.parseBoolean(value)));
|
||||
break;
|
||||
case CHANNEL_TYPE_VOLT:
|
||||
result.put(channel, new QuantityType<>(Double.parseDouble(value), Units.VOLT));
|
||||
break;
|
||||
case CHANNEL_TYPE_AMPERE:
|
||||
result.put(channel, new QuantityType<>(Double.parseDouble(value), Units.AMPERE));
|
||||
break;
|
||||
case CHANNEL_TYPE_KWH:
|
||||
result.put(channel, new QuantityType<>(Double.parseDouble(value),
|
||||
MetricPrefix.KILO(Units.WATT_HOUR)));
|
||||
break;
|
||||
case CHANNEL_TYPE_KW:
|
||||
result.put(channel,
|
||||
new QuantityType<>(Double.parseDouble(value), MetricPrefix.KILO(Units.WATT)));
|
||||
break;
|
||||
case CHANNEL_TYPE_DATE:
|
||||
result.put(channel, new DateTimeType(Utils.parseDate(value)));
|
||||
break;
|
||||
case CHANNEL_TYPE_STRING:
|
||||
result.put(channel, new StringType(value));
|
||||
break;
|
||||
case CHANNEL_TYPE_NUMBER:
|
||||
if (Utils.getChannelTypeId(channel).contains(CHANNEL_TYPENAME_INTEGER)) {
|
||||
// explicit type long is needed in case of integer/long values otherwise automatic
|
||||
// transformation to a decimal type is applied.
|
||||
result.put(channel, new DecimalType(Long.parseLong(value)));
|
||||
} else {
|
||||
result.put(channel, new DecimalType(Double.parseDouble(value)));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.warn("no mapping implemented for channel type '{}'", channelType);
|
||||
}
|
||||
|
||||
// call the custom handler to handle specific / composite channels which do not map 1:1 to JSON
|
||||
// fields.
|
||||
result.putAll(customResponseTransformer.transform(channel, value, jsonData));
|
||||
|
||||
} catch (NumberFormatException | DateTimeParseException ex) {
|
||||
logger.warn("caught exception while parsing data for channel {} (value '{}'). Exception: {}",
|
||||
channel.getUID().getId(), value, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -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.easee.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* exception whichs is used to state a validation error
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ValidationException extends Exception {
|
||||
private static final long serialVersionUID = -6479556472780307224L;
|
||||
|
||||
public ValidationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ValidationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ValidationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="easee" 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>Easee Binding</name>
|
||||
<description>This is the binding for Easee Wallboxes.</description>
|
||||
</binding:binding>
|
@ -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:easee:site">
|
||||
<parameter-group name="authentication">
|
||||
<label>Authentication</label>
|
||||
<description>Authentication settings.</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="connection">
|
||||
<label>Connection</label>
|
||||
<description>Connection settings.</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="general">
|
||||
<label>General</label>
|
||||
<description>General settings.</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="username" type="text" required="true" groupName="authentication">
|
||||
<label>Username</label>
|
||||
<description>The username to login at Easee Cloud service. This should be an e-mail address or phone number.</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true" groupName="authentication">
|
||||
<label>Password</label>
|
||||
<description>Your password to login at Easee Cloud service.</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="siteId" type="text" required="true" groupName="general">
|
||||
<label>Site ID</label>
|
||||
<description>The ID of the site containing the charger(s) and circuit(s) that should be integrated into openHAB.</description>
|
||||
</parameter>
|
||||
<parameter name="dataPollingInterval" type="integer" required="false" min="20" max="600" unit="s"
|
||||
groupName="connection">
|
||||
<label>Polling Interval</label>
|
||||
<description>Interval in which data is polled from EaseeCloud (in seconds).</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:easee:charger">
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>ID</label>
|
||||
<description>The ID of the charger that should be integrated into openHAB.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:easee:mastercharger">
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>ID</label>
|
||||
<description>The ID of the charger that should be integrated into openHAB.</description>
|
||||
</parameter>
|
||||
<parameter name="circuitId" type="text" required="true">
|
||||
<label>Circuit ID</label>
|
||||
<description>The ID of the circuit which is controlled by this charger.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
@ -0,0 +1,167 @@
|
||||
# binding
|
||||
|
||||
binding.easee.name = Easee Binding
|
||||
binding.easee.description = This is the binding for Easee Wallboxes.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.easee.charger.label = Easee Charger
|
||||
thing-type.easee.charger.description = Cloud connected Easee Charger.
|
||||
thing-type.easee.mastercharger.label = Easee Master Charger
|
||||
thing-type.easee.mastercharger.description = Cloud connected Easee Master Charger which also controls the circuit.
|
||||
thing-type.easee.site.label = Easee Site
|
||||
thing-type.easee.site.description = Cloud connection to an Easee site.
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.easee.charger.id.label = ID
|
||||
thing-type.config.easee.charger.id.description = The ID of the charger that should be integrated into openHAB.
|
||||
thing-type.config.easee.mastercharger.circuitId.label = Circuit ID
|
||||
thing-type.config.easee.mastercharger.circuitId.description = The ID of the circuit which is controlled by this charger.
|
||||
thing-type.config.easee.mastercharger.id.label = ID
|
||||
thing-type.config.easee.mastercharger.id.description = The ID of the charger that should be integrated into openHAB.
|
||||
thing-type.config.easee.site.dataPollingInterval.label = Polling Interval
|
||||
thing-type.config.easee.site.dataPollingInterval.description = Interval in which data is polled from EaseeCloud (in seconds).
|
||||
thing-type.config.easee.site.group.authentication.label = Authentication
|
||||
thing-type.config.easee.site.group.authentication.description = Authentication settings.
|
||||
thing-type.config.easee.site.group.connection.label = Connection
|
||||
thing-type.config.easee.site.group.connection.description = Connection settings.
|
||||
thing-type.config.easee.site.group.general.label = General
|
||||
thing-type.config.easee.site.group.general.description = General settings.
|
||||
thing-type.config.easee.site.password.label = Password
|
||||
thing-type.config.easee.site.password.description = Your password to login at Easee Cloud service.
|
||||
thing-type.config.easee.site.siteId.label = Site ID
|
||||
thing-type.config.easee.site.siteId.description = The ID of the site containing the charger(s) and circuit(s) that should be integrated into openHAB.
|
||||
thing-type.config.easee.site.username.label = Username
|
||||
thing-type.config.easee.site.username.description = The username to login at Easee Cloud service. This should be an e-mail address or phone number.
|
||||
|
||||
# channel group types
|
||||
|
||||
channel-group-type.easee.charger-commands.label = Charger Commands
|
||||
channel-group-type.easee.charger-commands.channel.genericCommand.label = Generic Charger Command
|
||||
channel-group-type.easee.charger-commands.channel.genericCommand.description = Sends a command to the charger. Write only channel.
|
||||
channel-group-type.easee.charger-commands.channel.pauseResume.label = Pause/Resume Charging
|
||||
channel-group-type.easee.charger-commands.channel.pauseResume.description = Pauses/Resumes charging. Pausing works by setting Dynamic Charger Current to 0.
|
||||
channel-group-type.easee.charger-commands.channel.startStop.label = Start/Stop Charging
|
||||
channel-group-type.easee.charger-commands.channel.startStop.description = Starts/Stops charging. Only relevant if authorization is required.
|
||||
channel-group-type.easee.charger-config.label = Charger Configuration
|
||||
channel-group-type.easee.charger-config.channel.authorizationRequired.label = Authorization Required
|
||||
channel-group-type.easee.charger-config.channel.authorizationRequired.description = Indicates if authorization is required to start charging.
|
||||
channel-group-type.easee.charger-config.channel.limitToSinglePhaseCharging.label = Limit To Single Phase Charging
|
||||
channel-group-type.easee.charger-config.channel.limitToSinglePhaseCharging.description = Indicates if charging should be limited to single phase mode.
|
||||
channel-group-type.easee.charger-config.channel.lockCablePermanently.label = Lock Cable Permanently
|
||||
channel-group-type.easee.charger-config.channel.lockCablePermanently.description = Lock Cable Permanently status of the wallbox.
|
||||
channel-group-type.easee.charger-config.channel.maxChargerCurrent.label = Max Charger Current
|
||||
channel-group-type.easee.charger-config.channel.maxChargerCurrent.description = Max charging current.
|
||||
channel-group-type.easee.charger-config.channel.phaseMode.label = Phase Mode
|
||||
channel-group-type.easee.charger-config.channel.phaseMode.description = Phase Mode.
|
||||
channel-group-type.easee.charger-latestSession.label = Last Charging Session
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionEnd.label = Session End
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionEnd.description = Date/Time when session ended.
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionEnergy.label = Total Session Energy
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionEnergy.description = Total Energy for last session.
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionStart.label = Session Start
|
||||
channel-group-type.easee.charger-latestSession.channel.sessionStart.description = Date/Time when session started.
|
||||
channel-group-type.easee.charger-state.label = Charger Status
|
||||
channel-group-type.easee.charger-state.channel.cableLocked.label = Cable Locked
|
||||
channel-group-type.easee.charger-state.channel.cableLocked.description = Cable Locked status of the wallbox.
|
||||
channel-group-type.easee.charger-state.channel.chargerFirmware.label = Current Firmware
|
||||
channel-group-type.easee.charger-state.channel.chargerFirmware.description = Current Firmware of the wallbox.
|
||||
channel-group-type.easee.charger-state.channel.chargerOpMode.label = Charger Operation Mode
|
||||
channel-group-type.easee.charger-state.channel.chargerOpMode.description = Current operation mode.
|
||||
channel-group-type.easee.charger-state.channel.dynamicChargerCurrent.label = Dynamic Charger Current
|
||||
channel-group-type.easee.charger-state.channel.dynamicChargerCurrent.description = Dynamic set charging current.
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP1.label = Dynamic Circuit Current P1
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP1.description = Dynamic set circuit current for phase 1.
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP2.label = Dynamic Circuit Current P2
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP2.description = Dynamic set circuit current for phase 2.
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP3.label = Dynamic Circuit Current P3
|
||||
channel-group-type.easee.charger-state.channel.dynamicCircuitCurrentP3.description = Dynamic set circuit current for phase 3.
|
||||
channel-group-type.easee.charger-state.channel.errorCode.label = Error Code
|
||||
channel-group-type.easee.charger-state.channel.errorCode.description = Error Code.
|
||||
channel-group-type.easee.charger-state.channel.fatalErrorCode.label = Fatal Error Code
|
||||
channel-group-type.easee.charger-state.channel.fatalErrorCode.description = Fatal Error Code.
|
||||
channel-group-type.easee.charger-state.channel.isOnline.label = Online
|
||||
channel-group-type.easee.charger-state.channel.isOnline.description = Online status of the wallbox.
|
||||
channel-group-type.easee.charger-state.channel.latestFirmware.label = Latest Firmware
|
||||
channel-group-type.easee.charger-state.channel.latestFirmware.description = Latest Firmware which is available for the wallbox.
|
||||
channel-group-type.easee.charger-state.channel.latestPulse.label = Latest Pulse
|
||||
channel-group-type.easee.charger-state.channel.latestPulse.description = Last data received from charger.
|
||||
channel-group-type.easee.charger-state.channel.lifetimeEnergy.label = Lifetime Energy
|
||||
channel-group-type.easee.charger-state.channel.lifetimeEnergy.description = Total lifetime energy.
|
||||
channel-group-type.easee.charger-state.channel.outputCurrent.label = Output Current
|
||||
channel-group-type.easee.charger-state.channel.outputCurrent.description = Actual charging current.
|
||||
channel-group-type.easee.charger-state.channel.reasonForNoCurrent.label = Reason for no Current
|
||||
channel-group-type.easee.charger-state.channel.reasonForNoCurrent.description = Reason for no Current.
|
||||
channel-group-type.easee.charger-state.channel.sessionEnergy.label = Session Energy
|
||||
channel-group-type.easee.charger-state.channel.sessionEnergy.description = Energy for current session.
|
||||
channel-group-type.easee.charger-state.channel.smartCharging.label = Smart Charging
|
||||
channel-group-type.easee.charger-state.channel.smartCharging.description = Smart Charging status of the wallbox.
|
||||
channel-group-type.easee.charger-state.channel.totalPower.label = Total Power
|
||||
channel-group-type.easee.charger-state.channel.totalPower.description = Total Power for all active phases.
|
||||
channel-group-type.easee.charger-state.channel.voltage.label = Voltage
|
||||
channel-group-type.easee.charger-state.channel.voltage.description = Voltage
|
||||
channel-group-type.easee.circuit-dynamicCurrent.label = Circuit Dynamic Current
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.dynamicCurrents.label = Dynamic Currents
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.dynamicCurrents.description = Dynamic circuit currents for phases 1,2,3.
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase1.label = Dynamic Current P1
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase1.description = Dynamic set circuit current for phase 1.
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase2.label = Dynamic Current P2
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase2.description = Dynamic set circuit current for phase 2.
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase3.label = Dynamic Current P3
|
||||
channel-group-type.easee.circuit-dynamicCurrent.channel.phase3.description = Dynamic set circuit current for phase 3.
|
||||
channel-group-type.easee.circuit-settings.label = Circuit Settings
|
||||
channel-group-type.easee.circuit-settings.channel.allowOfflineMaxCircuitCurrent.label = Allow Offline Max Circuit Current
|
||||
channel-group-type.easee.circuit-settings.channel.allowOfflineMaxCircuitCurrent.description = Allow Offline Max Circuit Current.
|
||||
channel-group-type.easee.circuit-settings.channel.enableIdleCurrent.label = Enable Idle Current
|
||||
channel-group-type.easee.circuit-settings.channel.enableIdleCurrent.description = This will block 6A Idle current for each charger in the current.
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP1.label = Max Current P1
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP1.description = Max circuit current for phase 1.
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP2.label = Max Current P2
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP2.description = Max circuit current for phase 2.
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP3.label = Max Current P3
|
||||
channel-group-type.easee.circuit-settings.channel.maxCircuitCurrentP3.description = Max circuit current for phase 3.
|
||||
channel-group-type.easee.circuit-settings.channel.maxCurrents.label = Max Currents
|
||||
channel-group-type.easee.circuit-settings.channel.maxCurrents.description = Max circuit currents for phases 1,2,3.
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP1.label = Offline Max Current P1
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP1.description = Max circuit current for phase 1 in offline mode.
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP2.label = Offline Max Current P2
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP2.description = Max circuit current for phase 2 in offline mode.
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP3.label = Offline Max Current P3
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCircuitCurrentP3.description = Max circuit current for phase 3 in offline mode.
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCurrents.label = Offline Max Currents
|
||||
channel-group-type.easee.circuit-settings.channel.offlineMaxCurrents.description = Offline Max circuit currents for phases 1,2,3.
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.easee.rwtype-charger-command.label = Charger Command
|
||||
channel-type.easee.rwtype-current.label = Electric Current
|
||||
channel-type.easee.rwtype-currents.label = Currents Phase 1;2;3
|
||||
channel-type.easee.rwtype-integer-phase-mode.label = Phase Mode
|
||||
channel-type.easee.rwtype-integer-phase-mode.state.option.1 = 1-Phase
|
||||
channel-type.easee.rwtype-integer-phase-mode.state.option.2 = Auto
|
||||
channel-type.easee.rwtype-integer-phase-mode.state.option.3 = 3-Phase
|
||||
channel-type.easee.rwtype-switch.label = Switch
|
||||
channel-type.easee.type-current.label = ElectricCurrent
|
||||
channel-type.easee.type-date.label = DateTime
|
||||
channel-type.easee.type-energy.label = Energy
|
||||
channel-type.easee.type-integer-charger-op-mode.label = Charger Operation Mode
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.0 = Offline
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.1 = Disconnected
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.2 = AwaitingStart
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.3 = Charging
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.4 = Completed
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.5 = Error
|
||||
channel-type.easee.type-integer-charger-op-mode.state.option.6 = ReadyToCharge
|
||||
channel-type.easee.type-integer.label = Generic Integer
|
||||
channel-type.easee.type-power.label = Power
|
||||
channel-type.easee.type-switch.label = Switch
|
||||
channel-type.easee.type-volt.label = ElectricPotential
|
||||
|
||||
# status translations
|
||||
|
||||
status.token.validated = "Access token validated"
|
||||
status.waiting.for.bridge = "Waiting for bridge to go online"
|
||||
status.waiting.for.login = "Waiting for web api login"
|
||||
status.no.valid.data = "No valid data received. This is most likely a configuration error"
|
||||
status.no.connection = "Charger might have no internet connection or fuse tripped"
|
@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="easee"
|
||||
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="charger-state">
|
||||
<label>Charger Status</label>
|
||||
<channels>
|
||||
<channel id="smartCharging" typeId="type-switch">
|
||||
<label>Smart Charging</label>
|
||||
<description>Smart Charging status of the wallbox.</description>
|
||||
</channel>
|
||||
<channel id="cableLocked" typeId="type-switch">
|
||||
<label>Cable Locked</label>
|
||||
<description>Cable Locked status of the wallbox.</description>
|
||||
</channel>
|
||||
<channel id="chargerOpMode" typeId="type-integer-charger-op-mode">
|
||||
<label>Charger Operation Mode</label>
|
||||
<description>Current operation mode.</description>
|
||||
</channel>
|
||||
<channel id="totalPower" typeId="type-power">
|
||||
<label>Total Power</label>
|
||||
<description>Total Power for all active phases.</description>
|
||||
</channel>
|
||||
<channel id="sessionEnergy" typeId="type-energy">
|
||||
<label>Session Energy</label>
|
||||
<description>Energy for current session.</description>
|
||||
</channel>
|
||||
<channel id="dynamicCircuitCurrentP1" typeId="type-current">
|
||||
<label>Dynamic Circuit Current P1</label>
|
||||
<description>Dynamic set circuit current for phase 1.</description>
|
||||
</channel>
|
||||
<channel id="dynamicCircuitCurrentP2" typeId="type-current">
|
||||
<label>Dynamic Circuit Current P2</label>
|
||||
<description>Dynamic set circuit current for phase 2.</description>
|
||||
</channel>
|
||||
<channel id="dynamicCircuitCurrentP3" typeId="type-current">
|
||||
<label>Dynamic Circuit Current P3</label>
|
||||
<description>Dynamic set circuit current for phase 3.</description>
|
||||
</channel>
|
||||
<channel id="latestPulse" typeId="type-date">
|
||||
<label>Latest Pulse</label>
|
||||
<description>Last data received from charger.</description>
|
||||
</channel>
|
||||
<channel id="chargerFirmware" typeId="type-integer">
|
||||
<label>Current Firmware</label>
|
||||
<description>Current Firmware of the wallbox.</description>
|
||||
</channel>
|
||||
<channel id="latestFirmware" typeId="type-integer">
|
||||
<label>Latest Firmware</label>
|
||||
<description>Latest Firmware which is available for the wallbox.</description>
|
||||
</channel>
|
||||
<channel id="voltage" typeId="type-volt">
|
||||
<label>Voltage</label>
|
||||
<description>Voltage</description>
|
||||
</channel>
|
||||
<channel id="outputCurrent" typeId="type-current">
|
||||
<label>Output Current</label>
|
||||
<description>Actual charging current.</description>
|
||||
</channel>
|
||||
<channel id="isOnline" typeId="type-switch">
|
||||
<label>Online</label>
|
||||
<description>Online status of the wallbox.</description>
|
||||
</channel>
|
||||
<channel id="dynamicChargerCurrent" typeId="rwtype-current">
|
||||
<label>Dynamic Charger Current</label>
|
||||
<description>Dynamic set charging current.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">ChangeConfiguration</property>
|
||||
<property name="validationExpression">(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="reasonForNoCurrent" typeId="type-integer">
|
||||
<label>Reason for no Current</label>
|
||||
<description>Reason for no Current.</description>
|
||||
</channel>
|
||||
<channel id="lifetimeEnergy" typeId="type-energy">
|
||||
<label>Lifetime Energy</label>
|
||||
<description>Total lifetime energy.</description>
|
||||
</channel>
|
||||
<channel id="errorCode" typeId="type-integer">
|
||||
<label>Error Code</label>
|
||||
<description>Error Code.</description>
|
||||
</channel>
|
||||
<channel id="fatalErrorCode" typeId="type-integer">
|
||||
<label>Fatal Error Code</label>
|
||||
<description>Fatal Error Code.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="charger-config">
|
||||
<label>Charger Configuration</label>
|
||||
<channels>
|
||||
<channel id="lockCablePermanently" typeId="rwtype-switch">
|
||||
<label>Lock Cable Permanently</label>
|
||||
<description>Lock Cable Permanently status of the wallbox.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">ChangeConfiguration</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="authorizationRequired" typeId="rwtype-switch">
|
||||
<label>Authorization Required</label>
|
||||
<description>Indicates if authorization is required to start charging.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">ChangeConfiguration</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="limitToSinglePhaseCharging" typeId="rwtype-switch">
|
||||
<label>Limit To Single Phase Charging</label>
|
||||
<description>Indicates if charging should be limited to single phase mode.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">ChangeConfiguration</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="phaseMode" typeId="rwtype-integer-phase-mode">
|
||||
<label>Phase Mode</label>
|
||||
<description>Phase Mode.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">ChangeConfiguration</property>
|
||||
<property name="validationExpression">(1|2|3)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="maxChargerCurrent" typeId="type-current">
|
||||
<label>Max Charger Current</label>
|
||||
<description>Max charging current.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="charger-latestSession">
|
||||
<label>Last Charging Session</label>
|
||||
<channels>
|
||||
<channel id="sessionEnergy" typeId="type-energy">
|
||||
<label>Total Session Energy</label>
|
||||
<description>Total Energy for last session.</description>
|
||||
</channel>
|
||||
<channel id="sessionStart" typeId="type-date">
|
||||
<label>Session Start</label>
|
||||
<description>Date/Time when session started.</description>
|
||||
</channel>
|
||||
<channel id="sessionEnd" typeId="type-date">
|
||||
<label>Session End</label>
|
||||
<description>Date/Time when session ended.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="charger-commands">
|
||||
<label>Charger Commands</label>
|
||||
<channels>
|
||||
<channel id="genericCommand" typeId="rwtype-charger-command">
|
||||
<label>Generic Charger Command</label>
|
||||
<description>Sends a command to the charger. Write only channel.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SendCommand</property>
|
||||
<property name="validationExpression">(reboot|update_firmware|poll_all|smart_charging|start_charging|stop_charging|pause_charging|resume_charging|toggle_charging|override_schedule)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="startStop" typeId="rwtype-switch">
|
||||
<label>Start/Stop Charging</label>
|
||||
<description>Starts/Stops charging. Only relevant if authorization is required.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SendCommandStartStop</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="pauseResume" typeId="rwtype-switch">
|
||||
<label>Pause/Resume Charging</label>
|
||||
<description>Pauses/Resumes charging. Pausing works by setting Dynamic Charger Current to 0.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SendCommandPauseResume</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
</channels>
|
||||
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="easee"
|
||||
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="circuit-settings">
|
||||
<label>Circuit Settings</label>
|
||||
<channels>
|
||||
<channel id="maxCircuitCurrentP1" typeId="type-current">
|
||||
<label>Max Current P1</label>
|
||||
<description>Max circuit current for phase 1.</description>
|
||||
</channel>
|
||||
<channel id="maxCircuitCurrentP2" typeId="type-current">
|
||||
<label>Max Current P2</label>
|
||||
<description>Max circuit current for phase 2.</description>
|
||||
</channel>
|
||||
<channel id="maxCircuitCurrentP3" typeId="type-current">
|
||||
<label>Max Current P3</label>
|
||||
<description>Max circuit current for phase 3.</description>
|
||||
</channel>
|
||||
<channel id="maxCurrents" typeId="rwtype-currents">
|
||||
<label>Max Currents</label>
|
||||
<description>Max circuit currents for phases 1,2,3.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SetMaxCircuitCurrents</property>
|
||||
<property name="validationExpression">(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="offlineMaxCircuitCurrentP1" typeId="type-current">
|
||||
<label>Offline Max Current P1</label>
|
||||
<description>Max circuit current for phase 1 in offline mode.</description>
|
||||
</channel>
|
||||
<channel id="offlineMaxCircuitCurrentP2" typeId="type-current">
|
||||
<label>Offline Max Current P2</label>
|
||||
<description>Max circuit current for phase 2 in offline mode.</description>
|
||||
</channel>
|
||||
<channel id="offlineMaxCircuitCurrentP3" typeId="type-current">
|
||||
<label>Offline Max Current P3</label>
|
||||
<description>Max circuit current for phase 3 in offline mode.</description>
|
||||
</channel>
|
||||
<channel id="offlineMaxCurrents" typeId="rwtype-currents">
|
||||
<label>Offline Max Currents</label>
|
||||
<description>Offline Max circuit currents for phases 1,2,3.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SetOfflineMaxCircuitCurrents</property>
|
||||
<property name="validationExpression">(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="enableIdleCurrent" typeId="rwtype-switch">
|
||||
<label>Enable Idle Current</label>
|
||||
<description>This will block 6A Idle current for each charger in the current.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SetCircuitSettings</property>
|
||||
<property name="validationExpression">.*</property>
|
||||
</properties>
|
||||
</channel>
|
||||
<channel id="allowOfflineMaxCircuitCurrent" typeId="type-switch">
|
||||
<label>Allow Offline Max Circuit Current</label>
|
||||
<description>Allow Offline Max Circuit Current.</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="circuit-dynamicCurrent">
|
||||
<label>Circuit Dynamic Current</label>
|
||||
<channels>
|
||||
<channel id="phase1" typeId="type-current">
|
||||
<label>Dynamic Current P1</label>
|
||||
<description>Dynamic set circuit current for phase 1.</description>
|
||||
</channel>
|
||||
<channel id="phase2" typeId="type-current">
|
||||
<label>Dynamic Current P2</label>
|
||||
<description>Dynamic set circuit current for phase 2.</description>
|
||||
</channel>
|
||||
<channel id="phase3" typeId="type-current">
|
||||
<label>Dynamic Current P3</label>
|
||||
<description>Dynamic set circuit current for phase 3.</description>
|
||||
</channel>
|
||||
<channel id="dynamicCurrents" typeId="rwtype-currents">
|
||||
<label>Dynamic Currents</label>
|
||||
<description>Dynamic circuit currents for phases 1,2,3.</description>
|
||||
<properties>
|
||||
<property name="writeCommand">SetDynamicCircuitCurrents</property>
|
||||
<property name="validationExpression">(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32);(0|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32)</property>
|
||||
</properties>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="easee"
|
||||
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="type-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-energy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy</label>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>ElectricCurrent</label>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="type-volt">
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>ElectricPotential</label>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-integer">
|
||||
<item-type>Number</item-type>
|
||||
<label>Generic Integer</label>
|
||||
<state pattern="%d" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-integer-charger-op-mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Charger Operation Mode</label>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Offline</option>
|
||||
<option value="1">Disconnected</option>
|
||||
<option value="2">AwaitingStart</option>
|
||||
<option value="3">Charging</option>
|
||||
<option value="4">Completed</option>
|
||||
<option value="5">Error</option>
|
||||
<option value="6">ReadyToCharge</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-date">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>DateTime</label>
|
||||
<state readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="easee"
|
||||
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="rwtype-switch">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch</label>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="rwtype-current">
|
||||
<item-type>Number:ElectricCurrent</item-type>
|
||||
<label>Electric Current</label>
|
||||
<state pattern="%.1f %unit%" readOnly="false">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rwtype-integer-phase-mode">
|
||||
<item-type>Number</item-type>
|
||||
<label>Phase Mode</label>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="1">1-Phase</option>
|
||||
<option value="2">Auto</option>
|
||||
<option value="3">3-Phase</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rwtype-currents">
|
||||
<item-type>String</item-type>
|
||||
<label>Currents Phase 1;2;3</label>
|
||||
<state pattern="%s" readOnly="false">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="rwtype-charger-command">
|
||||
<item-type>String</item-type>
|
||||
<label>Charger Command</label>
|
||||
<state pattern="%s" readOnly="false">
|
||||
</state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="easee"
|
||||
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="site">
|
||||
<label>Easee Site</label>
|
||||
<description>Cloud connection to an Easee site.</description>
|
||||
<config-description-ref uri="thing-type:easee:site"/>
|
||||
</bridge-type>
|
||||
<thing-type id="mastercharger">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="site"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Easee Master Charger</label>
|
||||
<description>Cloud connected Easee Master Charger which also controls the circuit.</description>
|
||||
<channel-groups>
|
||||
<channel-group typeId="charger-state" id="state"/>
|
||||
<channel-group typeId="charger-config" id="config"/>
|
||||
<channel-group typeId="charger-commands" id="commands"/>
|
||||
<channel-group typeId="charger-latestSession" id="latestSession"/>
|
||||
<channel-group typeId="circuit-dynamicCurrent" id="dynamicCurrent"/>
|
||||
<channel-group typeId="circuit-settings" id="settings"/>
|
||||
</channel-groups>
|
||||
<config-description-ref uri="thing-type:easee:mastercharger"/>
|
||||
</thing-type>
|
||||
<thing-type id="charger">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="site"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Easee Charger</label>
|
||||
<description>Cloud connected Easee Charger.</description>
|
||||
<channel-groups>
|
||||
<channel-group typeId="charger-state" id="state"/>
|
||||
<channel-group typeId="charger-config" id="config"/>
|
||||
<channel-group typeId="charger-commands" id="commands"/>
|
||||
<channel-group typeId="charger-latestSession" id="latestSession"/>
|
||||
</channel-groups>
|
||||
<config-description-ref uri="thing-type:easee:charger"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
@ -109,6 +109,7 @@
|
||||
<module>org.openhab.binding.dsmr</module>
|
||||
<module>org.openhab.binding.dwdpollenflug</module>
|
||||
<module>org.openhab.binding.dwdunwetter</module>
|
||||
<module>org.openhab.binding.easee</module>
|
||||
<module>org.openhab.binding.ecobee</module>
|
||||
<module>org.openhab.binding.ecotouch</module>
|
||||
<module>org.openhab.binding.ekey</module>
|
||||
|
Loading…
Reference in New Issue
Block a user