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 static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.TimeUnit;
21 import java.util.function.Consumer;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.gpio.internal.ChannelConfigurationException;
26 import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration;
27 import org.openhab.core.library.types.OnOffType;
28 import org.openhab.core.types.Command;
29 import org.openhab.core.types.RefreshType;
30 import org.openhab.core.types.State;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import eu.xeli.jpigpio.GPIO;
35 import eu.xeli.jpigpio.JPigpio;
36 import eu.xeli.jpigpio.PigpioException;
39 * Thing Handler for digital GPIO outputs.
41 * @author Nils Bauer - Initial contribution
42 * @author Jan N. Klug - Channel redesign
43 * @author Jeremy Rumpf - Refactored for network disruptions
46 public class PigpioDigitalOutputHandler implements ChannelHandler {
47 private final Logger logger = LoggerFactory.getLogger(PigpioDigitalOutputHandler.class);
49 private final GPIOOutputConfiguration configuration;
50 private final ScheduledExecutorService scheduler;
51 private final Integer gpioId;
52 private Integer pulseTimeout = -1;
53 private @Nullable String pulseCommand = "";
54 private @Nullable GPIO gpio;
55 private @Nullable Consumer<State> updateStatus;
58 * Constructor for PigpioDigitalOutputHandler
60 * @param configuration The channel configuration
61 * @param jPigpio The jPigpio instance
62 * @param updateStatus Is called when the state should be changed
64 * @throws PigpioException Can be thrown by Pigpio
65 * @throws ChannelConfigurationException Thrown on configuration error
67 public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, ScheduledExecutorService scheduler,
68 Consumer<State> updateStatus) throws PigpioException, ChannelConfigurationException {
69 this.configuration = configuration;
70 this.gpioId = configuration.gpioId;
71 this.scheduler = scheduler;
72 this.updateStatus = updateStatus;
74 if (this.gpioId <= 0) {
75 throw new ChannelConfigurationException("Invalid gpioId value.");
78 if (configuration.pulse.compareTo(BigDecimal.ZERO) > 0) {
80 this.pulseTimeout = configuration.pulse.intValue();
81 } catch (Exception e) {
82 throw new ChannelConfigurationException("Invalid expire value.");
86 if (configuration.pulseCommand.length() > 0) {
87 this.pulseCommand = configuration.pulseCommand.toUpperCase();
88 if (!PULSE_ON.equals(pulseCommand) && !PULSE_OFF.equals(pulseCommand)
89 && !PULSE_BLINK.equals(pulseCommand)) {
90 throw new ChannelConfigurationException("Invalid pulseCommand value.");
96 * Future to track pulse commands.
98 private @Nullable Future<?> pulseJob = null;
99 private @Nullable OnOffType lastPulseCommand;
102 * Used to only keep a single gpio command handle in flight
105 private Object handleLock = new Object();
108 public void handleCommand(Command command) throws PigpioException {
109 synchronized (handleLock) {
110 GPIO lgpio = this.gpio;
111 Consumer<State> lupdateStatus = this.updateStatus;
112 Future<?> job = this.pulseJob;
114 if (lgpio == null || lupdateStatus == null) {
115 logger.warn("An attempt to submit a command was made when the pigpiod was offline: {}",
120 if (command instanceof RefreshType) {
121 lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
122 } else if (command instanceof OnOffType) {
123 lgpio.setValue(configuration.invert != (OnOffType.ON.equals(command)));
124 lupdateStatus.accept((State) command);
126 if (this.pulseTimeout > 0 && this.pulseCommand != null) {
131 this.pulseJob = scheduler.schedule(() -> handlePulseCommand(command), this.pulseTimeout,
132 TimeUnit.MILLISECONDS);
138 public void handlePulseCommand(@Nullable Command command) {
139 OnOffType eCommand = OnOffType.OFF;
142 synchronized (handleLock) {
143 GPIO lgpio = this.gpio;
144 Consumer<State> lupdateStatus = this.updateStatus;
145 Future<?> job = this.pulseJob;
151 if (command instanceof OnOffType) {
152 if (this.pulseCommand != null) {
153 if (PULSE_ON.equals(this.pulseCommand)) {
154 eCommand = OnOffType.ON;
155 } else if (PULSE_OFF.equals(this.pulseCommand)) {
156 eCommand = OnOffType.OFF;
157 } else if (PULSE_BLINK.equals(this.pulseCommand)) {
158 if (OnOffType.ON.equals(command)) {
159 eCommand = OnOffType.OFF;
160 } else if (OnOffType.OFF.equals(command)) {
161 eCommand = OnOffType.ON;
165 if (OnOffType.ON.equals(command)) {
166 eCommand = OnOffType.OFF;
167 } else if (OnOffType.OFF.equals(command)) {
168 eCommand = OnOffType.ON;
172 logger.debug("gpio pulse command : {} {}", this.gpioId, eCommand.toString());
174 lgpio.setValue(configuration.invert != (OnOffType.ON.equals(eCommand)));
175 if (lupdateStatus != null) {
176 lupdateStatus.accept((State) eCommand);
179 lastPulseCommand = eCommand;
181 if (PULSE_BLINK.equals(this.pulseCommand) && this.pulseTimeout > 0) {
182 final OnOffType feCommand = eCommand;
186 this.pulseJob = scheduler.schedule(() -> handlePulseCommand(feCommand), this.pulseTimeout,
187 TimeUnit.MILLISECONDS);
191 } catch (Exception e) {
193 "Pulse command exception, {} command may not have been received by pigpiod resulting in an unknown state:",
194 eCommand.toString(), e);
199 * Configures the GPIO pin for OUTPUT.
201 public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
202 if (jPigpio == null) {
207 GPIO lgpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT);
209 lgpio.setDirection(JPigpio.PI_OUTPUT);
213 private void scheduleBlink() {
214 synchronized (handleLock) {
215 Future<?> job = this.pulseJob;
217 if (this.pulseTimeout > 0 && PULSE_BLINK.equals(configuration.pulseCommand)) {
221 if (this.lastPulseCommand != null) {
222 scheduler.schedule(() -> handlePulseCommand(this.lastPulseCommand), this.pulseTimeout,
223 TimeUnit.MILLISECONDS);
225 this.pulseJob = scheduler.schedule(() -> handlePulseCommand(OnOffType.OFF), this.pulseTimeout,
226 TimeUnit.MILLISECONDS);
233 public void dispose() {
234 synchronized (handleLock) {
235 Future<?> job = this.pulseJob;
240 this.updateStatus = null;