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