]> git.basschouten.com Git - openhab-addons.git/blob
4622ad6eb65920039484f1cef67494275c2ee339
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.modbus.e3dc.internal.handler;
14
15 import static org.openhab.binding.modbus.e3dc.internal.E3DCBindingConstants.*;
16 import static org.openhab.binding.modbus.e3dc.internal.modbus.E3DCModbusConstans.*;
17
18 import java.util.ArrayList;
19 import java.util.Optional;
20
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;
49
50 /**
51  * The {@link E3DCThingHandler} Basic modbus connection towards the E3DC device
52  *
53  * @author Bernd Weymann - Initial contribution
54  */
55 @NonNullByDefault
56 public class E3DCThingHandler extends BaseBridgeHandler {
57     public enum ReadStatus {
58         NOT_RECEIVED,
59         READ_SUCCESS,
60         READ_FAILED
61     }
62
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";
66
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";
71
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;
79
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;
88
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;
101
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;
111
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;
121
122     /**
123      * Communication interface to the slave endpoint we're connecting to
124      */
125     protected volatile @Nullable ModbusCommunicationInterface comms = null;
126     private int slaveId;
127
128     public E3DCThingHandler(Bridge thing) {
129         super(thing);
130
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);
138
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);
147
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);
160
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);
170     }
171
172     public @Nullable ModbusCommunicationInterface getComms() {
173         return comms;
174     }
175
176     public int getSlaveId() {
177         return slaveId;
178     }
179
180     @Override
181     public void handleCommand(ChannelUID channelUID, Command command) {
182         // no control of E3DC device possible yet
183     }
184
185     @Override
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);
198
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);
205                 } else {
206                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
207                             "E3DC Configuration missing");
208                 }
209             } // else state handling performed in connectEndPoint function
210         });
211     }
212
213     /**
214      * Get a reference to the modbus endpoint
215      */
216     private @Nullable ModbusCommunicationInterface connectEndpoint() {
217         if (comms != null) {
218             return comms;
219         }
220
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));
227             return null;
228         }
229         try {
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"));
235             return null;
236         }
237         if (comms == null) {
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));
242             return null;
243         } else {
244             return comms;
245         }
246     }
247
248     /**
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
251      *
252      * @return the endpoint handler or null if the bridge does not exist
253      */
254     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
255         Bridge bridge = getBridge();
256         if (bridge == null) {
257             logger.debug("Bridge is null");
258             return null;
259         }
260         if (bridge.getStatus() != ThingStatus.ONLINE) {
261             logger.debug("Bridge is not online");
262             return null;
263         }
264
265         ThingHandler handler = bridge.getHandler();
266         if (handler == null) {
267             logger.debug("Bridge handler is null");
268             return null;
269         }
270
271         if (handler instanceof ModbusEndpointThingHandler) {
272             ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
273             return slaveEndpoint;
274         } else {
275             logger.debug("Unexpected bridge handler: {}", handler);
276             return null;
277         }
278     }
279
280     /**
281      * Returns the channel UID for the specified group and channel id
282      *
283      * @param string the channel group
284      * @param string the channel id in that group
285      * @return the globally unique channel uid
286      */
287     private ChannelUID channelUID(Thing t, String group, String id) {
288         return new ChannelUID(t.getUID(), group, id);
289     }
290
291     void handleInfoResult(AsyncModbusReadResult result) {
292         if (infoRead != ReadStatus.READ_SUCCESS) {
293             // update status only if bit switches
294             infoRead = ReadStatus.READ_SUCCESS;
295             updateStatus();
296         }
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);
308         } else {
309             logger.debug("Unable to get {} from provider {}", DataType.INFO, dataParser.toString());
310         }
311     }
312
313     void handleInfoFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
314         if (infoRead != ReadStatus.READ_FAILED) {
315             // update status only if bit switches
316             infoRead = ReadStatus.READ_FAILED;
317             updateStatus();
318         }
319     }
320
321     void handleDataResult(AsyncModbusReadResult result) {
322         if (dataRead != ReadStatus.READ_SUCCESS) {
323             // update status only if bit switches
324             dataRead = ReadStatus.READ_SUCCESS;
325             updateStatus();
326         }
327         dataParser.handle(result);
328         // Update channels in emergency group
329         {
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);
341             } else {
342                 logger.debug("Unable to get {} from provider {}", DataType.EMERGENCY, dataParser.toString());
343             }
344         }
345
346         // Update channels in power group
347         {
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);
363             } else {
364                 logger.debug("Unable to get {} from provider {}", DataType.POWER, dataParser.toString());
365             }
366         }
367
368         // Update channels in strings group
369         {
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);
382             } else {
383                 logger.debug("Unable to get {} from provider {}", DataType.STRINGS, dataParser.toString());
384             }
385         }
386
387         listeners.forEach(l -> {
388             l.handle(result);
389         });
390     }
391
392     void handleDataFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
393         if (dataRead != ReadStatus.READ_FAILED) {
394             // update status only if bit switches
395             dataRead = ReadStatus.READ_FAILED;
396             updateStatus();
397         }
398         listeners.forEach(l -> {
399             l.handleError(result);
400         });
401     }
402
403     @Override
404     public void dispose() {
405         ModbusCommunicationInterface localComms = comms;
406         if (localComms != null) {
407             PollTask localInfoPoller = infoPoller;
408             if (localInfoPoller != null) {
409                 localComms.unregisterRegularPoll(localInfoPoller);
410             }
411             PollTask localDataPoller = dataPoller;
412             if (localDataPoller != null) {
413                 localComms.unregisterRegularPoll(localDataPoller);
414             }
415         }
416         // Comms will be close()'d by endpoint thing handler
417         comms = null;
418     }
419
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);
427                 } else {
428                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_DATA_READ_ERROR);
429                 }
430             } else {
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);
434                 } else {
435                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, DATA_READ_ERROR);
436                 }
437             }
438         } // else - one status isn't received yet - wait until both Modbus polls returns either success or error
439     }
440
441     @Override
442     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
443         listeners.add((E3DCWallboxThingHandler) childHandler);
444     }
445
446     @Override
447     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
448         listeners.remove(childHandler);
449     }
450 }