]> git.basschouten.com Git - openhab-addons.git/blob
eef3382f66e53030c3e566417fda4a7004927803
[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 thingHandler) {
280             return thingHandler;
281         } else {
282             logger.debug("Unexpected bridge handler: {}", handler);
283             return null;
284         }
285     }
286
287     /**
288      * Returns the channel UID for the specified group and channel id
289      *
290      * @param string the channel group
291      * @param string the channel id in that group
292      * @return the globally unique channel uid
293      */
294     private ChannelUID channelUID(Thing t, String group, String id) {
295         return new ChannelUID(t.getUID(), group, id);
296     }
297
298     void handleInfoResult(AsyncModbusReadResult result) {
299         if (infoRead != ReadStatus.READ_SUCCESS) {
300             // update status only if bit switches
301             infoRead = ReadStatus.READ_SUCCESS;
302             updateStatus();
303         }
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);
315         } else {
316             logger.debug("Unable to get {} from provider {}", DataType.INFO, dataParser.toString());
317         }
318     }
319
320     void handleInfoFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
321         if (infoRead != ReadStatus.READ_FAILED) {
322             // update status only if bit switches
323             infoRead = ReadStatus.READ_FAILED;
324             updateStatus();
325         }
326     }
327
328     void handleDataResult(AsyncModbusReadResult result) {
329         if (dataRead != ReadStatus.READ_SUCCESS) {
330             // update status only if bit switches
331             dataRead = ReadStatus.READ_SUCCESS;
332             updateStatus();
333         }
334         dataParser.handle(result);
335         // Update channels in emergency group
336         {
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);
348             } else {
349                 logger.debug("Unable to get {} from provider {}", DataType.EMERGENCY, dataParser.toString());
350             }
351         }
352
353         // Update channels in power group
354         {
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);
379                     }
380                 }
381             } else {
382                 logger.debug("Unable to get {} from provider {}", DataType.POWER, dataParser.toString());
383             }
384         }
385
386         // Update channels in strings group
387         {
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);
400             } else {
401                 logger.debug("Unable to get {} from provider {}", DataType.STRINGS, dataParser.toString());
402             }
403         }
404
405         listeners.forEach(l -> {
406             l.handle(result);
407         });
408     }
409
410     void handleDataFailure(AsyncModbusFailure<ModbusReadRequestBlueprint> result) {
411         if (dataRead != ReadStatus.READ_FAILED) {
412             // update status only if bit switches
413             dataRead = ReadStatus.READ_FAILED;
414             updateStatus();
415         }
416         listeners.forEach(l -> {
417             l.handleError(result);
418         });
419     }
420
421     @Override
422     public void dispose() {
423         ModbusCommunicationInterface localComms = comms;
424         if (localComms != null) {
425             PollTask localInfoPoller = infoPoller;
426             if (localInfoPoller != null) {
427                 localComms.unregisterRegularPoll(localInfoPoller);
428             }
429             PollTask localDataPoller = dataPoller;
430             if (localDataPoller != null) {
431                 localComms.unregisterRegularPoll(localDataPoller);
432             }
433         }
434         // Comms will be close()'d by endpoint thing handler
435         comms = null;
436     }
437
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);
445                 } else {
446                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, INFO_DATA_READ_ERROR);
447                 }
448             } else {
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);
452                 } else {
453                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, DATA_READ_ERROR);
454                 }
455             }
456         } // else - one status isn't received yet - wait until both Modbus polls returns either success or error
457     }
458
459     @Override
460     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
461         listeners.add((E3DCWallboxThingHandler) childHandler);
462     }
463
464     @Override
465     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
466         listeners.remove(childHandler);
467     }
468 }