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>
+++ /dev/null
-/**
- * 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;
- }
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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;
- }
- }
- }
-}
+++ /dev/null
-/**
- * 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";
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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;
- }
-}
+++ /dev/null
-/**
- * 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;
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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);
- }
-}
+++ /dev/null
-/**
- * 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
-}
+++ /dev/null
-/**
- * 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);
- }
- }
-}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * 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";
+}
--- /dev/null
+/**
+ * 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
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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
+}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}