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