]> git.basschouten.com Git - openhab-addons.git/blob
f2ccd8fc846bb3e2bcfc3cce67432e9f77684f22
[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.gpio.internal.handler;
14
15 import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
16
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.gpio.internal.ChannelConfigurationException;
25 import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration;
26 import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration;
27 import org.openhab.binding.gpio.internal.configuration.PigpioConfiguration;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.thing.type.ChannelTypeUID;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import eu.xeli.jpigpio.JPigpio;
42 import eu.xeli.jpigpio.PigpioException;
43 import eu.xeli.jpigpio.PigpioSocket;
44
45 /**
46  * Remote pigpio Handler
47  *
48  * This bridge is used to control remote pigpio instances.
49  *
50  * @author Nils Bauer - Initial contribution
51  * @author Jan N. Klug - Channel redesign
52  * @author Jeremy Rumpf - Improve JPigpio connection handling
53  */
54 @NonNullByDefault
55 public class PigpioRemoteHandler extends BaseThingHandler {
56     private final Logger logger = LoggerFactory.getLogger(PigpioRemoteHandler.class);
57     private final Map<ChannelUID, ChannelHandler> channelHandlers = new HashMap<>();
58
59     /**
60      * Instantiates a new pigpio remote bridge handler.
61      *
62      * @param thing the thing
63      */
64     public PigpioRemoteHandler(Thing thing) {
65         super(thing);
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         try {
71             synchronized (this.connectionLock) {
72                 ChannelHandler channelHandler = channelHandlers.get(channelUID);
73
74                 if (channelHandler == null || !(ThingStatus.ONLINE.equals(thing.getStatus()))) {
75                     // We raced with connectPollWorker and lost
76                     return;
77                 }
78
79                 if (channelHandler instanceof PigpioDigitalInputHandler inputHandler) {
80                     try {
81                         inputHandler.handleCommand(command);
82                     } catch (PigpioException pe) {
83                         logger.warn("Input command exception on channel {} {}", channelUID, pe.toString());
84                         if (pe.getErrorCode() == -99999999) {
85                             runDisconnectActions();
86                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
87                                     pe.getLocalizedMessage());
88                         }
89                     }
90                 } else if (channelHandler instanceof PigpioDigitalOutputHandler outputHandler) {
91                     try {
92                         outputHandler.handleCommand(command);
93                     } catch (PigpioException pe) {
94                         logger.warn("Output command exception on channel {} {}", channelUID, pe.toString());
95                         if (pe.getErrorCode() == -99999999) {
96                             runDisconnectActions();
97                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
98                                     pe.getLocalizedMessage());
99                         }
100                     }
101                 } else {
102                     logger.warn("Command received for an unknown channel: {}", channelUID);
103                 }
104             }
105         } catch (Exception e) {
106             logger.warn("Command exception on channel {} {}", channelUID, e.toString());
107         }
108     }
109
110     protected PigpioConfiguration config = new PigpioConfiguration();
111     protected @Nullable JPigpio jPigpio = null;
112
113     @Override
114     public void initialize() {
115         PigpioConfiguration lconfig = getConfigAs(PigpioConfiguration.class);
116         this.config = lconfig;
117
118         if (lconfig.host == null) {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
120                     "Cannot connect to PiGPIO Service on remote raspberry. IP address not set.");
121             return;
122         }
123         if (lconfig.port < 1 && lconfig.port > 65535) {
124             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
125                     "Cannot connect to PiGPIO Service on remote raspberry. Invalid Port.");
126             return;
127         }
128
129         createChannelHandlers();
130
131         logger.debug("gpio binding initialized");
132
133         connectionJob = scheduler.submit(() -> {
134             connectionPollWorker();
135         });
136     }
137
138     protected void clearChannelHandlers() {
139         for (ChannelHandler handler : channelHandlers.values()) {
140             handler.dispose();
141         }
142         channelHandlers.clear();
143     }
144
145     protected void createChannelHandlers() {
146         clearChannelHandlers();
147         this.getThing().getChannels().forEach(channel -> {
148             ChannelUID channelUID = channel.getUID();
149             ChannelTypeUID type = channel.getChannelTypeUID();
150
151             try {
152                 if (CHANNEL_TYPE_DIGITAL_INPUT.equals(type)) {
153                     GPIOInputConfiguration configuration = channel.getConfiguration().as(GPIOInputConfiguration.class);
154                     this.channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, scheduler,
155                             state -> updateState(channelUID.getId(), state)));
156                 } else if (CHANNEL_TYPE_DIGITAL_OUTPUT.equals(type)) {
157                     GPIOOutputConfiguration configuration = channel.getConfiguration()
158                             .as(GPIOOutputConfiguration.class);
159                     PigpioDigitalOutputHandler handler = new PigpioDigitalOutputHandler(configuration, scheduler,
160                             state -> updateState(channelUID.getId(), state));
161                     this.channelHandlers.put(channelUID, handler);
162                 }
163             } catch (PigpioException e) {
164                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
165                         String.format("Failed to initialize channel {} {}", channelUID, e.getLocalizedMessage()));
166             } catch (ChannelConfigurationException e) {
167                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
168                         String.format("Invalid configuration for channel {} {}", channelUID, e.getLocalizedMessage()));
169             }
170         });
171
172         logger.debug("gpio channels initialized");
173     }
174
175     protected void setChannelJPigpio(@Nullable JPigpio jPigpio) throws PigpioException {
176         if (this.channelHandlers.isEmpty()) {
177             createChannelHandlers();
178         }
179
180         for (ChannelHandler handler : this.channelHandlers.values()) {
181             handler.listen(jPigpio);
182         }
183
184         logger.debug("gpio jPigpio listening");
185     }
186
187     private @Nullable Future<?> connectionJob = null;
188     /**
189      * Syncronizes all socket related code
190      * to avoid racing.
191      */
192     private Object connectionLock = new Object();
193
194     protected void killConnectionPoll() {
195         if (this.connectionJob != null) {
196             synchronized (this.connectionLock) {
197                 if (this.connectionJob != null) {
198                     Future<?> job = this.connectionJob;
199                     this.connectionJob = null;
200                     if (job != null) {
201                         logger.debug("gpio connection poll : killing");
202                         job.cancel(true);
203                     }
204                 }
205             }
206         }
207     }
208
209     protected void connectionPollWorker() {
210         Thing thing = this.getThing();
211
212         synchronized (connectionLock) {
213             ThingStatus currentStatus = thing.getStatus();
214             JPigpio ljPigpio = this.jPigpio;
215
216             if (ThingStatus.ONLINE.equals(currentStatus) && ljPigpio != null) {
217                 // We are ONLINE and jPigpio is instantiated, this is the normal path
218                 try {
219                     logger.debug("gpio connection poll : CMD_TICK");
220                     ljPigpio.getCurrentTick();
221                 } catch (PigpioException e) {
222                     logger.debug("gpio connection poll : disconnect");
223                     runDisconnectActions();
224                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
225                             e.getLocalizedMessage());
226
227                     // We disconnected, reschedule ourselves to try a reconnect.
228                     // First, try a quick reconnect if the user specified a long(ish) interval
229                     int interval = this.config.heartBeatInterval;
230                     if (interval > 1000) {
231                         interval = 1000;
232                     }
233
234                     this.connectionJob = scheduler.schedule(() -> {
235                         connectionPollWorker();
236                     }, interval, TimeUnit.MILLISECONDS);
237
238                     logger.warn("Pigpiod disconnected : {}", this.config.host);
239
240                     return;
241                 }
242             } else {
243                 // We are OFFLINE and jPigpio may or may not be instantiated
244                 try {
245                     if (ljPigpio == null) {
246                         // First initialization or re-initialization after dispose()
247                         // jPigpio is not up and running yet.
248                         logger.debug("gpio connection poll : connecting");
249                         ljPigpio = new PigpioSocket(this.config.host, this.config.port);
250                         this.jPigpio = ljPigpio;
251                         setChannelJPigpio(ljPigpio);
252                         updateStatus(ThingStatus.ONLINE);
253                         runConnectActions();
254                     } else {
255                         // jPigpio is instantiated, but not connected.
256                         // Use it's internal reconnect logic.
257                         logger.debug("gpio connection poll : reconnecting");
258                         ljPigpio.reconnect();
259                         // jPigpio listeners are not re-established after reconnect.
260                         // We need to reinject them into the channel handlers.
261                         setChannelJPigpio(ljPigpio);
262                         updateStatus(ThingStatus.ONLINE);
263                         runReconnectActions();
264                     }
265
266                     logger.debug("Pigpiod connected : {}", this.config.host);
267                 } catch (PigpioException e) {
268                     logger.debug("gpio connection poll : failed, {}", e.getErrorCode());
269                     if (currentStatus.equals(ThingStatus.ONLINE) || currentStatus.equals(ThingStatus.INITIALIZING)) {
270                         runDisconnectActions();
271                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
272                                 e.getLocalizedMessage());
273                     }
274                 }
275             }
276
277             if (this.config.heartBeatInterval > 0) {
278                 this.connectionJob = scheduler.schedule(() -> {
279                     connectionPollWorker();
280                 }, this.config.heartBeatInterval, TimeUnit.MILLISECONDS);
281             } else {
282                 // User disabled periodic connections, one shot?
283                 logger.debug("gpio connection poll : disabled");
284                 this.connectionJob = null;
285             }
286         }
287     }
288
289     protected void runConnectActions() throws PigpioException {
290         if (this.config.inputConnectAction != null) {
291             if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
292                 refreshInputChannels();
293             }
294         }
295
296         if (this.config.outputConnectAction != null) {
297             if (ACTION_ALL_ON.equals(this.config.outputConnectAction)) {
298                 setOutputChannels(OnOffType.ON);
299             } else if (ACTION_ALL_OFF.equals(this.config.outputConnectAction)) {
300                 setOutputChannels(OnOffType.OFF);
301             } else if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
302                 refreshOutputChannels();
303             }
304         }
305     }
306
307     protected void runReconnectActions() throws PigpioException {
308         if (this.config.inputConnectAction != null) {
309             if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
310                 refreshInputChannels();
311             }
312         }
313
314         if (this.config.outputConnectAction != null) {
315             if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
316                 refreshOutputChannels();
317             }
318         }
319     }
320
321     protected void runDisconnectActions() {
322         if (this.config.inputDisconnectAction != null) {
323             if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
324                 undefInputChannels();
325             }
326         }
327
328         if (this.config.outputDisconnectAction != null) {
329             if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
330                 undefOutputChannels();
331             }
332         }
333     }
334
335     protected void refreshInputChannels() throws PigpioException {
336         logger.debug("gpio refresh input channels");
337         for (ChannelUID channelUID : channelHandlers.keySet()) {
338             ChannelHandler handler = channelHandlers.get(channelUID);
339             if (handler instanceof PigpioDigitalInputHandler) {
340                 handler.handleCommand(RefreshType.REFRESH);
341                 postCommand(channelUID, RefreshType.REFRESH);
342             }
343         }
344     }
345
346     protected void refreshOutputChannels() throws PigpioException {
347         logger.debug("gpio refresh output channels");
348         for (ChannelUID channelUID : this.channelHandlers.keySet()) {
349             ChannelHandler handler = this.channelHandlers.get(channelUID);
350             if (handler instanceof PigpioDigitalOutputHandler) {
351                 handler.handleCommand(RefreshType.REFRESH);
352                 postCommand(channelUID, RefreshType.REFRESH);
353             }
354         }
355     }
356
357     protected void undefInputChannels() {
358         logger.debug("gpio undef input channels");
359         for (ChannelUID channelUID : this.channelHandlers.keySet()) {
360             ChannelHandler handler = this.channelHandlers.get(channelUID);
361             if (handler instanceof PigpioDigitalInputHandler) {
362                 updateState(channelUID, UnDefType.UNDEF);
363             }
364         }
365     }
366
367     protected void undefOutputChannels() {
368         logger.debug("gpio undef output channels");
369         for (ChannelUID channelUID : channelHandlers.keySet()) {
370             ChannelHandler handler = channelHandlers.get(channelUID);
371             if (handler instanceof PigpioDigitalOutputHandler) {
372                 updateState(channelUID, UnDefType.UNDEF);
373             }
374         }
375     }
376
377     protected void setOutputChannels(OnOffType command) throws PigpioException {
378         logger.debug("gpio setting output channels: {}", command.toString());
379         for (ChannelUID channelUID : this.channelHandlers.keySet()) {
380             ChannelHandler handler = this.channelHandlers.get(channelUID);
381             if (handler instanceof PigpioDigitalOutputHandler) {
382                 handler.handleCommand(command);
383                 postCommand(channelUID, command);
384             }
385         }
386     }
387
388     @Override
389     public void dispose() {
390         try {
391             synchronized (this.connectionLock) {
392                 JPigpio ljPigpio = this.jPigpio;
393
394                 killConnectionPoll();
395
396                 if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
397                     undefInputChannels();
398                 }
399                 if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
400                     undefOutputChannels();
401                 }
402
403                 clearChannelHandlers();
404
405                 if (ljPigpio != null) {
406                     try {
407                         ljPigpio.gpioTerminate();
408                         this.jPigpio = null;
409                     } catch (PigpioException e) {
410                         // Best effort at a socket shutdown
411                     }
412                 }
413             }
414             logger.debug("gpio disposed");
415         } catch (Exception e) {
416             logger.debug("Dispose exception :", e);
417         }
418
419         super.dispose();
420     }
421 }