From a6160a01646599b2227b286a191d708cc2d55441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nagy=20Attila=20G=C3=A1bor?= Date: Fri, 24 Apr 2020 20:09:44 +0200 Subject: [PATCH] [modbus][sunspec] Support for SunSpec meters (#7441) * [sunspec] Support for SunSpec meters This commit adds support for SunSpec compatible meters. They are auto discovered and handled as separate things even if they are part of another device. This is due to the nature of the SunSpec format wich also handles the different report types as different blocks. * [sunspec] fixed file endings and missing unit declaration * [sunspec] meter block dto refactored This way we could get rid of ~250 loc. * [sunspec] make sure we don't fail if the address/block size contains decimal dots Some older installations of OpenHAB returned this values incorrectly Signed-off-by: Nagy Attila Gabor Signed-off-by: CSchlipp --- .../sunspec/internal/SunSpecConstants.java | 39 +++ .../internal/SunSpecHandlerFactory.java | 6 + .../sunspec/internal/dto/MeterModelBlock.java | 262 ++++++++++++++++++ .../handler/AbstractSunSpecHandler.java | 4 +- .../internal/handler/MeterHandler.java | 213 ++++++++++++++ .../internal/parser/MeterModelParser.java | 118 ++++++++ .../ESH-INF/thing/meter-channel-groups.xml | 49 ++++ .../ESH-INF/thing/meter-channel-types.xml | 125 +++++++++ .../resources/ESH-INF/thing/meter-types.xml | 141 ++++++++++ 9 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/dto/MeterModelBlock.java create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/MeterHandler.java create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/parser/MeterModelParser.java create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-groups.xml create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-types.xml create mode 100644 bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-types.xml diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecConstants.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecConstants.java index 49e6ccf207211..82588af9ef590 100644 --- a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecConstants.java +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecConstants.java @@ -37,12 +37,20 @@ public class SunSpecConstants { "inverter-split-phase"); public static final ThingTypeUID THING_TYPE_INVERTER_THREE_PHASE = new ThingTypeUID(BINDING_ID, "inverter-three-phase"); + public static final ThingTypeUID THING_TYPE_METER_SINGLE_PHASE = new ThingTypeUID(BINDING_ID, "meter-single-phase"); + public static final ThingTypeUID THING_TYPE_METER_SPLIT_PHASE = new ThingTypeUID(BINDING_ID, "meter-split-phase"); + public static final ThingTypeUID THING_TYPE_METER_WYE_PHASE = new ThingTypeUID(BINDING_ID, "meter-wye-phase"); + public static final ThingTypeUID THING_TYPE_METER_DELTA_PHASE = new ThingTypeUID(BINDING_ID, "meter-delta-phase"); // Block types public static final int COMMON_BLOCK = 1; public static final int INVERTER_SINGLE_PHASE = 101; public static final int INVERTER_SPLIT_PHASE = 102; public static final int INVERTER_THREE_PHASE = 103; + public static final int METER_SINGLE_PHASE = 201; + public static final int METER_SPLIT_PHASE = 202; + public static final int METER_WYE_PHASE = 203; + public static final int METER_DELTA_PHASE = 204; public static final int FINAL_BLOCK = 0xffff; /** @@ -53,6 +61,10 @@ public class SunSpecConstants { SUPPORTED_THING_TYPES_UIDS.put(INVERTER_SINGLE_PHASE, THING_TYPE_INVERTER_SINGLE_PHASE); SUPPORTED_THING_TYPES_UIDS.put(INVERTER_SPLIT_PHASE, THING_TYPE_INVERTER_SPLIT_PHASE); SUPPORTED_THING_TYPES_UIDS.put(INVERTER_THREE_PHASE, THING_TYPE_INVERTER_THREE_PHASE); + SUPPORTED_THING_TYPES_UIDS.put(METER_SINGLE_PHASE, THING_TYPE_METER_SINGLE_PHASE); + SUPPORTED_THING_TYPES_UIDS.put(METER_SPLIT_PHASE, THING_TYPE_METER_SPLIT_PHASE); + SUPPORTED_THING_TYPES_UIDS.put(METER_WYE_PHASE, THING_TYPE_METER_WYE_PHASE); + SUPPORTED_THING_TYPES_UIDS.put(METER_DELTA_PHASE, THING_TYPE_METER_DELTA_PHASE); } // properties @@ -90,6 +102,22 @@ public class SunSpecConstants { public static final String CHANNEL_AC_POWER_FACTOR = "ac-power-factor"; public static final String CHANNEL_AC_LIFETIME_ENERGY = "ac-lifetime-energy"; + // List of channels ids in AC general group for meter + public static final String CHANNEL_AC_AVERAGE_VOLTAGE_TO_N = "ac-average-voltage-to-n"; + public static final String CHANNEL_AC_AVERAGE_VOLTAGE_TO_NEXT = "ac-average-voltage-to-next"; + public static final String CHANNEL_AC_TOTAL_REAL_POWER = "ac-total-real-power"; + public static final String CHANNEL_AC_TOTAL_APPARENT_POWER = "ac-total-apparent-power"; + public static final String CHANNEL_AC_TOTAL_REACTIVE_POWER = "ac-total-reactive-power"; + public static final String CHANNEL_AC_AVERAGE_POWER_FACTOR = "ac-average-power-factor"; + public static final String CHANNEL_AC_TOTAL_EXPORTED_REAL_ENERGY = "ac-total-exported-real-energy"; + public static final String CHANNEL_AC_TOTAL_IMPORTED_REAL_ENERGY = "ac-total-imported-real-energy"; + public static final String CHANNEL_AC_TOTAL_EXPORTED_APPARENT_ENERGY = "ac-total-exported-apparent-energy"; + public static final String CHANNEL_AC_TOTAL_IMPORTED_APPARENT_ENERGY = "ac-total-imported-apparent-energy"; + public static final String CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q1 = "ac-total-imported-reactive-energy-q1"; + public static final String CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q2 = "ac-total-imported-reactive-energy-q2"; + public static final String CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q3 = "ac-total-exported-reactive-energy-q3"; + public static final String CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q4 = "ac-total-exported-reactive-energy-q4"; + // List of channel ids in AC phase group for inverter public static final String CHANNEL_AC_PHASE_CURRENT = "ac-phase-current"; public static final String CHANNEL_AC_VOLTAGE_TO_NEXT = "ac-voltage-to-next"; @@ -100,6 +128,17 @@ public class SunSpecConstants { public static final String CHANNEL_DC_VOLTAGE = "dc-voltage"; public static final String CHANNEL_DC_POWER = "dc-power"; + // List of channel ids in AC phase group for meter + public static final String CHANNEL_AC_REAL_POWER = "ac-real-power"; + public static final String CHANNEL_AC_EXPORTED_REAL_ENERGY = "ac-exported-real-energy"; + public static final String CHANNEL_AC_IMPORTED_REAL_ENERGY = "ac-imported-real-energy"; + public static final String CHANNEL_AC_EXPORTED_APPARENT_ENERGY = "ac-exported-apparent-energy"; + public static final String CHANNEL_AC_IMPORTED_APPARENT_ENERGY = "ac-imported-apparent-energy"; + public static final String CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q1 = "ac-imported-reactive-energy-q1"; + public static final String CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q2 = "ac-imported-reactive-energy-q2"; + public static final String CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q3 = "ac-exported-reactive-energy-q3"; + public static final String CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q4 = "ac-exported-reactive-energy-q4"; + // Expected SunSpec ID This is a magic constant to distinguish SunSpec compatible // devices from other modbus devices public static final long SUNSPEC_ID = 0x53756e53; diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecHandlerFactory.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecHandlerFactory.java index dc01c3d16cab8..db5ea79984427 100644 --- a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecHandlerFactory.java +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/SunSpecHandlerFactory.java @@ -22,6 +22,7 @@ import org.eclipse.smarthome.core.thing.binding.ThingHandler; import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; import org.openhab.binding.modbus.sunspec.internal.handler.InverterHandler; +import org.openhab.binding.modbus.sunspec.internal.handler.MeterHandler; import org.openhab.io.transport.modbus.ModbusManager; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; @@ -75,6 +76,11 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { || thingTypeUID.equals(THING_TYPE_INVERTER_THREE_PHASE)) { logger.debug("New InverterHandler created"); return new InverterHandler(thing, manager); + } else if (thingTypeUID.equals(THING_TYPE_METER_SINGLE_PHASE) + || thingTypeUID.equals(THING_TYPE_METER_SPLIT_PHASE) || thingTypeUID.equals(THING_TYPE_METER_WYE_PHASE) + || thingTypeUID.equals(THING_TYPE_METER_DELTA_PHASE)) { + logger.debug("New MeterHandler created"); + return new MeterHandler(thing, manager); } return null; diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/dto/MeterModelBlock.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/dto/MeterModelBlock.java new file mode 100644 index 0000000000000..46edae21274cd --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/dto/MeterModelBlock.java @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2010-2020 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.modbus.sunspec.internal.dto; + +import java.util.Optional; + +/** + * + * Data object for the parsed information from a sunspec meter + * + * @author Nagy Attila Gabor - Initial contribution + * + */ +public class MeterModelBlock { + + /** + * Sunspec device type id + */ + public Integer sunspecDID; + + /** + * Block length + */ + public Integer length; + + /** + * AC Total Current value + */ + public Short acCurrentTotal; + + /** + * Descriptors for phase A + */ + public PhaseBlock phaseA = new PhaseBlock(); + + /** + * Descriptors for phase B + */ + public PhaseBlock phaseB = new PhaseBlock(); + + /** + * Descriptors for phase C + */ + public PhaseBlock phaseC = new PhaseBlock(); + + /** + * AC Current scale factor + */ + public Short acCurrentSF; + + /** + * AC Voltage Line to line value + */ + public Optional acVoltageLineToNAverage; + + /** + * AC Voltage Line to N value + */ + public Optional acVoltageLineToLineAverage; + + /** + * AC Voltage scale factor + */ + public Short acVoltageSF; + + /** + * AC Frequency value + */ + public Short acFrequency; + + /** + * AC Frequency scale factor + */ + public Optional acFrequencySF; + + /** + * Total real power + */ + public Short acRealPowerTotal; + + /** + * AC Real Power Scale Factor + */ + public Short acRealPowerSF; + + /** + * Total apparent power + */ + public Optional acApparentPowerTotal; + + /** + * AC Apparent Power Scale Factor + */ + public Optional acApparentPowerSF; + + /** + * Total reactive power + */ + public Optional acReactivePowerTotal; + + /** + * AC Reactive Power Scale Factor + */ + public Optional acReactivePowerSF; + + /** + * Power factor + */ + public Optional acPowerFactor; + + /** + * Power factor scale factor + */ + public Optional acPowerFactorSF; + + /** + * Total exported real energy + */ + public Optional acExportedRealEnergyTotal; + + /** + * Total imported real energy + */ + public Long acImportedRealEnergyTotal; + + /** + * Real Energy Scale Factor + */ + public Short acRealEnergySF; + + /** + * Total exported apparent energy + */ + public Optional acExportedApparentEnergyTotal; + + /** + * Total imported apparent energy + */ + public Optional acImportedApparentEnergyTotal; + + /** + * Apparent Energy Scale Factor + */ + public Optional acApparentEnergySF; + + /** + * Quadrant 1: Total imported reactive energy + */ + public Optional acImportedReactiveEnergyQ1Total; + + /** + * Quadrant 2: Total imported reactive energy + */ + public Optional acImportedReactiveEnergyQ2Total; + + /** + * Quadrant 3: Total exported reactive energy + */ + public Optional acExportedReactiveEnergyQ3Total; + + /** + * Quadrant 4: Total exported reactive energy + */ + public Optional acExportedReactiveEnergyQ4Total; + + /** + * Reactive Energy Scale Factor + */ + public Optional acReactiveEnergySF; + + /** + * This subclass is used to store raw data for a single phase in + * multi phase meters. + */ + public static class PhaseBlock { + /** + * AC Phase A Current value + */ + public Optional acPhaseCurrent; + + /** + * AC Voltage Phase Phase to N value + */ + public Optional acVoltageToN; + + /** + * AC Voltage Phase Line to next Line value + */ + public Optional acVoltageToNext; + + /** + * Phase A AC real power + */ + public Optional acRealPower; + + /** + * Phase A AC apparent power + */ + public Optional acApparentPower; + + /** + * Phase A AC reactive power + */ + public Optional acReactivePower; + + /** + * Phase A Power factor + */ + public Optional acPowerFactor; + + /** + * Phase A exported real energy + */ + public Optional acExportedRealEnergy; + + /** + * Phase A imported real energy + */ + public Optional acImportedRealEnergy; + + /** + * Phase A exported apparent energy + */ + public Optional acExportedApparentEnergy; + + /** + * Phase A imported apparent energy + */ + public Optional acImportedApparentEnergy; + + /** + * Quadrant 1: Phase A imported reactive energy + */ + public Optional acImportedReactiveEnergyQ1; + + /** + * Quadrant 2: Phase A imported reactive energy + */ + public Optional acImportedReactiveEnergyQ2; + + /** + * Quadrant 3: Phase A exported reactive energy + */ + public Optional acExportedReactiveEnergyQ3; + + /** + * Quadrant 4: Phase A exported reactive energy + */ + public Optional acExportedReactiveEnergyQ4; + } + +} diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/AbstractSunSpecHandler.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/AbstractSunSpecHandler.java index 40016e6713beb..b963eec02a6cf 100644 --- a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/AbstractSunSpecHandler.java +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/AbstractSunSpecHandler.java @@ -181,8 +181,8 @@ private void startUp() { } try { ModelBlock block = new ModelBlock(); - block.address = Integer.parseInt(thing.getProperties().get(PROPERTY_BLOCK_ADDRESS)); - block.length = Integer.parseInt(thing.getProperties().get(PROPERTY_BLOCK_LENGTH)); + block.address = (int) Double.parseDouble(thing.getProperties().get(PROPERTY_BLOCK_ADDRESS)); + block.length = (int) Double.parseDouble(thing.getProperties().get(PROPERTY_BLOCK_LENGTH)); return block; } catch (NumberFormatException ex) { logger.debug("Could not parse address and length properties, error: {}", ex.getMessage()); diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/MeterHandler.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/MeterHandler.java new file mode 100644 index 0000000000000..5971aa1389498 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/handler/MeterHandler.java @@ -0,0 +1,213 @@ +/** + * Copyright (c) 2010-2020 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.modbus.sunspec.internal.handler; + +import static org.eclipse.smarthome.core.library.unit.SmartHomeUnits.*; +import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.Thing; +import org.openhab.binding.modbus.sunspec.internal.dto.MeterModelBlock; +import org.openhab.binding.modbus.sunspec.internal.parser.MeterModelParser; +import org.openhab.io.transport.modbus.ModbusManager; +import org.openhab.io.transport.modbus.ModbusRegisterArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This handler is responsible for handling data recieved from a sunspec meter + * + * @author Nagy Attila Gabor - Initial contribution + * + */ +@NonNullByDefault +public class MeterHandler extends AbstractSunSpecHandler { + + /** + * Parser used to convert incoming raw messages into model blocks + */ + private final MeterModelParser parser = new MeterModelParser(); + + /** + * Logger instance + */ + private final Logger logger = LoggerFactory.getLogger(MeterHandler.class); + + public MeterHandler(Thing thing, ModbusManager managerRef) { + super(thing, managerRef); + } + + /** + * Receive polled data, parse then update states + */ + @Override + protected void handlePolledData(ModbusRegisterArray registers) { + logger.trace("Model block received, size: {}", registers.size()); + + MeterModelBlock block = parser.parse(registers); + + // AC General group + updateTotalValues(block); + + updatePhaseValues(block, block.phaseA, GROUP_AC_PHASE_A); + + // Split phase, wye/delta phase + if (block.sunspecDID >= METER_SPLIT_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_SPLIT_PHASE) + || thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE) + || thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) { + updatePhaseValues(block, block.phaseB, GROUP_AC_PHASE_B); + } + + // Three phase (wye/delta) only + if (block.sunspecDID >= INVERTER_THREE_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE) + || thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) { + updatePhaseValues(block, block.phaseC, GROUP_AC_PHASE_C); + } + + resetCommunicationError(); + } + + /** + * Update the total states from the received block + * + * @param block + */ + private void updateTotalValues(MeterModelBlock block) { + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_CURRENT), + getScaled(block.acCurrentTotal, block.acCurrentSF, AMPERE)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_N), + getScaled(block.acVoltageLineToNAverage, block.acVoltageSF, VOLT)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_NEXT), + getScaled(block.acVoltageLineToLineAverage, block.acVoltageSF, VOLT)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_FREQUENCY), + getScaled(block.acFrequency, block.acFrequencySF.orElse((short) 1), HERTZ)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REAL_POWER), + getScaled(block.acRealPowerTotal, block.acRealPowerSF, WATT)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_APPARENT_POWER), + getScaled(block.acApparentPowerTotal, block.acApparentPowerSF, WATT)); // TODO: this should be VA + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REACTIVE_POWER), + getScaled(block.acReactivePowerTotal, block.acReactivePowerSF, WATT)); // TODO: this should be VAR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_POWER_FACTOR), + getScaled(block.acPowerFactor, block.acPowerFactorSF, PERCENT)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REAL_ENERGY), + getScaled(block.acExportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REAL_ENERGY), + getScaled(block.acImportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR)); + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_APPARENT_ENERGY), + getScaled(block.acExportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this + // should be + // VA_HOUR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_APPARENT_ENERGY), + getScaled(block.acImportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this + // should be + // VA_HOUR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q1), + getScaled(block.acImportedReactiveEnergyQ1Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q2), + getScaled(block.acImportedReactiveEnergyQ2Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q3), + getScaled(block.acExportedReactiveEnergyQ3Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q4), + getScaled(block.acExportedReactiveEnergyQ4Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + } + + /** + * Update phase related channels for the selected phase. + * + * @param block the main block for scale + * @param phaseBlock the block containing the raw values for the selected phase + * @param group channel group id for the output + */ + private void updatePhaseValues(MeterModelBlock block, MeterModelBlock.PhaseBlock phaseBlock, String group) { + updateState(channelUID(group, CHANNEL_AC_PHASE_CURRENT), + getScaled(phaseBlock.acPhaseCurrent, block.acCurrentSF, AMPERE)); + + updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_N), + getScaled(phaseBlock.acVoltageToN, block.acVoltageSF, VOLT)); + + updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_NEXT), + getScaled(phaseBlock.acVoltageToNext, block.acVoltageSF, VOLT)); + + updateState(channelUID(group, CHANNEL_AC_REAL_POWER), + getScaled(phaseBlock.acRealPower, block.acRealPowerSF, WATT)); + + updateState(channelUID(group, CHANNEL_AC_APPARENT_POWER), + getScaled(phaseBlock.acApparentPower, block.acApparentPowerSF, WATT)); // TODO: this should be VA + + updateState(channelUID(group, CHANNEL_AC_REACTIVE_POWER), + getScaled(phaseBlock.acReactivePower, block.acReactivePowerSF, WATT)); // TODO: this should be VAR + + updateState(channelUID(group, CHANNEL_AC_POWER_FACTOR), + getScaled(phaseBlock.acPowerFactor, block.acPowerFactorSF, PERCENT)); + + updateState(channelUID(group, CHANNEL_AC_EXPORTED_REAL_ENERGY), + getScaled(phaseBlock.acExportedRealEnergy, block.acRealEnergySF, WATT_HOUR)); + + updateState(channelUID(group, CHANNEL_AC_IMPORTED_REAL_ENERGY), + getScaled(phaseBlock.acImportedRealEnergy, block.acRealEnergySF, WATT_HOUR)); + + updateState(channelUID(group, CHANNEL_AC_EXPORTED_APPARENT_ENERGY), + getScaled(phaseBlock.acExportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this + // should be + // VA_HOUR + + updateState(channelUID(group, CHANNEL_AC_IMPORTED_APPARENT_ENERGY), + getScaled(phaseBlock.acImportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this + // should be + // VA_HOUR + + updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q1), + getScaled(phaseBlock.acImportedReactiveEnergyQ1, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q2), + getScaled(phaseBlock.acImportedReactiveEnergyQ2, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q3), + getScaled(phaseBlock.acExportedReactiveEnergyQ3, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + + updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q4), + getScaled(phaseBlock.acExportedReactiveEnergyQ4, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this + // should be + // VAR_HOUR + } + +} diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/parser/MeterModelParser.java b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/parser/MeterModelParser.java new file mode 100644 index 0000000000000..f6adb4a4188d6 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/java/org/openhab/binding/modbus/sunspec/internal/parser/MeterModelParser.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2020 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.modbus.sunspec.internal.parser; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.modbus.sunspec.internal.SunSpecConstants; +import org.openhab.binding.modbus.sunspec.internal.dto.MeterModelBlock; +import org.openhab.io.transport.modbus.ModbusRegisterArray; + +/** + * Parser for sunspec compatible meters + * + * @author Nagy Attila Gabor - Initial contribution + * + */ +@NonNullByDefault +public class MeterModelParser extends AbstractBaseParser implements SunspecParser { + + @Override + public MeterModelBlock parse(ModbusRegisterArray raw) { + MeterModelBlock block = new MeterModelBlock(); + + block.sunspecDID = extractUInt16(raw, 0, SunSpecConstants.METER_SINGLE_PHASE); + block.length = extractUInt16(raw, 1, raw.size()); + block.acCurrentTotal = extractInt16(raw, 2, (short) 0); + block.phaseA.acPhaseCurrent = extractOptionalInt16(raw, 3); + block.phaseB.acPhaseCurrent = extractOptionalInt16(raw, 4); + block.phaseC.acPhaseCurrent = extractOptionalInt16(raw, 5); + block.acCurrentSF = extractSunSSF(raw, 6); + + block.acVoltageLineToNAverage = extractOptionalInt16(raw, 7); + block.phaseA.acVoltageToN = extractOptionalInt16(raw, 8); + block.phaseB.acVoltageToN = extractOptionalInt16(raw, 9); + block.phaseC.acVoltageToN = extractOptionalInt16(raw, 10); + block.acVoltageLineToLineAverage = extractOptionalInt16(raw, 11); + block.phaseA.acVoltageToNext = extractOptionalInt16(raw, 12); + block.phaseB.acVoltageToNext = extractOptionalInt16(raw, 13); + block.phaseC.acVoltageToNext = extractOptionalInt16(raw, 14); + block.acVoltageSF = extractSunSSF(raw, 15); + + block.acFrequency = extractInt16(raw, 16, (short) 0); + block.acFrequencySF = extractOptionalSunSSF(raw, 17); + + block.acRealPowerTotal = extractInt16(raw, 18, (short) 0); + block.phaseA.acRealPower = extractOptionalInt16(raw, 19); + block.phaseB.acRealPower = extractOptionalInt16(raw, 20); + block.phaseC.acRealPower = extractOptionalInt16(raw, 21); + block.acRealPowerSF = extractSunSSF(raw, 22); + + block.acApparentPowerTotal = extractOptionalInt16(raw, 23); + block.phaseA.acApparentPower = extractOptionalInt16(raw, 24); + block.phaseB.acApparentPower = extractOptionalInt16(raw, 25); + block.phaseC.acApparentPower = extractOptionalInt16(raw, 26); + block.acApparentPowerSF = extractOptionalSunSSF(raw, 27); + + block.acReactivePowerTotal = extractOptionalInt16(raw, 28); + block.phaseA.acReactivePower = extractOptionalInt16(raw, 29); + block.phaseB.acReactivePower = extractOptionalInt16(raw, 30); + block.phaseC.acReactivePower = extractOptionalInt16(raw, 31); + block.acReactivePowerSF = extractOptionalSunSSF(raw, 32); + + block.acPowerFactor = extractOptionalInt16(raw, 33); + block.phaseA.acPowerFactor = extractOptionalInt16(raw, 34); + block.phaseB.acPowerFactor = extractOptionalInt16(raw, 35); + block.phaseC.acPowerFactor = extractOptionalInt16(raw, 36); + block.acPowerFactorSF = extractOptionalSunSSF(raw, 37); + + block.acExportedRealEnergyTotal = extractOptionalAcc32(raw, 38); + block.phaseA.acExportedRealEnergy = extractOptionalAcc32(raw, 40); + block.phaseB.acExportedRealEnergy = extractOptionalAcc32(raw, 42); + block.phaseC.acExportedRealEnergy = extractOptionalAcc32(raw, 44); + block.acImportedRealEnergyTotal = extractAcc32(raw, 46, 0); + block.phaseA.acImportedRealEnergy = extractOptionalAcc32(raw, 48); + block.phaseB.acImportedRealEnergy = extractOptionalAcc32(raw, 50); + block.phaseC.acImportedRealEnergy = extractOptionalAcc32(raw, 52); + block.acRealEnergySF = extractSunSSF(raw, 54); + + block.acExportedApparentEnergyTotal = extractOptionalAcc32(raw, 55); + block.phaseA.acExportedApparentEnergy = extractOptionalAcc32(raw, 57); + block.phaseB.acExportedApparentEnergy = extractOptionalAcc32(raw, 59); + block.phaseC.acExportedApparentEnergy = extractOptionalAcc32(raw, 61); + block.acImportedApparentEnergyTotal = extractOptionalAcc32(raw, 63); + block.phaseA.acImportedApparentEnergy = extractOptionalAcc32(raw, 65); + block.phaseB.acImportedApparentEnergy = extractOptionalAcc32(raw, 67); + block.phaseC.acImportedApparentEnergy = extractOptionalAcc32(raw, 69); + block.acApparentEnergySF = extractOptionalSunSSF(raw, 71); + + block.acImportedReactiveEnergyQ1Total = extractOptionalAcc32(raw, 72); + block.phaseA.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 74); + block.phaseB.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 76); + block.phaseC.acImportedReactiveEnergyQ1 = extractOptionalAcc32(raw, 78); + block.acImportedReactiveEnergyQ2Total = extractOptionalAcc32(raw, 80); + block.phaseA.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 82); + block.phaseB.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 84); + block.phaseC.acImportedReactiveEnergyQ2 = extractOptionalAcc32(raw, 86); + block.acExportedReactiveEnergyQ3Total = extractOptionalAcc32(raw, 88); + block.phaseA.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 90); + block.phaseB.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 92); + block.phaseC.acExportedReactiveEnergyQ3 = extractOptionalAcc32(raw, 94); + block.acExportedReactiveEnergyQ4Total = extractOptionalAcc32(raw, 96); + block.phaseA.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 98); + block.phaseB.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 100); + block.phaseC.acExportedReactiveEnergyQ4 = extractOptionalAcc32(raw, 102); + block.acReactiveEnergySF = extractOptionalSunSSF(raw, 104); + + return block; + } +} diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-groups.xml b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-groups.xml new file mode 100644 index 0000000000000..6dea65ac86b80 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-groups.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-types.xml b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-types.xml new file mode 100644 index 0000000000000..4d8fc9d2ec56a --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-channel-types.xml @@ -0,0 +1,125 @@ + + + + + Number:ElectricPotential + + + + + Number:ElectricPotential + + + + + Number:Power + + + + + Number:Power + + + + + Number:Power + + + + + Number:Power + + + + + Number:Dimensionless + + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + + Number:Energy + + + + diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-types.xml b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-types.xml new file mode 100644 index 0000000000000..efbae000321f1 --- /dev/null +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/ESH-INF/thing/meter-types.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + Single phase (AN or AB) meter supporting SunSpec mapping over modbus connection + Meter + + + + + + + + + + + + + + + + + uniqueAddress + + + + + + + + + + + + Split phase (ABN) meter supporting SunSpec mapping over modbus connection + Meter + + + + + + + + + + + + + + + + + + + + uniqueAddress + + + + + + + + + + + + Wye-connected three phase (ABCN) meter supporting SunSpec mapping over modbus connection + Meter + + + + + + + + + + + + + + + + + + + + + + + uniqueAddress + + + + + + + + + + + + Delta-connected three phase (ABC) meter supporting SunSpec mapping over modbus connection + Meter + + + + + + + + + + + + + + + + + + + + + + + uniqueAddress + + + +