]> git.basschouten.com Git - openhab-addons.git/blob
5ecb5daa73f1f928d8897934de29c76b45adc0a1
[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 java.util.Date;
16 import java.util.concurrent.ScheduledExecutorService;
17 import java.util.concurrent.TimeUnit;
18 import java.util.function.Consumer;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.gpio.internal.ChannelConfigurationException;
23 import org.openhab.binding.gpio.internal.GPIOBindingConstants;
24 import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration;
25 import org.openhab.core.library.types.OnOffType;
26 import org.openhab.core.types.Command;
27 import org.openhab.core.types.RefreshType;
28 import org.openhab.core.types.State;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import eu.xeli.jpigpio.GPIO;
33 import eu.xeli.jpigpio.GPIOListener;
34 import eu.xeli.jpigpio.JPigpio;
35 import eu.xeli.jpigpio.PigpioException;
36
37 /**
38  * Thing Handler for digital GPIO inputs.
39  *
40  * @author Nils Bauer - Initial contribution
41  * @author Jan N. Klug - Channel redesign
42  * @author Martin Dagarin - Pull Up/Down GPIO pin
43  * @author Jeremy Rumpf - Refactored for network disruptions
44  */
45 @NonNullByDefault
46 public class PigpioDigitalInputHandler implements ChannelHandler {
47     private final Logger logger = LoggerFactory.getLogger(PigpioDigitalInputHandler.class);
48     private Date lastChanged = new Date();
49
50     private final GPIOInputConfiguration configuration;
51     private final ScheduledExecutorService scheduler;
52     private final Integer gpioId;
53     private @Nullable GPIO gpio;
54     private @Nullable Consumer<State> updateStatus;
55     private Integer pullupdown = JPigpio.PI_PUD_OFF;
56     private final GPIOListener listener;
57     private int edgeMode = JPigpio.PI_EITHER_EDGE;
58
59     /**
60      * Constructor for PigpioDigitalOutputHandler
61      * 
62      * @param configuration The channel configuration
63      * @param jPigpio The jPigpio instance
64      * @param updateStatus Is called when the state should be changed
65      * 
66      * @throws PigpioException Can be thrown by Pigpio
67      * @throws ChannelConfigurationException Thrown on configuration error
68      */
69     public PigpioDigitalInputHandler(GPIOInputConfiguration configuration, ScheduledExecutorService scheduler,
70             Consumer<State> updateStatus) throws PigpioException, ChannelConfigurationException {
71         this.configuration = configuration;
72         this.scheduler = scheduler;
73         this.updateStatus = updateStatus;
74         this.gpioId = configuration.gpioId;
75
76         if (this.gpioId <= 0) {
77             throw new ChannelConfigurationException("Invalid gpioId value: " + this.gpioId);
78         }
79
80         String pullupdownStr = configuration.pullupdown.toUpperCase();
81         if (pullupdownStr.equals(GPIOBindingConstants.PUD_DOWN)) {
82             this.pullupdown = JPigpio.PI_PUD_DOWN;
83         } else if (pullupdownStr.equals(GPIOBindingConstants.PUD_UP)) {
84             this.pullupdown = JPigpio.PI_PUD_UP;
85         } else if (pullupdownStr.equals(GPIOBindingConstants.PUD_OFF)) {
86             this.pullupdown = JPigpio.PI_PUD_OFF;
87         } else {
88             throw new ChannelConfigurationException("Invalid pull up/down value.");
89         }
90
91         String edgeModeStr = configuration.edgeMode;
92         if (edgeModeStr.equals(GPIOBindingConstants.EDGE_RISING)) {
93             this.edgeMode = JPigpio.PI_RISING_EDGE;
94         } else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_FALLING)) {
95             this.edgeMode = JPigpio.PI_FALLING_EDGE;
96         } else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_EITHER)) {
97             this.edgeMode = JPigpio.PI_EITHER_EDGE;
98         } else {
99             throw new ChannelConfigurationException("Invalid edgeMode value.");
100         }
101
102         this.listener = new GPIOListener(this.gpioId, this.edgeMode) {
103             @Override
104             public void alert(int gpio, int level, long tick) {
105                 alertFunc(gpio, level, tick);
106             }
107         };
108     }
109
110     public void alertFunc(int gpio, int level, long tick) {
111         this.lastChanged = new Date();
112         Date thisChange = new Date();
113         if (configuration.debouncingTime > 0) {
114             scheduler.schedule(() -> afterDebounce(thisChange), configuration.debouncingTime, TimeUnit.MILLISECONDS);
115         } else {
116             afterDebounce(thisChange);
117         }
118     }
119
120     /**
121      * Syncronize debouncing callbacks to
122      * ensure they are not out of order.
123      */
124     private Object debounceLock = new Object();
125
126     private void afterDebounce(Date thisChange) {
127         synchronized (debounceLock) {
128             GPIO lgpio = this.gpio;
129             Consumer<State> lupdateStatus = this.updateStatus;
130
131             if (lgpio == null || lupdateStatus == null) {
132                 // We raced and went offline in the meantime.
133                 return;
134             }
135
136             try {
137                 // Check if value changed over time
138                 if (!thisChange.before(lastChanged)) {
139                     lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
140                 }
141             } catch (PigpioException e) {
142                 // -99999999 is communication related, we will let the Thing connect poll refresh it.
143                 if (e.getErrorCode() != -99999999) {
144                     logger.debug("Debounce exception :", e);
145                 }
146             }
147         }
148     }
149
150     /**
151      * Establishes or re-establishes a listener on the JPigpio
152      * instance for the configured gpio pin.
153      */
154     public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
155         if (jPigpio == null) {
156             this.gpio = null;
157             return;
158         }
159
160         GPIO lgpio = new GPIO(jPigpio, this.gpioId, JPigpio.PI_INPUT);
161         this.gpio = lgpio;
162
163         try {
164             lgpio.setDirection(JPigpio.PI_INPUT);
165             jPigpio.gpioSetPullUpDown(lgpio.getPin(), this.pullupdown);
166             jPigpio.removeCallback(this.listener);
167         } catch (PigpioException e) {
168             // If there is a communication error, the set alert below will throw.
169             if (e.getErrorCode() != -99999999) {
170                 logger.debug("Listen exception :", e);
171             }
172         }
173
174         jPigpio.gpioSetAlertFunc(lgpio.getPin(), this.listener);
175     }
176
177     @Override
178     public void handleCommand(Command command) throws PigpioException {
179         GPIO lgpio = this.gpio;
180         Consumer<State> lupdateStatus = this.updateStatus;
181
182         if (lgpio == null || lupdateStatus == null) {
183             logger.warn("An attempt to submit a command was made when pigpiod was offline: {}", command.toString());
184             return;
185         }
186
187         if (command instanceof RefreshType) {
188             lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
189         }
190     }
191
192     @Override
193     public void dispose() {
194         synchronized (debounceLock) {
195             GPIO lgpio = this.gpio;
196
197             updateStatus = null;
198             if (lgpio != null) {
199                 JPigpio ljPigpio = lgpio.getPigpio();
200                 if (ljPigpio != null) {
201                     try {
202                         ljPigpio.removeCallback(listener);
203                     } catch (PigpioException e) {
204                         // Best effort to remove listener,
205                         // the command socket could already be dead.
206                         if (e.getErrorCode() != -99999999) {
207                             logger.debug("Dispose exception :", e);
208                         }
209                     }
210                 }
211             }
212         }
213     }
214 }