diff --git a/bundles/org.openhab.binding.gpio/README.md b/bundles/org.openhab.binding.gpio/README.md
index 863d226d20a..51e88383af2 100644
--- a/bundles/org.openhab.binding.gpio/README.md
+++ b/bundles/org.openhab.binding.gpio/README.md
@@ -1,19 +1,19 @@
# GPIO Binding
-This binding adds GPIO support via the pigpio daemon to openhab.
-It requires the 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 () 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"}
```
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java
similarity index 69%
rename from bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java
rename to bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java
index 76f10c332d2..1569dd3d1f6 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java
@@ -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);
+ }
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java
index f23e90dd001..305ed05166b 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java
@@ -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";
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java
deleted file mode 100644
index 26005f95e11..00000000000
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java
+++ /dev/null
@@ -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;
-}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java
index 681097d4d82..120d84c9c35 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java
@@ -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?
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java
index 3121a348cfb..abf6f6131bd 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java
@@ -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;
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java
index 750fe6e49ef..d5073640814 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java
@@ -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";
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java
index 4e2ead631b7..9acb3d22bcb 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java
@@ -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;
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java
index 3958c688573..a87373b0248 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java
@@ -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();
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java
index 65086458a00..5ecb5daa73f 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java
@@ -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 updateStatus;
+ private final ScheduledExecutorService scheduler;
+ private final Integer gpioId;
+ private @Nullable GPIO gpio;
+ private @Nullable Consumer 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 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 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 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 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);
+ }
+ }
+ }
}
}
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java
index a48874c18ff..cb1dd34bab7 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java
@@ -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 updateStatus;
+ private final ScheduledExecutorService scheduler;
+ private final Integer gpioId;
+ private Integer pulseTimeout = -1;
+ private @Nullable String pulseCommand = "";
+ private @Nullable GPIO gpio;
+ private @Nullable Consumer 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 updateStatus) throws PigpioException, NoGpioIdException {
+ public PigpioDigitalOutputHandler(GPIOOutputConfiguration configuration, ScheduledExecutorService scheduler,
+ Consumer 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 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 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;
}
}
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java
index 3f561cd7449..f2ccd8fc846 100644
--- a/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java
+++ b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java
@@ -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();
}
}
diff --git a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties
index 564f6dc4fac..207beccff84 100644
--- a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties
+++ b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties
@@ -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
diff --git a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml
index f71d13ca666..8e17e0e559a 100644
--- a/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml
+++ b/bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml
@@ -18,6 +18,7 @@
network_addressNetwork address of the Raspberry Pi.
+ 127.0.0.1port
@@ -25,6 +26,141 @@
Port of pigpio on the remote Raspberry Pi.8888
+
+ time
+
+
+ Time in ms to send CMD_TICK calls on the communication socket.
+ Used to detect and recover from pigpiod
+ disconnects.
+
+ 30000
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+ true
+ NOTHING
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+ true
+ NOTHING
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+ true
+ NOTHING
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+ true
+ NOTHING
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+ true
+ NOTHING
+ true
+
+
+
+
+ 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.
+
+
+
+
+
+ true
+ NOTHING
+ true
+
@@ -35,18 +171,28 @@
-
+ GPIO pin to use as inputfalse
+
+ Inverts the GPIO pin state from the channel state.
+ Setting this to true can simulate an active low GPIO
+ pin.
+ time
- Time in ms to double check if value hasn't changed
+
+ 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.
+ 10true
@@ -61,6 +207,17 @@
trueOFF
+
+
+ Edge detection mode of the GPIO pin
+
+
+
+
+
+ true
+ EDGE_EITHER
+
@@ -76,6 +233,30 @@
false
+
+ Inverts the GPIO pin state from the channel state.
+ Setting this to true can simulate an active low GPIO
+ pin.
+
+
+
+
+ Issues the pulse command after the given number of milliseconds. Used to pulse outputs.
+ 0
+
+
+
+ The command to issue after the pulse duration to complete the pulse. Blink will alternate ON/OFF,
+ useful for beacons
+ or
+ flashing leds.
+
+
+
+
+
+ true
+ OFF