2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.modbus.sunspec.internal.handler;
15 import static org.openhab.binding.modbus.sunspec.internal.SunSpecConstants.*;
16 import static org.openhab.core.library.unit.Units.*;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.openhab.binding.modbus.sunspec.internal.dto.MeterModelBlock;
20 import org.openhab.binding.modbus.sunspec.internal.parser.MeterModelParser;
21 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
22 import org.openhab.core.thing.Thing;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * This handler is responsible for handling data recieved from a sunspec meter
29 * @author Nagy Attila Gabor - Initial contribution
33 public class MeterHandler extends AbstractSunSpecHandler {
36 * Parser used to convert incoming raw messages into model blocks
38 private final MeterModelParser parser = new MeterModelParser();
43 private final Logger logger = LoggerFactory.getLogger(MeterHandler.class);
45 public MeterHandler(Thing thing) {
50 * Receive polled data, parse then update states
53 protected void handlePolledData(ModbusRegisterArray registers) {
54 logger.trace("Model block received, size: {}", registers.size());
56 MeterModelBlock block = parser.parse(registers);
59 updateTotalValues(block);
61 updatePhaseValues(block, block.phaseA, GROUP_AC_PHASE_A);
63 // Split phase, wye/delta phase
64 if (block.sunspecDID >= METER_SPLIT_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_SPLIT_PHASE)
65 || thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE)
66 || thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) {
67 updatePhaseValues(block, block.phaseB, GROUP_AC_PHASE_B);
70 // Three phase (wye/delta) only
71 if (block.sunspecDID >= INVERTER_THREE_PHASE && (thing.getThingTypeUID().equals(THING_TYPE_METER_WYE_PHASE)
72 || thing.getThingTypeUID().equals(THING_TYPE_METER_DELTA_PHASE))) {
73 updatePhaseValues(block, block.phaseC, GROUP_AC_PHASE_C);
76 resetCommunicationError();
80 * Update the total states from the received block
84 private void updateTotalValues(MeterModelBlock block) {
85 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_CURRENT),
86 getScaled(block.acCurrentTotal, block.acCurrentSF, AMPERE));
88 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_N),
89 getScaled(block.acVoltageLineToNAverage, block.acVoltageSF, VOLT));
91 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_VOLTAGE_TO_NEXT),
92 getScaled(block.acVoltageLineToLineAverage, block.acVoltageSF, VOLT));
94 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_FREQUENCY),
95 getScaled(block.acFrequency, block.acFrequencySF.orElse((short) 1), HERTZ));
97 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REAL_POWER),
98 getScaled(block.acRealPowerTotal, block.acRealPowerSF, WATT));
100 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_APPARENT_POWER),
101 getScaled(block.acApparentPowerTotal, block.acApparentPowerSF, WATT)); // TODO: this should be VA
103 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_REACTIVE_POWER),
104 getScaled(block.acReactivePowerTotal, block.acReactivePowerSF, WATT)); // TODO: this should be VAR
106 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_AVERAGE_POWER_FACTOR),
107 getScaled(block.acPowerFactor, block.acPowerFactorSF, PERCENT));
109 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REAL_ENERGY),
110 getScaled(block.acExportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR));
112 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REAL_ENERGY),
113 getScaled(block.acImportedRealEnergyTotal, block.acRealEnergySF, WATT_HOUR));
115 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_APPARENT_ENERGY),
116 getScaled(block.acExportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
120 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_APPARENT_ENERGY),
121 getScaled(block.acImportedApparentEnergyTotal, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
125 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q1),
126 getScaled(block.acImportedReactiveEnergyQ1Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
130 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_IMPORTED_REACTIVE_ENERGY_Q2),
131 getScaled(block.acImportedReactiveEnergyQ2Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
135 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q3),
136 getScaled(block.acExportedReactiveEnergyQ3Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
140 updateState(channelUID(GROUP_AC_GENERAL, CHANNEL_AC_TOTAL_EXPORTED_REACTIVE_ENERGY_Q4),
141 getScaled(block.acExportedReactiveEnergyQ4Total, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
147 * Update phase related channels for the selected phase.
149 * @param block the main block for scale
150 * @param phaseBlock the block containing the raw values for the selected phase
151 * @param group channel group id for the output
153 private void updatePhaseValues(MeterModelBlock block, MeterModelBlock.PhaseBlock phaseBlock, String group) {
154 updateState(channelUID(group, CHANNEL_AC_PHASE_CURRENT),
155 getScaled(phaseBlock.acPhaseCurrent, block.acCurrentSF, AMPERE));
157 updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_N),
158 getScaled(phaseBlock.acVoltageToN, block.acVoltageSF, VOLT));
160 updateState(channelUID(group, CHANNEL_AC_VOLTAGE_TO_NEXT),
161 getScaled(phaseBlock.acVoltageToNext, block.acVoltageSF, VOLT));
163 updateState(channelUID(group, CHANNEL_AC_REAL_POWER),
164 getScaled(phaseBlock.acRealPower, block.acRealPowerSF, WATT));
166 updateState(channelUID(group, CHANNEL_AC_APPARENT_POWER),
167 getScaled(phaseBlock.acApparentPower, block.acApparentPowerSF, WATT)); // TODO: this should be VA
169 updateState(channelUID(group, CHANNEL_AC_REACTIVE_POWER),
170 getScaled(phaseBlock.acReactivePower, block.acReactivePowerSF, WATT)); // TODO: this should be VAR
172 updateState(channelUID(group, CHANNEL_AC_POWER_FACTOR),
173 getScaled(phaseBlock.acPowerFactor, block.acPowerFactorSF, PERCENT));
175 updateState(channelUID(group, CHANNEL_AC_EXPORTED_REAL_ENERGY),
176 getScaled(phaseBlock.acExportedRealEnergy, block.acRealEnergySF, WATT_HOUR));
178 updateState(channelUID(group, CHANNEL_AC_IMPORTED_REAL_ENERGY),
179 getScaled(phaseBlock.acImportedRealEnergy, block.acRealEnergySF, WATT_HOUR));
181 updateState(channelUID(group, CHANNEL_AC_EXPORTED_APPARENT_ENERGY),
182 getScaled(phaseBlock.acExportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
186 updateState(channelUID(group, CHANNEL_AC_IMPORTED_APPARENT_ENERGY),
187 getScaled(phaseBlock.acImportedApparentEnergy, block.acApparentEnergySF, WATT_HOUR)); // TODO: this
191 updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q1),
192 getScaled(phaseBlock.acImportedReactiveEnergyQ1, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
196 updateState(channelUID(group, CHANNEL_AC_IMPORTED_REACTIVE_ENERGY_Q2),
197 getScaled(phaseBlock.acImportedReactiveEnergyQ2, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
201 updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q3),
202 getScaled(phaseBlock.acExportedReactiveEnergyQ3, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this
206 updateState(channelUID(group, CHANNEL_AC_EXPORTED_REACTIVE_ENERGY_Q4),
207 getScaled(phaseBlock.acExportedReactiveEnergyQ4, block.acReactiveEnergySF, WATT_HOUR)); // TODO: this