]> git.basschouten.com Git - openhab-addons.git/commitdiff
[evcc] Initial contribution (#12611)
authorFlorian Hotze <florianh_dev@icloud.com>
Thu, 12 May 2022 17:31:45 +0000 (19:31 +0200)
committerGitHub <noreply@github.com>
Thu, 12 May 2022 17:31:45 +0000 (19:31 +0200)
Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
20 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.evcc/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.evcc/README.md [new file with mode: 0644]
bundles/org.openhab.binding.evcc/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccAPI.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccApiException.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Loadpoint.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Result.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Status.java [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/binding/binding.xml [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc.properties [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc_de.properties [new file with mode: 0644]
bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 07176c0424207547661623b237672beeafc62dfe..110ba738a9b8e80578a0470336fb7df0b7962ba5 100644 (file)
@@ -90,6 +90,7 @@
 /bundles/org.openhab.binding.enturno/ @klocsson
 /bundles/org.openhab.binding.epsonprojector/ @mlobstein
 /bundles/org.openhab.binding.etherrain/ @dfad1469
+/bundles/org.openhab.binding.evcc/ @florian-h05
 /bundles/org.openhab.binding.evohome/ @Nebula83
 /bundles/org.openhab.binding.exec/ @kgoderis
 /bundles/org.openhab.binding.feed/ @svilenvul
index e4b65e93f829315ffa4fd698eebfd9e8b47d1d22..3218cb7841bdff155688c5a6414e3b23549c2a7a 100644 (file)
       <artifactId>org.openhab.binding.etherrain</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.evcc</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.evohome</artifactId>
diff --git a/bundles/org.openhab.binding.evcc/NOTICE b/bundles/org.openhab.binding.evcc/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -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
diff --git a/bundles/org.openhab.binding.evcc/README.md b/bundles/org.openhab.binding.evcc/README.md
new file mode 100644 (file)
index 0000000..8b83a6d
--- /dev/null
@@ -0,0 +1,171 @@
+# evcc Binding
+
+This binding integrates [evcc - electric vehicle charging control](https://evcc.io), a project that provides a control center for electric vehicle charging.
+
+evcc controls your wallbox(es) with multiple charging modes and allows you to charge your ev with your photovoltaik's excess current.
+To provide an intelligent charging control, evcc supports over 30 wallboxes and over 20 energy meters/home energy management systems from many manufacturers as well as electric vehicles from over 20 car manufacturers.
+Furthermore, evcc calculates your money savings.
+
+This binding enables openHAB to retrieve status data from your evcc installation and to control the charging process.
+For more advanced features like calculated savings, you have to visit the web UI of evcc.
+
+## Supported Things
+
+- `device`: A running evcc installation.
+
+## Discovery
+
+No auto discovery supported.
+
+## Thing Configuration
+
+### `device` Thing Configuration
+
+| Parameter       | Type   | Description                                              | Advanced | Required |
+|-----------------|--------|----------------------------------------------------------|----------|----------|
+| url             | String | URL of evcc web UI, e.g. *https://demo.evcc.io*          | No       | Yes      |
+| refreshInterval | Number | Interval the status is polled in seconds (minimum is 15) | Yes      | No       |
+
+Default value for *refreshInterval* is 60 seconds.
+
+## Channels
+
+### General channels
+
+Those channels exist only once.
+Please note that some of them are only available when evcc is properly configured.
+
+| Channel                    | Type                 | Read/Write | Description                                                                                                  |
+|----------------------------|----------------------|------------|--------------------------------------------------------------------------------------------------------------|
+| general#batteryPower       | Number:Power         | R          | Current power from battery.                                                                                  |
+| general#batterySoC         | Number:Dimensionless | R          | Current State of Charge of battery.                                                                          |
+| general#batteryPrioritySoC | Number:Dimensionless | R          | State of State of Charge for which the battery has priority over charging the ev when charging mode is "pv". |
+| general#gridPower          | Number:Power         | R          | Current power from grid (negative means feed-in)                                                             |
+| general#homePower          | Number:Power         | R          | Current power taken by home.                                                                                 |
+| general#pvPower            | Number:Power         | R          | Current power from photovoltaik.                                                                             |
+
+
+### Loadpoint channels
+
+Those channels exist per configured loadpoint.
+Please note that you have to replace *N* with your loadpoint number.
+
+| Channel                             | Type                   | Read/Write | Description                                                                                         |
+|-------------------------------------|------------------------|------------|-----------------------------------------------------------------------------------------------------|
+| loadpointN#activePhases             | Number                 | R          | Current number of active phases while charging                                                      |
+| loadpointN#chargeCurrent            | Number:ElectricCurrent | R          | Current amperage per connected phase while charging                                                 |
+| loadpointN#chargeDuration           | Number:Time            | R          | Charging duration                                                                                   |
+| loadpointN#chargeRemainingDuration  | Number:Time            | R          | Remaining duration until target SoC is reached                                                      |
+| loadpointN#chargeRemainingEnergy    | Number:Energy          | R          | Remaining energy until target SoC is reached                                                        |
+| loadpointN#chargePower              | Number:Power           | R          | Current power of charging                                                                           |
+| loadpointN#chargedEnergy            | Number:Energy          | R          | Energy charged since plugged-in                                                                     |
+| loadpointN#charging                 | Switch                 | R          | Loadpoint is currently charging                                                                     |
+| loadpointN#enabled                  | Switch                 | R          | Charging enabled (mode is not "off")                                                                |
+| loadpointN#hasVehicle               | Switch                 | R          | Whether vehicle is configured for loadpoint                                                         |
+| loadpointN#maxCurrent               | Number:ElectricCurrent | RW         | Maximum amperage per connected phase with which the car should be charged                           |
+| loadpointN#minCurrent               | Number:ElectricCurrent | RW         | Minimum amperage per connected phase with which the car should be charged                           |
+| loadpointN#minSoC                   | Number:Dimensionless   | RW         | Charge immediately with maximum power up to the defined SoC, if the charge mode is not set to "off" |
+| loadpointN#mode                     | String                 | RW         | Charging mode: "off", "now", "minpv", "pv"                                                          |
+| loadpointN#phases                   | Number                 | RW         | The maximum number of phases which can be used                                                      |
+| loadpointN#targetSoC                | Number:Dimensionless   | RW         | Until which state of charge (SoC) should the vehicle be charged                                     |
+| loadpointN#targetTime               | DateTime               | RW         | When the target SoC should be reached                                                               |
+| loadpointN#targetTimeEnabled        | Switch                 | RW         | Target time for charging enabled                                                                    |
+| loadpointN#title                    | String                 | R          | Title of loadpoint                                                                                  |
+| loadpointN#vehicleConnected         | Switch                 | R          | Whether vehicle is connected to loadpoint                                                           |
+| loadpointN#vehicleConnectedDuration | Number:Time            | R          | Duration the vehicle is connected to loadpoint                                                      |
+| loadpointN#vehicleCapacity          | Number:Energy          | R          | Capacity of EV battery                                                                              |
+| loadpointN#vehicleOdometer          | Number:Length          | R          | Total distance travelled by EV                                                                      |
+| loadpointN#vehiclePresent           | Switch                 | R          | Whether evcc is able to get data from vehicle                                                       |
+| loadpointN#vehicleRange             | Number:Length          | R          | Battery range for EV                                                                                |
+| loadpointN#vehicleSoC               | Number:Dimensionless   | R          | Current State of Charge of EV                                                                       |
+| loadpointN#vehicleTitle             | String                 | R          | Name of EV                                                                                          |
+
+## Full Example
+
+### Thing(s)
+
+```
+Thing evcc:device:demo "evcc Demo" [url="https://demo.evcc.io", refreshInterval=60]
+```
+
+### Items
+
+```
+// General
+Number:Power              evcc_demo_batteryPower                           "Battery Power [%.1f kW]"                         <energy>          {channel="evcc:device:demo:general#batteryPower"}
+Number:Dimensionless      evcc_demo_batterySoC                             "Battery SoC [%d %%]"                             <batterylevel>    {channel="evcc:device:demo:general#batterySoC"}
+Number:Dimensionless      evcc_demo_batteryPrioritySoC                     "Battery Priority SoC [%d %%]"                    <batterylevel>    {channel="evcc:device:demo:general#batteryPrioritySoC"}
+Number:Power              evcc_demo_gridPower                              "Grid Power [%.1f kW]"                            <energy>          {channel="evcc:device:demo:general#gridPower"}
+Number:Power              evcc_demo_homePower                              "Home Power [%.1f kW]"                            <energy>          {channel="evcc:device:demo:general#homePower"}
+Number:Power              evcc_demo_pvPower                                "PV Power [%.1f kW]"                              <energy>          {channel="evcc:device:demo:general#pvPower"}
+
+// Loadpoint
+Number                    evcc_demo_loadpoint0_activePhases                "Active Phases [%d]"                                                {channel="evcc:device:demo:loadpoint0#activePhases"}
+Number:ElectricCurrent    evcc_demo_loadpoint0_chargeCurrent               "Charging current [%.0f A]"                       <energy>          {channel="evcc:device:demo:loadpoint0#chargeCurrent"}
+Number:Time               evcc_demo_loadpoint0_chargeDuration              "Charging duration [%1$tH:%1$tM]"                 <time>            {channel="evcc:device:demo:loadpoint0#chargeDuration"}
+Number:Time               evcc_demo_loadpoint0_chargeRemainingDuration     "Charging remaining duration [%1$tH:%1$tM]"       <time>            {channel="evcc:device:demo:loadpoint0#chargeRemainingDuration"}
+Number:Energy             evcc_demo_loadpoint0_chargeRemainingEnergy       "Charging remaining energy [%.1f kWh]"            <energy>          {channel="evcc:device:demo:loadpoint0#chargeRemainingEnergy"}
+Number:Power              evcc_demo_loadpoint0_chargePower                 "Charging power [%.1f kW]"                        <energy>          {channel="evcc:device:demo:loadpoint0#chargePower"}
+Number:Energy             evcc_demo_loadpoint0_chargedEnergy               "Charged energy [%.1f kWh]"                       <energy>          {channel="evcc:device:demo:loadpoint0#chargedEnergy"}
+Switch                    evcc_demo_loadpoint0_charging                    "Currently charging [%s]"                         <battery>         {channel="evcc:device:demo:loadpoint0#charging"}
+Switch                    evcc_demo_loadpoint0_enabled                     "Charging enabled [%s]"                           <switch>          {channel="evcc:device:demo:loadpoint0#enabled"}
+Switch                    evcc_demo_loadpoint0_hasVehicle                  "Vehicle configured [%s]"                         <switch>          {channel="evcc:device:demo:loadpoint0#hasVehicle"}
+Number:ElectricCurrent    evcc_demo_loadpoint0_maxCurrent                  "Maximum current [%.0f A]"                        <energy>          {channel="evcc:device:demo:loadpoint0#maxCurrent"}
+Number:ElectricCurrent    evcc_demo_loadpoint0_minCurrent                  "Minimum current [%.0f A]"                        <energy>          {channel="evcc:device:demo:loadpoint0#minCurrent"}
+Number:Dimensionless      evcc_demo_loadpoint0_minSoC                      "Minimum SoC [%d %%]"                             <batterylevel>    {channel="evcc:device:demo:loadpoint0#minSoC"}
+String                    evcc_demo_loadpoint0_mode                        "Mode [%s]"                                                         {channel="evcc:device:demo:loadpoint0#mode"}
+Number                    evcc_demo_loadpoint0_phases                      "Enabled phases [%d]"                                               {channel="evcc:device:demo:loadpoint0#phases"}
+Number:Dimensionless      evcc_demo_loadpoint0_targetSoC                   "Target SoC [%d %%]"                              <batterylevel>    {channel="evcc:device:demo:loadpoint0#targetSoC"}
+DateTime                  evcc_demo_loadpoint0_targetTime                  "Target time [%1$td.%1$tm.%1$tY, %1$tH:%1$tM]"    <time>            {channel="evcc:device:demo:loadpoint0#targetTime"}
+Switch                    evcc_demo_loadpoint0_targetTimeEnabled           "Target time enabled [%s]"                        <switch>          {channel="evcc:device:demo:loadpoint0#targetTimeEnabled"}
+String                    evcc_demo_loadpoint0_title                       "Loadpoint title [%s]"                            <text>            {channel="evcc:device:demo:loadpoint0#title"}
+// Vehicle on loadpoint
+Switch                    evcc_demo_loadpoint0_vehicleConnected            "Vehicle connected [%s]"                          <switch>          {channel="evcc:device:demo:loadpoint0#vehicleConnected"}
+Number:Time               evcc_demo_loadpoint0_vehicleConnectedDuration    "Vehicle connected duration [%.1f h]"             <time>            {channel="evcc:device:demo:loadpoint0#vehicleConnectedDuration"}
+Number:Energy             evcc_demo_loadpoint0_vehicleCapacity             "Vehicle capacity [%.0f kWH]"                     <batterylevel>    {channel="evcc:device:demo:loadpoint0#vehicleCapacity"}
+Number:Length             evcc_demo_loadpoint0_vehicleOdometer             "Vehicle odometer [%.1f km]"                                        {channel="evcc:device:demo:loadpoint0#vehicleOdometer"}
+Switch                    evcc_demo_loadpoint0_vehiclePresent              "Vehicle present [%s]"                            <switch>          {channel="evcc:device:demo:loadpoint0#vehiclePresent"}
+Number:Length             evcc_demo_loadpoint0_vehicleRange                "Vehicle Range [%.0f km]"                                           {channel="evcc:device:demo:loadpoint0#vehicleRange"}
+Number:Dimensionless      evcc_demo_loadpoint0_vehicleSoC                  "Vehicle SoC [%d %%]"                             <batterylevel>    {channel="evcc:device:demo:loadpoint0#vehicleSoC"}
+String                    evcc_demo_loadpoint0_vehicleName                 "Vehicle name [%s]"                               <text>            {channel="evcc:device:demo:loadpoint0#vehicleTitle"}
+```
+
+### Sitemap
+
+```
+sitemap evcc label="evcc Demo" {
+    Frame label="General" {
+        Text item=evcc_demo_batteryPower
+        Text item=evcc_demo_batterySoC
+        Text item=evcc_demo_gridPower
+        Text item=evcc_demo_homePower
+        Text item=evcc_demo_pvPower
+    }
+    Frame label="Loadpoint 0" {
+        Text item=evcc_demo_loadpoint0_title
+        Text item=evcc_demo_loadpoint0_enabled label="Charging" {
+            Text item=evcc_demo_loadpoint0_charging
+            Text item=evcc_demo_loadpoint0_chargePower
+            Text item=evcc_demo_loadpoint0_chargeCurrent
+            Text item=evcc_demo_loadpoint0_activePhases
+            Text item=evcc_demo_loadpoint0_chargeDuration
+            Text item=evcc_demo_loadpoint0_chargeRemainingDuration
+            Text item=evcc_demo_loadpoint0_chargeRemainingEnergy
+        }
+        Switch item=evcc_demo_loadpoint0_mode mappings=["off"="Stop","now"="Now","minpv"="Min + PV", "pv"="Only PV"]
+        Text label="Charging settings" icon="settings" {
+            Setpoint item=evcc_demo_loadpoint0_targetSoC minValue=5 maxValue=100 step=5
+            Setpoint item=evcc_demo_loadpoint0_minCurrent minValue=6 maxValue=96 step=2
+            Setpoint item=evcc_demo_loadpoint0_maxCurrent minValue=6 maxValue=96 step=2
+            Setpoint item=evcc_demo_loadpoint0_minSoC minValue=0 maxValue=100 step=5
+            Setpoint item=evcc_demo_loadpoint0_phases minValue=1 maxValue=3 step=2
+        }
+        Text item=evcc_demo_loadpoint0_vehicleName label="Vehicle" {
+            Text item=evcc_demo_loadpoint0_vehicleCapacity
+            Text item=evcc_demo_loadpoint0_vehicleOdometer
+            Text item=evcc_demo_loadpoint0_vehicleRange
+            Text item=evcc_demo_loadpoint0_vehicleSoC
+        }
+    }
+}
+```
diff --git a/bundles/org.openhab.binding.evcc/pom.xml b/bundles/org.openhab.binding.evcc/pom.xml
new file mode 100644 (file)
index 0000000..0c00c51
--- /dev/null
@@ -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.3.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.evcc</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: evcc Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.evcc/src/main/feature/feature.xml b/bundles/org.openhab.binding.evcc/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..0ebf35d
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.evcc-${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-evcc" description="evcc Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.evcc/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccBindingConstants.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccBindingConstants.java
new file mode 100644 (file)
index 0000000..e8b10ed
--- /dev/null
@@ -0,0 +1,131 @@
+/**
+ * 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.evcc.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.type.ChannelTypeUID;
+
+/**
+ * The {@link EvccBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+public class EvccBindingConstants {
+
+    private static final String BINDING_ID = "evcc";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
+
+    // List of all Channel Type UIDs
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_BATTERY_POWER = new ChannelTypeUID(BINDING_ID, "batteryPower");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_BATTERY_SOC = new ChannelTypeUID(BINDING_ID, "batterySoC");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_BATTERY_PRIORITY_SOC = new ChannelTypeUID(BINDING_ID,
+            "batteryPrioritySoC");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_GRID_POWER = new ChannelTypeUID(BINDING_ID, "gridPower");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_HOME_POWER = new ChannelTypeUID(BINDING_ID, "homePower");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_PV_POWER = new ChannelTypeUID(BINDING_ID, "pvPower");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_ACTIVE_PHASES = new ChannelTypeUID(BINDING_ID,
+            "activePhases");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGE_CURRENT = new ChannelTypeUID(BINDING_ID,
+            "chargeCurrent");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGE_DURATION = new ChannelTypeUID(BINDING_ID,
+            "chargeDuration");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGE_POWER = new ChannelTypeUID(BINDING_ID,
+            "chargePower");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGE_REMAINING_DURATION = new ChannelTypeUID(
+            BINDING_ID, "chargeRemainingDuration");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGE_REMAINING_ENERGY = new ChannelTypeUID(
+            BINDING_ID, "chargeRemainingEnergy");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGED_ENERGY = new ChannelTypeUID(BINDING_ID,
+            "chargedEnergy");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CHARGING = new ChannelTypeUID(BINDING_ID, "charging");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CONNECTED = new ChannelTypeUID(BINDING_ID,
+            "vehicleConnected");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_CONNECTED_DURATION = new ChannelTypeUID(BINDING_ID,
+            "vehicleConnectedDuration");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_ENABLED = new ChannelTypeUID(BINDING_ID, "enabled");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_HAS_VEHICLE = new ChannelTypeUID(BINDING_ID,
+            "hasVehicle");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_MAX_CURRENT = new ChannelTypeUID(BINDING_ID,
+            "maxCurrent");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_MIN_CURRENT = new ChannelTypeUID(BINDING_ID,
+            "minCurrent");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_MIN_SOC = new ChannelTypeUID(BINDING_ID, "minSoC");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_MODE = new ChannelTypeUID(BINDING_ID, "mode");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_PHASES = new ChannelTypeUID(BINDING_ID, "phases");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_TARGET_SOC = new ChannelTypeUID(BINDING_ID,
+            "targetSoC");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_TARGET_TIME = new ChannelTypeUID(BINDING_ID,
+            "targetTime");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_TARGET_TIME_ENABLED = new ChannelTypeUID(BINDING_ID,
+            "targetTimeEnabled");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_TITLE = new ChannelTypeUID(BINDING_ID, "title");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_CAPACITY = new ChannelTypeUID(BINDING_ID,
+            "vehicleCapacity");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_ODOMETER = new ChannelTypeUID(BINDING_ID,
+            "vehicleOdometer");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_PRESENT = new ChannelTypeUID(BINDING_ID,
+            "vehiclePresent");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_RANGE = new ChannelTypeUID(BINDING_ID,
+            "vehicleRange");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_SOC = new ChannelTypeUID(BINDING_ID,
+            "vehicleSoC");
+    public static final ChannelTypeUID CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_TITLE = new ChannelTypeUID(BINDING_ID,
+            "vehicleTitle");
+
+    // List of all Channel ids
+    public static final String CHANNEL_BATTERY_POWER = "batteryPower";
+    public static final String CHANNEL_BATTERY_SOC = "batterySoC";
+    public static final String CHANNEL_BATTERY_PRIORITY_SOC = "batteryPrioritySoC";
+    public static final String CHANNEL_GRID_POWER = "gridPower";
+    public static final String CHANNEL_HOME_POWER = "homePower";
+    public static final String CHANNEL_PV_POWER = "pvPower";
+    public static final String CHANNEL_LOADPOINT_ACTIVE_PHASES = "activePhases";
+    public static final String CHANNEL_LOADPOINT_CHARGE_CURRENT = "chargeCurrent";
+    public static final String CHANNEL_LOADPOINT_CHARGE_DURATION = "chargeDuration";
+    public static final String CHANNEL_LOADPOINT_CHARGE_POWER = "chargePower";
+    public static final String CHANNEL_LOADPOINT_CHARGE_REMAINING_DURATION = "chargeRemainingDuration";
+    public static final String CHANNEL_LOADPOINT_CHARGE_REMAINING_ENERGY = "chargeRemainingEnergy";
+    public static final String CHANNEL_LOADPOINT_CHARGED_ENERGY = "chargedEnergy";
+    public static final String CHANNEL_LOADPOINT_CHARGING = "charging";
+    public static final String CHANNEL_LOADPOINT_CONNECTED = "vehicleConnected";
+    public static final String CHANNEL_LOADPOINT_CONNECTED_DURATION = "vehicleConnectedDuration";
+    public static final String CHANNEL_LOADPOINT_ENABLED = "enabled";
+    public static final String CHANNEL_LOADPOINT_HAS_VEHICLE = "hasVehicle";
+    public static final String CHANNEL_LOADPOINT_MAX_CURRENT = "maxCurrent";
+    public static final String CHANNEL_LOADPOINT_MIN_CURRENT = "minCurrent";
+    public static final String CHANNEL_LOADPOINT_MIN_SOC = "minSoC";
+    public static final String CHANNEL_LOADPOINT_MODE = "mode";
+    public static final String CHANNEL_LOADPOINT_PHASES = "phases";
+    public static final String CHANNEL_LOADPOINT_TARGET_SOC = "targetSoC";
+    public static final String CHANNEL_LOADPOINT_TARGET_TIME = "targetTime";
+    /**
+     * Whether a target time is set on loadpoint.
+     */
+    public static final String CHANNEL_LOADPOINT_TARGET_TIME_ENABLED = "targetTimeEnabled";
+    public static final String CHANNEL_LOADPOINT_TITLE = "title";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_CAPACITY = "vehicleCapacity";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_ODOMETER = "vehicleOdometer";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_PRESENT = "vehiclePresent";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_RANGE = "vehicleRange";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_SOC = "vehicleSoC";
+    public static final String CHANNEL_LOADPOINT_VEHICLE_TITLE = "vehicleTitle";
+
+    public static final int CONNECTION_TIMEOUT_MILLISEC = 5000;
+    public static final int LONG_CONNECTION_TIMEOUT_MILLISEC = 60000;
+    public static final String EVCC_REST_API = "/api/";
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccConfiguration.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccConfiguration.java
new file mode 100644 (file)
index 0000000..afca028
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.evcc.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link EvccConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+public class EvccConfiguration {
+
+    /**
+     * URL of the evcc instance, e.g. https://demo.evcc.io
+     */
+    public @Nullable String url;
+    /**
+     * Interval for state fetching in seconds.
+     */
+    public int refreshInterval = 60;
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandler.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandler.java
new file mode 100644 (file)
index 0000000..f1c3948
--- /dev/null
@@ -0,0 +1,448 @@
+/**
+ * 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.evcc.internal;
+
+import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.evcc.internal.api.EvccAPI;
+import org.openhab.binding.evcc.internal.api.EvccApiException;
+import org.openhab.binding.evcc.internal.api.dto.Loadpoint;
+import org.openhab.binding.evcc.internal.api.dto.Result;
+import org.openhab.core.library.CoreItemFactory;
+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.MetricPrefix;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link EvccHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+public class EvccHandler extends BaseThingHandler {
+    private final Logger logger = LoggerFactory.getLogger(EvccHandler.class);
+    private @Nullable EvccAPI evccAPI;
+    private @Nullable ScheduledFuture<?> statePollingJob;
+
+    private @Nullable Result result;
+
+    private boolean batteryConfigured = false;
+    private boolean gridConfigured = false;
+    private boolean pvConfigured = false;
+
+    private int targetSoC = 100;
+    private boolean targetTimeEnabled = false;
+    private ZonedDateTime targetTimeZDT = ZonedDateTime.now().plusHours(12);
+
+    public EvccHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command.equals(RefreshType.REFRESH)) {
+            refresh();
+        } else {
+            logger.debug("Handling command {} ({}) for channel {}", command, command.getClass(), channelUID);
+            String groupId = channelUID.getGroupId();
+            if (groupId == null) {
+                return;
+            }
+            String channelIdWithoutGroup = channelUID.getIdWithoutGroup();
+            int loadpoint = Integer.parseInt(groupId.toString().substring(9));
+            EvccAPI evccAPI = this.evccAPI;
+            if (evccAPI == null) {
+                return;
+            }
+            try {
+                switch (channelIdWithoutGroup) {
+                    case CHANNEL_LOADPOINT_MODE:
+                        if (command instanceof StringType) {
+                            evccAPI.setMode(loadpoint, command.toString());
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_MIN_SOC:
+                        if (command instanceof QuantityType) {
+                            evccAPI.setMinSoC(loadpoint, ((QuantityType<?>) command).intValue());
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_TARGET_SOC:
+                        if (command instanceof QuantityType) {
+                            evccAPI.setTargetSoC(loadpoint, ((QuantityType<?>) command).intValue());
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_TARGET_TIME:
+                        if (command instanceof DateTimeType) {
+                            targetTimeZDT = ((DateTimeType) command).getZonedDateTime();
+                            ChannelUID channel = new ChannelUID(getThing().getUID(), "loadpoint" + loadpoint,
+                                    CHANNEL_LOADPOINT_TARGET_TIME);
+                            updateState(channel, new DateTimeType(targetTimeZDT));
+                            if (targetTimeEnabled) {
+                                try {
+                                    evccAPI.setTargetCharge(loadpoint, targetSoC, targetTimeZDT);
+                                } catch (DateTimeParseException e) {
+                                    logger.debug("Failed to set target charge", e);
+                                }
+                            }
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_TARGET_TIME_ENABLED:
+                        if (command == OnOffType.ON) {
+                            evccAPI.setTargetCharge(loadpoint, targetSoC, targetTimeZDT);
+                            targetTimeEnabled = true;
+                        } else if (command == OnOffType.OFF) {
+                            evccAPI.unsetTargetCharge(loadpoint);
+                            targetTimeEnabled = false;
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_PHASES:
+                        if (command instanceof DecimalType) {
+                            evccAPI.setPhases(loadpoint, ((DecimalType) command).intValue());
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_MIN_CURRENT:
+                        if (command instanceof QuantityType) {
+                            evccAPI.setMinCurrent(loadpoint, ((QuantityType<?>) command).intValue());
+                        }
+                        break;
+                    case CHANNEL_LOADPOINT_MAX_CURRENT:
+                        if (command instanceof QuantityType) {
+                            evccAPI.setMaxCurrent(loadpoint, ((QuantityType<?>) command).intValue());
+                        }
+                        break;
+                    default:
+                        return;
+                }
+            } catch (EvccApiException e) {
+                Throwable cause = e.getCause();
+                if (cause == null) {
+                    logger.debug("Failed to handle command {} for channel {}: {}", command, channelUID, e.getMessage());
+                } else {
+                    logger.debug("Failed to handle command {} for channel {}: {} -> {}", command, channelUID,
+                            e.getMessage(), cause.getMessage());
+                }
+            }
+            refresh();
+        }
+    }
+
+    @Override
+    public void initialize() {
+        EvccConfiguration config = getConfigAs(EvccConfiguration.class);
+        String url = config.url;
+        if (url == null || url.isBlank()) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                    "@text/offline.configuration-error.no-host");
+        } else {
+            this.evccAPI = new EvccAPI(url);
+            logger.debug("Setting up refresh job ...");
+            statePollingJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval,
+                    TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Refreshes from evcc.
+     * 
+     * First, checks connection and updates Thing status.
+     * Second, creates all available channels.
+     * Third, updates all channels.
+     */
+    private void refresh() {
+        logger.debug("Running refresh job ...");
+        EvccAPI evccAPI = null;
+        evccAPI = this.evccAPI;
+        if (evccAPI == null) {
+            return;
+        }
+        try {
+            this.result = evccAPI.getResult();
+        } catch (EvccApiException e) {
+            logger.debug("Failed to get state");
+        }
+        Result result = this.result;
+        if (result == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    "@text/offline.communication-error.request-failed");
+        } else {
+            String sitename = result.getSiteTitle();
+            int numberOfLoadpoints = result.getLoadpoints().length;
+            logger.debug("Found {} loadpoints on site {}.", numberOfLoadpoints, sitename);
+            updateStatus(ThingStatus.ONLINE);
+            batteryConfigured = result.getBatteryConfigured();
+            gridConfigured = result.getGridConfigured();
+            pvConfigured = result.getPvConfigured();
+            createChannelsGeneral();
+            updateChannelsGeneral();
+            for (int i = 0; i < numberOfLoadpoints; i++) {
+                createChannelsLoadpoint(i);
+                updateChannelsLoadpoint(i);
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> statePollingJob = this.statePollingJob;
+        if (statePollingJob != null) {
+            statePollingJob.cancel(true);
+            this.statePollingJob = null;
+        }
+    }
+
+    // Utility functions
+    private void createChannelsGeneral() {
+        final String channelGroup = "general";
+        if (batteryConfigured) {
+            createChannel(CHANNEL_BATTERY_POWER, channelGroup, CHANNEL_TYPE_UID_BATTERY_POWER, "Number:Power");
+            createChannel(CHANNEL_BATTERY_SOC, channelGroup, CHANNEL_TYPE_UID_BATTERY_SOC, "Number:Dimensionless");
+            createChannel(CHANNEL_BATTERY_PRIORITY_SOC, channelGroup, CHANNEL_TYPE_UID_BATTERY_PRIORITY_SOC,
+                    "Number:Dimensionless");
+        }
+        if (gridConfigured) {
+            createChannel(CHANNEL_GRID_POWER, channelGroup, CHANNEL_TYPE_UID_GRID_POWER, "Number:Power");
+        }
+        createChannel(CHANNEL_HOME_POWER, channelGroup, CHANNEL_TYPE_UID_HOME_POWER, "Number:Power");
+        if (pvConfigured) {
+            createChannel(CHANNEL_PV_POWER, channelGroup, CHANNEL_TYPE_UID_PV_POWER, "Number:Power");
+        }
+    }
+
+    private void createChannelsLoadpoint(int loadpointId) {
+        final String channelGroup = "loadpoint" + loadpointId;
+        createChannel(CHANNEL_LOADPOINT_ACTIVE_PHASES, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_ACTIVE_PHASES,
+                CoreItemFactory.NUMBER);
+        createChannel(CHANNEL_LOADPOINT_CHARGE_CURRENT, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CHARGE_CURRENT,
+                "Number:ElectricCurrent");
+        createChannel(CHANNEL_LOADPOINT_CHARGE_DURATION, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CHARGE_DURATION,
+                "Number:Time");
+        createChannel(CHANNEL_LOADPOINT_CHARGE_POWER, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CHARGE_POWER,
+                "Number:Power");
+        createChannel(CHANNEL_LOADPOINT_CHARGE_REMAINING_DURATION, channelGroup,
+                CHANNEL_TYPE_UID_LOADPOINT_CHARGE_REMAINING_DURATION, "Number:Time");
+        createChannel(CHANNEL_LOADPOINT_CHARGE_REMAINING_ENERGY, channelGroup,
+                CHANNEL_TYPE_UID_LOADPOINT_CHARGE_REMAINING_ENERGY, "Number:Energy");
+        createChannel(CHANNEL_LOADPOINT_CHARGED_ENERGY, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CHARGED_ENERGY,
+                "Number:Energy");
+        createChannel(CHANNEL_LOADPOINT_CHARGING, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CHARGING,
+                CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_CONNECTED, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CONNECTED,
+                CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_CONNECTED_DURATION, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_CONNECTED_DURATION,
+                "Number:Time");
+        createChannel(CHANNEL_LOADPOINT_ENABLED, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_ENABLED,
+                CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_HAS_VEHICLE, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_HAS_VEHICLE,
+                CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_MAX_CURRENT, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_MAX_CURRENT,
+                "Number:ElectricCurrent");
+        createChannel(CHANNEL_LOADPOINT_MIN_CURRENT, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_MIN_CURRENT,
+                "Number:ElectricCurrent");
+        createChannel(CHANNEL_LOADPOINT_MIN_SOC, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_MIN_SOC,
+                "Number:Dimensionless");
+        createChannel(CHANNEL_LOADPOINT_MODE, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_MODE, CoreItemFactory.STRING);
+        createChannel(CHANNEL_LOADPOINT_PHASES, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_PHASES,
+                CoreItemFactory.NUMBER);
+        createChannel(CHANNEL_LOADPOINT_TARGET_SOC, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_TARGET_SOC,
+                "Number:Dimensionless");
+        createChannel(CHANNEL_LOADPOINT_TARGET_TIME, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_TARGET_TIME,
+                CoreItemFactory.DATETIME);
+        createChannel(CHANNEL_LOADPOINT_TARGET_TIME_ENABLED, channelGroup,
+                CHANNEL_TYPE_UID_LOADPOINT_TARGET_TIME_ENABLED, CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_TITLE, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_TITLE, CoreItemFactory.STRING);
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_CAPACITY, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_CAPACITY,
+                "Number:Energy");
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_ODOMETER, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_ODOMETER,
+                "Number:Length");
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_PRESENT, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_PRESENT,
+                CoreItemFactory.SWITCH);
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_RANGE, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_RANGE,
+                "Number:Length");
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_SOC, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_SOC,
+                "Number:Dimensionless");
+        createChannel(CHANNEL_LOADPOINT_VEHICLE_TITLE, channelGroup, CHANNEL_TYPE_UID_LOADPOINT_VEHICLE_TITLE,
+                CoreItemFactory.STRING);
+    }
+
+    // Units and description for vars: https://docs.evcc.io/docs/reference/configuration/messaging/#msg
+    private void updateChannelsGeneral() {
+        Result result = this.result;
+        if (result == null) {
+            return;
+        }
+        ChannelUID channel;
+        boolean batteryConfigured = this.batteryConfigured;
+        if (batteryConfigured) {
+            double batteryPower = result.getBatteryPower();
+            channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_BATTERY_POWER);
+            updateState(channel, new QuantityType<>(batteryPower, Units.WATT));
+            int batterySoC = result.getBatterySoC();
+            channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_BATTERY_SOC);
+            updateState(channel, new QuantityType<>(batterySoC, Units.PERCENT));
+            int batteryPrioritySoC = result.getBatterySoC();
+            channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_BATTERY_PRIORITY_SOC);
+            updateState(channel, new QuantityType<>(batteryPrioritySoC, Units.PERCENT));
+        }
+        boolean gridConfigured = this.gridConfigured;
+        if (gridConfigured) {
+            double gridPower = result.getGridPower();
+            channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_GRID_POWER);
+            updateState(channel, new QuantityType<>(gridPower, Units.WATT));
+        }
+        double homePower = result.getHomePower();
+        channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_HOME_POWER);
+        updateState(channel, new QuantityType<>(homePower, Units.WATT));
+        boolean pvConfigured = this.pvConfigured;
+        if (pvConfigured) {
+            double pvPower = result.getPvPower();
+            channel = new ChannelUID(getThing().getUID(), "general", CHANNEL_PV_POWER);
+            updateState(channel, new QuantityType<>(pvPower, Units.WATT));
+        }
+    }
+
+    private void updateChannelsLoadpoint(int loadpointId) {
+        Result result = this.result;
+        if (result == null) {
+            return;
+        }
+        String loadpointName = "loadpoint" + loadpointId;
+        ChannelUID channel;
+        Loadpoint loadpoint = result.getLoadpoints()[loadpointId];
+        int activePhases = loadpoint.getActivePhases();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_ACTIVE_PHASES);
+        updateState(channel, new DecimalType(activePhases));
+        double chargeCurrent = loadpoint.getChargeCurrent();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGE_CURRENT);
+        updateState(channel, new QuantityType<>(chargeCurrent, Units.AMPERE));
+        long chargeDuration = loadpoint.getChargeDuration();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGE_DURATION);
+        updateState(channel, new QuantityType<>(chargeDuration, MetricPrefix.NANO(Units.SECOND)));
+        double chargePower = loadpoint.getChargePower();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGE_POWER);
+        updateState(channel, new QuantityType<>(chargePower, Units.WATT));
+        long chargeRemainingDuration = loadpoint.getChargeRemainingDuration();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGE_REMAINING_DURATION);
+        updateState(channel, new QuantityType<>(chargeRemainingDuration, MetricPrefix.NANO(Units.SECOND)));
+        double chargeRemainingEnergy = loadpoint.getChargeRemainingEnergy();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGE_REMAINING_ENERGY);
+        updateState(channel, new QuantityType<>(chargeRemainingEnergy, Units.WATT_HOUR));
+        double chargedEnergy = loadpoint.getChargedEnergy();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGED_ENERGY);
+        updateState(channel, new QuantityType<>(chargedEnergy, Units.WATT_HOUR));
+        boolean charging = loadpoint.getCharging();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CHARGING);
+        updateState(channel, OnOffType.from(charging));
+        boolean connected = loadpoint.getConnected();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CONNECTED);
+        updateState(channel, OnOffType.from(connected));
+        long connectedDuration = loadpoint.getConnectedDuration();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_CONNECTED_DURATION);
+        updateState(channel, new QuantityType<>(connectedDuration, MetricPrefix.NANO(Units.SECOND)));
+        boolean enabled = loadpoint.getEnabled();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_ENABLED);
+        updateState(channel, OnOffType.from(enabled));
+        boolean hasVehicle = loadpoint.getHasVehicle();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_HAS_VEHICLE);
+        updateState(channel, OnOffType.from(hasVehicle));
+        double maxCurrent = loadpoint.getMaxCurrent();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_MAX_CURRENT);
+        updateState(channel, new QuantityType<>(maxCurrent, Units.AMPERE));
+        double minCurrent = loadpoint.getMinCurrent();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_MIN_CURRENT);
+        updateState(channel, new QuantityType<>(minCurrent, Units.AMPERE));
+        int minSoC = loadpoint.getMinSoC();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_MIN_SOC);
+        updateState(channel, new QuantityType<>(minSoC, Units.PERCENT));
+        String mode = loadpoint.getMode();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_MODE);
+        updateState(channel, new StringType(mode));
+        int phases = loadpoint.getPhases();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_PHASES);
+        updateState(channel, new DecimalType(phases));
+        targetSoC = loadpoint.getTargetSoC();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_TARGET_SOC);
+        updateState(channel, new QuantityType<>(targetSoC, Units.PERCENT));
+        String targetTime = loadpoint.getTargetTime();
+        if (targetTime == null) {
+            channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_TARGET_TIME_ENABLED);
+            updateState(channel, OnOffType.OFF);
+            targetTimeEnabled = false;
+        } else {
+            this.targetTimeZDT = ZonedDateTime.parse(targetTime);
+            channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_TARGET_TIME);
+            updateState(channel, new DateTimeType(targetTimeZDT));
+            channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_TARGET_TIME_ENABLED);
+            updateState(channel, OnOffType.ON);
+            targetTimeEnabled = true;
+        }
+        String title = loadpoint.getTitle();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_TITLE);
+        updateState(channel, new StringType(title));
+        double vehicleCapacity = loadpoint.getVehicleCapacity();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_CAPACITY);
+        updateState(channel, new QuantityType<>(vehicleCapacity, Units.WATT_HOUR));
+        double vehicleOdometer = loadpoint.getVehicleOdometer();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_ODOMETER);
+        updateState(channel, new QuantityType<>(vehicleOdometer, MetricPrefix.KILO(SIUnits.METRE)));
+        boolean vehiclePresent = loadpoint.getVehiclePresent();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_PRESENT);
+        updateState(channel, OnOffType.from(vehiclePresent));
+        long vehicleRange = loadpoint.getVehicleRange();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_RANGE);
+        updateState(channel, new QuantityType<>(vehicleRange, MetricPrefix.KILO(SIUnits.METRE)));
+        int vehicleSoC = loadpoint.getVehicleSoC();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_SOC);
+        updateState(channel, new QuantityType<>(vehicleSoC, Units.PERCENT));
+        String vehicleTitle = loadpoint.getVehicleTitle();
+        channel = new ChannelUID(getThing().getUID(), loadpointName, CHANNEL_LOADPOINT_VEHICLE_TITLE);
+        updateState(channel, new StringType(vehicleTitle));
+    }
+
+    private void createChannel(String channel, String channelGroupId, ChannelTypeUID channelTypeUID, String itemType) {
+        ChannelUID channelToCheck = new ChannelUID(thing.getUID(), channelGroupId, channel);
+        if (thing.getChannel(channelToCheck) == null) {
+            ThingBuilder thingBuilder = editThing();
+            Channel testchannel = ChannelBuilder
+                    .create(new ChannelUID(getThing().getUID(), channelGroupId, channel), itemType)
+                    .withType(channelTypeUID).build();
+            thingBuilder.withChannel(testchannel);
+            updateThing(thingBuilder.build());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandlerFactory.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/EvccHandlerFactory.java
new file mode 100644 (file)
index 0000000..33ae00a
--- /dev/null
@@ -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.evcc.internal;
+
+import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+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.Component;
+
+/**
+ * The {@link EvccHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.evcc", service = ThingHandlerFactory.class)
+public class EvccHandlerFactory extends BaseThingHandlerFactory {
+
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_DEVICE);
+
+    @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_DEVICE.equals(thingTypeUID)) {
+            return new EvccHandler(thing);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccAPI.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccAPI.java
new file mode 100644 (file)
index 0000000..9cc4733
--- /dev/null
@@ -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.evcc.internal.api;
+
+import static org.openhab.binding.evcc.internal.EvccBindingConstants.EVCC_REST_API;
+import static org.openhab.binding.evcc.internal.EvccBindingConstants.LONG_CONNECTION_TIMEOUT_MILLISEC;
+
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.evcc.internal.api.dto.Result;
+import org.openhab.binding.evcc.internal.api.dto.Status;
+import org.openhab.core.io.net.http.HttpUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link EvccAPI} is responsible for API calls to evcc.
+ * 
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+public class EvccAPI {
+    private final Logger logger = LoggerFactory.getLogger(EvccAPI.class);
+    private final Gson gson = new Gson();
+    private String host = "";
+
+    public EvccAPI(String host) {
+        this.host = host;
+    }
+
+    /**
+     * Make a HTTP request.
+     * 
+     * @param url full request URL
+     * @param method reguest method, e.g. GET, POST
+     * @return the response body
+     * @throws {@link EvccApiException} if HTTP request failed
+     */
+    private String httpRequest(String url, String method) throws EvccApiException {
+        try {
+            String response = HttpUtil.executeUrl(method, url, LONG_CONNECTION_TIMEOUT_MILLISEC);
+            logger.trace("{} {} - {}", method, url, response);
+            return response;
+        } catch (IOException e) {
+            throw new EvccApiException("HTTP request failed for URL " + url, e);
+        }
+    }
+
+    // End utility functions
+
+    // API calls to evcc
+    /**
+     * Get the status from evcc.
+     * 
+     * @param host hostname of IP address of the evcc instance
+     * @return {@link Result} result object from API
+     * @throws {@link EvccApiException} if status request failed
+     */
+    public Result getResult() throws EvccApiException {
+        final String response = httpRequest(this.host + EVCC_REST_API + "state", "GET");
+        try {
+            Status status = gson.fromJson(response, Status.class);
+            if (status == null) {
+                throw new EvccApiException("Status is null");
+            }
+            return status.getResult();
+        } catch (JsonSyntaxException e) {
+            throw new EvccApiException("Error parsing response: " + response, e);
+        }
+    }
+
+    // Loadpoint specific API calls.
+    public String setMode(int loadpoint, String mode) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/mode/" + mode, "POST");
+    }
+
+    public String setMinSoC(int loadpoint, int minSoC) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/minsoc/" + minSoC, "POST");
+    }
+
+    public String setTargetSoC(int loadpoint, int targetSoC) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/targetsoc/" + targetSoC, "POST");
+    }
+
+    public String setPhases(int loadpoint, int phases) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/phases/" + phases, "POST");
+    }
+
+    public String setMinCurrent(int loadpoint, int minCurrent) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/mincurrent/" + minCurrent, "POST");
+    }
+
+    public String setMaxCurrent(int loadpoint, int maxCurrent) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/maxcurrent/" + maxCurrent, "POST");
+    }
+
+    public String setTargetCharge(int loadpoint, int targetSoC, ZonedDateTime targetTime) throws EvccApiException {
+        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/targetcharge/" + targetSoC + "/"
+                + targetTime.toLocalDateTime().format(formatter), "POST");
+    }
+
+    public String unsetTargetCharge(int loadpoint) throws EvccApiException {
+        return httpRequest(this.host + EVCC_REST_API + "loadpoints/" + loadpoint + "/targetcharge", "DELETE");
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccApiException.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/EvccApiException.java
new file mode 100644 (file)
index 0000000..f0af01d
--- /dev/null
@@ -0,0 +1,34 @@
+/**
+ * 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.evcc.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link EvccApiException} signals that an API request by {@link EvccAPI} failed.
+ * 
+ * @author Florian Hotze - Initial contribution
+ */
+@NonNullByDefault
+public class EvccApiException extends Exception {
+
+    private static final long serialVersionUID = -1935778974024277328L;
+
+    public EvccApiException(String message) {
+        super(message);
+    }
+
+    public EvccApiException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Loadpoint.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Loadpoint.java
new file mode 100644 (file)
index 0000000..732af3a
--- /dev/null
@@ -0,0 +1,316 @@
+/**
+ * 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.evcc.internal.api.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents a loadpoint object of the status response (/api/state).
+ * This DTO was written for evcc version 0.91.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+public class Loadpoint {
+    // Data types from https://github.com/evcc-io/evcc/blob/master/api/api.go
+    // and from https://docs.evcc.io/docs/reference/configuration/messaging/#msg
+
+    @SerializedName("activePhases")
+    private int activePhases;
+
+    @SerializedName("chargeCurrent")
+    private double chargeCurrent;
+
+    @SerializedName("chargeDuration")
+    private long chargeDuration;
+
+    @SerializedName("chargePower")
+    private double chargePower;
+
+    @SerializedName("chargeRemainingDuration")
+    private long chargeRemainingDuration;
+
+    @SerializedName("chargeRemainingEnergy")
+    private double chargeRemainingEnergy;
+
+    @SerializedName("chargedEnergy")
+    private double chargedEnergy;
+
+    @SerializedName("charging")
+    private boolean charging;
+
+    @SerializedName("connected")
+    private boolean connected;
+
+    @SerializedName("connectedDuration")
+    private long connectedDuration;
+
+    @SerializedName("enabled")
+    private boolean enabled;
+
+    @SerializedName("hasVehicle")
+    private boolean hasVehicle;
+
+    @SerializedName("loadpoint")
+    private int loadpoint;
+
+    @SerializedName("maxCurrent")
+    private double maxCurrent;
+
+    @SerializedName("minCurrent")
+    private double minCurrent;
+
+    @SerializedName("minSoC")
+    private int minSoC;
+
+    @SerializedName("mode")
+    private String mode;
+
+    @SerializedName("phases")
+    private int phases;
+
+    @SerializedName("pvAction")
+    private String pvAction;
+
+    @SerializedName("pvRemaining")
+    private long pvRemaining;
+
+    @SerializedName("targetSoC")
+    private int targetSoC;
+
+    @SerializedName("targetTime")
+    private String targetTime;
+
+    @SerializedName("title")
+    private String title;
+
+    @SerializedName("vehicleCapacity")
+    private long vehicleCapacity;
+
+    @SerializedName("vehicleOdometer")
+    private double vehicleOdometer;
+
+    @SerializedName("vehiclePresent")
+    private boolean vehiclePresent;
+
+    @SerializedName("vehicleRange")
+    private long vehicleRange;
+
+    @SerializedName("vehicleSoC")
+    private int vehicleSoC;
+
+    @SerializedName("vehicleTitle")
+    private String vehicleTitle;
+
+    /**
+     * @return number of active phases
+     */
+    public int getActivePhases() {
+        return activePhases;
+    }
+
+    /**
+     * @return charge current
+     */
+    public double getChargeCurrent() {
+        return chargeCurrent;
+    }
+
+    /**
+     * @return charge duration
+     */
+    public long getChargeDuration() {
+        return chargeDuration;
+    }
+
+    /**
+     * @return charge power
+     */
+    public double getChargePower() {
+        return chargePower;
+    }
+
+    /**
+     * @return charge remaining duration until the target SoC is reached
+     */
+    public long getChargeRemainingDuration() {
+        return chargeRemainingDuration;
+    }
+
+    /**
+     * @return charge remaining energy until the target SoC is reached
+     */
+    public double getChargeRemainingEnergy() {
+        return chargeRemainingEnergy;
+    }
+
+    /**
+     * @return charged energy
+     */
+    public double getChargedEnergy() {
+        return chargedEnergy;
+    }
+
+    /**
+     * @return whether loadpoint is charging a vehicle
+     */
+    public boolean getCharging() {
+        return charging;
+    }
+
+    /**
+     * @return whether a vehicle is connected to the loadpoint
+     */
+    public boolean getConnected() {
+        return connected;
+    }
+
+    /**
+     * @return vehicle connected duration
+     */
+    public long getConnectedDuration() {
+        return connectedDuration;
+    }
+
+    /**
+     * @return whether loadpoint is enabled
+     */
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    /**
+     * @return whether vehicle is configured for loadpoint
+     */
+    public boolean getHasVehicle() {
+        return hasVehicle;
+    }
+
+    /**
+     * @return loadpoint id
+     */
+    public int getLoadpoint() {
+        return loadpoint;
+    }
+
+    /**
+     * @return maximum current
+     */
+    public double getMaxCurrent() {
+        return maxCurrent;
+    }
+
+    /**
+     * @return minimum current
+     */
+    public double getMinCurrent() {
+        return minCurrent;
+    }
+
+    /**
+     * @return minimum state of charge
+     */
+    public int getMinSoC() {
+        return minSoC;
+    }
+
+    /**
+     * @return charging mode: off, now, minpv, pv
+     */
+    public String getMode() {
+        return mode;
+    }
+
+    /**
+     * @return number of enabled phases
+     */
+    public int getPhases() {
+        return phases;
+    }
+
+    /**
+     * @return the pv action
+     */
+    public String getPvAction() {
+        return pvAction;
+    }
+
+    /**
+     * @return the pv remaining
+     */
+    public long getPvRemaining() {
+        return pvRemaining;
+    }
+
+    /**
+     * @return target state of charge (SoC)
+     */
+    public int getTargetSoC() {
+        return targetSoC;
+    }
+
+    /**
+     * @return target time for the target state of charge
+     */
+    public String getTargetTime() {
+        return targetTime;
+    }
+
+    /**
+     * @return loadpoint's title/name
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * @return vehicle's capacity
+     */
+    public double getVehicleCapacity() {
+        return vehicleCapacity;
+    }
+
+    /**
+     * @return vehicle's odometer
+     */
+    public double getVehicleOdometer() {
+        return vehicleOdometer;
+    }
+
+    /**
+     * @return whether evcc is able to get data from vehicle
+     */
+    public boolean getVehiclePresent() {
+        return vehiclePresent;
+    }
+
+    /**
+     * @return vehicle's range
+     */
+    public long getVehicleRange() {
+        return vehicleRange;
+    }
+
+    /**
+     * @return vehicle's state of charge (SoC)
+     */
+    public int getVehicleSoC() {
+        return vehicleSoC;
+    }
+
+    /**
+     * @return vehicle's title/name
+     */
+    public String getVehicleTitle() {
+        return vehicleTitle;
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Result.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Result.java
new file mode 100644 (file)
index 0000000..6a613e8
--- /dev/null
@@ -0,0 +1,140 @@
+/**
+ * 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.evcc.internal.api.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the result object of the status response (/api/state).
+ * This DTO was written for evcc version 0.91.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+public class Result {
+    // Data types from https://github.com/evcc-io/evcc/blob/master/api/api.go
+    // and from https://docs.evcc.io/docs/reference/configuration/messaging/#msg
+
+    // TO DO LATER
+    // @SerializedName("auth")
+    // private Auth auth;
+
+    @SerializedName("batteryConfigured")
+    private boolean batteryConfigured;
+
+    @SerializedName("batteryPower")
+    private double batteryPower;
+
+    @SerializedName("batterySoC")
+    private int batterySoC;
+
+    @SerializedName("gridConfigured")
+    private boolean gridConfigured;
+
+    @SerializedName("gridPower")
+    private double gridPower;
+
+    @SerializedName("homePower")
+    private double homePower;
+
+    @SerializedName("loadpoints")
+    private Loadpoint[] loadpoints;
+
+    @SerializedName("prioritySoC")
+    private double batteryPrioritySoC;
+
+    @SerializedName("pvConfigured")
+    private boolean pvConfigured;
+
+    @SerializedName("pvPower")
+    private double pvPower;
+
+    @SerializedName("siteTitle")
+    private String siteTitle;
+
+    /**
+     * @return whether battery is configured
+     */
+    public boolean getBatteryConfigured() {
+        return batteryConfigured;
+    }
+
+    /**
+     * @return battery's power
+     */
+    public double getBatteryPower() {
+        return batteryPower;
+    }
+
+    /**
+     * @return battery's priority state of charge
+     */
+    public double getBatteryPrioritySoC() {
+        return batteryPrioritySoC;
+    }
+
+    /**
+     * @return battery's state of charge
+     */
+    public int getBatterySoC() {
+        return batterySoC;
+    }
+
+    /**
+     * @return whether grid is configured
+     */
+    public boolean getGridConfigured() {
+        return gridConfigured;
+    }
+
+    /**
+     * @return grid's power
+     */
+    public double getGridPower() {
+        return gridPower;
+    }
+
+    /**
+     * @return home's power
+     */
+    public double getHomePower() {
+        return homePower;
+    }
+
+    /**
+     * @return all configured loadpoints
+     */
+    public Loadpoint[] getLoadpoints() {
+        return loadpoints;
+    }
+
+    /**
+     * @return whether pv is configured
+     */
+    public boolean getPvConfigured() {
+        return pvConfigured;
+    }
+
+    /**
+     * @return pv's power
+     */
+    public double getPvPower() {
+        return pvPower;
+    }
+
+    /**
+     * @return site's title/name
+     */
+    public String getSiteTitle() {
+        return siteTitle;
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Status.java b/bundles/org.openhab.binding.evcc/src/main/java/org/openhab/binding/evcc/internal/api/dto/Status.java
new file mode 100644 (file)
index 0000000..b70e1f9
--- /dev/null
@@ -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.evcc.internal.api.dto;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the status response (/api/state).
+ * This DTO was written for evcc version 0.91.
+ *
+ * @author Florian Hotze - Initial contribution
+ */
+public class Status {
+
+    @SerializedName("result")
+    private Result result;
+
+    public Result getResult() {
+        return result;
+    }
+}
diff --git a/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644 (file)
index 0000000..14945a1
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="evcc" 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>evcc Binding</name>
+       <description>This is the binding for evcc (electric vehicle charging control) - soak up the sun.</description>
+
+</binding:binding>
diff --git a/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc.properties b/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc.properties
new file mode 100644 (file)
index 0000000..e63da7c
--- /dev/null
@@ -0,0 +1,111 @@
+# binding
+
+binding.evcc.name = evcc Binding
+binding.evcc.description = This is the binding for evcc (electric vehicle charging control) - soak up the sun.
+
+# thing types
+
+thing-type.evcc.device.label = evcc installation
+thing-type.evcc.device.description = A running evcc installation
+
+# thing types config
+
+thing-type.config.evcc.device.refreshInterval.label = Refresh Interval
+thing-type.config.evcc.device.refreshInterval.description = Interval the status is polled in seconds.
+thing-type.config.evcc.device.url.label = URL
+thing-type.config.evcc.device.url.description = URL of evcc web UI, e.g. https://demo.evcc.io
+
+# channel group types
+
+channel-group-type.evcc.general.label = General data
+channel-group-type.evcc.loadpoint.label = Loadpoint
+
+# channel types
+
+channel-type.evcc.activePhases.label = Charging active phases
+channel-type.evcc.activePhases.description = Current number of active phases while charging
+channel-type.evcc.batteryPower.label = Battery Power
+channel-type.evcc.batteryPower.description = Current power from battery
+channel-type.evcc.batteryPrioritySoC.label = Battery Priority SoC
+channel-type.evcc.batteryPrioritySoC.description = State of Charge for which the battery has priority over charging the ev when charging mode is "pv".
+channel-type.evcc.batterySoC.label = Battery SoC
+channel-type.evcc.batterySoC.description = Current State of Charge of battery
+channel-type.evcc.chargeCurrent.label = Charging current
+channel-type.evcc.chargeCurrent.description = Current amperage per connected phase while charging
+channel-type.evcc.chargeDuration.label = Charging duration
+channel-type.evcc.chargeDuration.description = Charging duration
+channel-type.evcc.chargePower.label = Charging power
+channel-type.evcc.chargePower.description = Current power of charging
+channel-type.evcc.chargeRemainingDuration.label = Charging remaining duration
+channel-type.evcc.chargeRemainingDuration.description = Remaining duration until target SoC is reached
+channel-type.evcc.chargeRemainingEnergy.label = Charging remaining energy
+channel-type.evcc.chargeRemainingEnergy.description = Remaining energy until target SoC is reached
+channel-type.evcc.chargedEnergy.label = Charged energy
+channel-type.evcc.chargedEnergy.description = Energy charged since plugged-in
+channel-type.evcc.charging.label = Charging state
+channel-type.evcc.charging.description = Loadpoint is currently charging
+channel-type.evcc.charging.state.option.ON = Charging
+channel-type.evcc.charging.state.option.OFF = Not charging
+channel-type.evcc.enabled.label = Charging enabled
+channel-type.evcc.enabled.description = Charging enabled (mode not "off")
+channel-type.evcc.enabled.state.option.ON = Enabled
+channel-type.evcc.enabled.state.option.OFF = Disabled
+channel-type.evcc.gridPower.label = Grid Power
+channel-type.evcc.gridPower.description = Current power from grid (negative means feed-in)
+channel-type.evcc.hasVehicle.label = Loadpoint has vehicle configuration
+channel-type.evcc.hasVehicle.description = Whether vehicle is configured for loadpoint
+channel-type.evcc.hasVehicle.state.option.ON = Configured
+channel-type.evcc.hasVehicle.state.option.OFF = Not configured
+channel-type.evcc.homePower.label = Home Power
+channel-type.evcc.homePower.description = Current power taken by home
+channel-type.evcc.maxCurrent.label = Charging max current
+channel-type.evcc.maxCurrent.description = Maximum amperage per connected phase with which the car should be charged
+channel-type.evcc.minCurrent.label = Charging min current
+channel-type.evcc.minCurrent.description = Minimum amperage per connected phase with which the car should be charged
+channel-type.evcc.minSoC.label = Charging min SoC
+channel-type.evcc.minSoC.description = Charge immediately with maximum power up to the defined SoC, if the charge mode is not set to "off"
+channel-type.evcc.mode.label = Charging mode
+channel-type.evcc.mode.description = Charging mode: "off", "now", "minpv", "pv"
+channel-type.evcc.mode.state.option.off = Off
+channel-type.evcc.mode.state.option.now = Now
+channel-type.evcc.mode.state.option.minpv = Min + PV
+channel-type.evcc.mode.state.option.pv = Only PV
+channel-type.evcc.phases.label = Charging enabled phases
+channel-type.evcc.phases.description = The maximum number of phases which can be used
+channel-type.evcc.pvPower.label = PV Power
+channel-type.evcc.pvPower.description = Current power from photovoltaik
+channel-type.evcc.targetSoC.label = Charging target SoC
+channel-type.evcc.targetSoC.description = Until which state of charge (SoC) should the vehicle be charged
+channel-type.evcc.targetTime.label = Charging target time
+channel-type.evcc.targetTime.description = When the target SoC should be reached
+channel-type.evcc.targetTimeEnabled.label = Charging target time enabled
+channel-type.evcc.targetTimeEnabled.description = Target time for charging enabled
+channel-type.evcc.targetTimeEnabled.state.option.ON = Enabled
+channel-type.evcc.targetTimeEnabled.state.option.OFF = Disabled
+channel-type.evcc.title.label = Loadpoint title
+channel-type.evcc.title.description = Title of loadpoint
+channel-type.evcc.vehicleCapacity.label = Vehicle capacity
+channel-type.evcc.vehicleCapacity.description = Capacity of EV battery
+channel-type.evcc.vehicleConnected.label = Vehicle connected
+channel-type.evcc.vehicleConnected.description = Whether vehicle is connected to loadpoint
+channel-type.evcc.vehicleConnected.state.option.ON = Connected
+channel-type.evcc.vehicleConnected.state.option.OFF = Not connected
+channel-type.evcc.vehicleConnectedDuration.label = Vehicle connected duration
+channel-type.evcc.vehicleConnectedDuration.description = Duration the vehicle is connected to loadpoint
+channel-type.evcc.vehicleOdometer.label = Vehicle odometer
+channel-type.evcc.vehicleOdometer.description = Total distance travelled by EV
+channel-type.evcc.vehiclePresent.label = Vehicle data access
+channel-type.evcc.vehiclePresent.description = Whether evcc is able to get data from vehicle
+channel-type.evcc.vehiclePresent.state.option.ON = Data access
+channel-type.evcc.vehiclePresent.state.option.OFF = No data access
+channel-type.evcc.vehicleRange.label = Vehicle range
+channel-type.evcc.vehicleRange.description = Battery range for EV
+channel-type.evcc.vehicleSoC.label = Vehicle SoC
+channel-type.evcc.vehicleSoC.description = Current State of Charge of EV
+channel-type.evcc.vehicleTitle.label = Vehicle title
+channel-type.evcc.vehicleTitle.description = Name of EV
+
+# channel types
+
+offline.configuration-error.no-host = No host configured
+offline.communication-error.request-failed = Request failed
diff --git a/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc_de.properties b/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/i18n/evcc_de.properties
new file mode 100644 (file)
index 0000000..8165a05
--- /dev/null
@@ -0,0 +1,111 @@
+# binding
+
+binding.evcc.name = evcc Binding
+binding.evcc.description = Dies ist das Binding für evcc (electric vehicle charging control) - Sonne tanken.
+
+# thing types
+
+thing-type.evcc.device.label = evcc Installation
+thing-type.evcc.device.description = Eine aktive evcc Installation.
+
+# thing types config
+
+thing-type.config.evcc.device.refreshInterval.label = Aktualisierungs-Interval
+thing-type.config.evcc.device.refreshInterval.description = Interval in Sekunden, in dem der Status abgerufen wird
+thing-type.config.evcc.device.url.label = URL
+thing-type.config.evcc.device.url.description = URL der evcc Web-Oberfläche, z.B. https://demo.evcc.io
+
+# channel group types
+
+channel-group-type.evcc.general.label = Generelle Daten
+channel-group-type.evcc.loadpoint.label = Ladepunkt
+
+# channel types
+
+channel-type.evcc.activePhases.label = Laden aktive Phasen
+channel-type.evcc.activePhases.description = Anzahl von Phasen, die beim Laden aktiv sind
+channel-type.evcc.batteryPower.label = Batterie Leistung
+channel-type.evcc.batteryPower.description = Aktuelle Leistung der Batterie
+channel-type.evcc.batteryPrioritySoC.label = Batterie Priorität-Ladestand
+channel-type.evcc.batteryPrioritySoC.description = Mindest-Ladestand der Batterie, unter dem die Batterie Priorität vor PV-Laden hat
+channel-type.evcc.batterySoC.label = Batterie Ladestand
+channel-type.evcc.batterySoC.description = Aktueller Ladestand der Batterie
+channel-type.evcc.chargeCurrent.label = Ladestromstärke
+channel-type.evcc.chargeCurrent.description = Aktuelle Stromstärke je Phases beim Laden
+channel-type.evcc.chargeDuration.label = Ladedauer
+channel-type.evcc.chargeDuration.description = Bisherige Ladedauer
+channel-type.evcc.chargePower.label = Ladeleistung
+channel-type.evcc.chargePower.description = Aktuelle Ladeleistung
+channel-type.evcc.chargeRemainingDuration.label = Verbleibende Ladedauer
+channel-type.evcc.chargeRemainingDuration.description = Verbleibende Dauer bis der Ziel-Füllstand erreicht ist
+channel-type.evcc.chargeRemainingEnergy.label = Verbleinbende Ladeenergie
+channel-type.evcc.chargeRemainingEnergy.description = Verbleibende Energie bis der Ziel-Füllstand erreicht ist
+channel-type.evcc.chargedEnergy.label = Geladene Energie
+channel-type.evcc.chargedEnergy.description = Geladene Energie seit das Fahrzeug angeschlossen ist
+channel-type.evcc.charging.label = Ladestatus
+channel-type.evcc.charging.description = Status Ladepunkt
+channel-type.evcc.charging.state.option.ON = Laden
+channel-type.evcc.charging.state.option.OFF = Nicht laden
+channel-type.evcc.enabled.label = Status Laden
+channel-type.evcc.enabled.description = Laden ist freigeschaltet/aktiviert (Modus ist nicht "off")
+channel-type.evcc.enabled.state.option.ON = Aktiviert
+channel-type.evcc.enabled.state.option.OFF = Deaktiviert
+channel-type.evcc.gridPower.label = Netzleistung
+channel-type.evcc.gridPower.description = Aktuelle Leistung vom Stromnetz (negativer Wert means bedeutet Einspeisung)
+channel-type.evcc.hasVehicle.label = Fahrzeug konfiguriert
+channel-type.evcc.hasVehicle.description = Fahrzeug ist für Ladepunkt konfiguriert
+channel-type.evcc.hasVehicle.state.option.ON = Konfiguriert
+channel-type.evcc.hasVehicle.state.option.OFF = Nicht konfiguriert
+channel-type.evcc.homePower.label = Leistungaufnahme Gebäude
+channel-type.evcc.homePower.description = Aktuelle Leistungsaufnahme des Gebäudes
+channel-type.evcc.maxCurrent.label = Laden maximale Stromstärke
+channel-type.evcc.maxCurrent.description = Maximale Stromstärke je Phases mit der das Auto geladen werden soll
+channel-type.evcc.minCurrent.label = Laden minimale Stromstärke
+channel-type.evcc.minCurrent.description = Minimale Stromstärke je Phases mit der das Auto geladen werden soll
+channel-type.evcc.minSoC.label = Minimaler Ladestand Fahrzeug
+channel-type.evcc.minSoC.description = Sofortiges Laden zu diesem Ladestand, wenn der Lademodus nicht "off" ist
+channel-type.evcc.mode.label = Lademodus
+channel-type.evcc.mode.description = Lademodus: "off", "now", "minpv", "pv"
+channel-type.evcc.mode.state.option.off = Aus
+channel-type.evcc.mode.state.option.now = Sofort
+channel-type.evcc.mode.state.option.minpv = Min. + PV
+channel-type.evcc.mode.state.option.pv = Nur PV
+channel-type.evcc.phases.label = Laden aktivierte Phasen
+channel-type.evcc.phases.description = Die maximale Anzahl an nutzbaren Phasen
+channel-type.evcc.pvPower.label = Solar Leistung
+channel-type.evcc.pvPower.description = Aktuelle Leistung von der Solar-Anlage
+channel-type.evcc.targetSoC.label = Ziel-Ladestand
+channel-type.evcc.targetSoC.description = Bis zu welchem Ladestand das Fahrzeug geladen werden soll
+channel-type.evcc.targetTime.label = Laden Ziel-Zeit
+channel-type.evcc.targetTime.description = Wann der Ziel-Ladestand erreicht werden soll
+channel-type.evcc.targetTimeEnabled.label = Laden Ziel-Zeit aktiviert
+channel-type.evcc.targetTimeEnabled.description = Ziel-Zeit für das Laden aktiviert
+channel-type.evcc.targetTimeEnabled.state.option.ON = aktiviert
+channel-type.evcc.targetTimeEnabled.state.option.OFF = deaktiviert
+channel-type.evcc.title.label = Ladepunkt Name
+channel-type.evcc.title.description = Name des Ladepunkts
+channel-type.evcc.vehicleCapacity.label = Fahrzeug Kapazität
+channel-type.evcc.vehicleCapacity.description = Kapazität der Fahrzeug-Batterie
+channel-type.evcc.vehicleConnected.label = Fahrzeug verbunden
+channel-type.evcc.vehicleConnected.description = Ob ein Fahrzeug mit dem Ladepunkt verbunden ist
+channel-type.evcc.vehicleConnected.state.option.ON = Verbunden
+channel-type.evcc.vehicleConnected.state.option.OFF = Nicht verbunden
+channel-type.evcc.vehicleConnectedDuration.label = Dauer Fahrzeug verbunden
+channel-type.evcc.vehicleConnectedDuration.description = Dauer seit der das Fahrzeug mit dem Ladepunkt verbunden ist
+channel-type.evcc.vehicleOdometer.label = Fahrzeug Kilometer-Zähler
+channel-type.evcc.vehicleOdometer.description = Gesamtstrecke die vom Fahrzeug zurückgelegt wurde
+channel-type.evcc.vehiclePresent.label = Fahrzeug Daten-Zugriff
+channel-type.evcc.vehiclePresent.description = Ob evcc auf die Fahrzeug-Daten zugreifen kann
+channel-type.evcc.vehiclePresent.state.option.ON = Daten-Zugriff
+channel-type.evcc.vehiclePresent.state.option.OFF = Kein Daten-Zugriff
+channel-type.evcc.vehicleRange.label = Fahrzeug Reichweite
+channel-type.evcc.vehicleRange.description = Fahrzeug-Batterie Reichweite
+channel-type.evcc.vehicleSoC.label = Fahrzeug Ladestand
+channel-type.evcc.vehicleSoC.description = Aktueller Ladestand der Fahrzeug-Batterie
+channel-type.evcc.vehicleTitle.label = Fahrzeug Name
+channel-type.evcc.vehicleTitle.description = Name des Fahrzeugs
+
+# channel types
+
+offline.configuration-error.no-host = Kein Host konfiguriert
+offline.communication-error.request-failed = Anfrage fehlgeschlagen
diff --git a/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.evcc/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..ecbf948
--- /dev/null
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="evcc"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="device">
+
+               <label>evcc installation</label>
+               <description>A running evcc installation</description>
+
+               <channel-groups>
+                       <channel-group id="general" typeId="general"/>
+                       <channel-group id="loadpoint0" typeId="loadpoint"/>
+                       <channel-group id="loadpoint1" typeId="loadpoint"/>
+                       <channel-group id="loadpoint2" typeId="loadpoint"/>
+                       <channel-group id="loadpoint3" typeId="loadpoint"/>
+                       <channel-group id="loadpoint4" typeId="loadpoint"/>
+                       <channel-group id="loadpoint5" typeId="loadpoint"/>
+                       <channel-group id="loadpoint6" typeId="loadpoint"/>
+                       <channel-group id="loadpoint7" typeId="loadpoint"/>
+                       <channel-group id="loadpoint8" typeId="loadpoint"/>
+                       <channel-group id="loadpoint9" typeId="loadpoint"/>
+               </channel-groups>
+
+               <config-description>
+                       <parameter name="url" type="text" required="true">
+                               <context>network-address</context>
+                               <label>URL</label>
+                               <description>URL of evcc web UI, e.g. https://demo.evcc.io</description>
+                       </parameter>
+                       <parameter name="refreshInterval" type="integer" unit="s" min="15">
+                               <label>Refresh Interval</label>
+                               <description>Interval the status is polled in seconds.</description>
+                               <default>60</default>
+                               <advanced>true</advanced>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-group-type id="general">
+               <label>General data</label>
+       </channel-group-type>
+       <channel-group-type id="loadpoint">
+               <label>Loadpoint</label>
+       </channel-group-type>
+
+       <!-- Units and description on: https://docs.evcc.io/docs/reference/configuration/messaging/#msg -->
+       <channel-type id="batteryPower">
+               <item-type>Number:Power</item-type>
+               <label>Battery Power</label>
+               <description>Current power from battery</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="batterySoC">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Battery SoC</label>
+               <description>Current State of Charge of battery</description>
+               <category>batterylevel</category>
+               <state pattern="%.0f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="batteryPrioritySoC">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Battery Priority SoC</label>
+               <description>State of Charge for which the battery has priority over charging the ev when charging mode is "pv".</description>
+               <category>batterylevel</category>
+               <state pattern="%.0f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="gridPower">
+               <item-type>Number:Power</item-type>
+               <label>Grid Power</label>
+               <description>Current power from grid (negative means feed-in)</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="homePower">
+               <item-type>Number:Power</item-type>
+               <label>Home Power</label>
+               <description>Current power taken by home</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="pvPower">
+               <item-type>Number:Power</item-type>
+               <label>PV Power</label>
+               <description>Current power from photovoltaik</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+
+       <!-- Channels for loadpoints -->
+       <channel-type id="activePhases">
+               <item-type>Number</item-type>
+               <label>Charging active phases</label>
+               <description>Current number of active phases while charging</description>
+               <category></category>
+               <state pattern="%d" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargeCurrent">
+               <item-type>Number:ElectricCurrent</item-type>
+               <label>Charging current</label>
+               <description>Current amperage per connected phase while charging</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargeDuration">
+               <item-type>Number:Time</item-type>
+               <label>Charging duration</label>
+               <description>Charging duration</description>
+               <category>Time</category>
+               <state pattern="%.1f min" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargePower">
+               <item-type>Number:Power</item-type>
+               <label>Charging power</label>
+               <description>Current power of charging</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargeRemainingDuration">
+               <item-type>Number:Time</item-type>
+               <label>Charging remaining duration</label>
+               <description>Remaining duration until target SoC is reached</description>
+               <category>Time</category>
+               <state pattern="%.1f min" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargeRemainingEnergy">
+               <item-type>Number:Energy</item-type>
+               <label>Charging remaining energy</label>
+               <description>Remaining energy until target SoC is reached</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="chargedEnergy">
+               <item-type>Number:Energy</item-type>
+               <label>Charged energy</label>
+               <description>Energy charged since plugged-in</description>
+               <category>Energy</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="charging">
+               <item-type>Switch</item-type>
+               <label>Charging state</label>
+               <description>Loadpoint is currently charging</description>
+               <category>Energy</category>
+               <state pattern="%f %unit%" readOnly="true">
+                       <options>
+                               <option value="ON">Charging</option>
+                               <option value="OFF">Not charging</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="enabled">
+               <item-type>Switch</item-type>
+               <label>Charging enabled</label>
+               <description>Charging enabled (mode not "off")</description>
+               <category>Switch</category>
+               <state readOnly="true">
+                       <options>
+                               <option value="ON">Enabled</option>
+                               <option value="OFF">Disabled</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="hasVehicle">
+               <item-type>Switch</item-type>
+               <label>Loadpoint has vehicle configuration</label>
+               <description>Whether vehicle is configured for loadpoint</description>
+               <category>Switch</category>
+               <state readOnly="true">
+                       <options>
+                               <option value="ON">Configured</option>
+                               <option value="OFF">Not configured</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="maxCurrent">
+               <item-type>Number:ElectricCurrent</item-type>
+               <label>Charging max current</label>
+               <description>Maximum amperage per connected phase with which the car should be charged</description>
+               <category>Energy</category>
+               <state min="0" step="1" pattern="%.0f %unit%" readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="minCurrent">
+               <item-type>Number:ElectricCurrent</item-type>
+               <label>Charging min current</label>
+               <description>Minimum amperage per connected phase with which the car should be charged</description>
+               <category>Energy</category>
+               <state min="0" step="1" pattern="%.0f %unit%" readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="minSoC">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Charging min SoC</label>
+               <description>Charge immediately with maximum power up to the defined SoC, if the charge mode is not set to "off"</description>
+               <category>Batterylevel</category>
+               <state min="0" step="1" max="100" pattern="%.0f %unit%" readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="mode">
+               <item-type>String</item-type>
+               <label>Charging mode</label>
+               <description>Charging mode: "off", "now", "minpv", "pv"</description>
+               <category>String</category>
+               <state readOnly="false">
+                       <options>
+                               <option value="off">Off</option>
+                               <option value="now">Now</option>
+                               <option value="minpv">Min + PV</option>
+                               <option value="pv">Only PV</option>
+                       </options>
+               </state>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="phases">
+               <item-type>Number</item-type>
+               <label>Charging enabled phases</label>
+               <description>The maximum number of phases which can be used</description>
+               <category>Energy</category>
+               <state min="0" step="1" max="3" pattern="%d" readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="targetSoC">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Charging target SoC</label>
+               <description>Until which state of charge (SoC) should the vehicle be charged</description>
+               <category>Batterylevel</category>
+               <state min="0" step="1" max="100" pattern="%.0f %unit%" readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="targetTime">
+               <item-type>DateTime</item-type>
+               <label>Charging target time</label>
+               <description>When the target SoC should be reached</description>
+               <category>Time</category>
+               <state readOnly="false"/>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="targetTimeEnabled">
+               <item-type>Switch</item-type>
+               <label>Charging target time enabled</label>
+               <description>Target time for charging enabled</description>
+               <category>Switch</category>
+               <state readOnly="false">
+                       <options>
+                               <option value="ON">Enabled</option>
+                               <option value="OFF">Disabled</option>
+                       </options>
+               </state>
+               <autoUpdatePolicy>veto</autoUpdatePolicy>
+       </channel-type>
+       <channel-type id="title">
+               <item-type>String</item-type>
+               <label>Loadpoint title</label>
+               <description>Title of loadpoint</description>
+               <category>Text</category>
+               <state readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehicleConnected">
+               <item-type>Switch</item-type>
+               <label>Vehicle connected</label>
+               <description>Whether vehicle is connected to loadpoint</description>
+               <category>Switch</category>
+               <state readOnly="true">
+                       <options>
+                               <option value="ON">Connected</option>
+                               <option value="OFF">Not connected</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="vehicleConnectedDuration">
+               <item-type>Number:Time</item-type>
+               <label>Vehicle connected duration</label>
+               <description>Duration the vehicle is connected to loadpoint</description>
+               <category>Time</category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehicleCapacity">
+               <item-type>Number:Energy</item-type>
+               <label>Vehicle capacity</label>
+               <description>Capacity of EV battery</description>
+               <category>Energy</category>
+               <state pattern="%.0f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehicleOdometer">
+               <item-type>Number:Length</item-type>
+               <label>Vehicle odometer</label>
+               <description>Total distance travelled by EV</description>
+               <category></category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehiclePresent">
+               <item-type>Switch</item-type>
+               <label>Vehicle data access</label>
+               <description>Whether evcc is able to get data from vehicle</description>
+               <category>Switch</category>
+               <state readOnly="true">
+                       <options>
+                               <option value="ON">Data access</option>
+                               <option value="OFF">No data access</option>
+                       </options>
+               </state>
+       </channel-type>
+       <channel-type id="vehicleRange">
+               <item-type>Number:Length</item-type>
+               <label>Vehicle range</label>
+               <description>Battery range for EV</description>
+               <category></category>
+               <state pattern="%.1f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehicleSoC">
+               <item-type>Number:Dimensionless</item-type>
+               <label>Vehicle SoC</label>
+               <description>Current State of Charge of EV</description>
+               <category>Batterylevel</category>
+               <state pattern="%.0f %unit%" readOnly="true"/>
+       </channel-type>
+       <channel-type id="vehicleTitle">
+               <item-type>String</item-type>
+               <label>Vehicle title</label>
+               <description>Name of EV</description>
+               <category>Text</category>
+               <state readOnly="true"/>
+       </channel-type>
+</thing:thing-descriptions>
index c2274f6475a5ec6e08bdfc9217f07da6f6e8ac19..3487366834bf5c2bbbc470adc55be6141af63924 100644 (file)
     <module>org.openhab.binding.enturno</module>
     <module>org.openhab.binding.epsonprojector</module>
     <module>org.openhab.binding.etherrain</module>
+    <module>org.openhab.binding.evcc</module>
     <module>org.openhab.binding.evohome</module>
     <module>org.openhab.binding.exec</module>
     <module>org.openhab.binding.feed</module>