From 24f4234e9b316db801f94eaeb0c9710f8e850000 Mon Sep 17 00:00:00 2001 From: Konstantin Polihronov Date: Tue, 7 Nov 2023 09:54:08 +0200 Subject: [PATCH] [solax] Support for three phase inverter X3 Hybrid G4 (#15710) Signed-off-by: Konstantin Polihronov --- bundles/org.openhab.binding.solax/README.md | 45 +++- .../solax/internal/SolaxBindingConstants.java | 75 ++++-- .../internal/SolaxLocalAccessHandler.java | 248 ++++++++++++++---- .../rawdata/LocalConnectRawDataBean.java | 147 +---------- .../solax/internal/model/InverterData.java | 191 +++++++++++--- .../solax/internal/model/InverterType.java | 38 ++- .../model/impl/CommonInverterData.java | 81 ++++++ .../model/impl/X1HybridG4InverterData.java | 110 ++++++++ .../model/impl/X3HybridG4InverterData.java | 223 ++++++++++++++++ .../internal/model/parsers/RawDataParser.java | 33 +++ .../model/parsers/X1HybridG4DataParser.java | 50 ++++ .../model/parsers/X3HybridG4DataParser.java | 58 ++++ .../resources/OH-INF/i18n/solax.properties | 53 +++- .../OH-INF/thing/localConnectInverter.xml | 112 +++++++- .../local_connect_inverter_type_update.xml | 134 ++++++++++ .../solax/internal/TestX1HybridG4Parser.java | 104 ++++++++ .../solax/internal/TestX3HybridG4Parser.java | 114 ++++++++ 17 files changed, 1554 insertions(+), 262 deletions(-) create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java create mode 100644 bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java create mode 100644 bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java diff --git a/bundles/org.openhab.binding.solax/README.md b/bundles/org.openhab.binding.solax/README.md index 6b35fb62ea100..9d914802ad3c9 100644 --- a/bundles/org.openhab.binding.solax/README.md +++ b/bundles/org.openhab.binding.solax/README.md @@ -16,6 +16,9 @@ In case the parsed information that comes with the binding out of the box differ |------------------------|------------|-------------------------------------------------------------------------------------| | local-connect-inverter | Thing | This is model representation of inverter with all the data available as a channels | +Note: Channels may vary depending on the inverter type and the availability of information for parsing the raw data. +If you're missing a channel this means that it's not supported for your inverter type. + ## Thing Configuration ### Local Connect Inverter Configuration @@ -28,12 +31,25 @@ In case the parsed information that comes with the binding out of the box differ ### Inverter Output Channels -| Channel | Type | Description | -|--------------------------|----------------------------|--------------------------------------------------| -| inverter-output-power | Number:Power | The output power of the inverter [W] | -| inverter-current | Number:ElectricCurrent | The output current of the inverter [A] | -| inverter-voltage | Number:ElectricPotential | The output voltage of the inverter [V] | -| inverter-frequency | Number:Frequency | The frequency of the output voltage [Hz] | +| Channel | Type | Description | +|---------------------------------|----------------------------|----------------------------------------------------------------| +| inverter-output-power | Number:Power | The output power of the inverter [W] | +| inverter-current | Number:ElectricCurrent | The output current of the inverter [A] | +| inverter-voltage | Number:ElectricPotential | The output voltage of the inverter [V] | +| inverter-frequency | Number:Frequency | The frequency of the electricity of the inverter [Hz] | +| inverter-output-power-phase1 | Number:Power | The output power of phase 1 of the inverter [W] | +| inverter-output-power-phase2 | Number:Power | The output power of phase 2 of the inverter [W] | +| inverter-output-power-phase3 | Number:Power | The output power of phase 3 of the inverter [W] | +| inverter-total-output-power | Number:Power | The total output power of all phases of the inverter [W] | +| inverter-current-phase1 | Number:ElectricCurrent | The output current of phase 1 of the inverter [A] | +| inverter-current-phase2 | Number:ElectricCurrent | The output current of phase 2 of the inverter [A] | +| inverter-current-phase3 | Number:ElectricCurrent | The output current of phase 3 of the inverter [A] | +| inverter-voltage-phase1 | Number:ElectricPotential | The output voltage of phase 1 of the inverter [V] | +| inverter-voltage-phase2 | Number:ElectricPotential | The output voltage of phase 2 of the inverter [V] | +| inverter-voltage-phase3 | Number:ElectricPotential | The output voltage of phase 3 of the inverter [V] | +| inverter-frequency-phase1 | Number:Frequency | The frequency of phase 1 of the inverter [Hz] | +| inverter-frequency-phase2 | Number:Frequency | The frequency of phase 2 of the inverter [Hz] | +| inverter-frequency-phase3 | Number:Frequency | The frequency of phase 3 of the inverter [Hz] | ### Photovoltaic Panels Production Channels @@ -71,6 +87,23 @@ In case the parsed information that comes with the binding out of the box differ | last-update-time | DateTime | Last time when a call has been made to the inverter | | raw-data | String | The raw data retrieved from inverter in JSON format. (Usable for channels not implemented. Can be consumed with the JSONpath transformation | +### Statistics / Usage related Channels + +| Channel | Type | Description | +|----------------------------------|----------------------------|-----------------------------------------------------------| +| power-usage | Number:Power | Current power usage / consumption of the building [W] | +| total-energy | Number:Energy | Total energy output from the inverter [kWh] | +| total-battery-discharge-energy | Number:Energy | Total energy from the battery [kWh] | +| total-battery-charge-energy | Number:Energy | Total energy to the battery [kWh] | +| total-pv-energy | Number:Energy | Total energy from the PV [kWh] | +| total-consumption | Number:Energy | Total energy consumed for the building [kWh] | +| total-feed-in-energy | Number:Energy | Total energy consumed from the electricity provider [kWh] | +| today-energy | Number:Energy | Energy output from the inverter for the day [kWh] | +| today-battery-discharge-energy | Number:Energy | Total energy from the battery output for the day [kWh] | +| today-battery-charge-energy | Number:Energy | Total energy charged to the battery for the day [kWh] | +| today-feed-in-energy | Number:Energy | Total energy charged to the battery for the day [kWh] | +| today-consumption | Number:Energy | Total energy consumed for the day [kWh] | + ### Properties | Property | Description | diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java index 74151b3aecbc7..52c871747c288 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxBindingConstants.java @@ -39,32 +39,67 @@ public class SolaxBindingConstants { public static final String PROPERTY_INVERTER_TYPE = "inverterType"; // List of all Channel ids - public static final String INVERTER_OUTPUT_POWER = "inverter-output-power"; - public static final String INVERTER_OUTPUT_CURRENT = "inverter-current"; - public static final String INVERTER_OUTPUT_VOLTAGE = "inverter-voltage"; - public static final String INVERTER_OUTPUT_FREQUENCY = "inverter-frequency"; + // Single phase specific + public static final String CHANNEL_INVERTER_OUTPUT_POWER = "inverter-output-power"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT = "inverter-current"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE = "inverter-voltage"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY = "inverter-frequency"; + public static final Set SINGLE_CHANNEL_SPECIFIC_CHANNEL_IDS = Set.of(CHANNEL_INVERTER_OUTPUT_POWER, + CHANNEL_INVERTER_OUTPUT_CURRENT, CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY); - public static final String INVERTER_PV1_POWER = "pv1-power"; - public static final String INVERTER_PV1_VOLTAGE = "pv1-voltage"; - public static final String INVERTER_PV1_CURRENT = "pv1-current"; + // Three phase specific + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE1 = "inverter-output-power-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE2 = "inverter-output-power-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_POWER_PHASE3 = "inverter-output-power-phase3"; + public static final String CHANNEL_INVERTER_TOTAL_OUTPUT_POWER = "inverter-total-output-power"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1 = "inverter-current-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2 = "inverter-current-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3 = "inverter-current-phase3"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1 = "inverter-voltage-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2 = "inverter-voltage-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3 = "inverter-voltage-phase3"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1 = "inverter-frequency-phase1"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2 = "inverter-frequency-phase2"; + public static final String CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3 = "inverter-frequency-phase3"; - public static final String INVERTER_PV2_POWER = "pv2-power"; - public static final String INVERTER_PV2_VOLTAGE = "pv2-voltage"; - public static final String INVERTER_PV2_CURRENT = "pv2-current"; + // Generic + public static final String CHANNEL_INVERTER_PV1_POWER = "pv1-power"; + public static final String CHANNEL_INVERTER_PV1_VOLTAGE = "pv1-voltage"; + public static final String CHANNEL_INVERTER_PV1_CURRENT = "pv1-current"; - public static final String INVERTER_PV_TOTAL_POWER = "pv-total-power"; - public static final String INVERTER_PV_TOTAL_CURRENT = "pv-total-current"; + public static final String CHANNEL_INVERTER_PV2_POWER = "pv2-power"; + public static final String CHANNEL_INVERTER_PV2_VOLTAGE = "pv2-voltage"; + public static final String CHANNEL_INVERTER_PV2_CURRENT = "pv2-current"; - public static final String BATTERY_POWER = "battery-power"; - public static final String BATTERY_VOLTAGE = "battery-voltage"; - public static final String BATTERY_CURRENT = "battery-current"; - public static final String BATTERY_TEMPERATURE = "battery-temperature"; - public static final String BATTERY_STATE_OF_CHARGE = "battery-level"; + public static final String CHANNEL_INVERTER_PV_TOTAL_POWER = "pv-total-power"; + public static final String CHANNEL_INVERTER_PV_TOTAL_CURRENT = "pv-total-current"; - public static final String FEED_IN_POWER = "feed-in-power"; + public static final String CHANNEL_BATTERY_POWER = "battery-power"; + public static final String CHANNEL_BATTERY_VOLTAGE = "battery-voltage"; + public static final String CHANNEL_BATTERY_CURRENT = "battery-current"; + public static final String CHANNEL_BATTERY_TEMPERATURE = "battery-temperature"; + public static final String CHANNEL_BATTERY_STATE_OF_CHARGE = "battery-level"; - public static final String TIMESTAMP = "last-update-time"; - public static final String RAW_DATA = "raw-data"; + public static final String CHANNEL_FEED_IN_POWER = "feed-in-power"; + + public static final String CHANNEL_TIMESTAMP = "last-update-time"; + public static final String CHANNEL_RAW_DATA = "raw-data"; + + // Totals + public static final String CHANNEL_POWER_USAGE = "power-usage"; + public static final String CHANNEL_TOTAL_ENERGY = "total-energy"; + public static final String CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY = "total-battery-discharge-energy"; + public static final String CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY = "total-battery-charge-energy"; + public static final String CHANNEL_TOTAL_PV_ENERGY = "total-pv-energy"; + public static final String CHANNEL_TOTAL_FEED_IN_ENERGY = "total-feed-in-energy"; + public static final String CHANNEL_TOTAL_CONSUMPTION = "total-consumption"; + + // Today totals + public static final String CHANNEL_TODAY_ENERGY = "today-energy"; + public static final String CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY = "today-battery-discharge-energy"; + public static final String CHANNEL_TODAY_BATTERY_CHARGE_ENERGY = "today-battery-charge-energy"; + public static final String CHANNEL_TODAY_FEED_IN_ENERGY = "today-feed-in-energy"; + public static final String CHANNEL_TODAY_CONSUMPTION = "today-consumption"; // I18N Keys protected static final String I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED = "@text/offline.communication-error.json-cannot-be-retrieved"; diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java index 7edcfaf216cf2..d53e7aeb40ae2 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/SolaxLocalAccessHandler.java @@ -14,25 +14,35 @@ import java.io.IOException; import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javax.measure.Quantity; +import javax.measure.Unit; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.solax.internal.connectivity.LocalHttpConnector; import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +65,10 @@ public class SolaxLocalAccessHandler extends BaseThingHandler { private @Nullable ScheduledFuture schedule; + private boolean alreadyRemovedUnsupportedChannels; + + private final Set unsupportedExistingChannels = new HashSet(); + public SolaxLocalAccessHandler(Thing thing) { super(thing); } @@ -79,7 +93,7 @@ private void retrieveData() { logger.debug("Raw data retrieved = {}", rawJsonData); if (rawJsonData != null && !rawJsonData.isEmpty()) { - updateData(rawJsonData); + updateFromData(rawJsonData); } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, SolaxBindingConstants.I18N_KEY_OFFLINE_COMMUNICATION_ERROR_JSON_CANNOT_BE_RETRIEVED); @@ -90,68 +104,181 @@ private void retrieveData() { } } - private void updateData(String rawJsonData) { + private void updateFromData(String rawJsonData) { try { - LocalConnectRawDataBean inverterParsedData = parseJson(rawJsonData); - updateThing(inverterParsedData); + LocalConnectRawDataBean rawDataBean = parseJson(rawJsonData); + InverterType inverterType = calculateInverterType(rawDataBean); + RawDataParser parser = inverterType.getParser(); + if (parser != null) { + if (!alreadyRemovedUnsupportedChannels) { + removeUnsupportedChannels(inverterType.getSupportedChannels()); + alreadyRemovedUnsupportedChannels = true; + } + + InverterData genericInverterData = parser.getData(rawDataBean); + updateChannels(parser, genericInverterData); + updateProperties(genericInverterData); + + if (getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + } else { + cancelSchedule(); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.configuration-error.parser-not-implemented [\"" + inverterType.name() + "\"]"); + } } catch (JsonParseException e) { logger.debug("Unable to deserialize from JSON.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } - private void updateThing(LocalConnectRawDataBean inverterParsedData) { - transferInverterDataToChannels(inverterParsedData); - - if (getThing().getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - } - private LocalConnectRawDataBean parseJson(String rawJsonData) { LocalConnectRawDataBean inverterParsedData = LocalConnectRawDataBean.fromJson(rawJsonData); - logger.debug("Received a new inverter data object. Data = {}", inverterParsedData.toStringDetailed()); + logger.debug("Received a new inverter JSON object. Data = {}", inverterParsedData.toString()); return inverterParsedData; } - private void transferInverterDataToChannels(InverterData data) { - updateProperty(Thing.PROPERTY_SERIAL_NUMBER, data.getWifiSerial()); - updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, data.getInverterType().name()); - - updateState(SolaxBindingConstants.INVERTER_OUTPUT_POWER, - new QuantityType<>(data.getInverterOutputPower(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_CURRENT, - new QuantityType<>(data.getInverterCurrent(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_VOLTAGE, - new QuantityType<>(data.getInverterVoltage(), Units.VOLT)); - updateState(SolaxBindingConstants.INVERTER_OUTPUT_FREQUENCY, - new QuantityType<>(data.getInverterFrequency(), Units.HERTZ)); - - updateState(SolaxBindingConstants.INVERTER_PV1_POWER, new QuantityType<>(data.getPV1Power(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV1_CURRENT, new QuantityType<>(data.getPV1Current(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_PV1_VOLTAGE, new QuantityType<>(data.getPV1Voltage(), Units.VOLT)); - - updateState(SolaxBindingConstants.INVERTER_PV2_POWER, new QuantityType<>(data.getPV2Power(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV2_CURRENT, new QuantityType<>(data.getPV2Current(), Units.AMPERE)); - updateState(SolaxBindingConstants.INVERTER_PV2_VOLTAGE, new QuantityType<>(data.getPV2Voltage(), Units.VOLT)); - - updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_POWER, - new QuantityType<>(data.getPVTotalPower(), Units.WATT)); - updateState(SolaxBindingConstants.INVERTER_PV_TOTAL_CURRENT, - new QuantityType<>(data.getPVTotalCurrent(), Units.AMPERE)); - - updateState(SolaxBindingConstants.BATTERY_POWER, new QuantityType<>(data.getBatteryPower(), Units.WATT)); - updateState(SolaxBindingConstants.BATTERY_CURRENT, new QuantityType<>(data.getBatteryCurrent(), Units.AMPERE)); - updateState(SolaxBindingConstants.BATTERY_VOLTAGE, new QuantityType<>(data.getBatteryVoltage(), Units.VOLT)); - updateState(SolaxBindingConstants.BATTERY_TEMPERATURE, - new QuantityType<>(data.getBatteryTemperature(), SIUnits.CELSIUS)); - updateState(SolaxBindingConstants.BATTERY_STATE_OF_CHARGE, - new QuantityType<>(data.getBatterySoC(), Units.PERCENT)); - - updateState(SolaxBindingConstants.FEED_IN_POWER, new QuantityType<>(data.getFeedInPower(), Units.WATT)); - - updateState(SolaxBindingConstants.TIMESTAMP, new DateTimeType(ZonedDateTime.now())); - updateState(SolaxBindingConstants.RAW_DATA, new StringType(data.getRawData())); + private InverterType calculateInverterType(LocalConnectRawDataBean rawDataBean) { + int type = rawDataBean.getType(); + return InverterType.fromIndex(type); + } + + private void updateProperties(InverterData genericInverterData) { + updateProperty(Thing.PROPERTY_SERIAL_NUMBER, genericInverterData.getWifiSerial()); + updateProperty(SolaxBindingConstants.PROPERTY_INVERTER_TYPE, genericInverterData.getInverterType().name()); + } + + private void updateChannels(RawDataParser parser, InverterData inverterData) { + updateState(SolaxBindingConstants.CHANNEL_RAW_DATA, new StringType(inverterData.getRawData())); + + Set supportedChannels = parser.getSupportedChannels(); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_POWER, inverterData.getPV1Power(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_CURRENT, inverterData.getPV1Current(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV1_VOLTAGE, inverterData.getPV1Voltage(), Units.VOLT, + supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_POWER, inverterData.getPV2Power(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_CURRENT, inverterData.getPV2Current(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV2_VOLTAGE, inverterData.getPV2Voltage(), Units.VOLT, + supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_POWER, inverterData.getPVTotalPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_PV_TOTAL_CURRENT, inverterData.getPVTotalCurrent(), + Units.AMPERE, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_POWER, inverterData.getBatteryPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_CURRENT, inverterData.getBatteryCurrent(), Units.AMPERE, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_VOLTAGE, inverterData.getBatteryVoltage(), Units.VOLT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_TEMPERATURE, inverterData.getBatteryTemperature(), + SIUnits.CELSIUS, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_BATTERY_STATE_OF_CHARGE, inverterData.getBatteryLevel(), + Units.PERCENT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_FEED_IN_POWER, inverterData.getFeedInPower(), Units.WATT, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_POWER_USAGE, inverterData.getPowerUsage(), Units.WATT, + supportedChannels); + + // Totals + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_ENERGY, inverterData.getTotalEnergy(), Units.KILOWATT_HOUR, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_DISCHARGE_ENERGY, + inverterData.getTotalBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, + inverterData.getTotalBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_PV_ENERGY, inverterData.getTotalPVEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_FEED_IN_ENERGY, inverterData.getTotalFeedInEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TOTAL_CONSUMPTION, inverterData.getTotalConsumption(), + Units.KILOWATT_HOUR, supportedChannels); + + // Today's + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_ENERGY, inverterData.getTodayEnergy(), Units.KILOWATT_HOUR, + supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY, + inverterData.getTodayBatteryDischargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, + inverterData.getTodayBatteryChargeEnergy(), Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_FEED_IN_ENERGY, inverterData.getTodayFeedInEnergy(), + Units.KILOWATT_HOUR, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_TODAY_CONSUMPTION, inverterData.getTodayConsumption(), + Units.KILOWATT_HOUR, supportedChannels); + + // Single phase specific channels + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER, inverterData.getInverterOutputPower(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT, inverterData.getInverterCurrent(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE, inverterData.getInverterVoltage(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY, inverterData.getInverterFrequency(), + Units.HERTZ, supportedChannels); + + // Three phase specific channels + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, inverterData.getOutputPowerPhase1(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, inverterData.getOutputPowerPhase2(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, inverterData.getOutputPowerPhase3(), + Units.WATT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, inverterData.getTotalOutputPower(), + Units.WATT, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, inverterData.getCurrentPhase1(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, inverterData.getCurrentPhase2(), + Units.AMPERE, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, inverterData.getCurrentPhase3(), + Units.AMPERE, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, inverterData.getVoltagePhase1(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, inverterData.getVoltagePhase2(), + Units.VOLT, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, inverterData.getVoltagePhase3(), + Units.VOLT, supportedChannels); + + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, inverterData.getFrequencyPhase1(), + Units.HERTZ, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, inverterData.getFrequencyPhase2(), + Units.HERTZ, supportedChannels); + updateChannel(SolaxBindingConstants.CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, inverterData.getFrequencyPhase3(), + Units.HERTZ, supportedChannels); + + // Binding provided data + updateState(SolaxBindingConstants.CHANNEL_TIMESTAMP, new DateTimeType(ZonedDateTime.now())); + } + + private void removeUnsupportedChannels(Set supportedChannels) { + if (supportedChannels.isEmpty()) { + return; + } + List channels = getThing().getChannels(); + List channelsToRemove = channels.stream() + .filter(channel -> !supportedChannels.contains(channel.getUID().getId())).toList(); + + if (!channelsToRemove.isEmpty()) { + if (logger.isDebugEnabled()) { + logRemovedChannels(channelsToRemove); + } + updateThing(editThing().withoutChannels(channelsToRemove).build()); + } + } + + private void logRemovedChannels(List channelsToRemove) { + List channelsToRemoveForLog = channelsToRemove.stream().map(channel -> channel.getUID().getId()) + .toList(); + logger.debug("Detected unsupported channels for the current inverter. Channels to be removed: {}", + channelsToRemoveForLog); } @Override @@ -162,10 +289,29 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void dispose() { super.dispose(); + cancelSchedule(); + } + + private void cancelSchedule() { ScheduledFuture schedule = this.schedule; if (schedule != null) { schedule.cancel(true); this.schedule = null; } } + + private > void updateChannel(String channelID, double value, Unit unit, + Set supportedChannels) { + if (supportedChannels.contains(channelID)) { + if (value > Short.MIN_VALUE) { + updateState(channelID, new QuantityType<>(value, unit)); + } else if (!unsupportedExistingChannels.contains(channelID)) { + updateState(channelID, UnDefType.UNDEF); + unsupportedExistingChannels.add(channelID); + logger.warn( + "Channel {} is marked as supported, but its value is out of the defined range. Value = {}. This is unexpected behaviour. Please file a bug.", + channelID, value); + } + } + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java index b6c978418ef35..7bf9fba12f7f2 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/connectivity/rawdata/LocalConnectRawDataBean.java @@ -16,11 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.model.InverterData; -import org.openhab.binding.solax.internal.model.InverterType; import org.openhab.binding.solax.internal.util.GsonSupplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; @@ -32,9 +28,7 @@ * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public class LocalConnectRawDataBean implements RawDataBean, InverterData { - - private final Logger logger = LoggerFactory.getLogger(LocalConnectRawDataBean.class); +public class LocalConnectRawDataBean implements RawDataBean { private @Nullable String sn; private @Nullable String ver; @@ -113,143 +107,4 @@ public static LocalConnectRawDataBean fromJson(String json) { deserializedObject.setRawData(json); return deserializedObject; } - - // Parsed inverter data interface implementation starts here - - @Override - public @Nullable String getWifiSerial() { - return getSn(); - } - - @Override - public @Nullable String getWifiVersion() { - return getVer(); - } - - @Override - public InverterType getInverterType() { - return InverterType.fromIndex(type); - } - - @Override - public short getInverterVoltage() { - return (short) (getData(0) / 10); - } - - @Override - public short getInverterCurrent() { - return (short) (getData(1) / 10); - } - - @Override - public short getInverterOutputPower() { - return getData(2); - } - - @Override - public short getInverterFrequency() { - return (short) (getData(3) / 100); - } - - @Override - public short getPV1Voltage() { - return (short) (getData(4) / 10); - } - - @Override - public short getPV1Current() { - return (short) (getData(6) / 10); - } - - @Override - public short getPV1Power() { - return getData(8); - } - - @Override - public short getPV2Voltage() { - return (short) (getData(5) / 10); - } - - @Override - public short getPV2Current() { - return (short) (getData(7) / 10); - } - - @Override - public short getPV2Power() { - return getData(9); - } - - @Override - public short getBatteryVoltage() { - return (short) (getData(14) / 100); - } - - @Override - public short getBatteryCurrent() { - return (short) (getData(15) / 100); - } - - @Override - public short getBatteryPower() { - return getData(16); - } - - @Override - public short getBatteryTemperature() { - return getData(17); - } - - @Override - public short getBatterySoC() { - return getData(18); - } - - @Override - public long getOnGridTotalYield() { - return packU16(11, 12) / 100; - } - - @Override - public short getOnGridDailyYield() { - return (short) (getData(13) / 10); - } - - @Override - public short getFeedInPower() { - return getData(32); - } - - @Override - public long getTotalFeedInEnergy() { - return packU16(34, 35) / 100; - } - - @Override - public long getTotalConsumption() { - return packU16(36, 37) / 100; - } - - private short getData(int index) { - try { - short[] dataArray = data; - if (dataArray != null) { - return dataArray[index]; - } - } catch (IndexOutOfBoundsException e) { - logger.debug("Tried to get data out of bounds of the raw data array.", e); - } - return 0; - } - - private long packU16(int indexMajor, int indexMinor) { - short major = getData(indexMajor); - short minor = getData(indexMinor); - if (major == 0) { - return minor; - } - - return ((major << 16) & 0xFFFF0000) | minor & 0xFFFF; - } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java index 64d8375ad4ec4..834c75285de19 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterData.java @@ -14,16 +14,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.solax.internal.connectivity.rawdata.RawDataBean; /** - * The {@link InverterData} interface should implement the interface that returns the parsed data in human readable code - * and format. + * The {@link InverterData} Interface for the parsed inverter data in meaningful format * * @author Konstantin Polihronov - Initial contribution */ @NonNullByDefault -public interface InverterData extends RawDataBean { +public interface InverterData { + @Nullable String getWifiSerial(); @@ -32,61 +31,185 @@ public interface InverterData extends RawDataBean { InverterType getInverterType(); - short getInverterVoltage(); + @Nullable + String getRawData(); + + default double getPV1Voltage() { + return Short.MIN_VALUE; + } + + default double getPV1Current() { + return Short.MIN_VALUE; + } + + default short getPV1Power() { + return Short.MIN_VALUE; + } + + default double getPV2Voltage() { + return Short.MIN_VALUE; + } + + default double getPV2Current() { + return Short.MIN_VALUE; + } + + default short getPV2Power() { + return Short.MIN_VALUE; + } + + default double getPVTotalPower() { + return getPV1Power() + getPV2Power(); + } - short getInverterCurrent(); + default double getPVTotalCurrent() { + return getPV1Current() + getPV2Current(); + } - short getInverterOutputPower(); + default double getBatteryVoltage() { + return Short.MIN_VALUE; + }; - short getInverterFrequency(); + default double getBatteryCurrent() { + return Short.MIN_VALUE; + }; - short getPV1Voltage(); + default short getBatteryPower() { + return Short.MIN_VALUE; + } - short getPV1Current(); + default short getBatteryTemperature() { + return Short.MIN_VALUE; + } - short getPV1Power(); + default short getBatteryLevel() { + return Short.MIN_VALUE; + } - short getPV2Voltage(); + default short getFeedInPower() { + return Short.MIN_VALUE; + } - short getPV2Current(); + default short getPowerUsage() { + return Short.MIN_VALUE; + } - short getPV2Power(); + default double getTotalEnergy() { + return Short.MIN_VALUE; + } - default short getPVTotalPower() { - return (short) (getPV1Power() + getPV2Power()); + default short getTotalBatteryDischargeEnergy() { + return Short.MIN_VALUE; } - default short getPVTotalCurrent() { - return (short) (getPV1Current() + getPV2Current()); + default short getTotalBatteryChargeEnergy() { + return Short.MIN_VALUE; } - short getBatteryVoltage(); // V / 100 + default double getTotalPVEnergy() { + return Short.MIN_VALUE; + } - short getBatteryCurrent(); // A / 100 + default short getTotalFeedInEnergy() { + return Short.MIN_VALUE; + } - short getBatteryPower(); // W + default double getTotalConsumption() { + return Short.MIN_VALUE; + } - short getBatteryTemperature(); // temperature C + default double getTodayEnergy() { + return Short.MIN_VALUE; + } - short getBatterySoC(); // % battery SoC + default double getTodayFeedInEnergy() { + return Short.MIN_VALUE; + } - long getOnGridTotalYield(); // KWh total Yeld from the sun (to the grid?) + default double getTodayConsumption() { + return Short.MIN_VALUE; + } - short getOnGridDailyYield(); // KWh daily Yeld from the sun (to the grid?) + default double getTodayBatteryDischargeEnergy() { + return Short.MIN_VALUE; + } - long getTotalFeedInEnergy(); // KWh all times + default double getTodayBatteryChargeEnergy() { + return Short.MIN_VALUE; + } - long getTotalConsumption(); // KWh all times + default double getInverterVoltage() { + return Short.MIN_VALUE; + } - short getFeedInPower(); + default double getInverterCurrent() { + return Short.MIN_VALUE; + } + + default short getInverterOutputPower() { + return Short.MIN_VALUE; + } + + default double getInverterFrequency() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase1() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase2() { + return Short.MIN_VALUE; + } + + default double getVoltagePhase3() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase1() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase2() { + return Short.MIN_VALUE; + } + + default double getCurrentPhase3() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase1() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase2() { + return Short.MIN_VALUE; + } + + default short getOutputPowerPhase3() { + return Short.MIN_VALUE; + } + + default short getTotalOutputPower() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase1() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase2() { + return Short.MIN_VALUE; + } + + default double getFrequencyPhase3() { + return Short.MIN_VALUE; + } default String toStringDetailed() { return "WifiSerial = " + getWifiSerial() + ", WifiVersion = " + getWifiVersion() + ", InverterType = " - + getInverterType() + ", InverterVoltage = " + getInverterVoltage() + "V, InverterCurrent = " - + getInverterCurrent() + "A, InverterPower = " + getInverterOutputPower() + "W, BatteryPower = " - + getBatteryPower() + "W, Battery SoC = " + getBatterySoC() + "%, FeedIn Power = " + getFeedInPower() - + "W, Total PV Power = " + (getPV1Power() + getPV2Power()) + "W, Total Consumption = " - + getTotalConsumption() + "kWh, Total Feed-in Energy = " + getTotalFeedInEnergy() - + "kWh, Total On-Grid Yield = " + getOnGridTotalYield() + "kWh."; + + getInverterType() + ", BatteryPower = " + getBatteryPower() + "W, Battery SoC = " + getBatteryLevel() + + "%, FeedIn Power = " + getFeedInPower() + "W, Total PV Power = " + (getPV1Power() + getPV2Power()) + + "W"; } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java index b8131fb16baf0..f8bf2a097ff72 100644 --- a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/InverterType.java @@ -12,9 +12,15 @@ */ package org.openhab.binding.solax.internal.model; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; +import org.openhab.binding.solax.internal.model.parsers.X1HybridG4DataParser; +import org.openhab.binding.solax.internal.model.parsers.X3HybridG4DataParser; /** * The {@link InverterType} class is enum representing the different inverter types with a simple logic to convert from @@ -38,18 +44,46 @@ public enum InverterType { A1_FIT(11), A1_GRID(12), J1_ESS(13), - X3_HYBRID_G4(14), - X1_HYBRID_G4(15), + X3_HYBRID_G4(14, new X3HybridG4DataParser()), + X1_HYBRID_G4(15, new X1HybridG4DataParser()), + X3_MIC_OR_PRO_G2(16), + X1_SPT(17), + X1_BOOST_OR_MINI_G4(18), + A1_HYB_G2(19), + A1_AC_G2(20), + A1_SMT_G2(21), + X3_FTH(22), + X3_MGA_G2(23), UNKNOWN(-1); private int typeIndex; + private @Nullable RawDataParser parser; + + private Set supportedChannels = new HashSet<>(); + InverterType(int typeIndex) { + this(typeIndex, null); + } + + InverterType(int typeIndex, @Nullable RawDataParser parser) { this.typeIndex = typeIndex; + this.parser = parser; + if (parser != null) { + this.supportedChannels = parser.getSupportedChannels(); + } } public static InverterType fromIndex(int index) { InverterType[] values = InverterType.values(); return Stream.of(values).filter(value -> value.typeIndex == index).findFirst().orElse(UNKNOWN); } + + public @Nullable RawDataParser getParser() { + return parser; + } + + public Set getSupportedChannels() { + return supportedChannels; + } } diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java new file mode 100644 index 0000000000000..a737d2478ac9e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/CommonInverterData.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CommonInverterData} is an abstract class that contains the common information, applicable for all + * inverters. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public abstract class CommonInverterData implements InverterData { + + private final Logger logger = LoggerFactory.getLogger(CommonInverterData.class); + + private LocalConnectRawDataBean data; + + public CommonInverterData(LocalConnectRawDataBean data) { + this.data = data; + } + + @Override + public @Nullable String getRawData() { + return data.getRawData(); + } + + @Override + public @Nullable String getWifiSerial() { + return data.getSn(); + } + + @Override + public @Nullable String getWifiVersion() { + return data.getVer(); + } + + @Override + public InverterType getInverterType() { + return InverterType.fromIndex(data.getType()); + } + + protected short getData(int index) { + try { + short[] dataArray = data.getData(); + if (dataArray != null) { + return dataArray[index]; + } + } catch (IndexOutOfBoundsException e) { + logger.debug("Tried to get data out of bounds of the raw data array.", e); + } + return 0; + } + + public long packU16(int indexMajor, int indexMinor) { + short major = getData(indexMajor); + short minor = getData(indexMinor); + if (major == 0) { + return minor; + } + + return Integer.toUnsignedLong(major << 16 | minor & 0xFFFF); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java new file mode 100644 index 0000000000000..792187f467e5a --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X1HybridG4InverterData.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; + +/** + * The {@link X1HybridG4InverterData} is an implementation of the single phased inverter data interface for X1 Hybrid G4 + * inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X1HybridG4InverterData extends CommonInverterData { + + public X1HybridG4InverterData(LocalConnectRawDataBean data) { + super(data); + } + + @Override + public double getInverterVoltage() { + return ((double) getData(0)) / 10; + } + + @Override + public double getInverterCurrent() { + return ((double) getData(1)) / 10; + } + + @Override + public short getInverterOutputPower() { + return getData(2); + } + + @Override + public double getInverterFrequency() { + return ((double) getData(3)) / 100; + } + + @Override + public short getFeedInPower() { + return getData(32); + } + + @Override + public double getPV1Voltage() { + return ((double) getData(4)) / 10; + } + + @Override + public double getPV2Voltage() { + return ((double) getData(5)) / 10; + } + + @Override + public double getPV1Current() { + return ((double) getData(6)) / 10; + } + + @Override + public double getPV2Current() { + return ((double) getData(7)) / 10; + } + + @Override + public short getPV1Power() { + return getData(8); + } + + @Override + public short getPV2Power() { + return getData(9); + } + + @Override + public double getBatteryVoltage() { + return ((double) getData(14)) / 100; + } + + @Override + public double getBatteryCurrent() { + return ((double) getData(15)) / 100; + } + + @Override + public short getBatteryPower() { + return getData(16); + } + + @Override + public short getBatteryTemperature() { + return getData(17); + } + + @Override + public short getBatteryLevel() { + return getData(18); + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java new file mode 100644 index 0000000000000..4c5673cb502b6 --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/impl/X3HybridG4InverterData.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.impl; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; + +/** + * The {@link X3HybridG4InverterData} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X3HybridG4InverterData extends CommonInverterData { + + public X3HybridG4InverterData(LocalConnectRawDataBean data) { + super(data); + } + + // Inverter data + + @Override + public double getVoltagePhase1() { + return ((double) getData(0)) / 10; + } + + @Override + public double getVoltagePhase2() { + return ((double) getData(1)) / 10; + } + + @Override + public double getVoltagePhase3() { + return ((double) getData(2)) / 10; + } + + @Override + public double getCurrentPhase1() { + return ((double) getData(3)) / 10; + } + + @Override + public double getCurrentPhase2() { + return ((double) getData(4)) / 10; + } + + @Override + public double getCurrentPhase3() { + return ((double) getData(5)) / 10; + } + + @Override + public short getOutputPowerPhase1() { + return getData(6); + } + + @Override + public short getOutputPowerPhase2() { + return getData(7); + } + + @Override + public short getOutputPowerPhase3() { + return getData(8); + } + + @Override + public short getTotalOutputPower() { + return getData(9); + } + + @Override + public double getPV1Voltage() { + return ((double) getData(10)) / 10; + } + + @Override + public double getPV2Voltage() { + return ((double) getData(11)) / 10; + } + + @Override + public double getPV1Current() { + return ((double) getData(12)) / 10; + } + + @Override + public double getPV2Current() { + return ((double) getData(13)) / 10; + } + + @Override + public short getPV1Power() { + return getData(14); + } + + @Override + public short getPV2Power() { + return getData(15); + } + + @Override + public double getFrequencyPhase1() { + return ((double) getData(16)) / 100; + } + + @Override + public double getFrequencyPhase2() { + return ((double) getData(17)) / 100; + } + + @Override + public double getFrequencyPhase3() { + return ((double) getData(18)) / 100; + } + + // Battery + + @Override + public double getBatteryVoltage() { + return ((double) getData(39)) / 100; + } + + @Override + public double getBatteryCurrent() { + return ((double) getData(40)) / 100; + } + + @Override + public short getBatteryPower() { + return getData(41); + } + + @Override + public short getBatteryTemperature() { + return getData(105); + } + + @Override + public short getBatteryLevel() { + return getData(103); + } + + // Feed in power + + @Override + public short getFeedInPower() { + return (short) (getData(34) - getData(35)); + } + + // Totals + + @Override + public short getPowerUsage() { + return getData(47); + } + + @Override + public double getTotalEnergy() { + return ((double) getData(68)) / 10; + } + + @Override + public short getTotalBatteryDischargeEnergy() { + return getData(74); + } + + @Override + public short getTotalBatteryChargeEnergy() { + return getData(76); + } + + @Override + public double getTotalPVEnergy() { + return ((double) getData(80)) / 10; + } + + @Override + public short getTotalFeedInEnergy() { + return getData(86); + } + + @Override + public double getTotalConsumption() { + return ((double) getData(88)) / 10; + } + + @Override + public double getTodayEnergy() { + return ((double) getData(82)) / 10; + } + + @Override + public double getTodayFeedInEnergy() { + return ((double) getData(90)) / 100; + } + + @Override + public double getTodayConsumption() { + return ((double) getData(92)) / 100; + } + + @Override + public double getTodayBatteryDischargeEnergy() { + return ((double) getData(78)) / 10; + } + + @Override + public double getTodayBatteryChargeEnergy() { + return ((double) getData(79)) / 10; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java new file mode 100644 index 0000000000000..c7547ce7641ab --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/RawDataParser.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.parsers; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; + +/** + * The {@link RawDataParser} declares generic parser implementation that parses raw data to generic inverter data which + * is common for all inverters. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public interface RawDataParser { + + InverterData getData(LocalConnectRawDataBean bean); + + Set getSupportedChannels(); +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java new file mode 100644 index 0000000000000..39444ba218bca --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X1HybridG4DataParser.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.parsers; + +import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.impl.X1HybridG4InverterData; + +/** + * The {@link SinglePhaseDataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the + * X1 Hybrid G4 inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X1HybridG4DataParser implements RawDataParser { + + private static final Set X1_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER, + CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER, + CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER, + CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT, + CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP, + CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER, CHANNEL_INVERTER_OUTPUT_CURRENT, + CHANNEL_INVERTER_OUTPUT_VOLTAGE, CHANNEL_INVERTER_OUTPUT_FREQUENCY); + + @Override + public InverterData getData(LocalConnectRawDataBean rawData) { + return new X1HybridG4InverterData(rawData); + } + + @Override + public Set getSupportedChannels() { + return X1_HYBRID_G4_SUPPORTED_CHANNELS; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java new file mode 100644 index 0000000000000..ff6cd90631e2f --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/java/org/openhab/binding/solax/internal/model/parsers/X3HybridG4DataParser.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal.model.parsers; + +import static org.openhab.binding.solax.internal.SolaxBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.impl.X3HybridG4InverterData; + +/** + * The {@link X3HybridG4DataParser} is the implementation that parses raw data into a SinglePhaseInverterData for the + * X3 Hybrid G4 inverter. + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class X3HybridG4DataParser implements RawDataParser { + + private static final Set X3_HYBRID_G4_SUPPORTED_CHANNELS = Set.of(CHANNEL_INVERTER_PV1_POWER, + CHANNEL_INVERTER_PV1_VOLTAGE, CHANNEL_INVERTER_PV1_CURRENT, CHANNEL_INVERTER_PV2_POWER, + CHANNEL_INVERTER_PV2_VOLTAGE, CHANNEL_INVERTER_PV2_CURRENT, CHANNEL_INVERTER_PV_TOTAL_POWER, + CHANNEL_INVERTER_PV_TOTAL_CURRENT, CHANNEL_BATTERY_POWER, CHANNEL_BATTERY_VOLTAGE, CHANNEL_BATTERY_CURRENT, + CHANNEL_BATTERY_TEMPERATURE, CHANNEL_BATTERY_STATE_OF_CHARGE, CHANNEL_FEED_IN_POWER, CHANNEL_TIMESTAMP, + CHANNEL_RAW_DATA, CHANNEL_INVERTER_OUTPUT_POWER_PHASE1, CHANNEL_INVERTER_OUTPUT_POWER_PHASE2, + CHANNEL_INVERTER_OUTPUT_POWER_PHASE3, CHANNEL_INVERTER_TOTAL_OUTPUT_POWER, + CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE1, CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE2, + CHANNEL_INVERTER_OUTPUT_CURRENT_PHASE3, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE1, + CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE2, CHANNEL_INVERTER_OUTPUT_VOLTAGE_PHASE3, + CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE1, CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE2, + CHANNEL_INVERTER_OUTPUT_FREQUENCY_PHASE3, CHANNEL_POWER_USAGE, CHANNEL_TOTAL_ENERGY, + CHANNEL_TOTAL_BATTERY_CHARGE_ENERGY, CHANNEL_TOTAL_PV_ENERGY, CHANNEL_TOTAL_CONSUMPTION, + CHANNEL_TODAY_ENERGY, CHANNEL_TODAY_FEED_IN_ENERGY, CHANNEL_TODAY_CONSUMPTION, + CHANNEL_TODAY_BATTERY_CHARGE_ENERGY, CHANNEL_TODAY_BATTERY_DISCHARGE_ENERGY); + + @Override + public InverterData getData(LocalConnectRawDataBean rawData) { + return new X3HybridG4InverterData(rawData); + } + + @Override + public Set getSupportedChannels() { + return X3_HYBRID_G4_SUPPORTED_CHANNELS; + } +} diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties index 3b132065b4704..518f83a8399be 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/i18n/solax.properties @@ -17,14 +17,42 @@ thing-type.solax.local-connect-inverter.channel.battery-temperature.label = Batt thing-type.solax.local-connect-inverter.channel.battery-temperature.description = Temperature of the battery thing-type.solax.local-connect-inverter.channel.battery-voltage.label = Battery Voltage thing-type.solax.local-connect-inverter.channel.battery-voltage.description = Electric voltage of the battery -thing-type.solax.local-connect-inverter.channel.feed-in-power.label = Feed-in Power +thing-type.solax.local-connect-inverter.channel.feed-in-power.label = Feed-In Power thing-type.solax.local-connect-inverter.channel.feed-in-power.description = Power to/from the electricity network. thing-type.solax.local-connect-inverter.channel.inverter-current.label = Inverter Input/Output Current thing-type.solax.local-connect-inverter.channel.inverter-current.description = Current to/from the inverter +thing-type.solax.local-connect-inverter.channel.inverter-current-phase1.label = Inverter Input/Output Current Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase1.description = Current to/from the inverter phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.label = Inverter Input/Output Current Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase2.description = Current to/from the inverter phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.label = Inverter Input/Output Current Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-current-phase3.description = Current to/from the inverter phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase1.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.label = Inverter Voltage Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase2.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-frequency-phase3.description = Voltage of the inverter's phase 3 thing-type.solax.local-connect-inverter.channel.inverter-output-power.label = Inverter Input/Output Power thing-type.solax.local-connect-inverter.channel.inverter-output-power.description = Power to/from the inverter +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase1.label = Inverter Input/Output Power Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase1.description = Power to/from the inverter phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.label = Inverter Input/Output Power Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase2.description = Power to/from the inverter phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.label = Inverter Input/Output Power Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-output-power-phase3.description = Power to/from the inverter phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.label = Inverter Input/Output Total Power +thing-type.solax.local-connect-inverter.channel.inverter-total-output-power.description = Power to/from the inverter on all phases thing-type.solax.local-connect-inverter.channel.inverter-voltage.label = Inverter Voltage thing-type.solax.local-connect-inverter.channel.inverter-voltage.description = Voltage of the inverter +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase1.label = Inverter Voltage Phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase1.description = Voltage of the inverter's phase 1 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.label = Inverter Voltage Phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase2.description = Voltage of the inverter's phase 2 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.label = Inverter Voltage Phase 3 +thing-type.solax.local-connect-inverter.channel.inverter-voltage-phase3.description = Voltage of the inverter's phase 3 +thing-type.solax.local-connect-inverter.channel.power-usage.label = Power Usage +thing-type.solax.local-connect-inverter.channel.power-usage.description = Current power consumption of the building thing-type.solax.local-connect-inverter.channel.pv-total-current.label = PV Total Current thing-type.solax.local-connect-inverter.channel.pv-total-current.description = The sum of PV currents from all strings thing-type.solax.local-connect-inverter.channel.pv-total-power.label = PV Total Power @@ -41,6 +69,28 @@ thing-type.solax.local-connect-inverter.channel.pv2-power.label = PV 2 Power thing-type.solax.local-connect-inverter.channel.pv2-power.description = Electric power of PV String 2 thing-type.solax.local-connect-inverter.channel.pv2-voltage.label = PV 2 Voltage thing-type.solax.local-connect-inverter.channel.pv2-voltage.description = Electric voltage of PV String 2 +thing-type.solax.local-connect-inverter.channel.today-battery-charge-energy.label = Today Battery Charge Energy +thing-type.solax.local-connect-inverter.channel.today-battery-charge-energy.description = Total energy charged to the battery for the day +thing-type.solax.local-connect-inverter.channel.today-battery-discharge-energy.label = Today Battery Discharge Energy +thing-type.solax.local-connect-inverter.channel.today-battery-discharge-energy.description = Total energy from the battery output for the day +thing-type.solax.local-connect-inverter.channel.today-consumption.label = Today Consumption +thing-type.solax.local-connect-inverter.channel.today-consumption.description = Energy consumed for the day +thing-type.solax.local-connect-inverter.channel.today-energy.label = Today Energy +thing-type.solax.local-connect-inverter.channel.today-energy.description = Energy output from the inverter for the day +thing-type.solax.local-connect-inverter.channel.today-feed-in-energy.label = Today Feed-In Energy +thing-type.solax.local-connect-inverter.channel.today-feed-in-energy.description = Energy consumed from the electricity provider for the day +thing-type.solax.local-connect-inverter.channel.total-battery-charge-energy.label = Total Battery Charge Energy +thing-type.solax.local-connect-inverter.channel.total-battery-charge-energy.description = Total energy charged to the battery +thing-type.solax.local-connect-inverter.channel.total-battery-discharge-energy.label = Total Battery Discharge Energy +thing-type.solax.local-connect-inverter.channel.total-battery-discharge-energy.description = Total energy from the battery output +thing-type.solax.local-connect-inverter.channel.total-consumption.label = Total Consumption +thing-type.solax.local-connect-inverter.channel.total-consumption.description = Total energy consumed from the building +thing-type.solax.local-connect-inverter.channel.total-energy.label = Total Energy +thing-type.solax.local-connect-inverter.channel.total-energy.description = Total energy output from the inverter +thing-type.solax.local-connect-inverter.channel.total-feed-in-energy.label = Total Feed-In Consumption +thing-type.solax.local-connect-inverter.channel.total-feed-in-energy.description = Total energy consumed from the electricity provider +thing-type.solax.local-connect-inverter.channel.total-pv-energy.label = Total PV Energy +thing-type.solax.local-connect-inverter.channel.total-pv-energy.description = Total energy produced by the PV # thing types config @@ -65,3 +115,4 @@ channel-type.solax.raw-data-type.description = The raw JSON data retrieved from # thing status descriptions offline.communication-error.json-cannot-be-retrieved = JSON data could not be retrieved. +offline.configuration-error.parser-not-implemented = Parser for inverter of type {0} is not implemented. diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml index 8a3c4f7982c48..67e4b20f18ad6 100644 --- a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/thing/localConnectInverter.xml @@ -10,6 +10,7 @@ The inverter representation that supports local connections via HTTP + Power to/from the inverter @@ -24,6 +25,61 @@ + + + + Power to/from the inverter phase 1 + + + + Power to/from the inverter phase 2 + + + + Power to/from the inverter phase 3 + + + + Power to/from the inverter on all phases + + + + Current to/from the inverter phase 1 + + + + Current to/from the inverter phase 2 + + + + Current to/from the inverter phase 3 + + + + Voltage of the inverter's phase 1 + + + + Voltage of the inverter's phase 2 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + + Voltage of the inverter's phase 3 + + + Electric voltage of PV String 1 @@ -77,17 +133,69 @@ The battery state of charge in percent - - + Power to/from the electricity network. + + + Current power consumption of the building + + + + Total energy output from the inverter + + + + Total energy from the battery output + + + + Total energy charged to the battery + + + + Total energy produced by the PV + + + + Total energy consumed from the building + + + + Total energy consumed from the electricity provider + + + + Energy output from the inverter for the day + + + + Total energy from the battery output for the day + + + + Total energy charged to the battery for the day + + + + Energy consumed from the electricity provider for the day + + + + Energy consumed for the day + + + + 1 + + diff --git a/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml new file mode 100644 index 0000000000000..1a1e54416d1fb --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/main/resources/OH-INF/update/local_connect_inverter_type_update.xml @@ -0,0 +1,134 @@ + + + + + + system:electric-power + + Power to/from the inverter phase 1 + + + system:electric-power + + Power to/from the inverter phase 2 + + + system:electric-power + + Power to/from the inverter phase 3 + + + system:electric-power + + Power to/from the inverter on all phases + + + system:electric-current + + Current to/from the inverter phase 1 + + + system:electric-current + + Current to/from the inverter phase 2 + + + system:electric-current + + Current to/from the inverter phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 1 + + + system:electric-voltage + + Voltage of the inverter's phase 2 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-voltage + + Voltage of the inverter's phase 3 + + + system:electric-power + + Current power consumption of the building + + + system:electric-energy + + Total energy output from the inverter + + + system:electric-energy + + Total energy from the battery output + + + system:electric-energy + + Total energy charged to the battery + + + system:electric-energy + + Total energy produced by the PV + + + system:electric-energy + + Total energy consumed from the building + + + system:electric-energy + + Total energy consumed from the electricity provider + + + system:electric-energy + + Energy output from the inverter for the day + + + system:electric-energy + + Total energy from the battery output for the day + + + system:electric-energy + + Total energy charged to the battery for the day + + + system:electric-energy + + Energy consumed from the electricity provider for the day + + + system:electric-energy + + Energy consumed for the day + + + + diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java new file mode 100644 index 0000000000000..5253a1275a25e --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX1HybridG4Parser.java @@ -0,0 +1,104 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; + +/** + * The {@link TestX1HybridG4Parser} Simple test that tests for proper parsing against a real data from the inverter + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestX1HybridG4Parser { + + private static final String RAW_DATA = """ + { + sn:SOME_SERIAL_NUMBER, + ver:3.008.10, + type:15, + Data:[ + 2388,21,460,4998,4483,4483,10,1,487,65, + 2,59781,0,70,12180,500,605,33,99,12000, + 0,23159,0,57,100,0,39,4501,0,0, + 0,0,12,0,13240,0,63348,2,448,43, + 256,1314,900,0,350,311,279,33,33,279,1,1,652,0,708,1,65077,65535,65386,65535,0,0,0,0,0,0,0,0,0,0,0,0,65068,65535,4500,0,61036,65535,10,0,90,0,0,12,0,116,7,57,0,0,2320,0,110,0,0,0,0,0,0,12544,7440,5896,594,521,9252,0,0,0,0,0,1,1201,0,0,3342,3336,7296,54,21302,14389,18753,12852,16692,12355,13618,21302,14389,18753,12852,16692,12355,13618,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1025,4609,1026,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + Information:[7.500,15,H4752TI1063020,8,1.24,0.00,1.21,1.03,0.00,1]} + """; + + @Test + public void testParser() { + LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(RAW_DATA); + int type = bean.getType(); + InverterType inverterType = InverterType.fromIndex(type); + assertEquals(InverterType.X1_HYBRID_G4, inverterType, "Inverter type not recognized properly"); + + RawDataParser parser = inverterType.getParser(); + assertNotNull(parser); + + InverterData data = parser.getData(bean); + assertEquals("SOME_SERIAL_NUMBER", data.getWifiSerial()); + assertEquals("3.008.10", data.getWifiVersion()); + + assertEquals(238.8, data.getInverterVoltage()); // [0] + assertEquals(2.1, data.getInverterCurrent()); // [1] + assertEquals(460, data.getInverterOutputPower()); // [2] + assertEquals(49.98, data.getInverterFrequency()); // [3] + + assertEquals(448.3, data.getPV1Voltage()); // [4] + assertEquals(448.3, data.getPV2Voltage()); // [5] + assertEquals(1, data.getPV1Current()); // [6] + assertEquals(0.1, data.getPV2Current()); // [7] + assertEquals(487, data.getPV1Power()); // [8] + assertEquals(65, data.getPV2Power()); // [9] + + assertEquals(121.8, data.getBatteryVoltage()); // [14] + assertEquals(5, data.getBatteryCurrent()); // [15] + assertEquals(605, data.getBatteryPower()); // [16] + assertEquals(33, data.getBatteryTemperature()); // [17] + assertEquals(99, data.getBatteryLevel()); // [18] + + assertEquals(12, data.getFeedInPower()); // [32] + } + + // Yield_Today: Data[13] / 10, + // Yield_Total: read32BitUnsigned(Data[11], Data[12]) / 10, + // PowerDc1: Data[8], + // PowerDc2: Data[9], + // BAT_Power: read16BitSigned(Data[16]), + // feedInPower: read32BitSigned(Data[32], Data[33]), + // GridAPower: read16BitSigned(Data[2]), + // FeedInEnergy: read32BitUnsigned(Data[34], Data[35]) / 100, + // ConsumeEnergy: read32BitUnsigned(Data[36], Data[37]) / 100, + // RunMode: Data[10], + // EPSAPower: read16BitSigned(Data[28]), + // Vdc1: Data[4] / 10, + // Vdc2: Data[5] / 10, + // Idc1: Data[6] / 10, + // Idc2: Data[7] / 10, + // EPSAVoltage: Data[29] / 10, + // EPSACurrent: read16BitSigned(Data[30]) / 10, + // BatteryCapacity: Data[18], + // BatteryVoltage: Data[14] / 100, + // BatteryTemperature: read16BitSigned(Data[17]), + // GridAVoltage: Data[0] / 10, + // GridACurrent: read16BitSigned(Data[1]) / 10, + // FreqacA: Data[3] / 100, +} diff --git a/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java new file mode 100644 index 0000000000000..55ee2723436ae --- /dev/null +++ b/bundles/org.openhab.binding.solax/src/test/java/org/openhab/binding/solax/internal/TestX3HybridG4Parser.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2010-2023 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.solax.internal; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.solax.internal.connectivity.rawdata.LocalConnectRawDataBean; +import org.openhab.binding.solax.internal.model.InverterData; +import org.openhab.binding.solax.internal.model.InverterType; +import org.openhab.binding.solax.internal.model.parsers.RawDataParser; + +/** + * The {@link TestX3HybridG4Parser} simple test that tests for proper parsing against a real data from the inverter + * + * @author Konstantin Polihronov - Initial contribution + */ +@NonNullByDefault +public class TestX3HybridG4Parser { + + String rawData = """ + { + sn:XYZ, + ver:3.005.01, + type:14,Data:[ + 2316,2329,2315,18,18,18,372,363,365,1100, + 12,23,34,45,56,67,4996,4996,4996,2, + 0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,65494,65535,0,0,0,31330, + 320,1034,3078,1,44,1100,256,1294,0,0, + 7445,5895,100,0,38,0,0,0,0,0, + 0,0,0,0,0,0,0,0,505,0, + 396,0,0,0,102,0,142,0,62,110, + 570,0,463,0,0,0,1925,0,369,0, + 506,1925,304,309,0,0,0,0,0,0, + 0,0,0,45,1,59,1,34,54,256, + 3504,2400,300,300,295,276,33,33,2,1620,779,15163,15163,14906,0,0,0,3270,3264,45581,0,20564,12339,18753,12353,18742,12356,13625,20564,12339,18754,12866,18743,14151,13104,20564,12339,18754,12866,18743,14151,12592,20564,12339,18754,12865,18738,12871,13620,0,0,0,0,0,0,0,1025,8195,769,259,0,31460,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + Information:[12.000,14,XY,8,1.23,0.00,1.24,1.09,0.00,1] + } + """; + + @Test + public void testParser() { + LocalConnectRawDataBean bean = LocalConnectRawDataBean.fromJson(rawData); + int type = bean.getType(); + InverterType inverterType = InverterType.fromIndex(type); + assertEquals(InverterType.X3_HYBRID_G4, inverterType, "Inverter type not recognized properly"); + + RawDataParser parser = inverterType.getParser(); + assertNotNull(parser); + + InverterData data = parser.getData(bean); + assertEquals("XYZ", data.getWifiSerial()); + assertEquals("3.005.01", data.getWifiVersion()); + + assertEquals(231.6, data.getVoltagePhase1()); // [0] + assertEquals(232.9, data.getVoltagePhase2()); // [1] + assertEquals(231.5, data.getVoltagePhase3()); // [2] + + assertEquals(1.8, data.getCurrentPhase1()); // [3] + assertEquals(1.8, data.getCurrentPhase2()); // [4] + assertEquals(1.8, data.getCurrentPhase3()); // [5] + + assertEquals(372, data.getOutputPowerPhase1()); // [6] + assertEquals(363, data.getOutputPowerPhase2()); // [7] + assertEquals(365, data.getOutputPowerPhase3()); // [8] + + assertEquals(1100, data.getTotalOutputPower()); // [9] + + assertEquals(1.2, data.getPV1Voltage()); // [10] + assertEquals(2.3, data.getPV2Voltage()); // [11] + assertEquals(3.4, data.getPV1Current()); // [12] + assertEquals(4.5, data.getPV2Current()); // [13] + assertEquals(56, data.getPV1Power()); // [14] + assertEquals(67, data.getPV2Power()); // [15] + + assertEquals(49.96, data.getFrequencyPhase1()); // [16] + assertEquals(49.96, data.getFrequencyPhase2()); // [17] + assertEquals(49.96, data.getFrequencyPhase3()); // [18] + + assertEquals(-41, data.getFeedInPower()); // [34] - [35] + + assertEquals(313.3, data.getBatteryVoltage()); // [39] + assertEquals(3.2, data.getBatteryCurrent()); // [40] + assertEquals(1034, data.getBatteryPower()); // [41] + assertEquals(45, data.getBatteryLevel()); // [103] + assertEquals(59, data.getBatteryTemperature()); // [105] + + // Totals + assertEquals(1294, data.getPowerUsage()); // [47] + assertEquals(50.5, data.getTotalEnergy()); // [68] + assertEquals(102, data.getTotalBatteryDischargeEnergy()); // [74] + assertEquals(142, data.getTotalBatteryChargeEnergy()); // [76] + assertEquals(57, data.getTotalPVEnergy()); // [80] + assertEquals(1925, data.getTotalFeedInEnergy()); // [86] + assertEquals(36.9, data.getTotalConsumption()); // [88] + assertEquals(46.3, data.getTodayEnergy()); // [82] / 10 + assertEquals(5.06, data.getTodayFeedInEnergy()); // [90] / 100 + assertEquals(3.04, data.getTodayConsumption()); // [92] / 100 + assertEquals(6.2, data.getTodayBatteryDischargeEnergy()); // [78] / 100 + assertEquals(11, data.getTodayBatteryChargeEnergy()); // [79] / 100 + } +}