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 + + + +