]> git.basschouten.com Git - openhab-addons.git/blob
b7b47587f04891b3429f114e882aaa82765d01ad
[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.studer.internal;
14
15 import static org.openhab.binding.modbus.studer.internal.StuderBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Optional;
19 import java.util.Set;
20
21 import javax.measure.Unit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
26 import org.openhab.binding.modbus.studer.internal.StuderParser.ModeXtender;
27 import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
28 import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
29 import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
30 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
31 import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
32 import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
33 import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
34 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
35 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
36 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
37 import org.openhab.core.io.transport.modbus.PollTask;
38 import org.openhab.core.library.types.DecimalType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.QuantityType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.State;
53 import org.openhab.core.types.UnDefType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 /**
58  * The {@link StuderHandler} is responsible for handling commands, which are
59  * sent to one of the channels.
60  *
61  * @author Giovanni Mirulla - Initial contribution
62  */
63 @NonNullByDefault
64 public class StuderHandler extends BaseThingHandler {
65
66     private final Logger logger = LoggerFactory.getLogger(StuderHandler.class);
67
68     private @Nullable StuderConfiguration config;
69
70     /**
71      * Array of tasks used to poll the device
72      */
73     private ArrayList<PollTask> pollTasks = new ArrayList<PollTask>();
74
75     /**
76      * Communication interface to the slave endpoint we're connecting to
77      */
78     protected volatile @Nullable ModbusCommunicationInterface comms = null;
79
80     /**
81      * Importing parser methods and enums
82      */
83     final StuderParser parser = new StuderParser();
84     /**
85      * Support variable for type of thing
86      */
87     protected ThingTypeUID type;
88
89     /**
90      * Array of registers of Studer slave to read, we store this once initialization is complete
91      */
92     private Integer[] registers = new Integer[0];
93
94     /**
95      * Instances of this handler
96      *
97      * @param thing the thing to handle
98      */
99     public StuderHandler(Thing thing) {
100         super(thing);
101         this.type = thing.getThingTypeUID();
102     }
103
104     @Override
105     public void handleCommand(ChannelUID channelUID, Command command) {
106         // Currently we do not support any commands
107     }
108
109     /**
110      * Initialization:
111      * Load the config object
112      * Connect to the slave bridge
113      * Get registers to poll
114      * Start the periodic polling
115      */
116     @Override
117     public void initialize() {
118         config = getConfigAs(StuderConfiguration.class);
119         logger.debug("Initializing thing with configuration: {}", thing.getConfiguration());
120
121         startUp();
122     }
123
124     /*
125      * This method starts the operation of this handler
126      * Connect to the slave bridge
127      * Get registers to poll
128      * Start the periodic polling
129      */
130     private void startUp() {
131         connectEndpoint();
132
133         if (comms == null || config == null) {
134             logger.debug("Invalid endpoint/config/manager ref for studer handler");
135             return;
136         }
137
138         if (!pollTasks.isEmpty()) {
139             return;
140         }
141
142         if (type.equals(THING_TYPE_BSP)) {
143             Set<Integer> keys = CHANNELS_BSP.keySet();
144             registers = keys.toArray(new Integer[keys.size()]);
145         } else if (type.equals(THING_TYPE_XTENDER)) {
146             Set<Integer> keys = CHANNELS_XTENDER.keySet();
147             registers = keys.toArray(new Integer[keys.size()]);
148         } else if (type.equals(THING_TYPE_VARIOTRACK)) {
149             Set<Integer> keys = CHANNELS_VARIOTRACK.keySet();
150             registers = keys.toArray(new Integer[keys.size()]);
151         } else if (type.equals(THING_TYPE_VARIOSTRING)) {
152             Set<Integer> keys = CHANNELS_VARIOSTRING.keySet();
153             registers = keys.toArray(new Integer[keys.size()]);
154         }
155
156         for (int r : registers) {
157             registerPollTask(r);
158
159         }
160     }
161
162     /**
163      * Dispose the binding correctly
164      */
165     @Override
166     public void dispose() {
167         tearDown();
168     }
169
170     /**
171      * Unregister the poll tasks and release the endpoint reference
172      */
173     private void tearDown() {
174         unregisterPollTasks();
175         unregisterEndpoint();
176     }
177
178     /**
179      * Get the endpoint handler from the bridge this handler is connected to
180      * Checks that we're connected to the right type of bridge
181      *
182      * @return the endpoint handler or null if the bridge does not exist
183      */
184     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
185         Bridge bridge = getBridge();
186         if (bridge == null) {
187             logger.debug("Bridge is null");
188             return null;
189         }
190         if (bridge.getStatus() != ThingStatus.ONLINE) {
191             logger.debug("Bridge is not online");
192             return null;
193         }
194
195         ThingHandler handler = bridge.getHandler();
196         if (handler == null) {
197             logger.debug("Bridge handler is null");
198             return null;
199         }
200
201         if (handler instanceof ModbusEndpointThingHandler thingHandler) {
202             return thingHandler;
203         } else {
204             logger.debug("Unexpected bridge handler: {}", handler);
205             return null;
206         }
207     }
208
209     /**
210      * Get a reference to the modbus endpoint
211      */
212     private void connectEndpoint() {
213         if (comms != null) {
214             return;
215         }
216
217         ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
218         if (slaveEndpointThingHandler == null) {
219             @SuppressWarnings("null")
220             String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
221             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
222                     String.format("Bridge '%s' is offline", label));
223             logger.debug("No bridge handler available -- aborting init for {}", label);
224             return;
225         }
226         comms = slaveEndpointThingHandler.getCommunicationInterface();
227         if (comms == null) {
228             @SuppressWarnings("null")
229             String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
230             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
231                     String.format("Bridge '%s' not completely initialized", label));
232             logger.debug("Bridge not initialized fully (no endpoint) -- aborting init for {}", this);
233             return;
234         }
235     }
236
237     /**
238      * Remove the endpoint if exists
239      */
240     private void unregisterEndpoint() {
241         // Comms will be close()'d by endpoint thing handler
242         comms = null;
243     }
244
245     private synchronized void unregisterPollTasks() {
246         if (pollTasks.isEmpty()) {
247             return;
248         }
249         logger.debug("Unregistering polling from ModbusManager");
250         ModbusCommunicationInterface mycomms = comms;
251         if (mycomms != null) {
252             for (PollTask t : pollTasks) {
253                 mycomms.unregisterRegularPoll(t);
254             }
255             pollTasks.clear();
256         }
257     }
258
259     /**
260      * Register poll task
261      * This is where we set up our regular poller
262      */
263     private synchronized void registerPollTask(int registerNumber) {
264         if (pollTasks.size() >= registers.length) {
265             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
266             throw new IllegalStateException("New pollTask invalid");
267         }
268         ModbusCommunicationInterface mycomms = comms;
269         StuderConfiguration studerConfig = config;
270         if (studerConfig == null || mycomms == null) {
271             throw new IllegalStateException("registerPollTask called without proper configuration");
272         }
273
274         logger.debug("Setting up regular polling");
275
276         ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(studerConfig.slaveAddress,
277                 ModbusReadFunctionCode.READ_INPUT_REGISTERS, registerNumber, 2, studerConfig.maxTries);
278         long refreshMillis = studerConfig.refresh * 1000;
279         PollTask pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
280             if (result.getRegisters().isPresent()) {
281                 ModbusRegisterArray reg = result.getRegisters().get();
282                 handlePolledData(registerNumber, reg);
283             } else {
284                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
285                 return;
286             }
287             if (getThing().getStatus() != ThingStatus.ONLINE) {
288                 updateStatus(ThingStatus.ONLINE);
289             }
290         }, this::handleError);
291         pollTasks.add(pollTask);
292     }
293
294     /**
295      * This method is called each time new data has been polled from the modbus slave
296      * The register array is first parsed, then each of the channels are updated
297      * to the new values
298      *
299      * @param registerNumber register readed
300      * @param registers byte array read from the modbus slave
301      */
302     protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
303         Optional<DecimalType> quantity = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.FLOAT32);
304         quantity.ifPresent(value -> {
305             if (type.equals(THING_TYPE_BSP)) {
306                 Unit<?> unit = UNIT_CHANNELS_BSP.get(registerNumber);
307                 if (unit != null) {
308                     internalUpdateState(CHANNELS_BSP.get(registerNumber), new QuantityType<>(value, unit));
309                 }
310             } else if (type.equals(THING_TYPE_XTENDER)) {
311                 handlePolledDataXtender(registerNumber, value);
312             } else if (type.equals(THING_TYPE_VARIOTRACK)) {
313                 handlePolledDataVarioTrack(registerNumber, value);
314             } else if (type.equals(THING_TYPE_VARIOSTRING)) {
315                 handlePolledDataVarioString(registerNumber, value);
316             }
317         });
318         resetCommunicationError();
319     }
320
321     /**
322      * This method is called each time new data has been polled from the VarioString slave
323      * The register array is first parsed, then each of the channels are updated
324      * to the new values
325      */
326     protected void handlePolledDataVarioString(int registerNumber, DecimalType quantity) {
327         switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
328             case CHANNEL_PV_OPERATING_MODE:
329             case CHANNEL_PV1_OPERATING_MODE:
330             case CHANNEL_PV2_OPERATING_MODE:
331                 VSMode vsmode = StuderParser.getVSModeByCode(quantity.intValue());
332                 if (vsmode == VSMode.UNKNOWN) {
333                     internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), UnDefType.UNDEF);
334                 } else {
335                     internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), new StringType(vsmode.name()));
336                 }
337                 break;
338             case CHANNEL_STATE_VARIOSTRING:
339                 OnOffType vsstate = StuderParser.getStateByCode(quantity.intValue());
340                 internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), vsstate);
341                 break;
342             default:
343                 Unit<?> unit = UNIT_CHANNELS_VARIOSTRING.get(registerNumber);
344                 if (unit != null) {
345                     internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), new QuantityType<>(quantity, unit));
346                 }
347         }
348     }
349
350     /**
351      * This method is called each time new data has been polled from the VarioTrack slave
352      * The register array is first parsed, then each of the channels are updated
353      * to the new values
354      */
355     protected void handlePolledDataVarioTrack(int registerNumber, DecimalType quantity) {
356         switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
357             case CHANNEL_MODEL_VARIOTRACK:
358                 VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
359                 if (type == VTType.UNKNOWN) {
360                     internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
361                 } else {
362                     internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(type.name()));
363                 }
364                 break;
365
366             case CHANNEL_OPERATING_MODE:
367                 VTMode vtmode = StuderParser.getVTModeByCode(quantity.intValue());
368                 if (vtmode == VTMode.UNKNOWN) {
369                     internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
370                 } else {
371                     internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(vtmode.name()));
372                 }
373                 break;
374
375             case CHANNEL_STATE_VARIOTRACK:
376                 OnOffType vtstate = StuderParser.getStateByCode(quantity.intValue());
377                 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), vtstate);
378                 break;
379             default:
380                 Unit<?> unit = UNIT_CHANNELS_VARIOTRACK.get(registerNumber);
381                 if (unit != null) {
382                     internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new QuantityType<>(quantity, unit));
383                 }
384         }
385     }
386
387     /**
388      * This method is called each time new data has been polled from the Xtender slave
389      * The register array is first parsed, then each of the channels are updated
390      * to the new values
391      */
392     protected void handlePolledDataXtender(int registerNumber, DecimalType quantity) {
393         switch (CHANNELS_XTENDER.get(registerNumber)) {
394             case CHANNEL_OPERATING_STATE:
395                 ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
396                 if (mode == ModeXtender.UNKNOWN) {
397                     internalUpdateState(CHANNELS_XTENDER.get(registerNumber), UnDefType.UNDEF);
398                 } else {
399                     internalUpdateState(CHANNELS_XTENDER.get(registerNumber), new StringType(mode.name()));
400                 }
401                 break;
402             case CHANNEL_STATE_INVERTER:
403                 OnOffType xtstate = StuderParser.getStateByCode(quantity.intValue());
404                 internalUpdateState(CHANNELS_XTENDER.get(registerNumber), xtstate);
405                 break;
406             default:
407                 Unit<?> unit = UNIT_CHANNELS_XTENDER.get(registerNumber);
408                 if (unit != null) {
409                     internalUpdateState(CHANNELS_XTENDER.get(registerNumber), new QuantityType<>(quantity, unit));
410                 }
411         }
412     }
413
414     /**
415      * Handle errors received during communication
416      */
417     protected void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
418         // Ignore all incoming data and errors if configuration is not correct
419         if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
420             return;
421         }
422         String msg = failure.getCause().getMessage();
423         String cls = failure.getCause().getClass().getName();
424         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
425                 String.format("Error with read: %s: %s", cls, msg));
426     }
427
428     /**
429      * Returns true, if we're in a CONFIGURATION_ERROR state
430      *
431      * @return
432      */
433     protected boolean hasConfigurationError() {
434         ThingStatusInfo statusInfo = getThing().getStatusInfo();
435         return statusInfo.getStatus() == ThingStatus.OFFLINE
436                 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
437     }
438
439     /**
440      * Reset communication status to ONLINE if we're in an OFFLINE state
441      */
442     protected void resetCommunicationError() {
443         ThingStatusInfo statusInfo = thing.getStatusInfo();
444         if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
445                 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
446             updateStatus(ThingStatus.ONLINE);
447         }
448     }
449
450     protected void internalUpdateState(@Nullable String channelUID, @Nullable State state) {
451         if (channelUID != null && state != null) {
452             super.updateState(channelUID, state);
453         }
454     }
455 }