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.e3dc.internal.handler;
15 import static org.openhab.binding.modbus.e3dc.internal.E3DCBindingConstants.*;
16 import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
18 import java.util.ArrayList;
19 import java.util.Optional;
21 import javax.measure.quantity.Energy;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.modbus.e3dc.internal.E3DCConfiguration;
26 import org.openhab.binding.modbus.e3dc.internal.dto.EmergencyBlock;
27 import org.openhab.binding.modbus.e3dc.internal.dto.InfoBlock;
28 import org.openhab.binding.modbus.e3dc.internal.dto.PowerBlock;
29 import org.openhab.binding.modbus.e3dc.internal.dto.StringBlock;
30 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
31 import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
32 import org.openhab.binding.modbus.e3dc.internal.modbus.Parser;
33 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
34 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
35 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
36 import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
37 import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
38 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
39 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
40 import org.openhab.core.io.transport.modbus.PollTask;
41 import org.openhab.core.library.types.QuantityType;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.types.Command;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 * The {@link E3DCThingHandler} Basic modbus connection towards the E3DC device
57 * @author Bernd Weymann - Initial contribution
60 public class E3DCThingHandler extends BaseBridgeHandler {
61 public enum ReadStatus {
67 static final String INFO_DATA_READ_ERROR = "Information And Data Modbus Read Errors";
68 static final String INFO_READ_ERROR = "Information Modbus Read Error";
69 static final String DATA_READ_ERROR = "Data Modbus Read Error";
71 static final String INFO_GROUP = "info";
72 static final String EMERGENCY_GROUP = "emergency";
73 static final String POWER_GROUP = "power";
74 static final String STRINGS_GROUP = "strings";
76 private ChannelUID modbusIdChannel;
77 private ChannelUID modbusVersionChannel;
78 private ChannelUID supportedRegistersChannel;
79 private ChannelUID manufacturerChannel;
80 private ChannelUID modelNameChannel;
81 private ChannelUID serialNumberChannel;
82 private ChannelUID firmwareChannel;
84 private ChannelUID epStatusChannel;
85 private ChannelUID batteryChargingLockedChannel;
86 private ChannelUID batteryDischargingLockedChannel;
87 private ChannelUID epPossibleChannel;
88 private ChannelUID weatherPredictedChargingChannel;
89 private ChannelUID regulationStatusChannel;
90 private ChannelUID chargeLockTimeChannel;
91 private ChannelUID dischargeLockTimeChannel;
93 private ChannelUID pvPowerSupplyChannel;
94 private ChannelUID batteryPowerSupplyChannel;
95 private ChannelUID batteryPowerConsumptionChannel;
96 private ChannelUID householdPowerConsumptionChannel;
97 private ChannelUID gridPowerConsumpitionChannel;
98 private ChannelUID gridPowerSupplyChannel;
99 private ChannelUID externalPowerSupplyChannel;
100 private ChannelUID wallboxPowerConsumptionChannel;
101 private ChannelUID wallboxPVPowerConsumptionChannel;
102 private ChannelUID autarkyChannel;
103 private ChannelUID selfConsumptionChannel;
104 private ChannelUID batterySOCChannel;
105 private ChannelUID batteryChargedChannel;
106 private ChannelUID batteryUnchargedChannel;
108 private ChannelUID string1AmpereChannel;
109 private ChannelUID string1VoltChannel;
110 private ChannelUID string1WattChannel;
111 private ChannelUID string2AmpereChannel;
112 private ChannelUID string2VoltChannel;
113 private ChannelUID string2WattChannel;
114 private ChannelUID string3AmpereChannel;
115 private ChannelUID string3VoltChannel;
116 private ChannelUID string3WattChannel;
118 private final ArrayList<E3DCWallboxThingHandler> listeners = new ArrayList<E3DCWallboxThingHandler>();
119 private final Logger logger = LoggerFactory.getLogger(E3DCThingHandler.class);
120 private final Parser dataParser = new Parser(DataType.DATA);
121 private ReadStatus dataRead = ReadStatus.NOT_RECEIVED;
122 private final Parser infoParser = new Parser(DataType.INFO);
123 private ReadStatus infoRead = ReadStatus.NOT_RECEIVED;
124 private @Nullable PollTask infoPoller;
125 private @Nullable PollTask dataPoller;
126 private @Nullable E3DCConfiguration config;
129 * Communication interface to the slave endpoint we're connecting to
131 protected volatile @Nullable ModbusCommunicationInterface comms = null;
134 public E3DCThingHandler(Bridge thing) {
137 modbusIdChannel = channelUID(thing, INFO_GROUP, MODBUS_ID_CHANNEL);
138 modbusVersionChannel = channelUID(thing, INFO_GROUP, MODBUS_FIRMWARE_CHANNEL);
139 supportedRegistersChannel = channelUID(thing, INFO_GROUP, SUPPORTED_REGISTERS_CHANNEL);
140 manufacturerChannel = channelUID(thing, INFO_GROUP, MANUFACTURER_NAME_CHANNEL);
141 modelNameChannel = channelUID(thing, INFO_GROUP, MODEL_NAME_CHANNEL);
142 serialNumberChannel = channelUID(thing, INFO_GROUP, SERIAL_NUMBER_CHANNEL);
143 firmwareChannel = channelUID(thing, INFO_GROUP, FIRMWARE_RELEASE_CHANNEL);
145 epStatusChannel = channelUID(thing, EMERGENCY_GROUP, EMERGENCY_POWER_STATUS);
146 batteryChargingLockedChannel = channelUID(thing, EMERGENCY_GROUP, BATTERY_CHARGING_LOCKED);
147 batteryDischargingLockedChannel = channelUID(thing, EMERGENCY_GROUP, BATTERY_DISCHARGING_LOCKED);
148 epPossibleChannel = channelUID(thing, EMERGENCY_GROUP, EMERGENCY_POWER_POSSIBLE);
149 weatherPredictedChargingChannel = channelUID(thing, EMERGENCY_GROUP, WEATHER_PREDICTED_CHARGING);
150 regulationStatusChannel = channelUID(thing, EMERGENCY_GROUP, REGULATION_STATUS);
151 chargeLockTimeChannel = channelUID(thing, EMERGENCY_GROUP, CHARGE_LOCK_TIME);
152 dischargeLockTimeChannel = channelUID(thing, EMERGENCY_GROUP, DISCHARGE_LOCK_TIME);
154 pvPowerSupplyChannel = channelUID(thing, POWER_GROUP, PV_POWER_SUPPLY_CHANNEL);
155 batteryPowerSupplyChannel = channelUID(thing, POWER_GROUP, BATTERY_POWER_SUPPLY_CHANNEL);
156 batteryPowerConsumptionChannel = channelUID(thing, POWER_GROUP, BATTERY_POWER_CONSUMPTION);
157 householdPowerConsumptionChannel = channelUID(thing, POWER_GROUP, HOUSEHOLD_POWER_CONSUMPTION_CHANNEL);
158 gridPowerConsumpitionChannel = channelUID(thing, POWER_GROUP, GRID_POWER_CONSUMPTION_CHANNEL);
159 gridPowerSupplyChannel = channelUID(thing, POWER_GROUP, GRID_POWER_SUPPLY_CHANNEL);
160 externalPowerSupplyChannel = channelUID(thing, POWER_GROUP, EXTERNAL_POWER_SUPPLY_CHANNEL);
161 wallboxPowerConsumptionChannel = channelUID(thing, POWER_GROUP, WALLBOX_POWER_CONSUMPTION_CHANNEL);
162 wallboxPVPowerConsumptionChannel = channelUID(thing, POWER_GROUP, WALLBOX_PV_POWER_CONSUMPTION_CHANNEL);
163 autarkyChannel = channelUID(thing, POWER_GROUP, AUTARKY_CHANNEL);
164 selfConsumptionChannel = channelUID(thing, POWER_GROUP, SELF_CONSUMPTION_CHANNEL);
165 batterySOCChannel = channelUID(thing, POWER_GROUP, BATTERY_STATE_OF_CHARGE_CHANNEL);
166 batteryChargedChannel = channelUID(thing, POWER_GROUP, BATTERY_CHARGED_CHANNEL);
167 batteryUnchargedChannel = channelUID(thing, POWER_GROUP, BATTERY_UNCHARGED_CHANNEL);
169 string1AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_CURRENT_CHANNEL);
170 string1VoltChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_VOLTAGE_CHANNEL);
171 string1WattChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_OUTPUT_CHANNEL);
172 string2AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_CURRENT_CHANNEL);
173 string2VoltChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_VOLTAGE_CHANNEL);
174 string2WattChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_OUTPUT_CHANNEL);
175 string3AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_CURRENT_CHANNEL);
176 string3VoltChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_VOLTAGE_CHANNEL);
177 string3WattChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_OUTPUT_CHANNEL);
180 public @Nullable ModbusCommunicationInterface getComms() {
184 public int getSlaveId() {
189 public void handleCommand(ChannelUID channelUID, Command command) {
190 // no control of E3DC device possible yet
194 public void initialize() {
195 updateStatus(ThingStatus.UNKNOWN);
196 scheduler.execute(() -> {
197 E3DCConfiguration localConfig = getConfigAs(E3DCConfiguration.class);
198 config = localConfig;
199 ModbusCommunicationInterface localComms = connectEndpoint();
200 if (localComms != null) {
201 // register low speed info poller
202 ModbusReadRequestBlueprint infoRequest = new ModbusReadRequestBlueprint(slaveId,
203 ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, INFO_REG_START, INFO_REG_SIZE, 3);
204 infoPoller = localComms.registerRegularPoll(infoRequest, INFO_POLL_REFRESH_TIME_MS, 0,
205 this::handleInfoResult, this::handleInfoFailure);
207 ModbusReadRequestBlueprint dataRequest = new ModbusReadRequestBlueprint(slaveId,
208 ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, POWER_REG_START,
209 REGISTER_LENGTH - INFO_REG_SIZE, 3);
210 if (config != null) {
211 dataPoller = localComms.registerRegularPoll(dataRequest, localConfig.refresh, 0,
212 this::handleDataResult, this::handleDataFailure);
214 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
215 "E3DC Configuration missing");
217 } // else state handling performed in connectEndPoint function
222 * Get a reference to the modbus endpoint
224 private @Nullable ModbusCommunicationInterface connectEndpoint() {
229 ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
230 if (slaveEndpointThingHandler == null) {
231 @SuppressWarnings("null")
232 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
233 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
234 String.format("Bridge '%s' is offline", label));
238 slaveId = slaveEndpointThingHandler.getSlaveId();
239 comms = slaveEndpointThingHandler.getCommunicationInterface();
240 } catch (EndpointNotInitializedException e) {
241 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
242 String.format("Slave Endpoint not initialized"));
246 @SuppressWarnings("null")
247 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
248 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
249 String.format("Bridge '%s' not completely initialized", label));
257 * Get the endpoint handler from the bridge this handler is connected to
258 * Checks that we're connected to the right type of bridge
260 * @return the endpoint handler or null if the bridge does not exist
262 private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
263 Bridge bridge = getBridge();
264 if (bridge == null) {
265 logger.debug("Bridge is null");
268 if (bridge.getStatus() != ThingStatus.ONLINE) {
269 logger.debug("Bridge is not online");
273 ThingHandler handler = bridge.getHandler();
274 if (handler == null) {
275 logger.debug("Bridge handler is null");
279 if (handler instanceof ModbusEndpointThingHandler thingHandler) {
282 logger.debug("Unexpected bridge handler: {}", handler);
288 * Returns the channel UID for the specified group and channel id
290 * @param string the channel group
291 * @param string the channel id in that group
292 * @return the globally unique channel uid
294 private ChannelUID channelUID(Thing t, String group, String id) {
295 return new ChannelUID(t.getUID(), group, id);
298 void handleInfoResult(AsyncModbusReadResult result) {
299 if (infoRead != ReadStatus.READ_SUCCESS) {
300 // update status only if bit switches
301 infoRead = ReadStatus.READ_SUCCESS;
304 infoParser.handle(result);
305 Optional<Data> blockOpt = infoParser.parse(DataType.INFO);
306 if (blockOpt.isPresent()) {
307 InfoBlock block = (InfoBlock) blockOpt.get();
308 updateState(modbusIdChannel, block.modbusId);
309 updateState(modbusVersionChannel, block.modbusVersion);
310 updateState(supportedRegistersChannel, block.supportedRegisters);
311 updateState(manufacturerChannel, block.manufacturer);
312 updateState(modelNameChannel, block.modelName);
313 updateState(serialNumberChannel, block.serialNumber);
314 updateState(firmwareChannel, block.firmware);
316 logger.debug("Unable to get {} from provider {}", DataType.INFO, dataParser.toString());
320 void handleInfoFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
321 if (infoRead != ReadStatus.READ_FAILED) {
322 // update status only if bit switches
323 infoRead = ReadStatus.READ_FAILED;
328 void handleDataResult(AsyncModbusReadResult result) {
329 if (dataRead != ReadStatus.READ_SUCCESS) {
330 // update status only if bit switches
331 dataRead = ReadStatus.READ_SUCCESS;
334 dataParser.handle(result);
335 // Update channels in emergency group
337 Optional<Data> blockOpt = dataParser.parse(DataType.EMERGENCY);
338 if (blockOpt.isPresent()) {
339 EmergencyBlock block = (EmergencyBlock) blockOpt.get();
340 updateState(epStatusChannel, block.epStatus);
341 updateState(batteryChargingLockedChannel, block.batteryChargingLocked);
342 updateState(batteryDischargingLockedChannel, block.batteryDischargingLocked);
343 updateState(epPossibleChannel, block.epPossible);
344 updateState(weatherPredictedChargingChannel, block.weatherPredictedCharging);
345 updateState(regulationStatusChannel, block.regulationStatus);
346 updateState(chargeLockTimeChannel, block.chargeLockTime);
347 updateState(dischargeLockTimeChannel, block.dischargeLockTime);
349 logger.debug("Unable to get {} from provider {}", DataType.EMERGENCY, dataParser.toString());
353 // Update channels in power group
355 Optional<Data> blockOpt = dataParser.parse(DataType.POWER);
356 if (blockOpt.isPresent()) {
357 PowerBlock block = (PowerBlock) blockOpt.get();
358 updateState(pvPowerSupplyChannel, block.pvPowerSupply);
359 updateState(batteryPowerSupplyChannel, block.batteryPowerSupply);
360 updateState(batteryPowerConsumptionChannel, block.batteryPowerConsumption);
361 updateState(householdPowerConsumptionChannel, block.householdPowerConsumption);
362 updateState(gridPowerConsumpitionChannel, block.gridPowerConsumpition);
363 updateState(gridPowerSupplyChannel, block.gridPowerSupply);
364 updateState(externalPowerSupplyChannel, block.externalPowerSupply);
365 updateState(wallboxPowerConsumptionChannel, block.wallboxPowerConsumption);
366 updateState(wallboxPVPowerConsumptionChannel, block.wallboxPVPowerConsumption);
367 updateState(autarkyChannel, block.autarky);
368 updateState(selfConsumptionChannel, block.selfConsumption);
369 updateState(batterySOCChannel, block.batterySOC);
370 if (config != null) {
371 if (config.batteryCapacity > 0) {
372 double soc = block.batterySOC.doubleValue();
373 QuantityType<Energy> charged = QuantityType.valueOf(soc * config.batteryCapacity / 100,
374 Units.KILOWATT_HOUR);
375 updateState(batteryChargedChannel, charged);
376 QuantityType<Energy> uncharged = QuantityType
377 .valueOf((100 - soc) * config.batteryCapacity / 100, Units.KILOWATT_HOUR);
378 updateState(batteryUnchargedChannel, uncharged);
382 logger.debug("Unable to get {} from provider {}", DataType.POWER, dataParser.toString());
386 // Update channels in strings group
388 Optional<Data> blockOpt = dataParser.parse(DataType.STRINGS);
389 if (blockOpt.isPresent()) {
390 StringBlock block = (StringBlock) blockOpt.get();
391 updateState(string1AmpereChannel, block.string1Ampere);
392 updateState(string1VoltChannel, block.string1Volt);
393 updateState(string1WattChannel, block.string1Watt);
394 updateState(string2AmpereChannel, block.string2Ampere);
395 updateState(string2VoltChannel, block.string2Volt);
396 updateState(string2WattChannel, block.string2Watt);
397 updateState(string3AmpereChannel, block.string3Ampere);
398 updateState(string3VoltChannel, block.string3Volt);
399 updateState(string3WattChannel, block.string3Watt);
401 logger.debug("Unable to get {} from provider {}", DataType.STRINGS, dataParser.toString());
405 listeners.forEach(l -> {
410 void handleDataFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
411 if (dataRead != ReadStatus.READ_FAILED) {
412 // update status only if bit switches
413 dataRead = ReadStatus.READ_FAILED;
416 listeners.forEach(l -> {
417 l.handleError(result);
422 public void dispose() {
423 ModbusCommunicationInterface localComms = comms;
424 if (localComms != null) {
425 PollTask localInfoPoller = infoPoller;
426 if (localInfoPoller != null) {
427 localComms.unregisterRegularPoll(localInfoPoller);
429 PollTask localDataPoller = dataPoller;
430 if (localDataPoller != null) {
431 localComms.unregisterRegularPoll(localDataPoller);
434 // Comms will be close()'d by endpoint thing handler
438 private void updateStatus() {
439 logger.debug("Status update: Info {} Data {} ", infoRead, dataRead);
440 if (infoRead != ReadStatus.NOT_RECEIVED && dataRead != ReadStatus.NOT_RECEIVED) {
441 if (infoRead == dataRead) {
442 // both reads are ok or else both failed
443 if (infoRead == ReadStatus.READ_SUCCESS) {
444 updateStatus(ThingStatus.ONLINE);
446 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_DATA_READ_ERROR);
449 // either info or data read failed - update status with details
450 if (infoRead == ReadStatus.READ_FAILED) {
451 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_READ_ERROR);
453 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, DATA_READ_ERROR);
456 } // else - one status isn't received yet - wait until both Modbus polls returns either success or error
460 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
461 listeners.add((E3DCWallboxThingHandler) childHandler);
465 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
466 listeners.remove(childHandler);