]> git.basschouten.com Git - openhab-addons.git/commitdiff
[kostalinverter] Fixed package structure (#9559)
authorHilbrand Bouwkamp <hilbrand@h72.nl>
Tue, 12 Jan 2021 21:15:21 +0000 (22:15 +0100)
committerGitHub <noreply@github.com>
Tue, 12 Jan 2021 21:15:21 +0000 (22:15 +0100)
The internal package was placed before the binding specific package, and it should be the other way around.

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
26 files changed:
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/KostalInverterFactory.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/ChannelConfig.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/SourceConfig.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/WebscrapeHandler.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationBindingConstants.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelDatatypes.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationConfiguration.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationEncryptionHelper.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHandler.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHttpHelper.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationInverterTypes.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationMappingInverterToChannel.java [deleted file]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/ChannelConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/SourceConfig.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/WebscrapeHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelDatatypes.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationEncryptionHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHttpHelper.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationInverterTypes.java [new file with mode: 0644]
bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationMappingInverterToChannel.java [new file with mode: 0644]

diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/KostalInverterFactory.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/KostalInverterFactory.java
deleted file mode 100644 (file)
index 47a3c49..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter;
-
-import static org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationBindingConstants.*;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
-import org.openhab.binding.internal.kostal.inverter.firstgeneration.WebscrapeHandler;
-import org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationHandler;
-import org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationInverterTypes;
-import org.openhab.core.io.net.http.HttpClientFactory;
-import org.openhab.core.thing.Thing;
-import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.thing.binding.BaseThingHandlerFactory;
-import org.openhab.core.thing.binding.ThingHandler;
-import org.openhab.core.thing.binding.ThingHandlerFactory;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * @author Christian Schneider - Initial contribution (as WebscrapeHandlerFactory.java)
- * @author René Stakemeier - extension for the third generation of KOSTAL inverters
- */
-@Component(service = ThingHandlerFactory.class, configurationPid = "binding.kostalinverter")
-@NonNullByDefault
-public class KostalInverterFactory extends BaseThingHandlerFactory {
-
-    private static final Map<ThingTypeUID, ThirdGenerationInverterTypes> SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS = new HashMap<>();
-    static {
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ42, ThirdGenerationInverterTypes.PIKOIQ_42);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ55, ThirdGenerationInverterTypes.PIKOIQ_55);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ70, ThirdGenerationInverterTypes.PIKOIQ_70);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ85, ThirdGenerationInverterTypes.PIKOIQ_85);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ100, ThirdGenerationInverterTypes.PIKOIQ_100);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS42WITHBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITH_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS55WITHBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITH_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS70WITHBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITH_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS85WITHBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITH_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS100WITHBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITH_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS42WITHOUTBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITHOUT_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS55WITHOUTBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITHOUT_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS70WITHOUTBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITHOUT_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS85WITHOUTBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITHOUT_BATTERY);
-        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS100WITHOUTBATTERY,
-                ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITHOUT_BATTERY);
-    }
-
-    public static final ThingTypeUID FIRST_GENERATION_INVERTER = new ThingTypeUID("kostalinverter", "kostalinverter");
-
-    private final HttpClient httpClient;
-
-    @Activate
-    public KostalInverterFactory(@Reference final HttpClientFactory httpClientFactory) {
-        this.httpClient = httpClientFactory.getCommonHttpClient();
-    }
-
-    @Override
-    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
-        return thingTypeUID.equals(FIRST_GENERATION_INVERTER)
-                || SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.keySet().contains(thingTypeUID);
-    }
-
-    @Override
-    protected @Nullable ThingHandler createHandler(Thing thing) {
-        // first generation
-        if (FIRST_GENERATION_INVERTER.equals(thing.getThingTypeUID())) {
-            return new WebscrapeHandler(thing);
-        }
-        // third generation
-        ThirdGenerationInverterTypes inverterType = SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS
-                .get(thing.getThingTypeUID());
-        if (inverterType != null) {
-            return new ThirdGenerationHandler(thing, httpClient, inverterType);
-        }
-        return null;
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/ChannelConfig.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/ChannelConfig.java
deleted file mode 100644 (file)
index 145c532..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.firstgeneration;
-
-import javax.measure.Unit;
-
-/**
- * @author Christian Schneider - Initial contribution
- * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
- */
-public class ChannelConfig {
-    public ChannelConfig(String id, String tag, int num, Unit<?> unit) {
-        this.id = id;
-        this.tag = tag;
-        this.num = num;
-        this.unit = unit;
-    }
-
-    String id;
-    String tag;
-    int num;
-    Unit<?> unit;
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/SourceConfig.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/SourceConfig.java
deleted file mode 100644 (file)
index 04c7060..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.firstgeneration;
-
-/**
- * @author Christian Schneider - Initial contribution
- */
-public class SourceConfig {
-    public String url;
-    public String userName;
-    public String password;
-    public int refreshInterval;
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/WebscrapeHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/firstgeneration/WebscrapeHandler.java
deleted file mode 100644 (file)
index 5238893..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.firstgeneration;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.measure.Unit;
-
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.types.StringType;
-import org.openhab.core.library.unit.Units;
-import org.openhab.core.thing.Channel;
-import org.openhab.core.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.types.Command;
-import org.openhab.core.types.State;
-import org.openhab.core.types.UnDefType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @author Christian Schneider - Initial contribution
- * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
- */
-public class WebscrapeHandler extends BaseThingHandler {
-    private Logger logger = LoggerFactory.getLogger(WebscrapeHandler.class);
-    private SourceConfig config;
-
-    private final List<ChannelConfig> channelConfigs = new ArrayList<>();
-
-    public WebscrapeHandler(Thing thing) {
-        super(thing);
-        channelConfigs.add(new ChannelConfig("acPower", "td", 4, Units.WATT));
-        channelConfigs.add(new ChannelConfig("totalEnergy", "td", 7, Units.KILOWATT_HOUR));
-        channelConfigs.add(new ChannelConfig("dayEnergy", "td", 10, Units.KILOWATT_HOUR));
-        channelConfigs.add(new ChannelConfig("status", "td", 13, null));
-        channelConfigs.add(new ChannelConfig("str1Voltage", "td", 19, Units.VOLT));
-        channelConfigs.add(new ChannelConfig("str1Current", "td", 25, Units.AMPERE));
-        channelConfigs.add(new ChannelConfig("str2Voltage", "td", 33, Units.VOLT));
-        channelConfigs.add(new ChannelConfig("str2Current", "td", 39, Units.AMPERE));
-        channelConfigs.add(new ChannelConfig("l1Voltage", "td", 22, Units.VOLT));
-        channelConfigs.add(new ChannelConfig("l1Power", "td", 28, Units.WATT));
-        channelConfigs.add(new ChannelConfig("l2Voltage", "td", 36, Units.VOLT));
-        channelConfigs.add(new ChannelConfig("l2Power", "td", 42, Units.WATT));
-        channelConfigs.add(new ChannelConfig("l3Voltage", "td", 46, Units.VOLT));
-        channelConfigs.add(new ChannelConfig("l3Power", "td", 49, Units.WATT));
-    }
-
-    @Override
-    public void initialize() {
-        config = getConfigAs(SourceConfig.class);
-        scheduler.scheduleWithFixedDelay(() -> {
-            try {
-                refresh();
-                updateStatus(ThingStatus.ONLINE);
-            } catch (Exception e) {
-                logger.debug("Error refreshing source '{}'", getThing().getUID(), e);
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                        e.getClass().getName() + ":" + e.getMessage());
-            }
-        }, 0, config.refreshInterval, TimeUnit.SECONDS);
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        // Read only
-    }
-
-    private void refresh() throws Exception {
-        Document doc = getDoc();
-        for (ChannelConfig cConfig : channelConfigs) {
-            Channel channel = getThing().getChannel(cConfig.id);
-            if (channel != null) {
-                String value = getTag(doc, cConfig.tag).get(cConfig.num);
-                updateState(channel.getUID(), getState(value, cConfig.unit));
-            }
-        }
-    }
-
-    private static List<String> getTag(Document doc, String tag) {
-        List<String> result = new ArrayList<>();
-        Iterator<Element> elIt = doc.getElementsByTag(tag).iterator();
-        while (elIt.hasNext()) {
-            String content = elIt.next().text();
-            content = content.replace("\u00A0", "").trim();
-            if (!content.isEmpty()) {
-                result.add(content);
-            }
-        }
-        return result;
-    }
-
-    private Document getDoc() throws IOException {
-        String login = config.userName + ":" + config.password;
-        String base64login = new String(Base64.getEncoder().encode(login.getBytes()));
-        return Jsoup.connect(config.url).header("Authorization", "Basic " + base64login).get();
-    }
-
-    private State getState(String value, Unit<?> unit) {
-        if (unit == null) {
-            return new StringType(value);
-        } else {
-            try {
-                return new QuantityType<>(new BigDecimal(value), unit);
-            } catch (NumberFormatException e) {
-                logger.debug("Error parsing value '{}'", value, e);
-                return UnDefType.UNDEF;
-            }
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationBindingConstants.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationBindingConstants.java
deleted file mode 100644 (file)
index 9390ccb..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.core.thing.ThingTypeUID;
-
-/**
- * The {@link ThirdGenerationBindingConstants} class defines common constants, which are
- * used across the whole binding.
- *
- * @author René Stakemeier - Initial contribution
- */
-@NonNullByDefault
-public class ThirdGenerationBindingConstants {
-
-    private static final String BINDING_ID = "kostalinverter";
-
-    // List of all constants used for the authentication
-    static final String USER_TYPE = "user";
-    static final String HMAC_SHA256_ALGORITHM = "HMACSHA256";
-    static final String SHA_256_HASH = "SHA-256";
-    static final int AES_GCM_TAG_LENGTH = 128; // bit count
-
-    // List of all Thing Type UIDs
-    public static final ThingTypeUID PIKOIQ42 = new ThingTypeUID(BINDING_ID, "PIKOIQ42");
-    public static final ThingTypeUID PIKOIQ55 = new ThingTypeUID(BINDING_ID, "PIKOIQ55");
-    public static final ThingTypeUID PIKOIQ70 = new ThingTypeUID(BINDING_ID, "PIKOIQ70");
-    public static final ThingTypeUID PIKOIQ85 = new ThingTypeUID(BINDING_ID, "PIKOIQ85");
-    public static final ThingTypeUID PIKOIQ100 = new ThingTypeUID(BINDING_ID, "PIKOIQ100");
-    public static final ThingTypeUID PLENTICOREPLUS42WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS42WITHOUTBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS55WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS55WITHOUTBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS70WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS70WITHOUTBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS85WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS85WITHOUTBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS100WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS100WITHOUTBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS42WITHBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS42WITHBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS55WITHBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS55WITHBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS70WITHBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS70WITHBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS85WITHBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS85WITHBATTERY");
-    public static final ThingTypeUID PLENTICOREPLUS100WITHBATTERY = new ThingTypeUID(BINDING_ID,
-            "PLENTICOREPLUS100WITHBATTERY");
-
-    // List of error messages
-    public static final String COMMUNICATION_ERROR_AUTHENTICATION = "Error during the initialisation of the authentication";
-    public static final String COMMUNICATION_ERROR_UNSUPPORTED_ENCODING = "The text encoding is not supported by this openHAB installation";
-    public static final String COMMUNICATION_ERROR_AES_ERROR = "The java installation does not support AES encryption in GCM mode";
-    public static final String COMMUNICATION_ERROR_HTTP = "HTTP communication error: No response from device";
-    public static final String COMMUNICATION_ERROR_JSON = "HTTP communication error: answer did not match the expected format";
-    public static final String COMMUNICATION_ERROR_API_CHANGED = "The API seems to have changed :-( Maybe this implementation has become incompatible with the device";
-    public static final String COMMUNICATION_ERROR_INCOMPATIBLE_DEVICE = "The device could not provide the required information. Please check, if you selected the right thing for your device!";
-    public static final String COMMUNICATION_ERROR_USER_ACCOUNT_LOCKED = "Your user account on the device is locked. Please reset the password by following the instructions on the device´s web frontend";
-    public static final String CONFIGURATION_ERROR_PASSWORD = "Wrong password";
-
-    // List of all Channel uids
-    public static final String CHANNEL_DEVICE_LOCAL_DC_POWER = "deviceLocalDCPower";
-    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_BATTERY = "deviceLocalHomeconsumptionFromBattery";
-    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_GRID = "deviceLocalHomeconsumptionFromGrid";
-    public static final String CHANNEL_DEVICE_LOCAL_OWNCONSUMPTION = "deviceLocalOwnconsumption";
-    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_PV = "deviceLocalHomeconsumptionFromPV";
-    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_TOTAL = "deviceLocalHomeconsumptionTotal";
-    public static final String CHANNEL_DEVICE_LOCAL_LIMIT_EVU_ABSOLUTE = "deviceLocalLimitEVUAbsolute";
-    public static final String CHANNEL_DEVICE_LOCAL_LIMIT_EVU_RELATIV = "deviceLocalLimitEVURelativ";
-    public static final String CHANNEL_DEVICE_LOCAL_WORKTIME = "deviceLocalWorktime";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_AMPERAGE = "deviceLocalACPhase1CurrentAmperage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_POWER = "deviceLocalACPhase1CurrentPower";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_VOLTAGE = "deviceLocalACPhase1CurrentVoltage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_AMPERAGE = "deviceLocalACPhase2CurrentAmperage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_POWER = "deviceLocalACPhase2CurrentPower";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_VOLTAGE = "deviceLocalACPhase2CurrentVoltage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_AMPERAGE = "deviceLocalACPhase3CurrentAmperage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_POWER = "deviceLocalACPhase3CurrentPower";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_VOLTAGE = "deviceLocalACPhase3CurrentVoltage";
-    public static final String CHANNEL_DEVICE_LOCAL_AC_CURRENT_POWER = "deviceLocalACCurrentPower";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_LOADING_CYCLES = "deviceLocalBatteryLoadingCycles";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_FULL_CHARGE_CAPACITY = "deviceLocalBatteryFullChargeCapacity";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_AMPERAGE = "deviceLocalBatteryAmperage";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_POWER = "deviceLocalBatteryPower";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_STATE_OF_CHARGE = "deviceLocalBatteryStageOfCharge";
-    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_VOLTAGE = "deviceLocalBatteryVoltage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_AMPERAGE = "deviceLocalPVString1Amperage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_POWER = "deviceLocalPVString1Power";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_VOLTAGE = "deviceLocalPVString1Voltage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_AMPERAGE = "deviceLocalPVString2Amperage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_POWER = "deviceLocalPVString2Power";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_VOLTAGE = "deviceLocalPVString2Voltage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_AMPERAGE = "deviceLocalPVString3Amperage";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_POWER = "deviceLocalPVString3Power";
-    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_VOLTAGE = "deviceLocalPVString3Voltage";
-    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_MC = "SCBEventErrorCountMc";
-    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_SFH = "SCBEventErrorCountSFH";
-    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_SCB = "SCBEventErrorCountSCB";
-    public static final String CHANNEL_SCB_EVENT_WARNING_COUNT_SCB = "SCBEventWarningCountSCB";
-    public static final String CHANNEL_STATISTIC_AUTARKY_DAY = "statisticAutarkyDay";
-    public static final String CHANNEL_STATISTIC_AUTARKY_MONTH = "statisticAutarkyMonth";
-    public static final String CHANNEL_STATISTIC_AUTARKY_TOTAL = "statisticAutarkyTotal";
-    public static final String CHANNEL_STATISTIC_AUTARKY_YEAR = "statisticAutarkyYear";
-    public static final String CHANNEL_STATISTIC_CO2SAVING_DAY = "statisticCo2SavingDay";
-    public static final String CHANNEL_STATISTIC_CO2SAVING_MONTH = "statisticCo2SavingMonth";
-    public static final String CHANNEL_STATISTIC_CO2SAVING_TOTAL = "statisticCo2SavingTotal";
-    public static final String CHANNEL_STATISTIC_CO2SAVING_YEAR = "statisticCo2SavingYear";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_DAY = "statisticHomeconsumptionDay";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_MONTH = "statisticHomeconsumptionMonth";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_TOTAL = "statisticHomeconsumptionTotal";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_YEAR = "statisticHomeconsumptionYear";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_DAY = "statisticHomeconsumptionFromBatteryDay";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_MONTH = "statisticHomeconsumptionFromBatteryMonth";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_TOTAL = "statisticHomeconsumptionFromBatteryTotal";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_YEAR = "statisticHomeconsumptionFromBatteryYear";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_DAY = "statisticHomeconsumptionFromGridDay";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_MONTH = "statisticHomeconsumptionFromGridMonth";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_TOTAL = "statisticHomeconsumptionFromGridTotal";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_YEAR = "statisticHomeconsumptionFromGridYear";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_DAY = "statisticHomeconsumptionFromPVDay";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_MONTH = "statisticHomeconsumptionFromPVMonth";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_TOTAL = "statisticHomeconsumptionFromPVTotal";
-    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_YEAR = "statisticHomeconsumptionFromPVYear";
-    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_DAY = "statisticOwnconsumptionRateDay";
-    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_MONTH = "statisticOwnconsumptionRateMonth";
-    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_TOTAL = "statisticOwnconsumptionRateTotal";
-    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_YEAR = "statisticOwnconsumptionRateYear";
-    public static final String CHANNEL_STATISTIC_YIELD_DAY = "statisticYieldDay";
-    public static final String CHANNEL_STATISTIC_YIELD_MONTH = "statisticYieldMonth";
-    public static final String CHANNEL_STATISTIC_YIELD_TOTAL = "statisticYieldTotal";
-    public static final String CHANNEL_STATISTIC_YIELD_YEAR = "statisticYieldYear";
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelDatatypes.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelDatatypes.java
deleted file mode 100644 (file)
index 1004ee2..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-/**
- * The {@link ThirdGenerationChannelDatatypes} enumeration contains the data types provided by the device
- *
- * @author René Stakemeier - Initial contribution
- */
-enum ThirdGenerationChannelDatatypes {
-    INTEGER,
-    PERCEMTAGE,
-    KILOGRAM,
-    SECONDS,
-    KILOWATT_HOUR,
-    WATT,
-    AMPERE,
-    AMPERE_HOUR,
-    VOLT
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java
deleted file mode 100644 (file)
index ebb299b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-/***
- * The {@link ThirdGenerationChannelMappingToWebApi}} is used to map the channel name to the web API commands
- *
- * @author René Stakemeier - Initial contribution
- *
- */
-class ThirdGenerationChannelMappingToWebApi {
-
-    String channelUID;
-    String moduleId;
-    String processdataId;
-    ThirdGenerationChannelDatatypes dataType;
-
-    /**
-     * Constructor of {@link ThirdGenerationChannelMappingToWebApi}
-     *
-     * @param channelUID The channel UUID
-     * @param moduleId module id (as defined by the web api)
-     * @param processdataId process data id (as defined by the web api)
-     * @param dataType data type of this channel
-     */
-    ThirdGenerationChannelMappingToWebApi(String channelUID, String moduleId, String processdataId,
-            ThirdGenerationChannelDatatypes dataType) {
-        this.channelUID = channelUID;
-        this.moduleId = moduleId;
-        this.processdataId = processdataId;
-        this.dataType = dataType;
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationConfiguration.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationConfiguration.java
deleted file mode 100644 (file)
index a77a6a2..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-/**
- * The {@link ThirdGenerationConfiguration} class contains fields mapping thing configuration parameters.
- *
- * @author René Stakemeier - Initial contribution
- */
-public class ThirdGenerationConfiguration {
-
-    public String url;
-    public String userPassword;
-    public int refreshInternalInSeconds;
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationEncryptionHelper.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationEncryptionHelper.java
deleted file mode 100644 (file)
index db4079d..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-import static org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationBindingConstants.*;
-
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Base64;
-import java.util.Random;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-
-/**
- * The {@link ThirdGenerationEncryptionHelper} is responsible for handling the encryption for the authentication
- * handlers.
- *
- * @author René Stakemeier - Initial contribution
- */
-final class ThirdGenerationEncryptionHelper {
-
-    private ThirdGenerationEncryptionHelper() {
-    }
-
-    /**
-     * This method generates the HMACSha256 encrypted value of the given value
-     *
-     * @param password Password used for encryption
-     * @param valueToEncrypt value to encrypt
-     * @return encrypted value
-     * @throws InvalidKeyException thrown if the key generated from the password is invalid
-     * @throws NoSuchAlgorithmException thrown if HMAC SHA 256 is not supported
-     */
-    static byte[] getHMACSha256(byte[] password, String valueToEncrypt)
-            throws InvalidKeyException, NoSuchAlgorithmException {
-        SecretKeySpec signingKey = new SecretKeySpec(password, HMAC_SHA256_ALGORITHM);
-        Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
-        mac.init(signingKey);
-        mac.update(valueToEncrypt.getBytes());
-        return mac.doFinal();
-    }
-
-    /**
-     * This methods generates the client proof.
-     * It is calculated as XOR between the {@link clientSignature} and the {@link serverSignature}
-     *
-     * @param clientSignature client signature
-     * @param serverSignature server signature
-     * @return client proof
-     */
-    static String createClientProof(byte[] clientSignature, byte[] serverSignature) {
-        byte[] result = new byte[clientSignature.length];
-        for (int i = 0; i < clientSignature.length; i++) {
-            result[i] = (byte) (0xff & (clientSignature[i] ^ serverSignature[i]));
-        }
-        return Base64.getEncoder().encodeToString(result);
-    }
-
-    /**
-     * Create the PBKDF2 hash
-     *
-     * @param password password
-     * @param salt salt
-     * @param rounds rounds
-     * @return hash
-     * @throws NoSuchAlgorithmException if PBKDF2WithHmacSHA256 is not supported
-     * @throws InvalidKeySpecException if the key specification is not supported
-     */
-    static byte[] getPBKDF2Hash(String password, byte[] salt, int rounds)
-            throws NoSuchAlgorithmException, InvalidKeySpecException {
-        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, rounds, 256);
-        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
-        return skf.generateSecret(spec).getEncoded();
-    }
-
-    /**
-     * Create the SHA256 hash value for the given byte array
-     *
-     * @param valueToHash byte array to get the hash value for
-     * @return the hash value
-     * @throws NoSuchAlgorithmException if SHA256 is not supported
-     */
-    static byte[] getSha256Hash(byte[] valueToHash) throws NoSuchAlgorithmException {
-        return MessageDigest.getInstance(SHA_256_HASH).digest(valueToHash);
-    }
-
-    /**
-     * Create the nonce (numbers used once) for the client for communication
-     *
-     * @return nonce
-     */
-    static String createClientNonce() {
-        Random generator = new Random();
-
-        // Randomize the random generator
-        byte[] randomizeArray = new byte[1024];
-        generator.nextBytes(randomizeArray);
-
-        // 3 words of 4 bytes are required for the handshake
-        byte[] nonceArray = new byte[12];
-        generator.nextBytes(nonceArray);
-
-        // return the base64 encoded value of the random words
-        return Base64.getMimeEncoder().encodeToString(nonceArray);
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHandler.java
deleted file mode 100644 (file)
index 7f815b7..0000000
+++ /dev/null
@@ -1,485 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-import static org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationBindingConstants.*;
-
-import java.io.UnsupportedEncodingException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.spec.InvalidKeySpecException;
-import java.util.Base64;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.openhab.core.library.types.DecimalType;
-import org.openhab.core.library.types.QuantityType;
-import org.openhab.core.library.unit.SIUnits;
-import org.openhab.core.library.unit.Units;
-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.types.Command;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-
-/**
- * The {@link ThirdGenerationHandler} is responsible for handling commands, which are
- * sent to one of the channels.
- *
- * @author René Stakemeier - Initial contribution
- */
-@NonNullByDefault
-public class ThirdGenerationHandler extends BaseThingHandler {
-
-    /*
-     * operations used for authentication
-     */
-    private static final String AUTH_START = "/auth/start";
-    private static final String AUTH_FINISH = "/auth/finish";
-    private static final String AUTH_CREATE_SESSION = "/auth/create_session";
-
-    /*
-     * operations used for gathering process data from the device
-     */
-    private static final String PROCESSDATA = "/processdata";
-
-    /*
-     * After the authentication the result (the session id) is stored here and used to "sign" future requests
-     */
-    private @Nullable String sessionId;
-    /*
-     * The configuration file containing the host, the password and the refresh interval
-     */
-    private @NonNullByDefault({}) ThirdGenerationConfiguration config;
-
-    private @Nullable ScheduledFuture<?> refreshScheduler;
-
-    private @Nullable HttpClient httpClient;
-
-    private ThirdGenerationInverterTypes inverterType;
-
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    /**
-     * Constructor of this class
-     *
-     * @param thing the thing
-     * @param httpClient the httpClient used for communication
-     * @param inverterType the type of the device
-     */
-    public ThirdGenerationHandler(Thing thing, HttpClient httpClient, ThirdGenerationInverterTypes inverterType) {
-        super(thing);
-        this.inverterType = inverterType;
-        this.httpClient = httpClient;
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        // All channels are readonly and updated by the scheduler
-    }
-
-    @Override
-    public void dispose() {
-        if (refreshScheduler != null) {
-            refreshScheduler.cancel(true);
-            refreshScheduler = null;
-        }
-        super.dispose();
-    }
-
-    @Override
-    public void initialize() {
-        config = getConfigAs(ThirdGenerationConfiguration.class);
-        // temporary value while initializing
-        updateStatus(ThingStatus.UNKNOWN);
-
-        // Start the authentication
-        scheduler.schedule(this::authenticate, 1, TimeUnit.SECONDS);
-
-        // Start the update scheduler as configured
-        refreshScheduler = scheduler.scheduleWithFixedDelay(this::updateChannelValues, 10,
-                config.refreshInternalInSeconds, TimeUnit.SECONDS);
-    }
-
-    /**
-     * The API supports the resolution of multiple values at a time
-     *
-     * Therefore this methods builds one request to gather all information for the current inverter.
-     * The list contains all channels as defined in {@link ThirdGenerationMappingInverterToChannel} for the
-     * current inverter
-     *
-     */
-    private void updateChannelValues() {
-        Map<String, List<ThirdGenerationChannelMappingToWebApi>> channelList = ThirdGenerationMappingInverterToChannel
-                .getModuleToChannelsMappingForInverter(inverterType);
-        JsonArray updateMessageJsonArray = getUpdateChannelMessage(channelList);
-
-        // Send the API request to get values for all channels
-        ContentResponse updateMessageContentResponse;
-        try {
-            updateMessageContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
-                    PROCESSDATA, updateMessageJsonArray, sessionId);
-            if (updateMessageContentResponse.getStatus() == 404) {
-                // Module not found
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                        COMMUNICATION_ERROR_INCOMPATIBLE_DEVICE);
-                return;
-            }
-            if (updateMessageContentResponse.getStatus() == 503) {
-                // Communication error (e.g. during initial boot of the SCB)
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                        COMMUNICATION_ERROR_HTTP);
-                return;
-            }
-            if (updateMessageContentResponse.getStatus() == 401) {
-                // session not valid (timed out? device rebooted?)
-                logger.info("Session expired - performing retry");
-                authenticate();
-                // Retry
-                updateMessageContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
-                        PROCESSDATA, updateMessageJsonArray, sessionId);
-            }
-        } catch (TimeoutException | ExecutionException e) {
-            // Communication problem
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
-            return;
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
-            return;
-        }
-        JsonArray updateMessageResultsJsonArray = ThirdGenerationHttpHelper
-                .getJsonArrayFromResponse(updateMessageContentResponse);
-
-        // Map the returned values back to the channels and update them
-        for (int i = 0; i < updateMessageResultsJsonArray.size(); i++) {
-            JsonObject moduleAnswer = updateMessageResultsJsonArray.get(i).getAsJsonObject();
-            String moduleName = moduleAnswer.get("moduleid").getAsString();
-            JsonArray processdata = moduleAnswer.get("processdata").getAsJsonArray();
-            for (int j = 0; j < processdata.size(); j++) {
-                // Update the channels with their new value
-                JsonObject newValueObject = processdata.get(j).getAsJsonObject();
-                String valueId = newValueObject.get("id").getAsString();
-                double valueAsDouble = newValueObject.get("value").getAsDouble();
-                ThirdGenerationChannelMappingToWebApi channel = channelList.get(moduleName).stream()
-                        .filter(c -> c.moduleId.equals(moduleName) && c.processdataId.equals(valueId)).findFirst()
-                        .get();
-                updateChannelValue(channel.channelUID, channel.dataType, valueAsDouble);
-            }
-        }
-        updateStatus(ThingStatus.ONLINE);
-    }
-
-    /**
-     * Update the channel to the given value.
-     * The value is set to the matching data (SITypes etc)
-     *
-     * @param channeluid Channel to update
-     * @param dataType target data type
-     * @param value value
-     */
-    private void updateChannelValue(String channeluid, ThirdGenerationChannelDatatypes dataType, Double value) {
-        switch (dataType) {
-            case INTEGER: {
-                updateState(channeluid, new DecimalType(value.longValue()));
-                break;
-            }
-            case PERCEMTAGE: {
-                updateState(channeluid, new QuantityType<>(value, Units.PERCENT));
-                break;
-            }
-            case KILOGRAM: {
-                updateState(channeluid, new QuantityType<>(value / 1000, SIUnits.KILOGRAM));
-                break;
-            }
-            case SECONDS: {
-                updateState(channeluid, new QuantityType<>(value, Units.SECOND));
-                break;
-            }
-            case KILOWATT_HOUR: {
-                updateState(channeluid, new QuantityType<>(value / 1000, Units.KILOWATT_HOUR));
-                break;
-            }
-            case WATT: {
-                updateState(channeluid, new QuantityType<>(value, Units.WATT));
-                break;
-            }
-            case AMPERE: {
-                updateState(channeluid, new QuantityType<>(value, Units.AMPERE));
-                break;
-            }
-            case AMPERE_HOUR: {
-                // Ampere hours is not a supported unit, but 1 AH is equal tp 3600 coulomb...
-                updateState(channeluid, new QuantityType<>(value * 3600, Units.COULOMB));
-                break;
-            }
-            case VOLT: {
-                updateState(channeluid, new QuantityType<>(value, Units.VOLT));
-                break;
-            }
-            default: {
-                // unknown datatype
-                logger.debug("{} not known!", dataType);
-            }
-        }
-    }
-
-    /**
-     * Creates the message which has to be send to the inverter to gather the current informations for all channels
-     *
-     * @param channelList channels of this thing
-     * @return the JSON array to send to the device
-     */
-    private JsonArray getUpdateChannelMessage(Map<String, List<ThirdGenerationChannelMappingToWebApi>> channelList) {
-        // Build the message to send to the inverter
-        JsonArray updateMessageJsonArray = new JsonArray();
-        for (Entry<String, List<ThirdGenerationChannelMappingToWebApi>> moduleId : channelList.entrySet()) {
-            JsonObject moduleJsonObject = new JsonObject();
-            moduleJsonObject.addProperty("moduleid", moduleId.getKey());
-
-            JsonArray processdataNames = new JsonArray();
-            for (ThirdGenerationChannelMappingToWebApi processdata : channelList.get(moduleId.getKey())) {
-                processdataNames.add(processdata.processdataId);
-            }
-            moduleJsonObject.add("processdataids", processdataNames);
-            updateMessageJsonArray.add(moduleJsonObject);
-        }
-        return updateMessageJsonArray;
-    }
-
-    /**
-     * This function is used to authenticate against the SCB.
-     * SCB uses PBKDF2 and AES256 GCM mode with a slightly modified authentication message.
-     * The authentication will fail on JRE < 8u162. since the security policy is set to "limited" by default (see readme
-     * for fix)
-     */
-    private final void authenticate() {
-        // Create random numbers
-        String clientNonce = ThirdGenerationEncryptionHelper.createClientNonce();
-        // Perform first step of authentication
-        JsonObject authMeJsonObject = new JsonObject();
-        authMeJsonObject.addProperty("username", USER_TYPE);
-        authMeJsonObject.addProperty("nonce", clientNonce);
-
-        ContentResponse authStartResponseContentResponse;
-        try {
-            authStartResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
-                    AUTH_START, authMeJsonObject);
-
-            // 200 is the desired status code
-            int statusCode = authStartResponseContentResponse.getStatus();
-
-            if (statusCode == 400) {
-                // Invalid user (which is hard coded and therefore can not be wrong until the api is changed by the
-                // manufacturer
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                        COMMUNICATION_ERROR_API_CHANGED);
-                return;
-            }
-            if (statusCode == 403) {
-                // User is logged
-                // This can happen, if the user had to many bad attempts of entering the password in the web
-                // front end
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
-                        COMMUNICATION_ERROR_USER_ACCOUNT_LOCKED);
-                return;
-            }
-
-            if (statusCode == 503) {
-                // internal communication error
-                // This can happen if the device is not ready yet for communication
-                updateStatus(ThingStatus.UNINITIALIZED);
-                return;
-            }
-        } catch (InterruptedException | TimeoutException | ExecutionException e) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
-            return;
-        }
-        JsonObject authMeResponseJsonObject = ThirdGenerationHttpHelper
-                .getJsonObjectFromResponse(authStartResponseContentResponse);
-
-        // Extract information from the response
-        String salt = authMeResponseJsonObject.get("salt").getAsString();
-        String serverNonce = authMeResponseJsonObject.get("nonce").getAsString();
-        int rounds = authMeResponseJsonObject.get("rounds").getAsInt();
-        String transactionId = authMeResponseJsonObject.get("transactionId").getAsString();
-
-        // Do the cryptography stuff (magic happens here)
-        byte[] saltedPasswort;
-        byte[] clientKey;
-        byte[] serverKey;
-        byte[] storedKey;
-        byte[] clientSignature;
-        byte[] serverSignature;
-        String authMessage;
-        try {
-            saltedPasswort = ThirdGenerationEncryptionHelper.getPBKDF2Hash(config.userPassword,
-                    Base64.getDecoder().decode(salt), rounds);
-            clientKey = ThirdGenerationEncryptionHelper.getHMACSha256(saltedPasswort, "Client Key");
-            serverKey = ThirdGenerationEncryptionHelper.getHMACSha256(saltedPasswort, "Server Key");
-            storedKey = ThirdGenerationEncryptionHelper.getSha256Hash(clientKey);
-            authMessage = String.format("n=%s,r=%s,r=%s,s=%s,i=%d,c=biws,r=%s", USER_TYPE, clientNonce, serverNonce,
-                    salt, rounds, serverNonce);
-            clientSignature = ThirdGenerationEncryptionHelper.getHMACSha256(storedKey, authMessage);
-            serverSignature = ThirdGenerationEncryptionHelper.getHMACSha256(serverKey, authMessage);
-        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | IllegalStateException e2) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                    COMMUNICATION_ERROR_AUTHENTICATION);
-            return;
-        }
-        String clientProof = ThirdGenerationEncryptionHelper.createClientProof(clientSignature, clientKey);
-        // Perform step 2 of the authentication
-        JsonObject authFinishJsonObject = new JsonObject();
-        authFinishJsonObject.addProperty("transactionId", transactionId);
-        authFinishJsonObject.addProperty("proof", clientProof);
-
-        ContentResponse authFinishResponseContentResponse;
-        JsonObject authFinishResponseJsonObject;
-        try {
-            authFinishResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
-                    AUTH_FINISH, authFinishJsonObject);
-            authFinishResponseJsonObject = ThirdGenerationHttpHelper
-                    .getJsonObjectFromResponse(authFinishResponseContentResponse);
-            // 200 is the desired status code
-            if (authFinishResponseContentResponse.getStatus() == 400) {
-                // Authentication failed
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                        CONFIGURATION_ERROR_PASSWORD);
-                return;
-            }
-        } catch (InterruptedException | TimeoutException | ExecutionException e3) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
-            return;
-        }
-
-        // Extract information from the response
-        byte[] signature = Base64.getDecoder().decode(authFinishResponseJsonObject.get("signature").getAsString());
-        String token = authFinishResponseJsonObject.get("token").getAsString();
-
-        // Validate provided signature against calculated signature
-        if (!java.util.Arrays.equals(serverSignature, signature)) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                    COMMUNICATION_ERROR_AUTHENTICATION);
-            return;
-        }
-
-        // Calculate protocol key
-        SecretKeySpec signingKey = new SecretKeySpec(storedKey, HMAC_SHA256_ALGORITHM);
-        Mac mac;
-        byte[] protocolKeyHMAC;
-        try {
-            mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
-            mac.init(signingKey);
-            mac.update("Session Key".getBytes());
-            mac.update(authMessage.getBytes());
-            mac.update(clientKey);
-            protocolKeyHMAC = mac.doFinal();
-        } catch (NoSuchAlgorithmException | InvalidKeyException e1) {
-            // Since the necessary libraries are provided, this should not happen
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
-                    COMMUNICATION_ERROR_AUTHENTICATION);
-            return;
-        }
-
-        byte[] data;
-        byte[] iv;
-
-        // AES GCM stuff
-        iv = new byte[16];
-
-        new SecureRandom().nextBytes(iv);
-
-        SecretKeySpec skeySpec = new SecretKeySpec(protocolKeyHMAC, "AES");
-        GCMParameterSpec param = new GCMParameterSpec(protocolKeyHMAC.length * 8 - AES_GCM_TAG_LENGTH, iv);
-
-        Cipher cipher;
-        try {
-            cipher = Cipher.getInstance("AES_256/GCM/NOPADDING");
-            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, param);
-        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
-                | InvalidAlgorithmParameterException e1) {
-            // The java installation does not support AES encryption in GCM mode
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                    COMMUNICATION_ERROR_AUTHENTICATION);
-            return;
-        }
-        try {
-            data = cipher.doFinal(token.getBytes("UTF-8"));
-        } catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e1) {
-            // No JSON answer received
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_JSON);
-            return;
-        }
-
-        byte[] ciphertext = new byte[data.length - AES_GCM_TAG_LENGTH / 8];
-        byte[] gcmTag = new byte[AES_GCM_TAG_LENGTH / 8];
-        System.arraycopy(data, 0, ciphertext, 0, data.length - AES_GCM_TAG_LENGTH / 8);
-        System.arraycopy(data, data.length - AES_GCM_TAG_LENGTH / 8, gcmTag, 0, AES_GCM_TAG_LENGTH / 8);
-
-        JsonObject createSessionJsonObject = new JsonObject();
-        createSessionJsonObject.addProperty("transactionId", transactionId);
-        createSessionJsonObject.addProperty("iv", Base64.getEncoder().encodeToString(iv));
-        createSessionJsonObject.addProperty("tag", Base64.getEncoder().encodeToString(gcmTag));
-        createSessionJsonObject.addProperty("payload", Base64.getEncoder().encodeToString(ciphertext));
-
-        // finally create the session for further communication
-        ContentResponse createSessionResponseContentResponse;
-        JsonObject createSessionResponseJsonObject;
-        try {
-            createSessionResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
-                    AUTH_CREATE_SESSION, createSessionJsonObject);
-            createSessionResponseJsonObject = ThirdGenerationHttpHelper
-                    .getJsonObjectFromResponse(createSessionResponseContentResponse);
-        } catch (InterruptedException | TimeoutException | ExecutionException e) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                    COMMUNICATION_ERROR_AUTHENTICATION);
-            return;
-        }
-        // 200 is the desired status code
-        if (createSessionResponseContentResponse.getStatus() == 400) {
-            // Authentication failed
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
-                    CONFIGURATION_ERROR_PASSWORD);
-            return;
-        }
-
-        sessionId = createSessionResponseJsonObject.get("sessionId").getAsString();
-
-        updateStatus(ThingStatus.ONLINE);
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHttpHelper.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationHttpHelper.java
deleted file mode 100644 (file)
index c20a995..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.util.StringContentProvider;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.HttpVersion;
-
-import com.google.gson.FieldNamingPolicy;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-
-/**
- * The {@link ThirdGenerationHttpHelper} is handling http communication with the device
- * handlers.
- *
- * @author René Stakemeier - Initial contribution
- */
-final class ThirdGenerationHttpHelper {
-
-    private ThirdGenerationHttpHelper() {
-    }
-
-    // base URL of the web api
-    private static final String WEB_API = "/api/v1";
-    // GSON handler
-    private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
-            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
-
-    /**
-     * Helper function to execute a HTTP post request
-     *
-     * @param httpClient httpClient to use for communication
-     * @param url IP or hostname or the device
-     * @param resource web API resource to post to
-     * @param parameters the JSON content to post
-     * @return the HTTP response for the created post request
-     * @throws ExecutionException Error during the execution of the http request
-     * @throws TimeoutException Connection timed out
-     * @throws InterruptedException Connection interrupted
-     */
-    static ContentResponse executeHttpPost(HttpClient httpClient, String url, String resource, JsonObject parameters)
-            throws InterruptedException, TimeoutException, ExecutionException {
-        return executeHttpPost(httpClient, url, resource, parameters, null);
-    }
-
-    /**
-     * Helper function to execute a HTTP post request
-     *
-     * @param httpClient httpClient to use for communication
-     * @param url IP or hostname or the device
-     * @param resource web API resource to post to
-     * @param sessionId optional session ID
-     * @param parameters the JSON content to post
-     * @return the HTTP response for the created post request
-     * @throws ExecutionException Error during the execution of the http request
-     * @throws TimeoutException Connection timed out
-     * @throws InterruptedException Connection interrupted
-     */
-    static ContentResponse executeHttpPost(HttpClient httpClient, String url, String resource, JsonElement parameters,
-            @Nullable String sessionId) throws InterruptedException, TimeoutException, ExecutionException {
-        Request response = httpClient.newRequest(String.format("%s/%s%s", url, WEB_API, resource), 80).scheme("http")
-                .agent("Jetty HTTP client").version(HttpVersion.HTTP_1_1).method(HttpMethod.POST)
-                .header(HttpHeader.ACCEPT, "application/json").header(HttpHeader.CONTENT_TYPE, "application/json")
-                .timeout(5, TimeUnit.SECONDS);
-        response.content(new StringContentProvider(parameters.toString()));
-        if (sessionId != null) {
-            response.header(HttpHeader.AUTHORIZATION, String.format("Session %s", sessionId));
-        }
-        return response.send();
-    }
-
-    /**
-     * Helper function to execute a HTTP get request
-     *
-     * @param httpClient httpClient to use for communication
-     * @param url IP or hostname or the device
-     * @param resource web API resource to get
-     * @return the HTTP response for the created get request
-     * @throws ExecutionException Error during the execution of the http request
-     * @throws TimeoutException Connection timed out
-     * @throws InterruptedException Connection interrupted
-     */
-    static ContentResponse executeHttpGet(HttpClient httpClient, String url, String resource)
-            throws InterruptedException, TimeoutException, ExecutionException {
-        return executeHttpGet(httpClient, url, resource, null);
-    }
-
-    /**
-     * Helper function to execute a HTTP get request
-     *
-     * @param httpClient httpClient to use for communication
-     * @param url IP or hostname or the device
-     * @param resource web API resource to get
-     * @param sessionId optional session ID
-     * @return the HTTP response for the created get request
-     * @throws ExecutionException Error during the execution of the http request
-     * @throws TimeoutException Connection timed out
-     * @throws InterruptedException Connection interrupted
-     * @throws Exception thrown if there are communication problems
-     */
-    static ContentResponse executeHttpGet(HttpClient httpClient, String url, String resource,
-            @Nullable String sessionId) throws InterruptedException, TimeoutException, ExecutionException {
-        Request response = httpClient.newRequest(String.format("%s/%s%s", url, WEB_API, resource), 80).scheme("http")
-                .agent("Jetty HTTP client").version(HttpVersion.HTTP_1_1).method(HttpMethod.GET)
-                .header(HttpHeader.ACCEPT, "application/json").header(HttpHeader.CONTENT_TYPE, "application/json")
-                .timeout(5, TimeUnit.SECONDS);
-        if (sessionId != null) {
-            response.header(HttpHeader.AUTHORIZATION, String.format("Session %s", sessionId));
-        }
-        return response.send();
-    }
-
-    /**
-     * Helper to extract the JsonArray from a HTTP response.
-     * Use only, if you expect a JsonArray and no other types (e.g. JSON array)!
-     *
-     * @param reponse the HTTP response
-     * @return the JSON object
-     */
-    static JsonArray getJsonArrayFromResponse(ContentResponse reponse) {
-        return GSON.fromJson(reponse.getContentAsString(), JsonArray.class);
-    }
-
-    /**
-     * Helper to extract the JSON object from a HTTP response.
-     * Use only, if you expect a JSON object and no other types (e.g. JSON array)!
-     *
-     * @param reponse the HTTP response
-     * @return the JSON object
-     */
-    static JsonObject getJsonObjectFromResponse(ContentResponse reponse) {
-        return GSON.fromJson(reponse.getContentAsString(), JsonObject.class);
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationInverterTypes.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationInverterTypes.java
deleted file mode 100644 (file)
index c8cefc9..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-/**
- * The {@link ThirdGenerationInverterTypes} contains the list of supported devices
- * Each one is represented by a dedicated thing
- *
- * @author René Stakemeier - Initial contribution
- */
-public enum ThirdGenerationInverterTypes {
-    PLENTICORE_PLUS_42_WITH_BATTERY,
-    PLENTICORE_PLUS_42_WITHOUT_BATTERY,
-    PLENTICORE_PLUS_55_WITH_BATTERY,
-    PLENTICORE_PLUS_55_WITHOUT_BATTERY,
-    PLENTICORE_PLUS_70_WITH_BATTERY,
-    PLENTICORE_PLUS_70_WITHOUT_BATTERY,
-    PLENTICORE_PLUS_85_WITH_BATTERY,
-    PLENTICORE_PLUS_85_WITHOUT_BATTERY,
-    PLENTICORE_PLUS_100_WITH_BATTERY,
-    PLENTICORE_PLUS_100_WITHOUT_BATTERY,
-    PIKOIQ_42,
-    PIKOIQ_55,
-    PIKOIQ_70,
-    PIKOIQ_85,
-    PIKOIQ_100
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationMappingInverterToChannel.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/internal/kostal/inverter/thirdgeneration/ThirdGenerationMappingInverterToChannel.java
deleted file mode 100644 (file)
index 6197e94..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
- * Copyright (c) 2010-2021 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.internal.kostal.inverter.thirdgeneration;
-
-import static org.openhab.binding.internal.kostal.inverter.thirdgeneration.ThirdGenerationBindingConstants.*;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * The {@link ThirdGenerationMappingInverterToChannel} is responsible for the management of the channels
- * available to inverters
- *
- * @author René Stakemeier - Initial contribution
- */
-class ThirdGenerationMappingInverterToChannel {
-
-    private static final Map<ThirdGenerationInverterTypes, List<ThirdGenerationChannelMappingToWebApi>> CHANNEL_MAPPING = new HashMap<>();
-
-    /*
-     * Assign the channels to the devices.
-     */
-    static {
-        List<ThirdGenerationInverterTypes> allInvertersList = Arrays.asList(ThirdGenerationInverterTypes.values());
-        List<ThirdGenerationInverterTypes> allBatteryInvertersList = Stream
-                .of(ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITH_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITH_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITH_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITH_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITH_BATTERY)
-                .collect(Collectors.toList());
-        List<ThirdGenerationInverterTypes> allInvertersWithThreeStringsList = Stream
-                .of(ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITHOUT_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITHOUT_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITHOUT_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITHOUT_BATTERY,
-                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITHOUT_BATTERY)
-                .collect(Collectors.toList());
-
-        // Channels available on all devices
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_DC_POWER, "devices:local", "Dc_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_GRID, "devices:local",
-                "HomeGrid_P", ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_OWNCONSUMPTION, "devices:local", "HomeOwn_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_PV, "devices:local", "HomePv_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_TOTAL, "devices:local", "Home_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_LIMIT_EVU_ABSOLUTE, "devices:local", "LimitEvuAbs",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_LIMIT_EVU_RELATIV, "devices:local", "LimitEvuRel",
-                ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_WORKTIME, "devices:local", "WorkTime",
-                ThirdGenerationChannelDatatypes.SECONDS);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_AMPERAGE, "devices:local:ac",
-                "L1_I", ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_POWER, "devices:local:ac", "L1_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_VOLTAGE, "devices:local:ac",
-                "L1_U", ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_AMPERAGE, "devices:local:ac",
-                "L2_I", ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_POWER, "devices:local:ac", "L2_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_VOLTAGE, "devices:local:ac",
-                "L2_U", ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_AMPERAGE, "devices:local:ac",
-                "L3_I", ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_POWER, "devices:local:ac", "L3_P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_VOLTAGE, "devices:local:ac",
-                "L3_U", ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_CURRENT_POWER, "devices:local:ac", "P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_AMPERAGE, "devices:local:pv1", "I",
-                ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_POWER, "devices:local:pv1", "P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_VOLTAGE, "devices:local:pv1", "U",
-                ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_AMPERAGE, "devices:local:pv2", "I",
-                ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_POWER, "devices:local:pv2", "P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_VOLTAGE, "devices:local:pv2", "U",
-                ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_MC, "scb:event", "ErrMc",
-                ThirdGenerationChannelDatatypes.INTEGER);
-        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_SFH, "scb:event", "ErrSFH",
-                ThirdGenerationChannelDatatypes.INTEGER);
-        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_SCB, "scb:event", "Event:ActiveErrorCnt",
-                ThirdGenerationChannelDatatypes.INTEGER);
-        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_WARNING_COUNT_SCB, "scb:event", "Event:ActiveWarningCnt",
-                ThirdGenerationChannelDatatypes.INTEGER);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:Autarky:Day", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_MONTH, "scb:statistic:EnergyFlow",
-                "Statistic:Autarky:Month", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_TOTAL, "scb:statistic:EnergyFlow",
-                "Statistic:Autarky:Total", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:Autarky:Year", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:CO2Saving:Day", ThirdGenerationChannelDatatypes.KILOGRAM);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_MONTH, "scb:statistic:EnergyFlow",
-                "Statistic:CO2Saving:Month", ThirdGenerationChannelDatatypes.KILOGRAM);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_TOTAL, "scb:statistic:EnergyFlow",
-                "Statistic:CO2Saving:Total", ThirdGenerationChannelDatatypes.KILOGRAM);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:CO2Saving:Year", ThirdGenerationChannelDatatypes.KILOGRAM);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHome:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_MONTH, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHome:Month", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_TOTAL, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHome:Total", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHome:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_DAY,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Day",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_MONTH,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Month",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_TOTAL,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Total",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_YEAR,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Year",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHomePv:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_MONTH,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomePv:Month",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_TOTAL,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomePv:Total",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:EnergyHomePv:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:OwnConsumptionRate:Day", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_MONTH, "scb:statistic:EnergyFlow",
-                "Statistic:OwnConsumptionRate:Month", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_TOTAL, "scb:statistic:EnergyFlow",
-                "Statistic:OwnConsumptionRate:Total", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:OwnConsumptionRate:Year", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_DAY, "scb:statistic:EnergyFlow",
-                "Statistic:Yield:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_MONTH, "scb:statistic:EnergyFlow",
-                "Statistic:Yield:Month", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_TOTAL, "scb:statistic:EnergyFlow",
-                "Statistic:Yield:Total", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_YEAR, "scb:statistic:EnergyFlow",
-                "Statistic:Yield:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-
-        // Plenticore Plus devices can be expanded with a battery.
-        // Additional channels become available, but the pv3 information are hidden (since the battery is attached
-        // there)
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_LOADING_CYCLES,
-                "devices:local:battery", "Cycles", ThirdGenerationChannelDatatypes.INTEGER);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_FULL_CHARGE_CAPACITY,
-                "devices:local:battery", "FullChargeCap_E", ThirdGenerationChannelDatatypes.AMPERE_HOUR);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_AMPERAGE, "devices:local:battery", "I",
-                ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_POWER, "devices:local:battery", "P",
-                ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_STATE_OF_CHARGE,
-                "devices:local:battery", "SoC", ThirdGenerationChannelDatatypes.PERCEMTAGE);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_VOLTAGE, "devices:local:battery", "U",
-                ThirdGenerationChannelDatatypes.VOLT);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_DAY,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Day",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_MONTH,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Month",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_TOTAL,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Total",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_YEAR,
-                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Year",
-                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
-        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_BATTERY, "devices:local",
-                "HomeBat_P", ThirdGenerationChannelDatatypes.WATT);
-        // Plenticore devices without battery have the pv3 settings available
-        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_AMPERAGE,
-                "devices:local:pv3", "I", ThirdGenerationChannelDatatypes.AMPERE);
-        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_POWER, "devices:local:pv3",
-                "P", ThirdGenerationChannelDatatypes.WATT);
-        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_VOLTAGE,
-                "devices:local:pv3", "U", ThirdGenerationChannelDatatypes.VOLT);
-    }
-
-    static Map<String, List<ThirdGenerationChannelMappingToWebApi>> getModuleToChannelsMappingForInverter(
-            ThirdGenerationInverterTypes inverter) {
-        Map<String, List<ThirdGenerationChannelMappingToWebApi>> results = new HashMap<>();
-        for (ThirdGenerationChannelMappingToWebApi mapping : CHANNEL_MAPPING.get(inverter)) {
-            List<ThirdGenerationChannelMappingToWebApi> channelList = null;
-            if (results.containsKey(mapping.moduleId)) {
-                channelList = results.get(mapping.moduleId);
-            } else {
-                channelList = new ArrayList<>();
-                results.put(mapping.moduleId, channelList);
-            }
-            channelList.add(mapping);
-        }
-        return results;
-    }
-
-    private static void addInverterChannel(ThirdGenerationInverterTypes inverter,
-            ThirdGenerationChannelMappingToWebApi mapping) {
-        if (!CHANNEL_MAPPING.containsKey(inverter)) {
-            CHANNEL_MAPPING.put(inverter, new ArrayList<>());
-        }
-        CHANNEL_MAPPING.get(inverter).add(mapping);
-    }
-
-    private static void addInverterChannel(ThirdGenerationInverterTypes inverter, String channelUID, String moduleId,
-            String processdataId, ThirdGenerationChannelDatatypes dataType) {
-        addInverterChannel(inverter,
-                new ThirdGenerationChannelMappingToWebApi(channelUID, moduleId, processdataId, dataType));
-    }
-
-    private static void addInverterChannel(List<ThirdGenerationInverterTypes> inverterList, String channelUID,
-            String moduleId, String processdataId, ThirdGenerationChannelDatatypes dataType) {
-        for (ThirdGenerationInverterTypes inverter : inverterList) {
-            addInverterChannel(inverter, channelUID, moduleId, processdataId, dataType);
-        }
-    }
-}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/KostalInverterFactory.java
new file mode 100644 (file)
index 0000000..79a00e2
--- /dev/null
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal;
+
+import static org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.kostalinverter.internal.firstgeneration.WebscrapeHandler;
+import org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationHandler;
+import org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationInverterTypes;
+import org.openhab.core.io.net.http.HttpClientFactory;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+/**
+ * @author Christian Schneider - Initial contribution (as WebscrapeHandlerFactory.java)
+ * @author René Stakemeier - extension for the third generation of KOSTAL inverters
+ */
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.kostalinverter")
+@NonNullByDefault
+public class KostalInverterFactory extends BaseThingHandlerFactory {
+
+    private static final Map<ThingTypeUID, ThirdGenerationInverterTypes> SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS = new HashMap<>();
+    static {
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ42, ThirdGenerationInverterTypes.PIKOIQ_42);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ55, ThirdGenerationInverterTypes.PIKOIQ_55);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ70, ThirdGenerationInverterTypes.PIKOIQ_70);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ85, ThirdGenerationInverterTypes.PIKOIQ_85);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PIKOIQ100, ThirdGenerationInverterTypes.PIKOIQ_100);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS42WITHBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITH_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS55WITHBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITH_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS70WITHBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITH_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS85WITHBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITH_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS100WITHBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITH_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS42WITHOUTBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITHOUT_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS55WITHOUTBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITHOUT_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS70WITHOUTBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITHOUT_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS85WITHOUTBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITHOUT_BATTERY);
+        SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.put(PLENTICOREPLUS100WITHOUTBATTERY,
+                ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITHOUT_BATTERY);
+    }
+
+    public static final ThingTypeUID FIRST_GENERATION_INVERTER = new ThingTypeUID("kostalinverter", "kostalinverter");
+
+    private final HttpClient httpClient;
+
+    @Activate
+    public KostalInverterFactory(@Reference final HttpClientFactory httpClientFactory) {
+        this.httpClient = httpClientFactory.getCommonHttpClient();
+    }
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return thingTypeUID.equals(FIRST_GENERATION_INVERTER)
+                || SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS.keySet().contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        // first generation
+        if (FIRST_GENERATION_INVERTER.equals(thing.getThingTypeUID())) {
+            return new WebscrapeHandler(thing);
+        }
+        // third generation
+        ThirdGenerationInverterTypes inverterType = SUPPORTED_THIRD_GENERATION_THING_TYPES_UIDS
+                .get(thing.getThingTypeUID());
+        if (inverterType != null) {
+            return new ThirdGenerationHandler(thing, httpClient, inverterType);
+        }
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/ChannelConfig.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/ChannelConfig.java
new file mode 100644 (file)
index 0000000..2216753
--- /dev/null
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.firstgeneration;
+
+import javax.measure.Unit;
+
+/**
+ * @author Christian Schneider - Initial contribution
+ * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
+ */
+public class ChannelConfig {
+    public ChannelConfig(String id, String tag, int num, Unit<?> unit) {
+        this.id = id;
+        this.tag = tag;
+        this.num = num;
+        this.unit = unit;
+    }
+
+    String id;
+    String tag;
+    int num;
+    Unit<?> unit;
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/SourceConfig.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/SourceConfig.java
new file mode 100644 (file)
index 0000000..deec9e7
--- /dev/null
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.firstgeneration;
+
+/**
+ * @author Christian Schneider - Initial contribution
+ */
+public class SourceConfig {
+    public String url;
+    public String userName;
+    public String password;
+    public int refreshInterval;
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/WebscrapeHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/firstgeneration/WebscrapeHandler.java
new file mode 100644 (file)
index 0000000..c635db3
--- /dev/null
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.firstgeneration;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.Unit;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.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.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Christian Schneider - Initial contribution
+ * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
+ */
+public class WebscrapeHandler extends BaseThingHandler {
+    private Logger logger = LoggerFactory.getLogger(WebscrapeHandler.class);
+    private SourceConfig config;
+
+    private final List<ChannelConfig> channelConfigs = new ArrayList<>();
+
+    public WebscrapeHandler(Thing thing) {
+        super(thing);
+        channelConfigs.add(new ChannelConfig("acPower", "td", 4, Units.WATT));
+        channelConfigs.add(new ChannelConfig("totalEnergy", "td", 7, Units.KILOWATT_HOUR));
+        channelConfigs.add(new ChannelConfig("dayEnergy", "td", 10, Units.KILOWATT_HOUR));
+        channelConfigs.add(new ChannelConfig("status", "td", 13, null));
+        channelConfigs.add(new ChannelConfig("str1Voltage", "td", 19, Units.VOLT));
+        channelConfigs.add(new ChannelConfig("str1Current", "td", 25, Units.AMPERE));
+        channelConfigs.add(new ChannelConfig("str2Voltage", "td", 33, Units.VOLT));
+        channelConfigs.add(new ChannelConfig("str2Current", "td", 39, Units.AMPERE));
+        channelConfigs.add(new ChannelConfig("l1Voltage", "td", 22, Units.VOLT));
+        channelConfigs.add(new ChannelConfig("l1Power", "td", 28, Units.WATT));
+        channelConfigs.add(new ChannelConfig("l2Voltage", "td", 36, Units.VOLT));
+        channelConfigs.add(new ChannelConfig("l2Power", "td", 42, Units.WATT));
+        channelConfigs.add(new ChannelConfig("l3Voltage", "td", 46, Units.VOLT));
+        channelConfigs.add(new ChannelConfig("l3Power", "td", 49, Units.WATT));
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(SourceConfig.class);
+        scheduler.scheduleWithFixedDelay(() -> {
+            try {
+                refresh();
+                updateStatus(ThingStatus.ONLINE);
+            } catch (Exception e) {
+                logger.debug("Error refreshing source '{}'", getThing().getUID(), e);
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        e.getClass().getName() + ":" + e.getMessage());
+            }
+        }, 0, config.refreshInterval, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        // Read only
+    }
+
+    private void refresh() throws Exception {
+        Document doc = getDoc();
+        for (ChannelConfig cConfig : channelConfigs) {
+            Channel channel = getThing().getChannel(cConfig.id);
+            if (channel != null) {
+                String value = getTag(doc, cConfig.tag).get(cConfig.num);
+                updateState(channel.getUID(), getState(value, cConfig.unit));
+            }
+        }
+    }
+
+    private static List<String> getTag(Document doc, String tag) {
+        List<String> result = new ArrayList<>();
+        Iterator<Element> elIt = doc.getElementsByTag(tag).iterator();
+        while (elIt.hasNext()) {
+            String content = elIt.next().text();
+            content = content.replace("\u00A0", "").trim();
+            if (!content.isEmpty()) {
+                result.add(content);
+            }
+        }
+        return result;
+    }
+
+    private Document getDoc() throws IOException {
+        String login = config.userName + ":" + config.password;
+        String base64login = new String(Base64.getEncoder().encode(login.getBytes()));
+        return Jsoup.connect(config.url).header("Authorization", "Basic " + base64login).get();
+    }
+
+    private State getState(String value, Unit<?> unit) {
+        if (unit == null) {
+            return new StringType(value);
+        } else {
+            try {
+                return new QuantityType<>(new BigDecimal(value), unit);
+            } catch (NumberFormatException e) {
+                logger.debug("Error parsing value '{}'", value, e);
+                return UnDefType.UNDEF;
+            }
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationBindingConstants.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationBindingConstants.java
new file mode 100644 (file)
index 0000000..d279b70
--- /dev/null
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link ThirdGenerationBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+@NonNullByDefault
+public class ThirdGenerationBindingConstants {
+
+    private static final String BINDING_ID = "kostalinverter";
+
+    // List of all constants used for the authentication
+    static final String USER_TYPE = "user";
+    static final String HMAC_SHA256_ALGORITHM = "HMACSHA256";
+    static final String SHA_256_HASH = "SHA-256";
+    static final int AES_GCM_TAG_LENGTH = 128; // bit count
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID PIKOIQ42 = new ThingTypeUID(BINDING_ID, "PIKOIQ42");
+    public static final ThingTypeUID PIKOIQ55 = new ThingTypeUID(BINDING_ID, "PIKOIQ55");
+    public static final ThingTypeUID PIKOIQ70 = new ThingTypeUID(BINDING_ID, "PIKOIQ70");
+    public static final ThingTypeUID PIKOIQ85 = new ThingTypeUID(BINDING_ID, "PIKOIQ85");
+    public static final ThingTypeUID PIKOIQ100 = new ThingTypeUID(BINDING_ID, "PIKOIQ100");
+    public static final ThingTypeUID PLENTICOREPLUS42WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS42WITHOUTBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS55WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS55WITHOUTBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS70WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS70WITHOUTBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS85WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS85WITHOUTBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS100WITHOUTBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS100WITHOUTBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS42WITHBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS42WITHBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS55WITHBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS55WITHBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS70WITHBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS70WITHBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS85WITHBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS85WITHBATTERY");
+    public static final ThingTypeUID PLENTICOREPLUS100WITHBATTERY = new ThingTypeUID(BINDING_ID,
+            "PLENTICOREPLUS100WITHBATTERY");
+
+    // List of error messages
+    public static final String COMMUNICATION_ERROR_AUTHENTICATION = "Error during the initialisation of the authentication";
+    public static final String COMMUNICATION_ERROR_UNSUPPORTED_ENCODING = "The text encoding is not supported by this openHAB installation";
+    public static final String COMMUNICATION_ERROR_AES_ERROR = "The java installation does not support AES encryption in GCM mode";
+    public static final String COMMUNICATION_ERROR_HTTP = "HTTP communication error: No response from device";
+    public static final String COMMUNICATION_ERROR_JSON = "HTTP communication error: answer did not match the expected format";
+    public static final String COMMUNICATION_ERROR_API_CHANGED = "The API seems to have changed :-( Maybe this implementation has become incompatible with the device";
+    public static final String COMMUNICATION_ERROR_INCOMPATIBLE_DEVICE = "The device could not provide the required information. Please check, if you selected the right thing for your device!";
+    public static final String COMMUNICATION_ERROR_USER_ACCOUNT_LOCKED = "Your user account on the device is locked. Please reset the password by following the instructions on the device´s web frontend";
+    public static final String CONFIGURATION_ERROR_PASSWORD = "Wrong password";
+
+    // List of all Channel uids
+    public static final String CHANNEL_DEVICE_LOCAL_DC_POWER = "deviceLocalDCPower";
+    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_BATTERY = "deviceLocalHomeconsumptionFromBattery";
+    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_GRID = "deviceLocalHomeconsumptionFromGrid";
+    public static final String CHANNEL_DEVICE_LOCAL_OWNCONSUMPTION = "deviceLocalOwnconsumption";
+    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_PV = "deviceLocalHomeconsumptionFromPV";
+    public static final String CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_TOTAL = "deviceLocalHomeconsumptionTotal";
+    public static final String CHANNEL_DEVICE_LOCAL_LIMIT_EVU_ABSOLUTE = "deviceLocalLimitEVUAbsolute";
+    public static final String CHANNEL_DEVICE_LOCAL_LIMIT_EVU_RELATIV = "deviceLocalLimitEVURelativ";
+    public static final String CHANNEL_DEVICE_LOCAL_WORKTIME = "deviceLocalWorktime";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_AMPERAGE = "deviceLocalACPhase1CurrentAmperage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_POWER = "deviceLocalACPhase1CurrentPower";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_VOLTAGE = "deviceLocalACPhase1CurrentVoltage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_AMPERAGE = "deviceLocalACPhase2CurrentAmperage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_POWER = "deviceLocalACPhase2CurrentPower";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_VOLTAGE = "deviceLocalACPhase2CurrentVoltage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_AMPERAGE = "deviceLocalACPhase3CurrentAmperage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_POWER = "deviceLocalACPhase3CurrentPower";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_VOLTAGE = "deviceLocalACPhase3CurrentVoltage";
+    public static final String CHANNEL_DEVICE_LOCAL_AC_CURRENT_POWER = "deviceLocalACCurrentPower";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_LOADING_CYCLES = "deviceLocalBatteryLoadingCycles";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_FULL_CHARGE_CAPACITY = "deviceLocalBatteryFullChargeCapacity";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_AMPERAGE = "deviceLocalBatteryAmperage";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_POWER = "deviceLocalBatteryPower";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_STATE_OF_CHARGE = "deviceLocalBatteryStageOfCharge";
+    public static final String CHANNEL_DEVICE_LOCAL_BATTERY_VOLTAGE = "deviceLocalBatteryVoltage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_AMPERAGE = "deviceLocalPVString1Amperage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_POWER = "deviceLocalPVString1Power";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_1_VOLTAGE = "deviceLocalPVString1Voltage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_AMPERAGE = "deviceLocalPVString2Amperage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_POWER = "deviceLocalPVString2Power";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_2_VOLTAGE = "deviceLocalPVString2Voltage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_AMPERAGE = "deviceLocalPVString3Amperage";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_POWER = "deviceLocalPVString3Power";
+    public static final String CHANNEL_DEVICE_LOCAL_PVSTRING_3_VOLTAGE = "deviceLocalPVString3Voltage";
+    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_MC = "SCBEventErrorCountMc";
+    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_SFH = "SCBEventErrorCountSFH";
+    public static final String CHANNEL_SCB_EVENT_ERROR_COUNT_SCB = "SCBEventErrorCountSCB";
+    public static final String CHANNEL_SCB_EVENT_WARNING_COUNT_SCB = "SCBEventWarningCountSCB";
+    public static final String CHANNEL_STATISTIC_AUTARKY_DAY = "statisticAutarkyDay";
+    public static final String CHANNEL_STATISTIC_AUTARKY_MONTH = "statisticAutarkyMonth";
+    public static final String CHANNEL_STATISTIC_AUTARKY_TOTAL = "statisticAutarkyTotal";
+    public static final String CHANNEL_STATISTIC_AUTARKY_YEAR = "statisticAutarkyYear";
+    public static final String CHANNEL_STATISTIC_CO2SAVING_DAY = "statisticCo2SavingDay";
+    public static final String CHANNEL_STATISTIC_CO2SAVING_MONTH = "statisticCo2SavingMonth";
+    public static final String CHANNEL_STATISTIC_CO2SAVING_TOTAL = "statisticCo2SavingTotal";
+    public static final String CHANNEL_STATISTIC_CO2SAVING_YEAR = "statisticCo2SavingYear";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_DAY = "statisticHomeconsumptionDay";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_MONTH = "statisticHomeconsumptionMonth";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_TOTAL = "statisticHomeconsumptionTotal";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_YEAR = "statisticHomeconsumptionYear";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_DAY = "statisticHomeconsumptionFromBatteryDay";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_MONTH = "statisticHomeconsumptionFromBatteryMonth";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_TOTAL = "statisticHomeconsumptionFromBatteryTotal";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_YEAR = "statisticHomeconsumptionFromBatteryYear";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_DAY = "statisticHomeconsumptionFromGridDay";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_MONTH = "statisticHomeconsumptionFromGridMonth";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_TOTAL = "statisticHomeconsumptionFromGridTotal";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_YEAR = "statisticHomeconsumptionFromGridYear";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_DAY = "statisticHomeconsumptionFromPVDay";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_MONTH = "statisticHomeconsumptionFromPVMonth";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_TOTAL = "statisticHomeconsumptionFromPVTotal";
+    public static final String CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_YEAR = "statisticHomeconsumptionFromPVYear";
+    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_DAY = "statisticOwnconsumptionRateDay";
+    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_MONTH = "statisticOwnconsumptionRateMonth";
+    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_TOTAL = "statisticOwnconsumptionRateTotal";
+    public static final String CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_YEAR = "statisticOwnconsumptionRateYear";
+    public static final String CHANNEL_STATISTIC_YIELD_DAY = "statisticYieldDay";
+    public static final String CHANNEL_STATISTIC_YIELD_MONTH = "statisticYieldMonth";
+    public static final String CHANNEL_STATISTIC_YIELD_TOTAL = "statisticYieldTotal";
+    public static final String CHANNEL_STATISTIC_YIELD_YEAR = "statisticYieldYear";
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelDatatypes.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelDatatypes.java
new file mode 100644 (file)
index 0000000..75b4e02
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+/**
+ * The {@link ThirdGenerationChannelDatatypes} enumeration contains the data types provided by the device
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+enum ThirdGenerationChannelDatatypes {
+    INTEGER,
+    PERCEMTAGE,
+    KILOGRAM,
+    SECONDS,
+    KILOWATT_HOUR,
+    WATT,
+    AMPERE,
+    AMPERE_HOUR,
+    VOLT
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationChannelMappingToWebApi.java
new file mode 100644 (file)
index 0000000..b74b31a
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+/***
+ * The {@link ThirdGenerationChannelMappingToWebApi}} is used to map the channel name to the web API commands
+ *
+ * @author René Stakemeier - Initial contribution
+ *
+ */
+class ThirdGenerationChannelMappingToWebApi {
+
+    String channelUID;
+    String moduleId;
+    String processdataId;
+    ThirdGenerationChannelDatatypes dataType;
+
+    /**
+     * Constructor of {@link ThirdGenerationChannelMappingToWebApi}
+     *
+     * @param channelUID The channel UUID
+     * @param moduleId module id (as defined by the web api)
+     * @param processdataId process data id (as defined by the web api)
+     * @param dataType data type of this channel
+     */
+    ThirdGenerationChannelMappingToWebApi(String channelUID, String moduleId, String processdataId,
+            ThirdGenerationChannelDatatypes dataType) {
+        this.channelUID = channelUID;
+        this.moduleId = moduleId;
+        this.processdataId = processdataId;
+        this.dataType = dataType;
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationConfiguration.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationConfiguration.java
new file mode 100644 (file)
index 0000000..9683751
--- /dev/null
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+/**
+ * The {@link ThirdGenerationConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+public class ThirdGenerationConfiguration {
+
+    public String url;
+    public String userPassword;
+    public int refreshInternalInSeconds;
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationEncryptionHelper.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationEncryptionHelper.java
new file mode 100644 (file)
index 0000000..7c3a07e
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+import static org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationBindingConstants.*;
+
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Base64;
+import java.util.Random;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * The {@link ThirdGenerationEncryptionHelper} is responsible for handling the encryption for the authentication
+ * handlers.
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+final class ThirdGenerationEncryptionHelper {
+
+    private ThirdGenerationEncryptionHelper() {
+    }
+
+    /**
+     * This method generates the HMACSha256 encrypted value of the given value
+     *
+     * @param password Password used for encryption
+     * @param valueToEncrypt value to encrypt
+     * @return encrypted value
+     * @throws InvalidKeyException thrown if the key generated from the password is invalid
+     * @throws NoSuchAlgorithmException thrown if HMAC SHA 256 is not supported
+     */
+    static byte[] getHMACSha256(byte[] password, String valueToEncrypt)
+            throws InvalidKeyException, NoSuchAlgorithmException {
+        SecretKeySpec signingKey = new SecretKeySpec(password, HMAC_SHA256_ALGORITHM);
+        Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+        mac.init(signingKey);
+        mac.update(valueToEncrypt.getBytes());
+        return mac.doFinal();
+    }
+
+    /**
+     * This methods generates the client proof.
+     * It is calculated as XOR between the {@link clientSignature} and the {@link serverSignature}
+     *
+     * @param clientSignature client signature
+     * @param serverSignature server signature
+     * @return client proof
+     */
+    static String createClientProof(byte[] clientSignature, byte[] serverSignature) {
+        byte[] result = new byte[clientSignature.length];
+        for (int i = 0; i < clientSignature.length; i++) {
+            result[i] = (byte) (0xff & (clientSignature[i] ^ serverSignature[i]));
+        }
+        return Base64.getEncoder().encodeToString(result);
+    }
+
+    /**
+     * Create the PBKDF2 hash
+     *
+     * @param password password
+     * @param salt salt
+     * @param rounds rounds
+     * @return hash
+     * @throws NoSuchAlgorithmException if PBKDF2WithHmacSHA256 is not supported
+     * @throws InvalidKeySpecException if the key specification is not supported
+     */
+    static byte[] getPBKDF2Hash(String password, byte[] salt, int rounds)
+            throws NoSuchAlgorithmException, InvalidKeySpecException {
+        PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, rounds, 256);
+        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+        return skf.generateSecret(spec).getEncoded();
+    }
+
+    /**
+     * Create the SHA256 hash value for the given byte array
+     *
+     * @param valueToHash byte array to get the hash value for
+     * @return the hash value
+     * @throws NoSuchAlgorithmException if SHA256 is not supported
+     */
+    static byte[] getSha256Hash(byte[] valueToHash) throws NoSuchAlgorithmException {
+        return MessageDigest.getInstance(SHA_256_HASH).digest(valueToHash);
+    }
+
+    /**
+     * Create the nonce (numbers used once) for the client for communication
+     *
+     * @return nonce
+     */
+    static String createClientNonce() {
+        Random generator = new Random();
+
+        // Randomize the random generator
+        byte[] randomizeArray = new byte[1024];
+        generator.nextBytes(randomizeArray);
+
+        // 3 words of 4 bytes are required for the handshake
+        byte[] nonceArray = new byte[12];
+        generator.nextBytes(nonceArray);
+
+        // return the base64 encoded value of the random words
+        return Base64.getMimeEncoder().encodeToString(nonceArray);
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHandler.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHandler.java
new file mode 100644 (file)
index 0000000..b4c16e0
--- /dev/null
@@ -0,0 +1,485 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+import static org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationBindingConstants.*;
+
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+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.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link ThirdGenerationHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+@NonNullByDefault
+public class ThirdGenerationHandler extends BaseThingHandler {
+
+    /*
+     * operations used for authentication
+     */
+    private static final String AUTH_START = "/auth/start";
+    private static final String AUTH_FINISH = "/auth/finish";
+    private static final String AUTH_CREATE_SESSION = "/auth/create_session";
+
+    /*
+     * operations used for gathering process data from the device
+     */
+    private static final String PROCESSDATA = "/processdata";
+
+    /*
+     * After the authentication the result (the session id) is stored here and used to "sign" future requests
+     */
+    private @Nullable String sessionId;
+    /*
+     * The configuration file containing the host, the password and the refresh interval
+     */
+    private @NonNullByDefault({}) ThirdGenerationConfiguration config;
+
+    private @Nullable ScheduledFuture<?> refreshScheduler;
+
+    private @Nullable HttpClient httpClient;
+
+    private ThirdGenerationInverterTypes inverterType;
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /**
+     * Constructor of this class
+     *
+     * @param thing the thing
+     * @param httpClient the httpClient used for communication
+     * @param inverterType the type of the device
+     */
+    public ThirdGenerationHandler(Thing thing, HttpClient httpClient, ThirdGenerationInverterTypes inverterType) {
+        super(thing);
+        this.inverterType = inverterType;
+        this.httpClient = httpClient;
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        // All channels are readonly and updated by the scheduler
+    }
+
+    @Override
+    public void dispose() {
+        if (refreshScheduler != null) {
+            refreshScheduler.cancel(true);
+            refreshScheduler = null;
+        }
+        super.dispose();
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(ThirdGenerationConfiguration.class);
+        // temporary value while initializing
+        updateStatus(ThingStatus.UNKNOWN);
+
+        // Start the authentication
+        scheduler.schedule(this::authenticate, 1, TimeUnit.SECONDS);
+
+        // Start the update scheduler as configured
+        refreshScheduler = scheduler.scheduleWithFixedDelay(this::updateChannelValues, 10,
+                config.refreshInternalInSeconds, TimeUnit.SECONDS);
+    }
+
+    /**
+     * The API supports the resolution of multiple values at a time
+     *
+     * Therefore this methods builds one request to gather all information for the current inverter.
+     * The list contains all channels as defined in {@link ThirdGenerationMappingInverterToChannel} for the
+     * current inverter
+     *
+     */
+    private void updateChannelValues() {
+        Map<String, List<ThirdGenerationChannelMappingToWebApi>> channelList = ThirdGenerationMappingInverterToChannel
+                .getModuleToChannelsMappingForInverter(inverterType);
+        JsonArray updateMessageJsonArray = getUpdateChannelMessage(channelList);
+
+        // Send the API request to get values for all channels
+        ContentResponse updateMessageContentResponse;
+        try {
+            updateMessageContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
+                    PROCESSDATA, updateMessageJsonArray, sessionId);
+            if (updateMessageContentResponse.getStatus() == 404) {
+                // Module not found
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                        COMMUNICATION_ERROR_INCOMPATIBLE_DEVICE);
+                return;
+            }
+            if (updateMessageContentResponse.getStatus() == 503) {
+                // Communication error (e.g. during initial boot of the SCB)
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                        COMMUNICATION_ERROR_HTTP);
+                return;
+            }
+            if (updateMessageContentResponse.getStatus() == 401) {
+                // session not valid (timed out? device rebooted?)
+                logger.info("Session expired - performing retry");
+                authenticate();
+                // Retry
+                updateMessageContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
+                        PROCESSDATA, updateMessageJsonArray, sessionId);
+            }
+        } catch (TimeoutException | ExecutionException e) {
+            // Communication problem
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
+            return;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
+            return;
+        }
+        JsonArray updateMessageResultsJsonArray = ThirdGenerationHttpHelper
+                .getJsonArrayFromResponse(updateMessageContentResponse);
+
+        // Map the returned values back to the channels and update them
+        for (int i = 0; i < updateMessageResultsJsonArray.size(); i++) {
+            JsonObject moduleAnswer = updateMessageResultsJsonArray.get(i).getAsJsonObject();
+            String moduleName = moduleAnswer.get("moduleid").getAsString();
+            JsonArray processdata = moduleAnswer.get("processdata").getAsJsonArray();
+            for (int j = 0; j < processdata.size(); j++) {
+                // Update the channels with their new value
+                JsonObject newValueObject = processdata.get(j).getAsJsonObject();
+                String valueId = newValueObject.get("id").getAsString();
+                double valueAsDouble = newValueObject.get("value").getAsDouble();
+                ThirdGenerationChannelMappingToWebApi channel = channelList.get(moduleName).stream()
+                        .filter(c -> c.moduleId.equals(moduleName) && c.processdataId.equals(valueId)).findFirst()
+                        .get();
+                updateChannelValue(channel.channelUID, channel.dataType, valueAsDouble);
+            }
+        }
+        updateStatus(ThingStatus.ONLINE);
+    }
+
+    /**
+     * Update the channel to the given value.
+     * The value is set to the matching data (SITypes etc)
+     *
+     * @param channeluid Channel to update
+     * @param dataType target data type
+     * @param value value
+     */
+    private void updateChannelValue(String channeluid, ThirdGenerationChannelDatatypes dataType, Double value) {
+        switch (dataType) {
+            case INTEGER: {
+                updateState(channeluid, new DecimalType(value.longValue()));
+                break;
+            }
+            case PERCEMTAGE: {
+                updateState(channeluid, new QuantityType<>(value, Units.PERCENT));
+                break;
+            }
+            case KILOGRAM: {
+                updateState(channeluid, new QuantityType<>(value / 1000, SIUnits.KILOGRAM));
+                break;
+            }
+            case SECONDS: {
+                updateState(channeluid, new QuantityType<>(value, Units.SECOND));
+                break;
+            }
+            case KILOWATT_HOUR: {
+                updateState(channeluid, new QuantityType<>(value / 1000, Units.KILOWATT_HOUR));
+                break;
+            }
+            case WATT: {
+                updateState(channeluid, new QuantityType<>(value, Units.WATT));
+                break;
+            }
+            case AMPERE: {
+                updateState(channeluid, new QuantityType<>(value, Units.AMPERE));
+                break;
+            }
+            case AMPERE_HOUR: {
+                // Ampere hours is not a supported unit, but 1 AH is equal tp 3600 coulomb...
+                updateState(channeluid, new QuantityType<>(value * 3600, Units.COULOMB));
+                break;
+            }
+            case VOLT: {
+                updateState(channeluid, new QuantityType<>(value, Units.VOLT));
+                break;
+            }
+            default: {
+                // unknown datatype
+                logger.debug("{} not known!", dataType);
+            }
+        }
+    }
+
+    /**
+     * Creates the message which has to be send to the inverter to gather the current informations for all channels
+     *
+     * @param channelList channels of this thing
+     * @return the JSON array to send to the device
+     */
+    private JsonArray getUpdateChannelMessage(Map<String, List<ThirdGenerationChannelMappingToWebApi>> channelList) {
+        // Build the message to send to the inverter
+        JsonArray updateMessageJsonArray = new JsonArray();
+        for (Entry<String, List<ThirdGenerationChannelMappingToWebApi>> moduleId : channelList.entrySet()) {
+            JsonObject moduleJsonObject = new JsonObject();
+            moduleJsonObject.addProperty("moduleid", moduleId.getKey());
+
+            JsonArray processdataNames = new JsonArray();
+            for (ThirdGenerationChannelMappingToWebApi processdata : channelList.get(moduleId.getKey())) {
+                processdataNames.add(processdata.processdataId);
+            }
+            moduleJsonObject.add("processdataids", processdataNames);
+            updateMessageJsonArray.add(moduleJsonObject);
+        }
+        return updateMessageJsonArray;
+    }
+
+    /**
+     * This function is used to authenticate against the SCB.
+     * SCB uses PBKDF2 and AES256 GCM mode with a slightly modified authentication message.
+     * The authentication will fail on JRE < 8u162. since the security policy is set to "limited" by default (see readme
+     * for fix)
+     */
+    private final void authenticate() {
+        // Create random numbers
+        String clientNonce = ThirdGenerationEncryptionHelper.createClientNonce();
+        // Perform first step of authentication
+        JsonObject authMeJsonObject = new JsonObject();
+        authMeJsonObject.addProperty("username", USER_TYPE);
+        authMeJsonObject.addProperty("nonce", clientNonce);
+
+        ContentResponse authStartResponseContentResponse;
+        try {
+            authStartResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
+                    AUTH_START, authMeJsonObject);
+
+            // 200 is the desired status code
+            int statusCode = authStartResponseContentResponse.getStatus();
+
+            if (statusCode == 400) {
+                // Invalid user (which is hard coded and therefore can not be wrong until the api is changed by the
+                // manufacturer
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                        COMMUNICATION_ERROR_API_CHANGED);
+                return;
+            }
+            if (statusCode == 403) {
+                // User is logged
+                // This can happen, if the user had to many bad attempts of entering the password in the web
+                // front end
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                        COMMUNICATION_ERROR_USER_ACCOUNT_LOCKED);
+                return;
+            }
+
+            if (statusCode == 503) {
+                // internal communication error
+                // This can happen if the device is not ready yet for communication
+                updateStatus(ThingStatus.UNINITIALIZED);
+                return;
+            }
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
+            return;
+        }
+        JsonObject authMeResponseJsonObject = ThirdGenerationHttpHelper
+                .getJsonObjectFromResponse(authStartResponseContentResponse);
+
+        // Extract information from the response
+        String salt = authMeResponseJsonObject.get("salt").getAsString();
+        String serverNonce = authMeResponseJsonObject.get("nonce").getAsString();
+        int rounds = authMeResponseJsonObject.get("rounds").getAsInt();
+        String transactionId = authMeResponseJsonObject.get("transactionId").getAsString();
+
+        // Do the cryptography stuff (magic happens here)
+        byte[] saltedPasswort;
+        byte[] clientKey;
+        byte[] serverKey;
+        byte[] storedKey;
+        byte[] clientSignature;
+        byte[] serverSignature;
+        String authMessage;
+        try {
+            saltedPasswort = ThirdGenerationEncryptionHelper.getPBKDF2Hash(config.userPassword,
+                    Base64.getDecoder().decode(salt), rounds);
+            clientKey = ThirdGenerationEncryptionHelper.getHMACSha256(saltedPasswort, "Client Key");
+            serverKey = ThirdGenerationEncryptionHelper.getHMACSha256(saltedPasswort, "Server Key");
+            storedKey = ThirdGenerationEncryptionHelper.getSha256Hash(clientKey);
+            authMessage = String.format("n=%s,r=%s,r=%s,s=%s,i=%d,c=biws,r=%s", USER_TYPE, clientNonce, serverNonce,
+                    salt, rounds, serverNonce);
+            clientSignature = ThirdGenerationEncryptionHelper.getHMACSha256(storedKey, authMessage);
+            serverSignature = ThirdGenerationEncryptionHelper.getHMACSha256(serverKey, authMessage);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | IllegalStateException e2) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    COMMUNICATION_ERROR_AUTHENTICATION);
+            return;
+        }
+        String clientProof = ThirdGenerationEncryptionHelper.createClientProof(clientSignature, clientKey);
+        // Perform step 2 of the authentication
+        JsonObject authFinishJsonObject = new JsonObject();
+        authFinishJsonObject.addProperty("transactionId", transactionId);
+        authFinishJsonObject.addProperty("proof", clientProof);
+
+        ContentResponse authFinishResponseContentResponse;
+        JsonObject authFinishResponseJsonObject;
+        try {
+            authFinishResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
+                    AUTH_FINISH, authFinishJsonObject);
+            authFinishResponseJsonObject = ThirdGenerationHttpHelper
+                    .getJsonObjectFromResponse(authFinishResponseContentResponse);
+            // 200 is the desired status code
+            if (authFinishResponseContentResponse.getStatus() == 400) {
+                // Authentication failed
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                        CONFIGURATION_ERROR_PASSWORD);
+                return;
+            }
+        } catch (InterruptedException | TimeoutException | ExecutionException e3) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_HTTP);
+            return;
+        }
+
+        // Extract information from the response
+        byte[] signature = Base64.getDecoder().decode(authFinishResponseJsonObject.get("signature").getAsString());
+        String token = authFinishResponseJsonObject.get("token").getAsString();
+
+        // Validate provided signature against calculated signature
+        if (!java.util.Arrays.equals(serverSignature, signature)) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    COMMUNICATION_ERROR_AUTHENTICATION);
+            return;
+        }
+
+        // Calculate protocol key
+        SecretKeySpec signingKey = new SecretKeySpec(storedKey, HMAC_SHA256_ALGORITHM);
+        Mac mac;
+        byte[] protocolKeyHMAC;
+        try {
+            mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+            mac.init(signingKey);
+            mac.update("Session Key".getBytes());
+            mac.update(authMessage.getBytes());
+            mac.update(clientKey);
+            protocolKeyHMAC = mac.doFinal();
+        } catch (NoSuchAlgorithmException | InvalidKeyException e1) {
+            // Since the necessary libraries are provided, this should not happen
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
+                    COMMUNICATION_ERROR_AUTHENTICATION);
+            return;
+        }
+
+        byte[] data;
+        byte[] iv;
+
+        // AES GCM stuff
+        iv = new byte[16];
+
+        new SecureRandom().nextBytes(iv);
+
+        SecretKeySpec skeySpec = new SecretKeySpec(protocolKeyHMAC, "AES");
+        GCMParameterSpec param = new GCMParameterSpec(protocolKeyHMAC.length * 8 - AES_GCM_TAG_LENGTH, iv);
+
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance("AES_256/GCM/NOPADDING");
+            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, param);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
+                | InvalidAlgorithmParameterException e1) {
+            // The java installation does not support AES encryption in GCM mode
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    COMMUNICATION_ERROR_AUTHENTICATION);
+            return;
+        }
+        try {
+            data = cipher.doFinal(token.getBytes("UTF-8"));
+        } catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException e1) {
+            // No JSON answer received
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, COMMUNICATION_ERROR_JSON);
+            return;
+        }
+
+        byte[] ciphertext = new byte[data.length - AES_GCM_TAG_LENGTH / 8];
+        byte[] gcmTag = new byte[AES_GCM_TAG_LENGTH / 8];
+        System.arraycopy(data, 0, ciphertext, 0, data.length - AES_GCM_TAG_LENGTH / 8);
+        System.arraycopy(data, data.length - AES_GCM_TAG_LENGTH / 8, gcmTag, 0, AES_GCM_TAG_LENGTH / 8);
+
+        JsonObject createSessionJsonObject = new JsonObject();
+        createSessionJsonObject.addProperty("transactionId", transactionId);
+        createSessionJsonObject.addProperty("iv", Base64.getEncoder().encodeToString(iv));
+        createSessionJsonObject.addProperty("tag", Base64.getEncoder().encodeToString(gcmTag));
+        createSessionJsonObject.addProperty("payload", Base64.getEncoder().encodeToString(ciphertext));
+
+        // finally create the session for further communication
+        ContentResponse createSessionResponseContentResponse;
+        JsonObject createSessionResponseJsonObject;
+        try {
+            createSessionResponseContentResponse = ThirdGenerationHttpHelper.executeHttpPost(httpClient, config.url,
+                    AUTH_CREATE_SESSION, createSessionJsonObject);
+            createSessionResponseJsonObject = ThirdGenerationHttpHelper
+                    .getJsonObjectFromResponse(createSessionResponseContentResponse);
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    COMMUNICATION_ERROR_AUTHENTICATION);
+            return;
+        }
+        // 200 is the desired status code
+        if (createSessionResponseContentResponse.getStatus() == 400) {
+            // Authentication failed
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+                    CONFIGURATION_ERROR_PASSWORD);
+            return;
+        }
+
+        sessionId = createSessionResponseJsonObject.get("sessionId").getAsString();
+
+        updateStatus(ThingStatus.ONLINE);
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHttpHelper.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationHttpHelper.java
new file mode 100644 (file)
index 0000000..9759b6f
--- /dev/null
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpVersion;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link ThirdGenerationHttpHelper} is handling http communication with the device
+ * handlers.
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+final class ThirdGenerationHttpHelper {
+
+    private ThirdGenerationHttpHelper() {
+    }
+
+    // base URL of the web api
+    private static final String WEB_API = "/api/v1";
+    // GSON handler
+    private static final Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
+            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
+
+    /**
+     * Helper function to execute a HTTP post request
+     *
+     * @param httpClient httpClient to use for communication
+     * @param url IP or hostname or the device
+     * @param resource web API resource to post to
+     * @param parameters the JSON content to post
+     * @return the HTTP response for the created post request
+     * @throws ExecutionException Error during the execution of the http request
+     * @throws TimeoutException Connection timed out
+     * @throws InterruptedException Connection interrupted
+     */
+    static ContentResponse executeHttpPost(HttpClient httpClient, String url, String resource, JsonObject parameters)
+            throws InterruptedException, TimeoutException, ExecutionException {
+        return executeHttpPost(httpClient, url, resource, parameters, null);
+    }
+
+    /**
+     * Helper function to execute a HTTP post request
+     *
+     * @param httpClient httpClient to use for communication
+     * @param url IP or hostname or the device
+     * @param resource web API resource to post to
+     * @param sessionId optional session ID
+     * @param parameters the JSON content to post
+     * @return the HTTP response for the created post request
+     * @throws ExecutionException Error during the execution of the http request
+     * @throws TimeoutException Connection timed out
+     * @throws InterruptedException Connection interrupted
+     */
+    static ContentResponse executeHttpPost(HttpClient httpClient, String url, String resource, JsonElement parameters,
+            @Nullable String sessionId) throws InterruptedException, TimeoutException, ExecutionException {
+        Request response = httpClient.newRequest(String.format("%s/%s%s", url, WEB_API, resource), 80).scheme("http")
+                .agent("Jetty HTTP client").version(HttpVersion.HTTP_1_1).method(HttpMethod.POST)
+                .header(HttpHeader.ACCEPT, "application/json").header(HttpHeader.CONTENT_TYPE, "application/json")
+                .timeout(5, TimeUnit.SECONDS);
+        response.content(new StringContentProvider(parameters.toString()));
+        if (sessionId != null) {
+            response.header(HttpHeader.AUTHORIZATION, String.format("Session %s", sessionId));
+        }
+        return response.send();
+    }
+
+    /**
+     * Helper function to execute a HTTP get request
+     *
+     * @param httpClient httpClient to use for communication
+     * @param url IP or hostname or the device
+     * @param resource web API resource to get
+     * @return the HTTP response for the created get request
+     * @throws ExecutionException Error during the execution of the http request
+     * @throws TimeoutException Connection timed out
+     * @throws InterruptedException Connection interrupted
+     */
+    static ContentResponse executeHttpGet(HttpClient httpClient, String url, String resource)
+            throws InterruptedException, TimeoutException, ExecutionException {
+        return executeHttpGet(httpClient, url, resource, null);
+    }
+
+    /**
+     * Helper function to execute a HTTP get request
+     *
+     * @param httpClient httpClient to use for communication
+     * @param url IP or hostname or the device
+     * @param resource web API resource to get
+     * @param sessionId optional session ID
+     * @return the HTTP response for the created get request
+     * @throws ExecutionException Error during the execution of the http request
+     * @throws TimeoutException Connection timed out
+     * @throws InterruptedException Connection interrupted
+     * @throws Exception thrown if there are communication problems
+     */
+    static ContentResponse executeHttpGet(HttpClient httpClient, String url, String resource,
+            @Nullable String sessionId) throws InterruptedException, TimeoutException, ExecutionException {
+        Request response = httpClient.newRequest(String.format("%s/%s%s", url, WEB_API, resource), 80).scheme("http")
+                .agent("Jetty HTTP client").version(HttpVersion.HTTP_1_1).method(HttpMethod.GET)
+                .header(HttpHeader.ACCEPT, "application/json").header(HttpHeader.CONTENT_TYPE, "application/json")
+                .timeout(5, TimeUnit.SECONDS);
+        if (sessionId != null) {
+            response.header(HttpHeader.AUTHORIZATION, String.format("Session %s", sessionId));
+        }
+        return response.send();
+    }
+
+    /**
+     * Helper to extract the JsonArray from a HTTP response.
+     * Use only, if you expect a JsonArray and no other types (e.g. JSON array)!
+     *
+     * @param reponse the HTTP response
+     * @return the JSON object
+     */
+    static JsonArray getJsonArrayFromResponse(ContentResponse reponse) {
+        return GSON.fromJson(reponse.getContentAsString(), JsonArray.class);
+    }
+
+    /**
+     * Helper to extract the JSON object from a HTTP response.
+     * Use only, if you expect a JSON object and no other types (e.g. JSON array)!
+     *
+     * @param reponse the HTTP response
+     * @return the JSON object
+     */
+    static JsonObject getJsonObjectFromResponse(ContentResponse reponse) {
+        return GSON.fromJson(reponse.getContentAsString(), JsonObject.class);
+    }
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationInverterTypes.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationInverterTypes.java
new file mode 100644 (file)
index 0000000..f49b03d
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+/**
+ * The {@link ThirdGenerationInverterTypes} contains the list of supported devices
+ * Each one is represented by a dedicated thing
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+public enum ThirdGenerationInverterTypes {
+    PLENTICORE_PLUS_42_WITH_BATTERY,
+    PLENTICORE_PLUS_42_WITHOUT_BATTERY,
+    PLENTICORE_PLUS_55_WITH_BATTERY,
+    PLENTICORE_PLUS_55_WITHOUT_BATTERY,
+    PLENTICORE_PLUS_70_WITH_BATTERY,
+    PLENTICORE_PLUS_70_WITHOUT_BATTERY,
+    PLENTICORE_PLUS_85_WITH_BATTERY,
+    PLENTICORE_PLUS_85_WITHOUT_BATTERY,
+    PLENTICORE_PLUS_100_WITH_BATTERY,
+    PLENTICORE_PLUS_100_WITHOUT_BATTERY,
+    PIKOIQ_42,
+    PIKOIQ_55,
+    PIKOIQ_70,
+    PIKOIQ_85,
+    PIKOIQ_100
+}
diff --git a/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationMappingInverterToChannel.java b/bundles/org.openhab.binding.kostalinverter/src/main/java/org/openhab/binding/kostalinverter/internal/thirdgeneration/ThirdGenerationMappingInverterToChannel.java
new file mode 100644 (file)
index 0000000..3d61b8a
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ * Copyright (c) 2010-2021 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.kostalinverter.internal.thirdgeneration;
+
+import static org.openhab.binding.kostalinverter.internal.thirdgeneration.ThirdGenerationBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * The {@link ThirdGenerationMappingInverterToChannel} is responsible for the management of the channels
+ * available to inverters
+ *
+ * @author René Stakemeier - Initial contribution
+ */
+class ThirdGenerationMappingInverterToChannel {
+
+    private static final Map<ThirdGenerationInverterTypes, List<ThirdGenerationChannelMappingToWebApi>> CHANNEL_MAPPING = new HashMap<>();
+
+    /*
+     * Assign the channels to the devices.
+     */
+    static {
+        List<ThirdGenerationInverterTypes> allInvertersList = Arrays.asList(ThirdGenerationInverterTypes.values());
+        List<ThirdGenerationInverterTypes> allBatteryInvertersList = Stream
+                .of(ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITH_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITH_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITH_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITH_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITH_BATTERY)
+                .collect(Collectors.toList());
+        List<ThirdGenerationInverterTypes> allInvertersWithThreeStringsList = Stream
+                .of(ThirdGenerationInverterTypes.PLENTICORE_PLUS_42_WITHOUT_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_55_WITHOUT_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_70_WITHOUT_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_85_WITHOUT_BATTERY,
+                        ThirdGenerationInverterTypes.PLENTICORE_PLUS_100_WITHOUT_BATTERY)
+                .collect(Collectors.toList());
+
+        // Channels available on all devices
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_DC_POWER, "devices:local", "Dc_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_GRID, "devices:local",
+                "HomeGrid_P", ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_OWNCONSUMPTION, "devices:local", "HomeOwn_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_PV, "devices:local", "HomePv_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_TOTAL, "devices:local", "Home_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_LIMIT_EVU_ABSOLUTE, "devices:local", "LimitEvuAbs",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_LIMIT_EVU_RELATIV, "devices:local", "LimitEvuRel",
+                ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_WORKTIME, "devices:local", "WorkTime",
+                ThirdGenerationChannelDatatypes.SECONDS);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_AMPERAGE, "devices:local:ac",
+                "L1_I", ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_POWER, "devices:local:ac", "L1_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_1_CURRENT_VOLTAGE, "devices:local:ac",
+                "L1_U", ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_AMPERAGE, "devices:local:ac",
+                "L2_I", ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_POWER, "devices:local:ac", "L2_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_2_CURRENT_VOLTAGE, "devices:local:ac",
+                "L2_U", ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_AMPERAGE, "devices:local:ac",
+                "L3_I", ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_POWER, "devices:local:ac", "L3_P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_PHASE_3_CURRENT_VOLTAGE, "devices:local:ac",
+                "L3_U", ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_AC_CURRENT_POWER, "devices:local:ac", "P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_AMPERAGE, "devices:local:pv1", "I",
+                ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_POWER, "devices:local:pv1", "P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_1_VOLTAGE, "devices:local:pv1", "U",
+                ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_AMPERAGE, "devices:local:pv2", "I",
+                ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_POWER, "devices:local:pv2", "P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersList, CHANNEL_DEVICE_LOCAL_PVSTRING_2_VOLTAGE, "devices:local:pv2", "U",
+                ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_MC, "scb:event", "ErrMc",
+                ThirdGenerationChannelDatatypes.INTEGER);
+        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_SFH, "scb:event", "ErrSFH",
+                ThirdGenerationChannelDatatypes.INTEGER);
+        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_ERROR_COUNT_SCB, "scb:event", "Event:ActiveErrorCnt",
+                ThirdGenerationChannelDatatypes.INTEGER);
+        addInverterChannel(allInvertersList, CHANNEL_SCB_EVENT_WARNING_COUNT_SCB, "scb:event", "Event:ActiveWarningCnt",
+                ThirdGenerationChannelDatatypes.INTEGER);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:Autarky:Day", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_MONTH, "scb:statistic:EnergyFlow",
+                "Statistic:Autarky:Month", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_TOTAL, "scb:statistic:EnergyFlow",
+                "Statistic:Autarky:Total", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_AUTARKY_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:Autarky:Year", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:CO2Saving:Day", ThirdGenerationChannelDatatypes.KILOGRAM);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_MONTH, "scb:statistic:EnergyFlow",
+                "Statistic:CO2Saving:Month", ThirdGenerationChannelDatatypes.KILOGRAM);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_TOTAL, "scb:statistic:EnergyFlow",
+                "Statistic:CO2Saving:Total", ThirdGenerationChannelDatatypes.KILOGRAM);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_CO2SAVING_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:CO2Saving:Year", ThirdGenerationChannelDatatypes.KILOGRAM);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHome:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_MONTH, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHome:Month", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_TOTAL, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHome:Total", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHome:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_DAY,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Day",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_MONTH,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Month",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_TOTAL,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Total",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_GRID_YEAR,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeGrid:Year",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHomePv:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_MONTH,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomePv:Month",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_TOTAL,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomePv:Total",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_PV_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:EnergyHomePv:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:OwnConsumptionRate:Day", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_MONTH, "scb:statistic:EnergyFlow",
+                "Statistic:OwnConsumptionRate:Month", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_TOTAL, "scb:statistic:EnergyFlow",
+                "Statistic:OwnConsumptionRate:Total", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_OWNCONSUMPTION_RATE_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:OwnConsumptionRate:Year", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_DAY, "scb:statistic:EnergyFlow",
+                "Statistic:Yield:Day", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_MONTH, "scb:statistic:EnergyFlow",
+                "Statistic:Yield:Month", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_TOTAL, "scb:statistic:EnergyFlow",
+                "Statistic:Yield:Total", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allInvertersList, CHANNEL_STATISTIC_YIELD_YEAR, "scb:statistic:EnergyFlow",
+                "Statistic:Yield:Year", ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+
+        // Plenticore Plus devices can be expanded with a battery.
+        // Additional channels become available, but the pv3 information are hidden (since the battery is attached
+        // there)
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_LOADING_CYCLES,
+                "devices:local:battery", "Cycles", ThirdGenerationChannelDatatypes.INTEGER);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_FULL_CHARGE_CAPACITY,
+                "devices:local:battery", "FullChargeCap_E", ThirdGenerationChannelDatatypes.AMPERE_HOUR);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_AMPERAGE, "devices:local:battery", "I",
+                ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_POWER, "devices:local:battery", "P",
+                ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_STATE_OF_CHARGE,
+                "devices:local:battery", "SoC", ThirdGenerationChannelDatatypes.PERCEMTAGE);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_BATTERY_VOLTAGE, "devices:local:battery", "U",
+                ThirdGenerationChannelDatatypes.VOLT);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_DAY,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Day",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_MONTH,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Month",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_TOTAL,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Total",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_STATISTIC_HOMECONSUMPTION_FROM_BATTERIE_YEAR,
+                "scb:statistic:EnergyFlow", "Statistic:EnergyHomeBat:Year",
+                ThirdGenerationChannelDatatypes.KILOWATT_HOUR);
+        addInverterChannel(allBatteryInvertersList, CHANNEL_DEVICE_LOCAL_HOMECONSUMPTION_FROM_BATTERY, "devices:local",
+                "HomeBat_P", ThirdGenerationChannelDatatypes.WATT);
+        // Plenticore devices without battery have the pv3 settings available
+        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_AMPERAGE,
+                "devices:local:pv3", "I", ThirdGenerationChannelDatatypes.AMPERE);
+        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_POWER, "devices:local:pv3",
+                "P", ThirdGenerationChannelDatatypes.WATT);
+        addInverterChannel(allInvertersWithThreeStringsList, CHANNEL_DEVICE_LOCAL_PVSTRING_3_VOLTAGE,
+                "devices:local:pv3", "U", ThirdGenerationChannelDatatypes.VOLT);
+    }
+
+    static Map<String, List<ThirdGenerationChannelMappingToWebApi>> getModuleToChannelsMappingForInverter(
+            ThirdGenerationInverterTypes inverter) {
+        Map<String, List<ThirdGenerationChannelMappingToWebApi>> results = new HashMap<>();
+        for (ThirdGenerationChannelMappingToWebApi mapping : CHANNEL_MAPPING.get(inverter)) {
+            List<ThirdGenerationChannelMappingToWebApi> channelList = null;
+            if (results.containsKey(mapping.moduleId)) {
+                channelList = results.get(mapping.moduleId);
+            } else {
+                channelList = new ArrayList<>();
+                results.put(mapping.moduleId, channelList);
+            }
+            channelList.add(mapping);
+        }
+        return results;
+    }
+
+    private static void addInverterChannel(ThirdGenerationInverterTypes inverter,
+            ThirdGenerationChannelMappingToWebApi mapping) {
+        if (!CHANNEL_MAPPING.containsKey(inverter)) {
+            CHANNEL_MAPPING.put(inverter, new ArrayList<>());
+        }
+        CHANNEL_MAPPING.get(inverter).add(mapping);
+    }
+
+    private static void addInverterChannel(ThirdGenerationInverterTypes inverter, String channelUID, String moduleId,
+            String processdataId, ThirdGenerationChannelDatatypes dataType) {
+        addInverterChannel(inverter,
+                new ThirdGenerationChannelMappingToWebApi(channelUID, moduleId, processdataId, dataType));
+    }
+
+    private static void addInverterChannel(List<ThirdGenerationInverterTypes> inverterList, String channelUID,
+            String moduleId, String processdataId, ThirdGenerationChannelDatatypes dataType) {
+        for (ThirdGenerationInverterTypes inverter : inverterList) {
+            addInverterChannel(inverter, channelUID, moduleId, processdataId, dataType);
+        }
+    }
+}