]> git.basschouten.com Git - openhab-addons.git/blob
cb1dd34bab796ed0c9450e851834c79b31634d10
[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.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;
22
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;
33
34 import eu.xeli.jpigpio.GPIO;
35 import eu.xeli.jpigpio.JPigpio;
36 import eu.xeli.jpigpio.PigpioException;
37
38 /**
39  * Thing Handler for digital GPIO outputs.
40  *
41  * @author Nils Bauer - Initial contribution
42  * @author Jan N. Klug - Channel redesign
43  * @author Jeremy Rumpf - Refactored for network disruptions
44  */
45 @NonNullByDefault
46 public class PigpioDigitalOutputHandler implements ChannelHandler {
47     private final Logger logger = LoggerFactory.getLogger(PigpioDigitalOutputHandler.class);
48
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;
56
57     /**
58      * Constructor for PigpioDigitalOutputHandler
59      * 
60      * @param configuration The channel configuration
61      * @param jPigpio The jPigpio instance
62      * @param updateStatus Is called when the state should be changed
63      * 
64      * @throws PigpioException Can be thrown by Pigpio
65      * @throws ChannelConfigurationException Thrown on configuration error
66      */
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;
73
74         if (this.gpioId <= 0) {
75             throw new ChannelConfigurationException("Invalid gpioId value.");
76         }
77
78         if (configuration.pulse.compareTo(BigDecimal.ZERO) > 0) {
79             try {
80                 this.pulseTimeout = configuration.pulse.intValue();
81             } catch (Exception e) {
82                 throw new ChannelConfigurationException("Invalid expire value.");
83             }
84         }
85
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.");
91             }
92         }
93     }
94
95     /**
96      * Future to track pulse commands.
97      */
98     private @Nullable Future<?> pulseJob = null;
99     private @Nullable OnOffType lastPulseCommand;
100
101     /**
102      * Used to only keep a single gpio command handle in flight
103      * at a time.
104      */
105     private Object handleLock = new Object();
106
107     @Override
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;
113
114             if (lgpio == null || lupdateStatus == null) {
115                 logger.warn("An attempt to submit a command was made when the pigpiod was offline: {}",
116                         command.toString());
117                 return;
118             }
119
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);
125
126                 if (this.pulseTimeout > 0 && this.pulseCommand != null) {
127                     if (job != null) {
128                         job.cancel(false);
129                     }
130
131                     this.pulseJob = scheduler.schedule(() -> handlePulseCommand(command), this.pulseTimeout,
132                             TimeUnit.MILLISECONDS);
133                 }
134             }
135         }
136     }
137
138     public void handlePulseCommand(@Nullable Command command) {
139         OnOffType eCommand = OnOffType.OFF;
140
141         try {
142             synchronized (handleLock) {
143                 GPIO lgpio = this.gpio;
144                 Consumer<State> lupdateStatus = this.updateStatus;
145                 Future<?> job = this.pulseJob;
146
147                 if (lgpio == null) {
148                     return;
149                 }
150
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;
162                             }
163                         }
164                     } else {
165                         if (OnOffType.ON.equals(command)) {
166                             eCommand = OnOffType.OFF;
167                         } else if (OnOffType.OFF.equals(command)) {
168                             eCommand = OnOffType.ON;
169                         }
170                     }
171
172                     logger.debug("gpio pulse command : {} {}", this.gpioId, eCommand.toString());
173
174                     lgpio.setValue(configuration.invert != (OnOffType.ON.equals(eCommand)));
175                     if (lupdateStatus != null) {
176                         lupdateStatus.accept((State) eCommand);
177                     }
178
179                     lastPulseCommand = eCommand;
180
181                     if (PULSE_BLINK.equals(this.pulseCommand) && this.pulseTimeout > 0) {
182                         final OnOffType feCommand = eCommand;
183                         if (job != null) {
184                             job.cancel(false);
185                         }
186                         this.pulseJob = scheduler.schedule(() -> handlePulseCommand(feCommand), this.pulseTimeout,
187                                 TimeUnit.MILLISECONDS);
188                     }
189                 }
190             }
191         } catch (Exception e) {
192             logger.warn(
193                     "Pulse command exception, {} command may not have been received by pigpiod resulting in an unknown state:",
194                     eCommand.toString(), e);
195         }
196     }
197
198     /**
199      * Configures the GPIO pin for OUTPUT.
200      */
201     public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
202         if (jPigpio == null) {
203             this.gpio = null;
204             return;
205         }
206
207         GPIO lgpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT);
208         this.gpio = lgpio;
209         lgpio.setDirection(JPigpio.PI_OUTPUT);
210         scheduleBlink();
211     }
212
213     private void scheduleBlink() {
214         synchronized (handleLock) {
215             Future<?> job = this.pulseJob;
216
217             if (this.pulseTimeout > 0 && PULSE_BLINK.equals(configuration.pulseCommand)) {
218                 if (job != null) {
219                     job.cancel(false);
220                 }
221                 if (this.lastPulseCommand != null) {
222                     scheduler.schedule(() -> handlePulseCommand(this.lastPulseCommand), this.pulseTimeout,
223                             TimeUnit.MILLISECONDS);
224                 } else {
225                     this.pulseJob = scheduler.schedule(() -> handlePulseCommand(OnOffType.OFF), this.pulseTimeout,
226                             TimeUnit.MILLISECONDS);
227                 }
228             }
229         }
230     }
231
232     @Override
233     public void dispose() {
234         synchronized (handleLock) {
235             Future<?> job = this.pulseJob;
236
237             if (job != null) {
238                 job.cancel(true);
239             }
240             this.updateStatus = null;
241             this.gpio = null;
242         }
243     }
244 }