[GPIO] Update GPIO binding to fix issues and provide new functionality (#13643)

* [GPIO] Update the GPIO binding to fix issues and provide new functionality.

* Add the ability to recover from network disconnects to pigpiod.
* Provide actions to what the binding does on pigpiod connect/disconnect/reconnect.
* Provide the ability to pulse outputs in a one-shot/momentary fashion.
* Provide edge level configuration for gpio outputs.
* Fix automated style checks.
* Attempt to squash jenkins build warnings/errors.
* Correct Null annotations and adjust log levels in certain areas.
* Fix bracing per checkstyle review.
* Normalize gpiod/pigpiod to pigpiod. openhab/openHAB normalized to OpenHAB.
* Fix a copy/paste error. Output channels should not be referred as input. Attempt to clarify plurals.
* Convert strings to defined constants.
* Convert pulse command strings to binding constants.
* Java17 instanceof pattern matching.
* Nit, fix missed pulse command string to binding constant.
* Add missing quotes in demo.things file definition.

Workaround #11039
Fixes #11038
Fixes #13376

Signed-off-by: Jeremy Rumpf <rumpf.99@gmail.com>
This commit is contained in:
Jeremy Rumpf 2023-10-30 16:20:42 -04:00 committed by GitHub
parent 597f01efe1
commit b24f3a2feb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1159 additions and 166 deletions

View File

@ -1,19 +1,19 @@
# GPIO Binding
This binding adds GPIO support via the pigpio daemon to openhab.
It requires the pigpio (<http://abyz.me.uk/rpi/pigpio/>) to be running on the pi that should be controlled.
This binding adds GPIO support via the pigpiod daemon to openHAB.
It requires the pigpiod daemon (<http://abyz.me.uk/rpi/pigpio/>) to be installed on the pi that should be controlled.
## Supported Things
### pigpio-remote
This thing represents a remote pigpio instance running as daemon on a raspberry pi.
This thing represents a remote pigpiod instance running as daemon on a raspberry pi.
## Thing Configuration
### Pigpio Remote (`pigpio-remote`)
On a raspberry (or a compatible device) you have to install pigpio:
On a raspberry (or a compatible device) you have to install pigpiod.
```shell
sudo apt-get install pigpiod
@ -39,71 +39,204 @@ ExecStart=/usr/bin/pigpiod
sudo systemctl daemon-reload
```
Now that Remote GPIO is enabled, get the daemon going (even if installed with apt-get):
Now that Remote GPIO is enabled, get the pigpiod daemon going (even if installed with apt-get):
```shell
sudo systemctl enable pigpiod
sudo systemctl start pigpiod
```
In openHAB, set `host` to the address of the pi and the `port` to the port of pigpio (default: 8888).
## General Configuration
Note: If you are running Pigpio on same host as openHAB, then set host to **::1**.
Binding general configuration options. If you do not see all options, ensure `Show Advanced` is selected.
### Host
Set `Host` to the address of the Pi that pigpiod is running on. Default is 127.0.0.1 (IPV4).
Note: If you are running pigpiod on same host as openHAB, set the host to 127.0.0.1 (IPV4) or ::1 (IPV6).
### Port
Set `Port` to the network port that pigpiod is listening on. Default is 8888.
### Heart Beat Interval
The binding will poll pigpiod running on the Pi to determine if a network disconnect has occurred.
This is the interval in milliseconds that the heart beat poll occurs. Defaults to 30000 (30 seconds).
## Input Channel Connect Action
Input Channel Connect Action determines what happens when the binding initially connects to pigpiod.
This action only occurs once after binding startup.
- **Do Nothing:** The default, do nothing. Input channels will retain their default value (UNDEF).
- **Refresh Channel:** Issues a refresh command on the input channels. This will refresh the channels from pigpiod causing the gpio pin state to reflect on the channel state.
Input Channel Disconnect Connect Action:
### Input Channel Disconnect Connect Action
Input Channel Disconnect Connect Action determines what happens when the binding disconnects from pigpiod.
- **Do Nothing:** The default, do nothing. Input channels will retain their current value.
- **Set Undef:** Sets the input channel states to UNDEF to indicate that pigpiod has disconnected.
### Input Channel Reconnect Connect Action
Input Channel Reconnect Action determines what happens when the binding reconnects to pigpiod
after a disconnect. This action does not occur on the initial binding connect to pigpiod.
startup.
- **Do Nothing:** The default, do nothing. Input channels will retain their current value.
- **Refresh Channel:** Issues a refresh command on the input channels. This will refresh the channels from
pigpiod causing the gpio pin state to reflect on the channel state.
### Output Channel Connect Action
Output Channel Connect Action determines what happens when the binding initially connects to pigpiod.
This action only occurs once after binding startup.
- **Do Nothing:** The default, do nothing. Output channels will retain their default value (UNDEF).
- **All On:** Issues a ON command to all configured output channels.
- **All Off:** Issues a OFF command to all configured output channels.
- **Refresh Channel:** Issues a refresh command on the output channels. This will refresh the channels from
pigpiod causing the gpio pin state to reflect on the channel state. NOTE: This does
not update the gpio pin state on the Pi itself. It only updates the channel state
within openHAB.
### Output Channel Disconnect Connect Action
Output Channel Disconnect Connect Action determines what happens when the binding disconnects from pigpiod.
- **Do Nothing:** he default, do nothing. Input channels will retain their current value.
- **Set Undef:** Sets the output channel states to UNDEF to indicate that pigpiod has disconnected.
### Output Channel Reconnect Connect Action
Output Channel Reconnect Action determines what happens when the binding reconnects to pigpiod
after a disconnect. This action does not occur on the initial binding connect to pigpiod.
- **Do Nothing:** The default, do nothing. Output channels will retain their current value.
- **Refresh Channel:** Issues a refresh command on the output channels. This will refresh the channels from
pigpiod causing the gpio pin state to reflect on the channel state. NOTE: This does
not update the gpio pin state on the Pi itself. It only updates the channel state
within openHAB.
## Channels
### Pigpio Remote
The binding has two channel types.
One for gpio input pins, and another for gpio output pins.
| channel | type | description |
|-----------------------|--------|---------------------------------|
| pigpio-digital-input | Switch | Read-only value of the gpio pin |
| pigpio-digital-output | Switch | Controls the gpio pin |
### GPIO digital input channel
### GPIO pigpio-digital-input channel configuration
Set the number of the pin in `gpioId`.
If you want to invert the value, set `invert` to true.
To prevent incorrect change events, you can adjust the `debouncingTime`.
Using `pullupdown` you can enable pull up or pull down resistor (OFF = Off, DOWN = Pull Down, UP = Pull Up).
Input channels provide a read-only value of the gpio pin state using the `OnOffType` datatype.
### GPIO digital output channel
GPIO Pin:
Set the number of the pin in `gpioId`.
If you want to invert the value, set `invert` to true.
The gpio pin number on the Pi that the channel will monitor.
## Full Example
Invert:
Inverts the value of the gpio pin before reflecting the value on the channel.
Useful for active low gpio pins where you want the channel state to reflect an ON value when the pin is low (OFF).
Delay Time:
Sets a delay value in milliseconds that the gpio pin must remain at prior to updating the channel state.
This is the same as switch debouncing or hysteresis.
Default value is 10 milliseconds.
Increase this value for noisy inputs.
Pull Up/Down Resistor:
Sets the mode of operation for the internal pull up/ down resistor on the gpio pin.
Set this to OFF if you use external pull up/down resistors.
Edge Detection Mode:
Sets the mode of operation for the pin edge detection mode.
If you are not sure what the use case is for this, leave at the default value of `Either Edge`.
This is the most common mode that gpio inputs are used.
### GPIO pigpio-digital-output channel configuration
Output channels provide a means of controlling the output value of the gpio pin using the `OnOffType` datatype.
GPIO Pin:
The gpio pin number on the Pi that the channel will control.
Invert:
Inverts the value of the channel state before commanding the gpio pin.
Useful to simulate active low gpio pins.
Pulse:
Time in milliseconds that must elapse before the Pulse Command is sent to the channel.
Default value is 0, which disables the Pulse feature.
Pulse Command:
Together with the Pulse configuration, can be used to create a one shot or momentary output.
This is useful to simulate momentary button presses or to drive motors for a predefined amount
of time.
- **Off:** When the ON command is issued to the channel. The Pulse feature will send an OFF command
after the Pulse duration.
- **On:** When the OFF command is issued to the channel. The Pulse feature will send an
ON command after the Pulse duration.
- **Blink:** Cycles the channel ON, OFF, ON indefinitely with a 50% duty cycle. The Blink
operation continues regardless of the commanded channel state. This was originaly
developed as a way to flash a status LED to visually confirm that a remote pigpiod
instance has connectivity to openHAB.
## Config file example
Example for users who still prefer configuration files.
demo.things:
```java
Thing gpio:pigpio-remote:sample-pi-1 "Sample-Pi 1" [host="192.168.2.36", port=8888] {
Channels:
Type pigpio-digital-input : sample-input-1 [ gpioId=10]
Type pigpio-digital-input : sample-input-2 [ gpioId=14, invert=true]
Type pigpio-digital-output : sample-output-1 [ gpioId=3]
}
Thing gpio:pigpio-remote:mypi "MyPi GPIO" [ host="192.168.1.5", port=8888,
heartBeatInterval=10000,
inputConnectAction="REFRESH", # REFRESH,NOTHING
inputDisconnectAction="NOTHING", # SETUNDEF,NOTHING
inputReconnectAction="REFRESH", # REFRESH,NOTHING
outputConnectAction="REFRESH", # ALLOFF,ALLON,REFRESH,NOTHING
outputDisconnectAction="SETUNDEF", # SETUNDEF,NOTHING
outputReconnectAction="REFRESH" ] # REFRESH,NOTHING
{
Channels:
Type pigpio-digital-output : BCM18 [ gpioId=18,invert=false,pulse=3000,pulseCommand="BLINK" ] # OFF,ON,BLINK
Thing gpio:pigpio-remote:sample-pi-2 "Sample-Pi 2" [host="192.168.2.37", port=8888] {
Channels:
Type pigpio-digital-input : sample-input-3 [ gpioId=16, debouncingTime=20]
Type pigpio-digital-input : sample-input-4 [ gpioId=17, invert=true, debouncingTime=5, pullupdown="UP"]
Type pigpio-digital-output : sample-output-2 [ gpioId=4, invert=true]
}
Type pigpio-digital-output : GPO4 [ gpioId=4, invert=true,pulse=5000,pulseCommand="OFF" ]
Type pigpio-digital-output : GPO17 [ gpioId=17,invert=false,pulse=500,pulseCommand="ON" ]
Type pigpio-digital-output : GPO27 [ gpioId=27,invert=false ]
Type pigpio-digital-output : GPO22 [ gpioId=22,invert=true ]
Type pigpio-digital-input : GPI23 [ gpioId=23,debouncingTime=50,pullupdown="UP",invert=true ] # OFF,DOWN,UP
Type pigpio-digital-input : GPI24 [ gpioId=24,debouncingTime=50,pullupdown="UP",invert=true ]
Type pigpio-digital-input : GPI25 [ gpioId=25,debouncingTime=50,pullupdown="UP",invert=true ]
Type pigpio-digital-input : GPI12 [ gpioId=12,debouncingTime=50,pullupdown="UP",invert=true ]
Type pigpio-digital-input : GPI16 [ gpioId=16,debouncingTime=50,pullupdown="UP",invert=true ]
Type pigpio-digital-input : GPI20 [ gpioId=20,debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_EITHER" ] # EITHER,RISING,FALLING
Type pigpio-digital-input : GPI21 [ gpioId=21,debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_RISING" ]
Type pigpio-digital-input : GPI5 [ gpioId=5, debouncingTime=50,pullupdown="UP",invert=true,edgeMode="EDGE_FALLING" ]
Type pigpio-digital-input : GPI6 [ gpioId=6, debouncingTime=50,pullupdown="UP",invert=true ]
Type pigpio-digital-input : GPI13 [ gpioId=13,debouncingTime=50,pullupdown="DOWN",invert=false ]
Type pigpio-digital-input : GPI26 [ gpioId=26,debouncingTime=50,pullupdown="OFF",invert=false ]
}
```
demo.items:
```java
Switch SampleInput1 {channel="gpio:pigpio-remote:sample-pi-1:sample-input-1"}
Switch SampleOutput1 {channel="gpio:pigpio-remote:sample-pi-1:sample-output-1"}
```
demo.sitemap:
```perl
sitemap demo label="Main Menu"
{
Switch item=SampleInput1
Switch item=SampleOutput1
}
Switch SampleInput1 {channel="gpio:pigpio-remote:mypi:GPI23"}
Switch SampleOutput1 {channel="gpio:pigpio-remote:mypi:GPO4"}
```

View File

@ -15,11 +15,15 @@ package org.openhab.binding.gpio.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Is thrown when invalid GPIO pin Pull Up/Down resistor configuration is set
* Is thrown when a channel configuration is invalid
*
* @author Martin Dagarin - Initial contribution
* @author Jeremy Rumpf - Initial contribution
*/
@NonNullByDefault
public class InvalidPullUpDownException extends Exception {
public class ChannelConfigurationException extends Exception {
private static final long serialVersionUID = -1281107134439928767L;
public ChannelConfigurationException(String message) {
super(message);
}
}

View File

@ -22,6 +22,7 @@ import org.openhab.core.thing.type.ChannelTypeUID;
*
* @author Nils Bauer - Initial contribution
* @author Martin Dagarin - Pull Up/Down GPIO pin
* @author Jeremy Rumpf - Added Action/Edge constants
*/
@NonNullByDefault
public class GPIOBindingConstants {
@ -43,12 +44,27 @@ public class GPIOBindingConstants {
public static final String DEBOUNCING_TIME = "debouncing_time";
public static final String STRICT_DEBOUNCING = "debouncing_strict";
public static final String PULLUPDOWN_RESISTOR = "pullupdown";
public static final String ACTION_SET_UNDEF = "SETUNDEF";
public static final String ACTION_NOTHING = "NOTHING";
public static final String ACTION_REFRESH = "REFRESH";
public static final String ACTION_ALL_ON = "ALLON";
public static final String ACTION_ALL_OFF = "ALLOFF";
// Pull Up/Down modes
public static final String PUD_OFF = "OFF";
public static final String PUD_DOWN = "DOWN";
public static final String PUD_UP = "UP";
// Pulse
public static final String PULSE_OFF = "OFF";
public static final String PULSE_ON = "ON";
public static final String PULSE_BLINK = "BLINK";
// Edge modes
public static final String EDGE_EITHER = "EDGE_EITHER";
public static final String EDGE_RISING = "EDGE_RISING";
public static final String EDGE_FALLING = "EDGE_FALLING";
// GPIO config properties
public static final String GPIO_ID = "gpioId";
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.gpio.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Is thrown when no gpio id is provided
*
* @author Nils Bauer - Initial contribution
*/
@NonNullByDefault
public class NoGpioIdException extends Exception {
private static final long serialVersionUID = -1281107134439928767L;
}

View File

@ -13,7 +13,6 @@
package org.openhab.binding.gpio.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link GPIOConfiguration} class contains fields mapping thing configuration parameters.
@ -26,7 +25,7 @@ public class GPIOConfiguration {
/**
* The id of the gpio pin.
*/
public @Nullable Integer gpioId;
public Integer gpioId = 0;
/**
* Should the input/output be inverted?

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.gpio.internal.configuration;
import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
@ -31,5 +33,12 @@ public class GPIOInputConfiguration extends GPIOConfiguration {
* Setup a pullup resistor on the GPIO pin
* OFF = PI_PUD_OFF, DOWN = PI_PUD_DOWN, UP = PI_PUD_UP
*/
public String pullupdown = "OFF";
public String pullupdown = PUD_OFF;
/**
* Sets the input detection type.
* EDGE_EITHER = PI_EITHER_EDGE, EDGE_FALLING = PI_FALLING_EDGE,
* EDGE_RISING = PI_RISING_EDGE
*/
public String edgeMode = EDGE_EITHER;
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.gpio.internal.configuration;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
@ -21,5 +23,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/
@NonNullByDefault
public class GPIOOutputConfiguration extends GPIOConfiguration {
public BigDecimal pulse = new BigDecimal(0);
public String pulseCommand = "OFF";
}

View File

@ -32,4 +32,41 @@ public class PigpioConfiguration {
* Port of pigpio on the remote raspberry pi
*/
public int port = 8888;
/**
* Interval to send heartbeat checks
*/
public int heartBeatInterval = 60000;
/**
* Input channel action on connect
* (First connect after INITIALIATION)
*/
public @Nullable String inputConnectAction;
/**
* Input channel action on reconnect
*/
public @Nullable String inputReconnectAction;
/**
* Input channel action on disconnect
*/
public @Nullable String inputDisconnectAction;
/**
* Output channel action on connect
* (First connect after INITIALIATION)
*/
public @Nullable String outputConnectAction;
/**
* Output channel action on reconnect
*/
public @Nullable String outputReconnectAction;
/**
* Output channel action on disconnect
*/
public @Nullable String outputDisconnectAction;
}

View File

@ -13,8 +13,12 @@
package org.openhab.binding.gpio.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Command;
import eu.xeli.jpigpio.JPigpio;
import eu.xeli.jpigpio.PigpioException;
/**
* The {@link ChannelHandler} provides an interface for different pin
* configuration handlers
@ -24,5 +28,20 @@ import org.openhab.core.types.Command;
@NonNullByDefault
public interface ChannelHandler {
void handleCommand(Command command);
/**
* Handles a Command being sent from the
* Openhab framework.
*/
void handleCommand(Command command) throws PigpioException;
/**
* (Re)Establishes the JPigpio listeners.
*/
void listen(@Nullable JPigpio jPigpio) throws PigpioException;
/**
* Terminates sending Channels status updates and
* shuts down any JPigpio listeners.
*/
void dispose();
}

View File

@ -18,9 +18,9 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpio.internal.ChannelConfigurationException;
import org.openhab.binding.gpio.internal.GPIOBindingConstants;
import org.openhab.binding.gpio.internal.InvalidPullUpDownException;
import org.openhab.binding.gpio.internal.NoGpioIdException;
import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
@ -30,6 +30,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.xeli.jpigpio.GPIO;
import eu.xeli.jpigpio.GPIOListener;
import eu.xeli.jpigpio.JPigpio;
import eu.xeli.jpigpio.PigpioException;
@ -39,64 +40,174 @@ import eu.xeli.jpigpio.PigpioException;
* @author Nils Bauer - Initial contribution
* @author Jan N. Klug - Channel redesign
* @author Martin Dagarin - Pull Up/Down GPIO pin
* @author Jeremy Rumpf - Refactored for network disruptions
*/
@NonNullByDefault
public class PigpioDigitalInputHandler implements ChannelHandler {
private final Logger logger = LoggerFactory.getLogger(PigpioDigitalInputHandler.class);
private Date lastChanged = new Date();
private final GPIOInputConfiguration configuration;
private final GPIO gpio;
private final Consumer<State> updateStatus;
private final ScheduledExecutorService scheduler;
private final Integer gpioId;
private @Nullable GPIO gpio;
private @Nullable Consumer<State> updateStatus;
private Integer pullupdown = JPigpio.PI_PUD_OFF;
private final GPIOListener listener;
private int edgeMode = JPigpio.PI_EITHER_EDGE;
public PigpioDigitalInputHandler(GPIOInputConfiguration configuration, JPigpio jPigpio,
ScheduledExecutorService scheduler, Consumer<State> updateStatus)
throws PigpioException, InvalidPullUpDownException, NoGpioIdException {
/**
* Constructor for PigpioDigitalOutputHandler
*
* @param configuration The channel configuration
* @param jPigpio The jPigpio instance
* @param updateStatus Is called when the state should be changed
*
* @throws PigpioException Can be thrown by Pigpio
* @throws ChannelConfigurationException Thrown on configuration error
*/
public PigpioDigitalInputHandler(GPIOInputConfiguration configuration, ScheduledExecutorService scheduler,
Consumer<State> updateStatus) throws PigpioException, ChannelConfigurationException {
this.configuration = configuration;
this.scheduler = scheduler;
this.updateStatus = updateStatus;
Integer gpioId = configuration.gpioId;
if (gpioId == null) {
throw new NoGpioIdException();
this.gpioId = configuration.gpioId;
if (this.gpioId <= 0) {
throw new ChannelConfigurationException("Invalid gpioId value: " + this.gpioId);
}
Integer pullupdown = JPigpio.PI_PUD_OFF;
String pullupdownStr = configuration.pullupdown.toUpperCase();
if (pullupdownStr.equals(GPIOBindingConstants.PUD_DOWN)) {
pullupdown = JPigpio.PI_PUD_DOWN;
this.pullupdown = JPigpio.PI_PUD_DOWN;
} else if (pullupdownStr.equals(GPIOBindingConstants.PUD_UP)) {
pullupdown = JPigpio.PI_PUD_UP;
this.pullupdown = JPigpio.PI_PUD_UP;
} else if (pullupdownStr.equals(GPIOBindingConstants.PUD_OFF)) {
this.pullupdown = JPigpio.PI_PUD_OFF;
} else {
if (!pullupdownStr.equals(GPIOBindingConstants.PUD_OFF)) {
throw new InvalidPullUpDownException();
}
throw new ChannelConfigurationException("Invalid pull up/down value.");
}
gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_INPUT);
jPigpio.gpioSetAlertFunc(gpio.getPin(), (gpio, level, tick) -> {
lastChanged = new Date();
Date thisChange = new Date();
scheduler.schedule(() -> afterDebounce(thisChange), configuration.debouncingTime, TimeUnit.MILLISECONDS);
});
jPigpio.gpioSetPullUpDown(gpio.getPin(), pullupdown);
String edgeModeStr = configuration.edgeMode;
if (edgeModeStr.equals(GPIOBindingConstants.EDGE_RISING)) {
this.edgeMode = JPigpio.PI_RISING_EDGE;
} else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_FALLING)) {
this.edgeMode = JPigpio.PI_FALLING_EDGE;
} else if (edgeModeStr.equals(GPIOBindingConstants.EDGE_EITHER)) {
this.edgeMode = JPigpio.PI_EITHER_EDGE;
} else {
throw new ChannelConfigurationException("Invalid edgeMode value.");
}
this.listener = new GPIOListener(this.gpioId, this.edgeMode) {
@Override
public void alert(int gpio, int level, long tick) {
alertFunc(gpio, level, tick);
}
};
}
public void alertFunc(int gpio, int level, long tick) {
this.lastChanged = new Date();
Date thisChange = new Date();
if (configuration.debouncingTime > 0) {
scheduler.schedule(() -> afterDebounce(thisChange), configuration.debouncingTime, TimeUnit.MILLISECONDS);
} else {
afterDebounce(thisChange);
}
}
/**
* Syncronize debouncing callbacks to
* ensure they are not out of order.
*/
private Object debounceLock = new Object();
private void afterDebounce(Date thisChange) {
try {
// Check if value changed over time
if (!thisChange.before(lastChanged)) {
updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue()));
synchronized (debounceLock) {
GPIO lgpio = this.gpio;
Consumer<State> lupdateStatus = this.updateStatus;
if (lgpio == null || lupdateStatus == null) {
// We raced and went offline in the meantime.
return;
}
try {
// Check if value changed over time
if (!thisChange.before(lastChanged)) {
lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
}
} catch (PigpioException e) {
// -99999999 is communication related, we will let the Thing connect poll refresh it.
if (e.getErrorCode() != -99999999) {
logger.debug("Debounce exception :", e);
}
}
}
}
/**
* Establishes or re-establishes a listener on the JPigpio
* instance for the configured gpio pin.
*/
public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
if (jPigpio == null) {
this.gpio = null;
return;
}
GPIO lgpio = new GPIO(jPigpio, this.gpioId, JPigpio.PI_INPUT);
this.gpio = lgpio;
try {
lgpio.setDirection(JPigpio.PI_INPUT);
jPigpio.gpioSetPullUpDown(lgpio.getPin(), this.pullupdown);
jPigpio.removeCallback(this.listener);
} catch (PigpioException e) {
logger.warn("Unknown pigpio exception", e);
// If there is a communication error, the set alert below will throw.
if (e.getErrorCode() != -99999999) {
logger.debug("Listen exception :", e);
}
}
jPigpio.gpioSetAlertFunc(lgpio.getPin(), this.listener);
}
@Override
public void handleCommand(Command command) throws PigpioException {
GPIO lgpio = this.gpio;
Consumer<State> lupdateStatus = this.updateStatus;
if (lgpio == null || lupdateStatus == null) {
logger.warn("An attempt to submit a command was made when pigpiod was offline: {}", command.toString());
return;
}
if (command instanceof RefreshType) {
lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
}
}
@Override
public void handleCommand(Command command) {
if (command instanceof RefreshType) {
try {
updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue()));
} catch (PigpioException e) {
logger.warn("Unknown pigpio exception while handling Refresh", e);
public void dispose() {
synchronized (debounceLock) {
GPIO lgpio = this.gpio;
updateStatus = null;
if (lgpio != null) {
JPigpio ljPigpio = lgpio.getPigpio();
if (ljPigpio != null) {
try {
ljPigpio.removeCallback(listener);
} catch (PigpioException e) {
// Best effort to remove listener,
// the command socket could already be dead.
if (e.getErrorCode() != -99999999) {
logger.debug("Dispose exception :", e);
}
}
}
}
}
}

View File

@ -12,10 +12,17 @@
*/
package org.openhab.binding.gpio.internal.handler;
import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
import java.math.BigDecimal;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gpio.internal.NoGpioIdException;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpio.internal.ChannelConfigurationException;
import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
@ -33,16 +40,19 @@ import eu.xeli.jpigpio.PigpioException;
*
* @author Nils Bauer - Initial contribution
* @author Jan N. Klug - Channel redesign
* @author Jeremy Rumpf - Refactored for network disruptions
*/
@NonNullByDefault
public class PigpioDigitalOutputHandler implements ChannelHandler {
/** The logger. */
private final Logger logger = LoggerFactory.getLogger(PigpioDigitalOutputHandler.class);
private final GPIOOutputConfiguration configuration;
private final GPIO gpio;
private final Consumer<State> updateStatus;
private final ScheduledExecutorService scheduler;
private final Integer gpioId;
private Integer pulseTimeout = -1;
private @Nullable String pulseCommand = "";
private @Nullable GPIO gpio;
private @Nullable Consumer<State> updateStatus;
/**
* Constructor for PigpioDigitalOutputHandler
@ -52,34 +62,183 @@ public class PigpioDigitalOutputHandler implements ChannelHandler {
* @param updateStatus Is called when the state should be changed
*
* @throws PigpioException Can be thrown by Pigpio
* @throws NoGpioIdException Is thrown when no gpioId is defined
* @throws ChannelConfigurationException Thrown on configuration error
*/
public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, JPigpio jPigpio,
Consumer<State> updateStatus) throws PigpioException, NoGpioIdException {
public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, ScheduledExecutorService scheduler,
Consumer<State> updateStatus) throws PigpioException, ChannelConfigurationException {
this.configuration = configuration;
this.gpioId = configuration.gpioId;
this.scheduler = scheduler;
this.updateStatus = updateStatus;
Integer gpioId = configuration.gpioId;
if (gpioId == null) {
throw new NoGpioIdException();
if (this.gpioId <= 0) {
throw new ChannelConfigurationException("Invalid gpioId value.");
}
if (configuration.pulse.compareTo(BigDecimal.ZERO) > 0) {
try {
this.pulseTimeout = configuration.pulse.intValue();
} catch (Exception e) {
throw new ChannelConfigurationException("Invalid expire value.");
}
}
if (configuration.pulseCommand.length() > 0) {
this.pulseCommand = configuration.pulseCommand.toUpperCase();
if (!PULSE_ON.equals(pulseCommand) && !PULSE_OFF.equals(pulseCommand)
&& !PULSE_BLINK.equals(pulseCommand)) {
throw new ChannelConfigurationException("Invalid pulseCommand value.");
}
}
}
/**
* Future to track pulse commands.
*/
private @Nullable Future<?> pulseJob = null;
private @Nullable OnOffType lastPulseCommand;
/**
* Used to only keep a single gpio command handle in flight
* at a time.
*/
private Object handleLock = new Object();
@Override
public void handleCommand(Command command) throws PigpioException {
synchronized (handleLock) {
GPIO lgpio = this.gpio;
Consumer<State> lupdateStatus = this.updateStatus;
Future<?> job = this.pulseJob;
if (lgpio == null || lupdateStatus == null) {
logger.warn("An attempt to submit a command was made when the pigpiod was offline: {}",
command.toString());
return;
}
if (command instanceof RefreshType) {
lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
} else if (command instanceof OnOffType) {
lgpio.setValue(configuration.invert != (OnOffType.ON.equals(command)));
lupdateStatus.accept((State) command);
if (this.pulseTimeout > 0 && this.pulseCommand != null) {
if (job != null) {
job.cancel(false);
}
this.pulseJob = scheduler.schedule(() -> handlePulseCommand(command), this.pulseTimeout,
TimeUnit.MILLISECONDS);
}
}
}
}
public void handlePulseCommand(@Nullable Command command) {
OnOffType eCommand = OnOffType.OFF;
try {
synchronized (handleLock) {
GPIO lgpio = this.gpio;
Consumer<State> lupdateStatus = this.updateStatus;
Future<?> job = this.pulseJob;
if (lgpio == null) {
return;
}
if (command instanceof OnOffType) {
if (this.pulseCommand != null) {
if (PULSE_ON.equals(this.pulseCommand)) {
eCommand = OnOffType.ON;
} else if (PULSE_OFF.equals(this.pulseCommand)) {
eCommand = OnOffType.OFF;
} else if (PULSE_BLINK.equals(this.pulseCommand)) {
if (OnOffType.ON.equals(command)) {
eCommand = OnOffType.OFF;
} else if (OnOffType.OFF.equals(command)) {
eCommand = OnOffType.ON;
}
}
} else {
if (OnOffType.ON.equals(command)) {
eCommand = OnOffType.OFF;
} else if (OnOffType.OFF.equals(command)) {
eCommand = OnOffType.ON;
}
}
logger.debug("gpio pulse command : {} {}", this.gpioId, eCommand.toString());
lgpio.setValue(configuration.invert != (OnOffType.ON.equals(eCommand)));
if (lupdateStatus != null) {
lupdateStatus.accept((State) eCommand);
}
lastPulseCommand = eCommand;
if (PULSE_BLINK.equals(this.pulseCommand) && this.pulseTimeout > 0) {
final OnOffType feCommand = eCommand;
if (job != null) {
job.cancel(false);
}
this.pulseJob = scheduler.schedule(() -> handlePulseCommand(feCommand), this.pulseTimeout,
TimeUnit.MILLISECONDS);
}
}
}
} catch (Exception e) {
logger.warn(
"Pulse command exception, {} command may not have been received by pigpiod resulting in an unknown state:",
eCommand.toString(), e);
}
}
/**
* Configures the GPIO pin for OUTPUT.
*/
public void listen(@Nullable JPigpio jPigpio) throws PigpioException {
if (jPigpio == null) {
this.gpio = null;
return;
}
GPIO lgpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT);
this.gpio = lgpio;
lgpio.setDirection(JPigpio.PI_OUTPUT);
scheduleBlink();
}
private void scheduleBlink() {
synchronized (handleLock) {
Future<?> job = this.pulseJob;
if (this.pulseTimeout > 0 && PULSE_BLINK.equals(configuration.pulseCommand)) {
if (job != null) {
job.cancel(false);
}
if (this.lastPulseCommand != null) {
scheduler.schedule(() -> handlePulseCommand(this.lastPulseCommand), this.pulseTimeout,
TimeUnit.MILLISECONDS);
} else {
this.pulseJob = scheduler.schedule(() -> handlePulseCommand(OnOffType.OFF), this.pulseTimeout,
TimeUnit.MILLISECONDS);
}
}
}
this.gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT);
}
@Override
public void handleCommand(Command command) {
if (command instanceof RefreshType) {
try {
updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue()));
} catch (PigpioException e) {
logger.warn("Unknown pigpio exception while handling Refresh", e);
}
}
if (command instanceof OnOffType) {
try {
gpio.setValue(configuration.invert != (OnOffType.ON.equals(command)));
} catch (PigpioException e) {
logger.warn("An error occured while changing the gpio value: {}", e.getMessage());
public void dispose() {
synchronized (handleLock) {
Future<?> job = this.pulseJob;
if (job != null) {
job.cancel(true);
}
this.updateStatus = null;
this.gpio = null;
}
}
}

View File

@ -16,13 +16,16 @@ import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gpio.internal.InvalidPullUpDownException;
import org.openhab.binding.gpio.internal.NoGpioIdException;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpio.internal.ChannelConfigurationException;
import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration;
import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration;
import org.openhab.binding.gpio.internal.configuration.PigpioConfiguration;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
@ -30,6 +33,8 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,6 +49,7 @@ import eu.xeli.jpigpio.PigpioSocket;
*
* @author Nils Bauer - Initial contribution
* @author Jan N. Klug - Channel redesign
* @author Jeremy Rumpf - Improve JPigpio connection handling
*/
@NonNullByDefault
public class PigpioRemoteHandler extends BaseThingHandler {
@ -61,56 +67,355 @@ public class PigpioRemoteHandler extends BaseThingHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
ChannelHandler channelHandler = channelHandlers.get(channelUID);
if (channelHandler != null) {
channelHandler.handleCommand(command);
try {
synchronized (this.connectionLock) {
ChannelHandler channelHandler = channelHandlers.get(channelUID);
if (channelHandler == null || !(ThingStatus.ONLINE.equals(thing.getStatus()))) {
// We raced with connectPollWorker and lost
return;
}
if (channelHandler instanceof PigpioDigitalInputHandler inputHandler) {
try {
inputHandler.handleCommand(command);
} catch (PigpioException pe) {
logger.warn("Input command exception on channel {} {}", channelUID, pe.toString());
if (pe.getErrorCode() == -99999999) {
runDisconnectActions();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
pe.getLocalizedMessage());
}
}
} else if (channelHandler instanceof PigpioDigitalOutputHandler outputHandler) {
try {
outputHandler.handleCommand(command);
} catch (PigpioException pe) {
logger.warn("Output command exception on channel {} {}", channelUID, pe.toString());
if (pe.getErrorCode() == -99999999) {
runDisconnectActions();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
pe.getLocalizedMessage());
}
}
} else {
logger.warn("Command received for an unknown channel: {}", channelUID);
}
}
} catch (Exception e) {
logger.warn("Command exception on channel {} {}", channelUID, e.toString());
}
}
protected PigpioConfiguration config = new PigpioConfiguration();
protected @Nullable JPigpio jPigpio = null;
@Override
public void initialize() {
PigpioConfiguration config = getConfigAs(PigpioConfiguration.class);
String host = config.host;
int port = config.port;
JPigpio jPigpio;
if (host == null) {
PigpioConfiguration lconfig = getConfigAs(PigpioConfiguration.class);
this.config = lconfig;
if (lconfig.host == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Cannot connect to PiGPIO Service on remote raspberry. IP address not set.");
return;
}
try {
jPigpio = new PigpioSocket(host, port);
updateStatus(ThingStatus.ONLINE);
} catch (PigpioException e) {
if (e.getErrorCode() == PigpioException.PI_BAD_SOCKET_PORT) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port out of range");
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
e.getLocalizedMessage());
}
if (lconfig.port < 1 && lconfig.port > 65535) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Cannot connect to PiGPIO Service on remote raspberry. Invalid Port.");
return;
}
thing.getChannels().forEach(channel -> {
createChannelHandlers();
logger.debug("gpio binding initialized");
connectionJob = scheduler.submit(() -> {
connectionPollWorker();
});
}
protected void clearChannelHandlers() {
for (ChannelHandler handler : channelHandlers.values()) {
handler.dispose();
}
channelHandlers.clear();
}
protected void createChannelHandlers() {
clearChannelHandlers();
this.getThing().getChannels().forEach(channel -> {
ChannelUID channelUID = channel.getUID();
ChannelTypeUID type = channel.getChannelTypeUID();
try {
if (CHANNEL_TYPE_DIGITAL_INPUT.equals(type)) {
GPIOInputConfiguration configuration = channel.getConfiguration().as(GPIOInputConfiguration.class);
channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, jPigpio, scheduler,
this.channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, scheduler,
state -> updateState(channelUID.getId(), state)));
} else if (CHANNEL_TYPE_DIGITAL_OUTPUT.equals(type)) {
GPIOOutputConfiguration configuration = channel.getConfiguration()
.as(GPIOOutputConfiguration.class);
channelHandlers.put(channelUID, new PigpioDigitalOutputHandler(configuration, jPigpio,
state -> updateState(channelUID.getId(), state)));
PigpioDigitalOutputHandler handler = new PigpioDigitalOutputHandler(configuration, scheduler,
state -> updateState(channelUID.getId(), state));
this.channelHandlers.put(channelUID, handler);
}
} catch (PigpioException e) {
logger.warn("Failed to initialize {}: {}", channelUID, e.getMessage());
} catch (InvalidPullUpDownException e) {
logger.warn("Failed to initialize {}: Invalid Pull Up/Down resistor configuration", channelUID);
} catch (NoGpioIdException e) {
logger.warn("Failed to initialize {}: GpioId is not set", channelUID);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
String.format("Failed to initialize channel {} {}", channelUID, e.getLocalizedMessage()));
} catch (ChannelConfigurationException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
String.format("Invalid configuration for channel {} {}", channelUID, e.getLocalizedMessage()));
}
});
logger.debug("gpio channels initialized");
}
protected void setChannelJPigpio(@Nullable JPigpio jPigpio) throws PigpioException {
if (this.channelHandlers.isEmpty()) {
createChannelHandlers();
}
for (ChannelHandler handler : this.channelHandlers.values()) {
handler.listen(jPigpio);
}
logger.debug("gpio jPigpio listening");
}
private @Nullable Future<?> connectionJob = null;
/**
* Syncronizes all socket related code
* to avoid racing.
*/
private Object connectionLock = new Object();
protected void killConnectionPoll() {
if (this.connectionJob != null) {
synchronized (this.connectionLock) {
if (this.connectionJob != null) {
Future<?> job = this.connectionJob;
this.connectionJob = null;
if (job != null) {
logger.debug("gpio connection poll : killing");
job.cancel(true);
}
}
}
}
}
protected void connectionPollWorker() {
Thing thing = this.getThing();
synchronized (connectionLock) {
ThingStatus currentStatus = thing.getStatus();
JPigpio ljPigpio = this.jPigpio;
if (ThingStatus.ONLINE.equals(currentStatus) && ljPigpio != null) {
// We are ONLINE and jPigpio is instantiated, this is the normal path
try {
logger.debug("gpio connection poll : CMD_TICK");
ljPigpio.getCurrentTick();
} catch (PigpioException e) {
logger.debug("gpio connection poll : disconnect");
runDisconnectActions();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
e.getLocalizedMessage());
// We disconnected, reschedule ourselves to try a reconnect.
// First, try a quick reconnect if the user specified a long(ish) interval
int interval = this.config.heartBeatInterval;
if (interval > 1000) {
interval = 1000;
}
this.connectionJob = scheduler.schedule(() -> {
connectionPollWorker();
}, interval, TimeUnit.MILLISECONDS);
logger.warn("Pigpiod disconnected : {}", this.config.host);
return;
}
} else {
// We are OFFLINE and jPigpio may or may not be instantiated
try {
if (ljPigpio == null) {
// First initialization or re-initialization after dispose()
// jPigpio is not up and running yet.
logger.debug("gpio connection poll : connecting");
ljPigpio = new PigpioSocket(this.config.host, this.config.port);
this.jPigpio = ljPigpio;
setChannelJPigpio(ljPigpio);
updateStatus(ThingStatus.ONLINE);
runConnectActions();
} else {
// jPigpio is instantiated, but not connected.
// Use it's internal reconnect logic.
logger.debug("gpio connection poll : reconnecting");
ljPigpio.reconnect();
// jPigpio listeners are not re-established after reconnect.
// We need to reinject them into the channel handlers.
setChannelJPigpio(ljPigpio);
updateStatus(ThingStatus.ONLINE);
runReconnectActions();
}
logger.debug("Pigpiod connected : {}", this.config.host);
} catch (PigpioException e) {
logger.debug("gpio connection poll : failed, {}", e.getErrorCode());
if (currentStatus.equals(ThingStatus.ONLINE) || currentStatus.equals(ThingStatus.INITIALIZING)) {
runDisconnectActions();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
e.getLocalizedMessage());
}
}
}
if (this.config.heartBeatInterval > 0) {
this.connectionJob = scheduler.schedule(() -> {
connectionPollWorker();
}, this.config.heartBeatInterval, TimeUnit.MILLISECONDS);
} else {
// User disabled periodic connections, one shot?
logger.debug("gpio connection poll : disabled");
this.connectionJob = null;
}
}
}
protected void runConnectActions() throws PigpioException {
if (this.config.inputConnectAction != null) {
if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
refreshInputChannels();
}
}
if (this.config.outputConnectAction != null) {
if (ACTION_ALL_ON.equals(this.config.outputConnectAction)) {
setOutputChannels(OnOffType.ON);
} else if (ACTION_ALL_OFF.equals(this.config.outputConnectAction)) {
setOutputChannels(OnOffType.OFF);
} else if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
refreshOutputChannels();
}
}
}
protected void runReconnectActions() throws PigpioException {
if (this.config.inputConnectAction != null) {
if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
refreshInputChannels();
}
}
if (this.config.outputConnectAction != null) {
if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
refreshOutputChannels();
}
}
}
protected void runDisconnectActions() {
if (this.config.inputDisconnectAction != null) {
if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
undefInputChannels();
}
}
if (this.config.outputDisconnectAction != null) {
if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
undefOutputChannels();
}
}
}
protected void refreshInputChannels() throws PigpioException {
logger.debug("gpio refresh input channels");
for (ChannelUID channelUID : channelHandlers.keySet()) {
ChannelHandler handler = channelHandlers.get(channelUID);
if (handler instanceof PigpioDigitalInputHandler) {
handler.handleCommand(RefreshType.REFRESH);
postCommand(channelUID, RefreshType.REFRESH);
}
}
}
protected void refreshOutputChannels() throws PigpioException {
logger.debug("gpio refresh output channels");
for (ChannelUID channelUID : this.channelHandlers.keySet()) {
ChannelHandler handler = this.channelHandlers.get(channelUID);
if (handler instanceof PigpioDigitalOutputHandler) {
handler.handleCommand(RefreshType.REFRESH);
postCommand(channelUID, RefreshType.REFRESH);
}
}
}
protected void undefInputChannels() {
logger.debug("gpio undef input channels");
for (ChannelUID channelUID : this.channelHandlers.keySet()) {
ChannelHandler handler = this.channelHandlers.get(channelUID);
if (handler instanceof PigpioDigitalInputHandler) {
updateState(channelUID, UnDefType.UNDEF);
}
}
}
protected void undefOutputChannels() {
logger.debug("gpio undef output channels");
for (ChannelUID channelUID : channelHandlers.keySet()) {
ChannelHandler handler = channelHandlers.get(channelUID);
if (handler instanceof PigpioDigitalOutputHandler) {
updateState(channelUID, UnDefType.UNDEF);
}
}
}
protected void setOutputChannels(OnOffType command) throws PigpioException {
logger.debug("gpio setting output channels: {}", command.toString());
for (ChannelUID channelUID : this.channelHandlers.keySet()) {
ChannelHandler handler = this.channelHandlers.get(channelUID);
if (handler instanceof PigpioDigitalOutputHandler) {
handler.handleCommand(command);
postCommand(channelUID, command);
}
}
}
@Override
public void dispose() {
try {
synchronized (this.connectionLock) {
JPigpio ljPigpio = this.jPigpio;
killConnectionPoll();
if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
undefInputChannels();
}
if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
undefOutputChannels();
}
clearChannelHandlers();
if (ljPigpio != null) {
try {
ljPigpio.gpioTerminate();
this.jPigpio = null;
} catch (PigpioException e) {
// Best effort at a socket shutdown
}
}
}
logger.debug("gpio disposed");
} catch (Exception e) {
logger.debug("Dispose exception :", e);
}
super.dispose();
}
}

