]> git.basschouten.com Git - openhab-addons.git/blob
a17e0b3d11ca09554983156ab04f51f8fb799889
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
24 import org.openhab.binding.modbus.studer.internal.StuderParser.ModeXtender;
25 import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
26 import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
27 import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.ThingStatusInfo;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.binding.BaseThingHandler;
39 import org.openhab.core.thing.binding.ThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.UnDefType;
42 import org.openhab.io.transport.modbus.AsyncModbusFailure;
43 import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
44 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
45 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
46 import org.openhab.io.transport.modbus.ModbusRegisterArray;
47 import org.openhab.io.transport.modbus.PollTask;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * The {@link StuderHandler} is responsible for handling commands, which are
53  * sent to one of the channels.
54  *
55  * @author Giovanni Mirulla - Initial contribution
56  */
57 @NonNullByDefault
58 public class StuderHandler extends BaseThingHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(StuderHandler.class);
61
62     private @Nullable StuderConfiguration config;
63
64     /**
65      * Array of tasks used to poll the device
66      */
67     private ArrayList<PollTask> pollTasks = new ArrayList<PollTask>();
68
69     /**
70      * Communication interface to the slave endpoint we're connecting to
71      */
72     protected volatile @Nullable ModbusCommunicationInterface comms = null;
73
74     /**
75      * Importing parser methods and enums
76      */
77     final StuderParser parser = new StuderParser();
78     /**
79      * Support variable for type of thing
80      */
81     protected ThingTypeUID type;
82
83     /**
84      * Array of registers of Studer slave to read, we store this once initialization is complete
85      */
86     private Integer[] registers = new Integer[0];
87
88     /**
89      * Instances of this handler
90      *
91      * @param thing the thing to handle
92      * @param type the type of thing to handle
93      * @param slaveAddress the address of thing
94      * @param refreshSec the address of thing
95      */
96     public StuderHandler(Thing thing) {
97         super(thing);
98         this.type = thing.getThingTypeUID();
99     }
100
101     @Override
102     public void handleCommand(ChannelUID channelUID, Command command) {
103
104         // Currently we do not support any commands
105     }
106
107     /**
108      * Initialization:
109      * Load the config object
110      * Connect to the slave bridge
111      * Get registers to poll
112      * Start the periodic polling
113      */
114     @Override
115     public void initialize() {
116         config = getConfigAs(StuderConfiguration.class);
117         logger.debug("Initializing thing with configuration: {}", thing.getConfiguration());
118
119         startUp();
120     }
121
122     /*
123      * This method starts the operation of this handler
124      * Connect to the slave bridge
125      * Get registers to poll
126      * Start the periodic polling
127      */
128     private void startUp() {
129
130         connectEndpoint();
131
132         if (comms == null || config == null) {
133             logger.debug("Invalid endpoint/config/manager ref for studer handler");
134             return;
135         }
136
137         if (!pollTasks.isEmpty()) {
138             return;
139         }
140
141         if (type.equals(THING_TYPE_BSP)) {
142             Set<Integer> keys = CHANNELS_BSP.keySet();
143             registers = keys.toArray(new Integer[keys.size()]);
144         } else if (type.equals(THING_TYPE_XTENDER)) {
145             Set<Integer> keys = CHANNELS_XTENDER.keySet();
146             registers = keys.toArray(new Integer[keys.size()]);
147         } else if (type.equals(THING_TYPE_VARIOTRACK)) {
148             Set<Integer> keys = CHANNELS_VARIOTRACK.keySet();
149             registers = keys.toArray(new Integer[keys.size()]);
150         } else if (type.equals(THING_TYPE_VARIOSTRING)) {
151             Set<Integer> keys = CHANNELS_VARIOSTRING.keySet();
152             registers = keys.toArray(new Integer[keys.size()]);
153         }
154
155         for (int r : registers) {
156             registerPollTask(r);
157
158         }
159     }
160
161     /**
162      * Dispose the binding correctly
163      */
164     @Override
165     public void dispose() {
166         tearDown();
167     }
168
169     /**
170      * Unregister the poll tasks and release the endpoint reference
171      */
172     private void tearDown() {
173         unregisterPollTasks();
174         unregisterEndpoint();
175     }
176
177     /**
178      * Get the endpoint handler from the bridge this handler is connected to
179      * Checks that we're connected to the right type of bridge
180      *
181      * @return the endpoint handler or null if the bridge does not exist
182      */
183     private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
184         Bridge bridge = getBridge();
185         if (bridge == null) {
186             logger.debug("Bridge is null");
187             return null;
188         }
189         if (bridge.getStatus() != ThingStatus.ONLINE) {
190             logger.debug("Bridge is not online");
191             return null;
192         }
193
194         ThingHandler handler = bridge.getHandler();
195         if (handler == null) {
196             logger.debug("Bridge handler is null");
197             return null;
198         }
199
200         if (handler instanceof ModbusEndpointThingHandler) {
201             ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
202             return slaveEndpoint;
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 n register readed
300      * @param registers byte array read from the modbus slave
301      */
302     protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
303         String hexString = registers.toHexString().toString();
304         Float quantity = parser.hexToFloat(hexString);
305         if (quantity != null) {
306             if (type.equals(THING_TYPE_BSP)) {
307                 updateState(CHANNELS_BSP.get(registerNumber),
308                         new QuantityType<>(quantity, UNIT_CHANNELS_BSP.get(registerNumber)));
309             } else if (type.equals(THING_TYPE_XTENDER)) {
310                 handlePolledDataXtender(registerNumber, quantity);
311             } else if (type.equals(THING_TYPE_VARIOTRACK)) {
312                 handlePolledDataVarioTrack(registerNumber, quantity);
313             } else if (type.equals(THING_TYPE_VARIOSTRING)) {
314                 handlePolledDataVarioString(registerNumber, quantity);
315             }
316         }
317         resetCommunicationError();
318     }
319
320     /**
321      * This method is called each time new data has been polled from the VarioString slave
322      * The register array is first parsed, then each of the channels are updated
323      * to the new values
324      */
325     protected void handlePolledDataVarioString(int registerNumber, Float quantity) {
326         switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
327             case CHANNEL_PV_OPERATING_MODE:
328             case CHANNEL_PV1_OPERATING_MODE:
329             case CHANNEL_PV2_OPERATING_MODE:
330                 VSMode vsmode = StuderParser.getVSModeByCode(quantity.intValue());
331                 if (vsmode == VSMode.UNKNOWN) {
332                     updateState(CHANNELS_VARIOSTRING.get(registerNumber), UnDefType.UNDEF);
333                 } else {
334                     updateState(CHANNELS_VARIOSTRING.get(registerNumber), new StringType(vsmode.name()));
335                 }
336                 break;
337             case CHANNEL_STATE_VARIOSTRING:
338                 OnOffType vsstate = StuderParser.getStateByCode(quantity.intValue());
339                 updateState(CHANNELS_VARIOSTRING.get(registerNumber), vsstate);
340                 break;
341             default:
342                 updateState(CHANNELS_VARIOSTRING.get(registerNumber),
343                         new QuantityType<>(quantity, UNIT_CHANNELS_VARIOSTRING.get(registerNumber)));
344         }
345     }
346
347     /**
348      * This method is called each time new data has been polled from the VarioTrack slave
349      * The register array is first parsed, then each of the channels are updated
350      * to the new values
351      */
352     protected void handlePolledDataVarioTrack(int registerNumber, Float quantity) {
353         switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
354             case CHANNEL_MODEL_VARIOTRACK:
355                 VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
356                 if (type == VTType.UNKNOWN) {
357                     updateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
358                 } else {
359                     updateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(type.name()));
360                 }
361                 break;
362
363             case CHANNEL_OPERATING_MODE:
364                 VTMode vtmode = StuderParser.getVTModeByCode(quantity.intValue());
365                 if (vtmode == VTMode.UNKNOWN) {
366                     updateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
367                 } else {
368                     updateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(vtmode.name()));
369                 }
370                 break;
371
372             case CHANNEL_STATE_VARIOTRACK:
373                 OnOffType vtstate = StuderParser.getStateByCode(quantity.intValue());
374                 updateState(CHANNELS_VARIOTRACK.get(registerNumber), vtstate);
375                 break;
376             default:
377                 updateState(CHANNELS_VARIOTRACK.get(registerNumber),
378                         new QuantityType<>(quantity, UNIT_CHANNELS_VARIOTRACK.get(registerNumber)));
379         }
380     }
381
382     /**
383      * This method is called each time new data has been polled from the Xtender slave
384      * The register array is first parsed, then each of the channels are updated
385      * to the new values
386      */
387     protected void handlePolledDataXtender(int registerNumber, Float quantity) {
388         switch (CHANNELS_XTENDER.get(registerNumber)) {
389             case CHANNEL_OPERATING_STATE:
390                 ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
391                 if (mode == ModeXtender.UNKNOWN) {
392                     updateState(CHANNELS_XTENDER.get(registerNumber), UnDefType.UNDEF);
393                 } else {
394                     updateState(CHANNELS_XTENDER.get(registerNumber), new StringType(mode.name()));
395                 }
396                 break;
397             case CHANNEL_STATE_INVERTER:
398                 OnOffType xtstate = StuderParser.getStateByCode(quantity.intValue());
399                 updateState(CHANNELS_XTENDER.get(registerNumber), xtstate);
400                 break;
401             default:
402                 updateState(CHANNELS_XTENDER.get(registerNumber),
403                         new QuantityType<>(quantity, UNIT_CHANNELS_XTENDER.get(registerNumber)));
404         }
405     }
406
407     /**
408      * Handle errors received during communication
409      */
410     protected void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
411         // Ignore all incoming data and errors if configuration is not correct
412         if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
413             return;
414         }
415         String msg = failure.getCause().getMessage();
416         String cls = failure.getCause().getClass().getName();
417         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
418                 String.format("Error with read: %s: %s", cls, msg));
419     }
420
421     /**
422      * Returns true, if we're in a CONFIGURATION_ERROR state
423      *
424      * @return
425      */
426     protected boolean hasConfigurationError() {
427         ThingStatusInfo statusInfo = getThing().getStatusInfo();
428         return statusInfo.getStatus() == ThingStatus.OFFLINE
429                 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
430     }
431
432     /**
433      * Reset communication status to ONLINE if we're in an OFFLINE state
434      */
435     protected void resetCommunicationError() {
436         ThingStatusInfo statusInfo = thing.getStatusInfo();
437         if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
438                 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
439             updateStatus(ThingStatus.ONLINE);
440         }
441     }
442 }