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 org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.modbus.e3dc.internal.E3DCConfiguration;
24 import org.openhab.binding.modbus.e3dc.internal.dto.EmergencyBlock;
25 import org.openhab.binding.modbus.e3dc.internal.dto.InfoBlock;
26 import org.openhab.binding.modbus.e3dc.internal.dto.PowerBlock;
27 import org.openhab.binding.modbus.e3dc.internal.dto.StringBlock;
28 import org.openhab.binding.modbus.e3dc.internal.modbus.Data;
29 import org.openhab.binding.modbus.e3dc.internal.modbus.Data.DataType;
30 import org.openhab.binding.modbus.e3dc.internal.modbus.Parser;
31 import org.openhab.binding.modbus.handler.EndpointNotInitializedException;
32 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
33 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
34 import org.openhab.core.io.transport.modbus.AsyncModbusReadResult;
35 import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
36 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
37 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
38 import org.openhab.core.io.transport.modbus.PollTask;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.binding.BaseBridgeHandler;
45 import org.openhab.core.thing.binding.ThingHandler;
46 import org.openhab.core.types.Command;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link E3DCThingHandler} Basic modbus connection towards the E3DC device
53 * @author Bernd Weymann - Initial contribution
56 public class E3DCThingHandler extends BaseBridgeHandler {
57 public enum ReadStatus {
63 static final String INFO_DATA_READ_ERROR = "Information And Data Modbus Read Errors";
64 static final String INFO_READ_ERROR = "Information Modbus Read Error";
65 static final String DATA_READ_ERROR = "Data Modbus Read Error";
67 static final String INFO_GROUP = "info";
68 static final String EMERGENCY_GROUP = "emergency";
69 static final String POWER_GROUP = "power";
70 static final String STRINGS_GROUP = "strings";
72 private ChannelUID modbusIdChannel;
73 private ChannelUID modbusVersionChannel;
74 private ChannelUID supportedRegistersChannel;
75 private ChannelUID manufacturerChannel;
76 private ChannelUID modelNameChannel;
77 private ChannelUID serialNumberChannel;
78 private ChannelUID firmwareChannel;
80 private ChannelUID epStatusChannel;
81 private ChannelUID batteryChargingLockedChannel;
82 private ChannelUID batteryDischargingLockedChannel;
83 private ChannelUID epPossibleChannel;
84 private ChannelUID weatherPredictedChargingChannel;
85 private ChannelUID regulationStatusChannel;
86 private ChannelUID chargeLockTimeChannel;
87 private ChannelUID dischargeLockTimeChannel;
89 private ChannelUID pvPowerSupplyChannel;
90 private ChannelUID batteryPowerSupplyChannel;
91 private ChannelUID batteryPowerConsumptionChannel;
92 private ChannelUID householdPowerConsumptionChannel;
93 private ChannelUID gridPowerConsumpitionChannel;
94 private ChannelUID gridPowerSupplyChannel;
95 private ChannelUID externalPowerSupplyChannel;
96 private ChannelUID wallboxPowerConsumptionChannel;
97 private ChannelUID wallboxPVPowerConsumptionChannel;
98 private ChannelUID autarkyChannel;
99 private ChannelUID selfConsumptionChannel;
100 private ChannelUID batterySOCChannel;
102 private ChannelUID string1AmpereChannel;
103 private ChannelUID string1VoltChannel;
104 private ChannelUID string1WattChannel;
105 private ChannelUID string2AmpereChannel;
106 private ChannelUID string2VoltChannel;
107 private ChannelUID string2WattChannel;
108 private ChannelUID string3AmpereChannel;
109 private ChannelUID string3VoltChannel;
110 private ChannelUID string3WattChannel;
112 private final ArrayList<E3DCWallboxThingHandler> listeners = new ArrayList<E3DCWallboxThingHandler>();
113 private final Logger logger = LoggerFactory.getLogger(E3DCThingHandler.class);
114 private final Parser dataParser = new Parser(DataType.DATA);
115 private ReadStatus dataRead = ReadStatus.NOT_RECEIVED;
116 private final Parser infoParser = new Parser(DataType.INFO);
117 private ReadStatus infoRead = ReadStatus.NOT_RECEIVED;
118 private @Nullable PollTask infoPoller;
119 private @Nullable PollTask dataPoller;
120 private @Nullable E3DCConfiguration config;
123 * Communication interface to the slave endpoint we're connecting to
125 protected volatile @Nullable ModbusCommunicationInterface comms = null;
128 public E3DCThingHandler(Bridge thing) {
131 modbusIdChannel = channelUID(thing, INFO_GROUP, MODBUS_ID_CHANNEL);
132 modbusVersionChannel = channelUID(thing, INFO_GROUP, MODBUS_FIRMWARE_CHANNEL);
133 supportedRegistersChannel = channelUID(thing, INFO_GROUP, SUPPORTED_REGISTERS_CHANNEL);
134 manufacturerChannel = channelUID(thing, INFO_GROUP, MANUFACTURER_NAME_CHANNEL);
135 modelNameChannel = channelUID(thing, INFO_GROUP, MODEL_NAME_CHANNEL);
136 serialNumberChannel = channelUID(thing, INFO_GROUP, SERIAL_NUMBER_CHANNEL);
137 firmwareChannel = channelUID(thing, INFO_GROUP, FIRMWARE_RELEASE_CHANNEL);
139 epStatusChannel = channelUID(thing, EMERGENCY_GROUP, EMERGENCY_POWER_STATUS);
140 batteryChargingLockedChannel = channelUID(thing, EMERGENCY_GROUP, BATTERY_CHARGING_LOCKED);
141 batteryDischargingLockedChannel = channelUID(thing, EMERGENCY_GROUP, BATTERY_DISCHARGING_LOCKED);
142 epPossibleChannel = channelUID(thing, EMERGENCY_GROUP, EMERGENCY_POWER_POSSIBLE);
143 weatherPredictedChargingChannel = channelUID(thing, EMERGENCY_GROUP, WEATHER_PREDICTED_CHARGING);
144 regulationStatusChannel = channelUID(thing, EMERGENCY_GROUP, REGULATION_STATUS);
145 chargeLockTimeChannel = channelUID(thing, EMERGENCY_GROUP, CHARGE_LOCK_TIME);
146 dischargeLockTimeChannel = channelUID(thing, EMERGENCY_GROUP, DISCHARGE_LOCK_TIME);
148 pvPowerSupplyChannel = channelUID(thing, POWER_GROUP, PV_POWER_SUPPLY_CHANNEL);
149 batteryPowerSupplyChannel = channelUID(thing, POWER_GROUP, BATTERY_POWER_SUPPLY_CHANNEL);
150 batteryPowerConsumptionChannel = channelUID(thing, POWER_GROUP, BATTERY_POWER_CONSUMPTION);
151 householdPowerConsumptionChannel = channelUID(thing, POWER_GROUP, HOUSEHOLD_POWER_CONSUMPTION_CHANNEL);
152 gridPowerConsumpitionChannel = channelUID(thing, POWER_GROUP, GRID_POWER_CONSUMPTION_CHANNEL);
153 gridPowerSupplyChannel = channelUID(thing, POWER_GROUP, GRID_POWER_SUPPLY_CHANNEL);
154 externalPowerSupplyChannel = channelUID(thing, POWER_GROUP, EXTERNAL_POWER_SUPPLY_CHANNEL);
155 wallboxPowerConsumptionChannel = channelUID(thing, POWER_GROUP, WALLBOX_POWER_CONSUMPTION_CHANNEL);
156 wallboxPVPowerConsumptionChannel = channelUID(thing, POWER_GROUP, WALLBOX_PV_POWER_CONSUMPTION_CHANNEL);
157 autarkyChannel = channelUID(thing, POWER_GROUP, AUTARKY_CHANNEL);
158 selfConsumptionChannel = channelUID(thing, POWER_GROUP, SELF_CONSUMPTION_CHANNEL);
159 batterySOCChannel = channelUID(thing, POWER_GROUP, BATTERY_STATE_OF_CHARGE_CHANNEL);
161 string1AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_CURRENT_CHANNEL);
162 string1VoltChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_VOLTAGE_CHANNEL);
163 string1WattChannel = channelUID(thing, STRINGS_GROUP, STRING1_DC_OUTPUT_CHANNEL);
164 string2AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_CURRENT_CHANNEL);
165 string2VoltChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_VOLTAGE_CHANNEL);
166 string2WattChannel = channelUID(thing, STRINGS_GROUP, STRING2_DC_OUTPUT_CHANNEL);
167 string3AmpereChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_CURRENT_CHANNEL);
168 string3VoltChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_VOLTAGE_CHANNEL);
169 string3WattChannel = channelUID(thing, STRINGS_GROUP, STRING3_DC_OUTPUT_CHANNEL);
172 public @Nullable ModbusCommunicationInterface getComms() {
176 public int getSlaveId() {
181 public void handleCommand(ChannelUID channelUID, Command command) {
182 // no control of E3DC device possible yet
186 public void initialize() {
187 updateStatus(ThingStatus.UNKNOWN);
188 scheduler.execute(() -> {
189 E3DCConfiguration localConfig = getConfigAs(E3DCConfiguration.class);
190 config = localConfig;
191 ModbusCommunicationInterface localComms = connectEndpoint();
192 if (localComms != null) {
193 // register low speed info poller
194 ModbusReadRequestBlueprint infoRequest = new ModbusReadRequestBlueprint(slaveId,
195 ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, INFO_REG_START, INFO_REG_SIZE, 3);
196 infoPoller = localComms.registerRegularPoll(infoRequest, INFO_POLL_REFRESH_TIME_MS, 0,
197 this::handleInfoResult, this::handleInfoFailure);
199 ModbusReadRequestBlueprint dataRequest = new ModbusReadRequestBlueprint(slaveId,
200 ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, POWER_REG_START,
201 REGISTER_LENGTH - INFO_REG_SIZE, 3);
202 if (config != null) {
203 dataPoller = localComms.registerRegularPoll(dataRequest, localConfig.refresh, 0,
204 this::handleDataResult, this::handleDataFailure);
206 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
207 "E3DC Configuration missing");
209 } // else state handling performed in connectEndPoint function
214 * Get a reference to the modbus endpoint
216 private @Nullable ModbusCommunicationInterface connectEndpoint() {
221 ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
222 if (slaveEndpointThingHandler == null) {
223 @SuppressWarnings("null")
224 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
225 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
226 String.format("Bridge '%s' is offline", label));
230 slaveId = slaveEndpointThingHandler.getSlaveId();
231 comms = slaveEndpointThingHandler.getCommunicationInterface();
232 } catch (EndpointNotInitializedException e) {
233 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
234 String.format("Slave Endpoint not initialized"));
238 @SuppressWarnings("null")
239 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
240 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
241 String.format("Bridge '%s' not completely initialized", label));
249 * Get the endpoint handler from the bridge this handler is connected to
250 * Checks that we're connected to the right type of bridge
252 * @return the endpoint handler or null if the bridge does not exist
254 private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
255 Bridge bridge = getBridge();
256 if (bridge == null) {
257 logger.debug("Bridge is null");
260 if (bridge.getStatus() != ThingStatus.ONLINE) {
261 logger.debug("Bridge is not online");
265 ThingHandler handler = bridge.getHandler();
266 if (handler == null) {
267 logger.debug("Bridge handler is null");
271 if (handler instanceof ModbusEndpointThingHandler) {
272 ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
273 return slaveEndpoint;
275 logger.debug("Unexpected bridge handler: {}", handler);
281 * Returns the channel UID for the specified group and channel id
283 * @param string the channel group
284 * @param string the channel id in that group
285 * @return the globally unique channel uid
287 private ChannelUID channelUID(Thing t, String group, String id) {
288 return new ChannelUID(t.getUID(), group, id);
291 void handleInfoResult(AsyncModbusReadResult result) {
292 if (infoRead != ReadStatus.READ_SUCCESS) {
293 // update status only if bit switches
294 infoRead = ReadStatus.READ_SUCCESS;
297 infoParser.handle(result);
298 Optional<Data> blockOpt = infoParser.parse(DataType.INFO);
299 if (blockOpt.isPresent()) {
300 InfoBlock block = (InfoBlock) blockOpt.get();
301 updateState(modbusIdChannel, block.modbusId);
302 updateState(modbusVersionChannel, block.modbusVersion);
303 updateState(supportedRegistersChannel, block.supportedRegisters);
304 updateState(manufacturerChannel, block.manufacturer);
305 updateState(modelNameChannel, block.modelName);
306 updateState(serialNumberChannel, block.serialNumber);
307 updateState(firmwareChannel, block.firmware);
309 logger.debug("Unable to get {} from provider {}", DataType.INFO, dataParser.toString());
313 void handleInfoFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
314 if (infoRead != ReadStatus.READ_FAILED) {
315 // update status only if bit switches
316 infoRead = ReadStatus.READ_FAILED;
321 void handleDataResult(AsyncModbusReadResult result) {
322 if (dataRead != ReadStatus.READ_SUCCESS) {
323 // update status only if bit switches
324 dataRead = ReadStatus.READ_SUCCESS;
327 dataParser.handle(result);
328 // Update channels in emergency group
330 Optional<Data> blockOpt = dataParser.parse(DataType.EMERGENCY);
331 if (blockOpt.isPresent()) {
332 EmergencyBlock block = (EmergencyBlock) blockOpt.get();
333 updateState(epStatusChannel, block.epStatus);
334 updateState(batteryChargingLockedChannel, block.batteryChargingLocked);
335 updateState(batteryDischargingLockedChannel, block.batteryDischargingLocked);
336 updateState(epPossibleChannel, block.epPossible);
337 updateState(weatherPredictedChargingChannel, block.weatherPredictedCharging);
338 updateState(regulationStatusChannel, block.regulationStatus);
339 updateState(chargeLockTimeChannel, block.chargeLockTime);
340 updateState(dischargeLockTimeChannel, block.dischargeLockTime);
342 logger.debug("Unable to get {} from provider {}", DataType.EMERGENCY, dataParser.toString());
346 // Update channels in power group
348 Optional<Data> blockOpt = dataParser.parse(DataType.POWER);
349 if (blockOpt.isPresent()) {
350 PowerBlock block = (PowerBlock) blockOpt.get();
351 updateState(pvPowerSupplyChannel, block.pvPowerSupply);
352 updateState(batteryPowerSupplyChannel, block.batteryPowerSupply);
353 updateState(batteryPowerConsumptionChannel, block.batteryPowerConsumption);
354 updateState(householdPowerConsumptionChannel, block.householdPowerConsumption);
355 updateState(gridPowerConsumpitionChannel, block.gridPowerConsumpition);
356 updateState(gridPowerSupplyChannel, block.gridPowerSupply);
357 updateState(externalPowerSupplyChannel, block.externalPowerSupply);
358 updateState(wallboxPowerConsumptionChannel, block.wallboxPowerConsumption);
359 updateState(wallboxPVPowerConsumptionChannel, block.wallboxPVPowerConsumption);
360 updateState(autarkyChannel, block.autarky);
361 updateState(selfConsumptionChannel, block.selfConsumption);
362 updateState(batterySOCChannel, block.batterySOC);
364 logger.debug("Unable to get {} from provider {}", DataType.POWER, dataParser.toString());
368 // Update channels in strings group
370 Optional<Data> blockOpt = dataParser.parse(DataType.STRINGS);
371 if (blockOpt.isPresent()) {
372 StringBlock block = (StringBlock) blockOpt.get();
373 updateState(string1AmpereChannel, block.string1Ampere);
374 updateState(string1VoltChannel, block.string1Volt);
375 updateState(string1WattChannel, block.string1Watt);
376 updateState(string2AmpereChannel, block.string2Ampere);
377 updateState(string2VoltChannel, block.string2Volt);
378 updateState(string2WattChannel, block.string2Watt);
379 updateState(string3AmpereChannel, block.string3Ampere);
380 updateState(string3VoltChannel, block.string3Volt);
381 updateState(string3WattChannel, block.string3Watt);
383 logger.debug("Unable to get {} from provider {}", DataType.STRINGS, dataParser.toString());
387 listeners.forEach(l -> {
392 void handleDataFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
393 if (dataRead != ReadStatus.READ_FAILED) {
394 // update status only if bit switches
395 dataRead = ReadStatus.READ_FAILED;
398 listeners.forEach(l -> {
399 l.handleError(result);
404 public void dispose() {
405 ModbusCommunicationInterface localComms = comms;
406 if (localComms != null) {
407 PollTask localInfoPoller = infoPoller;
408 if (localInfoPoller != null) {
409 localComms.unregisterRegularPoll(localInfoPoller);
411 PollTask localDataPoller = dataPoller;
412 if (localDataPoller != null) {
413 localComms.unregisterRegularPoll(localDataPoller);
416 // Comms will be close()'d by endpoint thing handler
420 private void updateStatus() {
421 logger.debug("Status update: Info {} Data {} ", infoRead, dataRead);
422 if (infoRead != ReadStatus.NOT_RECEIVED && dataRead != ReadStatus.NOT_RECEIVED) {
423 if (infoRead == dataRead) {
424 // both reads are ok or else both failed
425 if (infoRead == ReadStatus.READ_SUCCESS) {
426 updateStatus(ThingStatus.ONLINE);
428 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_DATA_READ_ERROR);
431 // either info or data read failed - update status with details
432 if (infoRead == ReadStatus.READ_FAILED) {
433 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_READ_ERROR);
435 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, DATA_READ_ERROR);
438 } // else - one status isn't received yet - wait until both Modbus polls returns either success or error
442 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
443 listeners.add((E3DCWallboxThingHandler) childHandler);
447 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
448 listeners.remove(childHandler);