]> git.basschouten.com Git - openhab-addons.git/blob
f5104dd5adcffecbaac7579292ee5deff7fe9420
[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 javax.measure.quantity.Energy;
22
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;
53
54 /**
55  * The {@link E3DCThingHandler} Basic modbus connection towards the E3DC device
56  *
57  * @author Bernd Weymann - Initial contribution
58  */
59 @NonNullByDefault
60 public class E3DCThingHandler extends BaseBridgeHandler {
61     public enum ReadStatus {
62         NOT_RECEIVED,
63         READ_SUCCESS,
64         READ_FAILED
65     }
66
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";
70
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";
75
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;
83
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;
92
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;
107
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;
117
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;
127
128     /**
129      * Communication interface to the slave endpoint we're connecting to
130      */
131     protected volatile @Nullable ModbusCommunicationInterface comms = null;
132     private int slaveId;
133
134     public E3DCThingHandler(Bridge thing) {
135         super(thing);
136
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);
144
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);
153
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);
168
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);
178     }
179
180     public @Nullable ModbusCommunicationInterface getComms() {
181         return comms;
182     }
183
184     public int getSlaveId() {
185         return slaveId;
186     }
187
188     @Override
189     public void handleCommand(ChannelUID channelUID, Command command) {
190         // no control of E3DC device possible yet
191     }
192
193     @Override
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);
206
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);
213                 } else {
214                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
215                             "E3DC Configuration missing");
216                 }
217             } // else state handling performed in connectEndPoint function
218         });
219     }
220
221     /**
222      * Get a reference to the modbus endpoint
223      */
224     private @Nullable ModbusCommunicationInterface connectEndpoint() {
225         if (comms != null) {
226             return comms;
227         }
228
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));
235             return null;
236         }
237         try {
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"));
243             return null;
244         }
245         if (comms == null) {
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));
250             return null;
251         } else {
252             return comms;
253         }
254     }
255
256     /**
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
259      *
260      * @return the endpoint handler or null if the bridge does not exist
261      */
262     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
263         Bridge bridge = getBridge();
264         if (bridge == null) {
265             logger.debug("Bridge is null");
266             return null;
267         }
268         if (bridge.getStatus() != ThingStatus.ONLINE) {
269             logger.debug("Bridge is not online");
270             return null;
271         }
272
273         ThingHandler handler = bridge.getHandler();
274         if (handler == null) {
275             logger.debug("Bridge handler is null");
276             return null;
277         }
278
279         if (handler instanceof ModbusEndpointThingHandler) {
280             ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
281             return slaveEndpoint;
282         } else {
283             logger.debug("Unexpected bridge handler: {}", handler);
284             return null;
285         }
286     }
287
288     /**
289      * Returns the channel UID for the specified group and channel id
290      *
291      * @param string the channel group
292      * @param string the channel id in that group
293      * @return the globally unique channel uid
294      */
295     private ChannelUID channelUID(Thing t, String group, String id) {
296         return new ChannelUID(t.getUID(), group, id);
297     }
298
299     void handleInfoResult(AsyncModbusReadResult result) {
300         if (infoRead != ReadStatus.READ_SUCCESS) {
301             // update status only if bit switches
302             infoRead = ReadStatus.READ_SUCCESS;
303             updateStatus();
304         }
305         infoParser.handle(result);
306         Optional<Data> blockOpt = infoParser.parse(DataType.INFO);
307         if (blockOpt.isPresent()) {
308             InfoBlock block = (InfoBlock) blockOpt.get();
309             updateState(modbusIdChannel, block.modbusId);
310             updateState(modbusVersionChannel, block.modbusVersion);
311             updateState(supportedRegistersChannel, block.supportedRegisters);
312             updateState(manufacturerChannel, block.manufacturer);
313             updateState(modelNameChannel, block.modelName);
314             updateState(serialNumberChannel, block.serialNumber);
315             updateState(firmwareChannel, block.firmware);
316         } else {
317             logger.debug("Unable to get {} from provider {}", DataType.INFO, dataParser.toString());
318         }
319     }
320
321     void handleInfoFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
322         if (infoRead != ReadStatus.READ_FAILED) {
323             // update status only if bit switches
324             infoRead = ReadStatus.READ_FAILED;
325             updateStatus();
326         }
327     }
328
329     void handleDataResult(AsyncModbusReadResult result) {
330         if (dataRead != ReadStatus.READ_SUCCESS) {
331             // update status only if bit switches
332             dataRead = ReadStatus.READ_SUCCESS;
333             updateStatus();
334         }
335         dataParser.handle(result);
336         // Update channels in emergency group
337         {
338             Optional<Data> blockOpt = dataParser.parse(DataType.EMERGENCY);
339             if (blockOpt.isPresent()) {
340                 EmergencyBlock block = (EmergencyBlock) blockOpt.get();
341                 updateState(epStatusChannel, block.epStatus);
342                 updateState(batteryChargingLockedChannel, block.batteryChargingLocked);
343                 updateState(batteryDischargingLockedChannel, block.batteryDischargingLocked);
344                 updateState(epPossibleChannel, block.epPossible);
345                 updateState(weatherPredictedChargingChannel, block.weatherPredictedCharging);
346                 updateState(regulationStatusChannel, block.regulationStatus);
347                 updateState(chargeLockTimeChannel, block.chargeLockTime);
348                 updateState(dischargeLockTimeChannel, block.dischargeLockTime);
349             } else {
350                 logger.debug("Unable to get {} from provider {}", DataType.EMERGENCY, dataParser.toString());
351             }
352         }
353
354         // Update channels in power group
355         {
356             Optional<Data> blockOpt = dataParser.parse(DataType.POWER);
357             if (blockOpt.isPresent()) {
358                 PowerBlock block = (PowerBlock) blockOpt.get();
359                 updateState(pvPowerSupplyChannel, block.pvPowerSupply);
360                 updateState(batteryPowerSupplyChannel, block.batteryPowerSupply);
361                 updateState(batteryPowerConsumptionChannel, block.batteryPowerConsumption);
362                 updateState(householdPowerConsumptionChannel, block.householdPowerConsumption);
363                 updateState(gridPowerConsumpitionChannel, block.gridPowerConsumpition);
364                 updateState(gridPowerSupplyChannel, block.gridPowerSupply);
365                 updateState(externalPowerSupplyChannel, block.externalPowerSupply);
366                 updateState(wallboxPowerConsumptionChannel, block.wallboxPowerConsumption);
367                 updateState(wallboxPVPowerConsumptionChannel, block.wallboxPVPowerConsumption);
368                 updateState(autarkyChannel, block.autarky);
369                 updateState(selfConsumptionChannel, block.selfConsumption);
370                 updateState(batterySOCChannel, block.batterySOC);
371                 if (config != null) {
372                     if (config.batteryCapacity > 0) {
373                         double soc = block.batterySOC.doubleValue();
374                         QuantityType<Energy> charged = QuantityType.valueOf(soc * config.batteryCapacity / 100,
375                                 Units.KILOWATT_HOUR);
376                         updateState(batteryChargedChannel, charged);
377                         QuantityType<Energy> uncharged = QuantityType
378                                 .valueOf((100 - soc) * config.batteryCapacity / 100, Units.KILOWATT_HOUR);
379                         updateState(batteryUnchargedChannel, uncharged);
380                     }
381                 }
382             } else {
383                 logger.debug("Unable to get {} from provider {}", DataType.POWER, dataParser.toString());
384             }
385         }
386
387         // Update channels in strings group
388         {
389             Optional<Data> blockOpt = dataParser.parse(DataType.STRINGS);
390             if (blockOpt.isPresent()) {
391                 StringBlock block = (StringBlock) blockOpt.get();
392                 updateState(string1AmpereChannel, block.string1Ampere);
393                 updateState(string1VoltChannel, block.string1Volt);
394                 updateState(string1WattChannel, block.string1Watt);
395                 updateState(string2AmpereChannel, block.string2Ampere);
396                 updateState(string2VoltChannel, block.string2Volt);
397                 updateState(string2WattChannel, block.string2Watt);
398                 updateState(string3AmpereChannel, block.string3Ampere);
399                 updateState(string3VoltChannel, block.string3Volt);
400                 updateState(string3WattChannel, block.string3Watt);
401             } else {
402                 logger.debug("Unable to get {} from provider {}", DataType.STRINGS, dataParser.toString());
403             }
404         }
405
406         listeners.forEach(l -> {
407             l.handle(result);
408         });
409     }
410
411     void handleDataFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
412         if (dataRead != ReadStatus.READ_FAILED) {
413             // update status only if bit switches
414             dataRead = ReadStatus.READ_FAILED;
415             updateStatus();
416         }
417         listeners.forEach(l -> {
418             l.handleError(result);
419         });
420     }
421
422     @Override
423     public void dispose() {
424         ModbusCommunicationInterface localComms = comms;
425         if (localComms != null) {
426             PollTask localInfoPoller = infoPoller;
427             if (localInfoPoller != null) {
428                 localComms.unregisterRegularPoll(localInfoPoller);
429             }
430             PollTask localDataPoller = dataPoller;
431             if (localDataPoller != null) {
432                 localComms.unregisterRegularPoll(localDataPoller);
433             }
434         }
435         // Comms will be close()'d by endpoint thing handler
436         comms = null;
437     }
438
439     private void updateStatus() {
440         logger.debug("Status update: Info {} Data {} ", infoRead, dataRead);
441         if (infoRead != ReadStatus.NOT_RECEIVED && dataRead != ReadStatus.NOT_RECEIVED) {
442             if (infoRead == dataRead) {
443                 // both reads are ok or else both failed
444                 if (infoRead == ReadStatus.READ_SUCCESS) {
445                     updateStatus(ThingStatus.ONLINE);
446                 } else {
447                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_DATA_READ_ERROR);
448                 }
449             } else {
450                 // either info or data read failed - update status with details
451                 if (infoRead == ReadStatus.READ_FAILED) {
452                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_READ_ERROR);
453                 } else {
454                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, DATA_READ_ERROR);
455                 }
456             }
457         } // else - one status isn't received yet - wait until both Modbus polls returns either success or error
458     }
459
460     @Override
461     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
462         listeners.add((E3DCWallboxThingHandler) childHandler);
463     }
464
465     @Override
466     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
467         listeners.remove(childHandler);
468     }
469 }