View File

@ -10,8 +10,36 @@ thing-type.gpio.pigpio-remote.description = The remote pigpio thing represents a
# thing types config
thing-type.config.gpio.pigpio-remote.heartBeatInterval.label = Heart Beat Interval
thing-type.config.gpio.pigpio-remote.heartBeatInterval.description = Time in ms to send CMD_TICK calls on the communication socket. Used to detect and recover from pigpiod disconnects.
thing-type.config.gpio.pigpio-remote.host.label = Network Address
thing-type.config.gpio.pigpio-remote.host.description = Network address of the Raspberry Pi.
thing-type.config.gpio.pigpio-remote.inputConnectAction.label = Input Channel Connect Action
thing-type.config.gpio.pigpio-remote.inputConnectAction.description = When a pigpiod connection is first established after binding INITIALIZATION. The desired action to perform on input channels. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.inputConnectAction.option.REFRESH = Refresh Channel
thing-type.config.gpio.pigpio-remote.inputConnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.inputDisconnectAction.label = Input Channel Disconnect Action
thing-type.config.gpio.pigpio-remote.inputDisconnectAction.description = When a pigpiod disconnect is encountered. The desired action to perform on input channel. SETUNDEF: Set all configured channels to UNDEF. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.inputDisconnectAction.option.SETUNDEF = Set Undef
thing-type.config.gpio.pigpio-remote.inputDisconnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.inputReconnectAction.label = Input Channel Reconnect Action
thing-type.config.gpio.pigpio-remote.inputReconnectAction.description = When a pigpiod connection is re-established after being disconnected. The desired action to perform on input channels. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.inputReconnectAction.option.REFRESH = Refresh Channel
thing-type.config.gpio.pigpio-remote.inputReconnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.outputConnectAction.label = Output Channel Connect Action
thing-type.config.gpio.pigpio-remote.outputConnectAction.description = When a pigpiod connection is first established after binding INITIALIZATION. The desired action to perform on outputs. ALLOFF: Update the GPIO pin to OFF. ALLON: Update the GPIO pin to ON. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.outputConnectAction.option.ALLOFF = All OFF
thing-type.config.gpio.pigpio-remote.outputConnectAction.option.ALLON = All ON
thing-type.config.gpio.pigpio-remote.outputConnectAction.option.REFRESH = Refresh Channel
thing-type.config.gpio.pigpio-remote.outputConnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.outputDisconnectAction.label = Output Channel Disconnect Action
thing-type.config.gpio.pigpio-remote.outputDisconnectAction.description = When a pigpiod disconnect is encountered. The desired action to perform on outputs. SETUNDEF: Set all configured channels to UNDEF. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.outputDisconnectAction.option.SETUNDEF = Set Undef
thing-type.config.gpio.pigpio-remote.outputDisconnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.outputReconnectAction.label = Output Channel Reconnect Action
thing-type.config.gpio.pigpio-remote.outputReconnectAction.description = When a pigpiod connection is re-established after being disconnected. The desired action to perform on outputs. REFRESH: Send a REFRESH command to the channel. NOTHING: Leave all channels at their current state.
thing-type.config.gpio.pigpio-remote.outputReconnectAction.option.REFRESH = Refresh Channel
thing-type.config.gpio.pigpio-remote.outputReconnectAction.option.NOTHING = Do Nothing
thing-type.config.gpio.pigpio-remote.port.label = Port
thing-type.config.gpio.pigpio-remote.port.description = Port of pigpio on the remote Raspberry Pi.
@ -25,10 +53,16 @@ channel-type.gpio.pigpio-digital-output.description = Set digital state of a GPI
# channel types config
channel-type.config.gpio.pigpio-digital-input.debouncingTime.label = Delay Time
channel-type.config.gpio.pigpio-digital-input.debouncingTime.description = Time in ms to double check if value hasn't changed
channel-type.config.gpio.pigpio-digital-input.debouncingTime.description = Time in ms to double check if value hasn't changed. Be sure that the maximum latency of your network is not greater than two times this value.
channel-type.config.gpio.pigpio-digital-input.edgeMode.label = Edge Detection Mode
channel-type.config.gpio.pigpio-digital-input.edgeMode.description = Edge detection mode of the GPIO pin
channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_EITHER = Either Edge
channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_FALLING = Falling Edge
channel-type.config.gpio.pigpio-digital-input.edgeMode.option.EDGE_RISING = Rising Edge
channel-type.config.gpio.pigpio-digital-input.gpioId.label = GPIO Pin
channel-type.config.gpio.pigpio-digital-input.gpioId.description = GPIO pin to use as input
channel-type.config.gpio.pigpio-digital-input.invert.label = Invert
channel-type.config.gpio.pigpio-digital-input.invert.description = Inverts the GPIO pin state from the channel state. Setting this to true can simulate an active low GPIO pin.
channel-type.config.gpio.pigpio-digital-input.pullupdown.label = Pull Up/Down Resistor
channel-type.config.gpio.pigpio-digital-input.pullupdown.description = Configure Pull Up/Down Resistor of GPIO pin
channel-type.config.gpio.pigpio-digital-input.pullupdown.option.OFF = Off
@ -37,3 +71,11 @@ channel-type.config.gpio.pigpio-digital-input.pullupdown.option.UP = Pull Up
channel-type.config.gpio.pigpio-digital-output.gpioId.label = GPIO Pin
channel-type.config.gpio.pigpio-digital-output.gpioId.description = GPIO pin to use as output
channel-type.config.gpio.pigpio-digital-output.invert.label = Invert
channel-type.config.gpio.pigpio-digital-output.invert.description = Inverts the GPIO pin state from the channel state. Setting this to true can simulate an active low GPIO pin.
channel-type.config.gpio.pigpio-digital-output.pulse.label = Pulse
channel-type.config.gpio.pigpio-digital-output.pulse.description = Issues the pulse command after the given number of milliseconds. Used to pulse outputs.
channel-type.config.gpio.pigpio-digital-output.pulseCommand.label = Pulse Command
channel-type.config.gpio.pigpio-digital-output.pulseCommand.description = The command to issue after the pulse duration to complete the pulse. Blink will alternate ON/OFF, useful for beacons or flashing leds.
channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.OFF = Off
channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.ON = On
channel-type.config.gpio.pigpio-digital-output.pulseCommand.option.BLINK = Blink

