2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.gpio.internal.handler;
15 import java.util.Date;
16 import java.util.concurrent.ScheduledExecutorService;
17 import java.util.concurrent.TimeUnit;
18 import java.util.function.Consumer;
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;
32 import eu.xeli.jpigpio.GPIO;
33 import eu.xeli.jpigpio.GPIOListener;
34 import eu.xeli.jpigpio.JPigpio;
35 import eu.xeli.jpigpio.PigpioException;
38 * Thing Handler for digital GPIO inputs.
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
46 public class PigpioDigitalInputHandler implements ChannelHandler {
47 private final Logger logger = LoggerFactory.getLogger(PigpioDigitalInputHandler.class);
48 private Date lastChanged = new Date();
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;
60 * Constructor for PigpioDigitalOutputHandler
62 * @param configuration The channel configuration
63 * @param jPigpio The jPigpio instance
64 * @param updateStatus Is called when the state should be changed
66 * @throws PigpioException Can be thrown by Pigpio
67 * @throws ChannelConfigurationException Thrown on configuration error
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;
76 if (this.gpioId <= 0) {
77 throw new ChannelConfigurationException("Invalid gpioId value: " + this.gpioId);
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;
88 throw new ChannelConfigurationException("Invalid pull up/down value.");
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;
99 throw new ChannelConfigurationException("Invalid edgeMode value.");
102 this.listener = new GPIOListener(this.gpioId, this.edgeMode) {
104 public void alert(int gpio, int level, long tick) {
105 alertFunc(gpio, level, tick);
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);
116 afterDebounce(thisChange);
121 * Syncronize debouncing callbacks to
122 * ensure they are not out of order.
124 private Object debounceLock = new Object();
126 private void afterDebounce(Date thisChange) {
127 synchronized (debounceLock) {
128 GPIO lgpio = this.gpio;
129 Consumer<State> lupdateStatus = this.updateStatus;
131 if (lgpio == null || lupdateStatus == null) {
132 // We raced and went offline in the meantime.
137 // Check if value changed over time
138 if (!thisChange.before(lastChanged)) {
139 lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
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);
151 * Establishes or re-establishes a listener on the JPigpio
152 * instance for the configured gpio pin.
154 public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
155 if (jPigpio == null) {
160 GPIO lgpio = new GPIO(jPigpio, this.gpioId, JPigpio.PI_INPUT);
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);
174 jPigpio.gpioSetAlertFunc(lgpio.getPin(), this.listener);
178 public void handleCommand(Command command) throws PigpioException {
179 GPIO lgpio = this.gpio;
180 Consumer<State> lupdateStatus = this.updateStatus;
182 if (lgpio == null || lupdateStatus == null) {
183 logger.warn("An attempt to submit a command was made when pigpiod was offline: {}", command.toString());
187 if (command instanceof RefreshType) {
188 lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
193 public void dispose() {
194 synchronized (debounceLock) {
195 GPIO lgpio = this.gpio;
199 JPigpio ljPigpio = lgpio.getPigpio();
200 if (ljPigpio != null) {
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);