View File

@ -18,6 +18,7 @@
<context>network_address</context>
<label>Network Address</label>
<description>Network address of the Raspberry Pi.</description>
<default>127.0.0.1</default>
</parameter>
<parameter name="port" type="integer" min="0" max="65535">
<context>port</context>
@ -25,6 +26,141 @@
<description>Port of pigpio on the remote Raspberry Pi.</description>
<default>8888</default>
</parameter>
<parameter name="heartBeatInterval" type="integer" min="100" max="2147483647">
<context>time</context>
<label>Heart Beat Interval</label>
<description>
Time in ms to send CMD_TICK calls on the communication socket.
Used to detect and recover from pigpiod
disconnects.
</description>
<default>30000</default>
<advanced>true</advanced>
</parameter>
<parameter name="inputConnectAction" type="text">
<label>Input Channel Connect Action</label>
<description>
When a pigpiod connection is first established after
binding INITIALIZATION.
The desired action to
perform on input channels.
REFRESH: Send a REFRESH command
to the channel.
NOTHING: Leave
all channels
at their
current
state.
</description>
<options>
<option value="REFRESH">Refresh Channel</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
<parameter name="inputDisconnectAction" type="text">
<label>Input Channel Disconnect Action</label>
<description>
When a pigpiod disconnect is encountered.
The desired action to perform on input channel.
SETUNDEF: Set
all configured channels to UNDEF.
NOTHING: Leave all channels at their current state.
</description>
<options>
<option value="SETUNDEF">Set Undef</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
<parameter name="inputReconnectAction" type="text">
<label>Input Channel Reconnect Action</label>
<description>
When a pigpiod connection is re-established after being disconnected.
The desired action to perform on
input channels.
REFRESH: Send a REFRESH command
to the channel.
NOTHING: Leave all
channels at their
current
state.
</description>
<options>
<option value="REFRESH">Refresh Channel</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
<parameter name="outputConnectAction" type="text">
<label>Output Channel Connect Action</label>
<description>
When a pigpiod connection is first established after
binding INITIALIZATION.
The desired action to
perform on outputs.
ALLOFF: Update the GPIO pin to OFF.
ALLON: Update the GPIO pin to ON.
REFRESH: Send a REFRESH
command
to the channel.
NOTHING: Leave all
channels at their current
state.
</description>
<options>
<option value="ALLOFF">All OFF</option>
<option value="ALLON">All ON</option>
<option value="REFRESH">Refresh Channel</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
<parameter name="outputDisconnectAction" type="text">
<label>Output Channel Disconnect Action</label>
<description>
When a pigpiod disconnect is encountered.
The desired action to perform on outputs.
SETUNDEF: Set all
configured channels to UNDEF.
NOTHING: Leave all channels at their current state.
</description>
<options>
<option value="SETUNDEF">Set Undef</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
<parameter name="outputReconnectAction" type="text">
<label>Output Channel Reconnect Action</label>
<description>
When a pigpiod connection is re-established after being disconnected.
The desired action to perform on
outputs.
REFRESH: Send a REFRESH command
to the channel.
NOTHING: Leave all
channels at their current
state.
</description>
<options>
<option value="REFRESH">Refresh Channel</option>
<option value="NOTHING">Do Nothing</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NOTHING</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
@ -35,18 +171,28 @@
<state readOnly="true"/>
<config-description>
<parameter name="gpioId" type="integer" required="true">
<parameter name="gpioId" type="integer" required="true" min="1" max="2147483647">
<label>GPIO Pin</label>
<description>GPIO pin to use as input</description>
</parameter>
<parameter name="invert" type="boolean">
<default>false</default>
<label>Invert</label>
<description>
Inverts the GPIO pin state from the channel state.
Setting this to true can simulate an active low GPIO
pin.
</description>
</parameter>
<parameter name="debouncingTime" type="integer" min="0">
<context>time</context>
<label>Delay Time</label>
<description>Time in ms to double check if value hasn't changed</description>
<description>
Time in ms to double check if value hasn't changed.
Be sure that the maximum latency of your
network is
not greater than two times this value.
</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
@ -61,6 +207,17 @@
<limitToOptions>true</limitToOptions>
<default>OFF</default>
</parameter>
<parameter name="edgeMode" type="text">
<label>Edge Detection Mode</label>
<description>Edge detection mode of the GPIO pin</description>
<options>
<option value="EDGE_EITHER">Either Edge</option>
<option value="EDGE_FALLING">Falling Edge</option>
<option value="EDGE_RISING">Rising Edge</option>
</options>
<limitToOptions>true</limitToOptions>
<default>EDGE_EITHER</default>
</parameter>
</config-description>
</channel-type>
@ -76,6 +233,30 @@
<parameter name="invert" type="boolean">
<default>false</default>
<label>Invert</label>
<description>
Inverts the GPIO pin state from the channel state.
Setting this to true can simulate an active low GPIO
pin.
</description>
</parameter>
<parameter name="pulse" type="integer" min="0" max="2147483647">
<label>Pulse</label>
<description>Issues the pulse command after the given number of milliseconds. Used to pulse outputs.</description>
<default>0</default>
</parameter>
<parameter name="pulseCommand" type="text">
<label>Pulse Command</label>
<description>The command to issue after the pulse duration to complete the pulse. Blink will alternate ON/OFF,
useful for beacons
or
flashing leds.</description>
<options>
<option value="OFF">Off</option>
<option value="ON">On</option>
<option value="BLINK">Blink</option>
</options>
<limitToOptions>true</limitToOptions>
<default>OFF</default>
</parameter>
</config-description>
</channel-type>