]> git.basschouten.com Git - openhab-addons.git/commitdiff
[GPIO] Update GPIO binding to fix issues and provide new functionality (#13643)
authorJeremy Rumpf <82414029+jeremyrumpf@users.noreply.github.com>
Mon, 30 Oct 2023 20:20:42 +0000 (16:20 -0400)
committerGitHub <noreply@github.com>
Mon, 30 Oct 2023 20:20:42 +0000 (21:20 +0100)
* [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>
15 files changed:
bundles/org.openhab.binding.gpio/README.md
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java [new file with mode: 0644]
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/GPIOBindingConstants.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/InvalidPullUpDownException.java [deleted file]
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/NoGpioIdException.java [deleted file]
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOConfiguration.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOInputConfiguration.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/GPIOOutputConfiguration.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/configuration/PigpioConfiguration.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/ChannelHandler.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalInputHandler.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioDigitalOutputHandler.java
bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/handler/PigpioRemoteHandler.java
bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/i18n/gpio.properties
bundles/org.openhab.binding.gpio/src/main/resources/OH-INF/thing/pigpio-remote.xml

index 863d226d20ae614fd3bc12ac31e3ca127eba32bd..51e88383af2e0b98a06592c99d16d30700465a68 100644 (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
+
+Input channels provide a read-only value of the gpio pin state using the `OnOffType` datatype.
 
-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).
+GPIO Pin:
 
-### GPIO digital output channel
+The gpio pin number on the Pi that the channel will monitor.
 
-Set the number of the pin in `gpioId`.
-If you want to invert the value, set `invert` to true.
+Invert:
 
-## Full Example
+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: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]
-}
+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
+
+                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/ChannelConfigurationException.java b/bundles/org.openhab.binding.gpio/src/main/java/org/openhab/binding/gpio/internal/ChannelConfigurationException.java
new file mode 100644 (file)
index 0000000..1569dd3
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * 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 a channel configuration is invalid
+ *
+ * @author Jeremy Rumpf - Initial contribution
+ */
+@NonNullByDefault
+public class ChannelConfigurationException extends Exception {
+    private static final long serialVersionUID = -1281107134439928767L;
+
+    public ChannelConfigurationException(String message) {
+        super(message);
+    }
+}
index f23e90dd0019356e3cd6f420c95a1c1594e35c74..305ed05166bdea5b277a4e7cdc8052e091414c45 100644 (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";
 }
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/InvalidPullUpDownException.java
deleted file mode 100644 (file)
index 76f10c3..0000000
+++ /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 invalid GPIO pin Pull Up/Down resistor configuration is set
- *
- * @author Martin Dagarin - Initial contribution
- */
-@NonNullByDefault
-public class InvalidPullUpDownException extends Exception {
-    private static final long serialVersionUID = -1281107134439928767L;
-}
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 (file)
index 26005f9..0000000
+++ /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;
-}
index 681097d4d82f2a0b174f489df6e0a54e8ba153fa..120d84c9c353f37107e30356bcb6a63a41269055 100644 (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?
index 3121a348cfb89b879598f5f5e2628dcaec7a2988..abf6f6131bda14ebca76099b34c28eddf2e35abc 100644 (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;
 }
index 750fe6e49efc084a1084c513eaa8dcdb2c95f376..d507364081496f0eb43ce6a9aefa3ad586c795a7 100644 (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";
 }
index 4e2ead631b76d45d442812025c026d53b9f2b7e8..9acb3d22bcb0dae197cc2fabc1b0e9a1d9fcc088 100644 (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;
 }
index 3958c688573312e3ea8fb96ac133d3ceba3da8d0..a87373b02486ce01b1dcb764df050d7d10306824 100644 (file)
 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();
 }
index 65086458a0071080b3467fd28e4ea1bbd12e6042..5ecb5daa73f1f928d8897934de29c76b45adc0a1 100644 (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.");
+        }
+
+        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.");
         }
-        gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_INPUT);
-        jPigpio.gpioSetAlertFunc(gpio.getPin(), (gpio, level, tick) -> {
-            lastChanged = new Date();
-            Date thisChange = new Date();
+
+        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);
-        });
-        jPigpio.gpioSetPullUpDown(gpio.getPin(), pullupdown);
+        } 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) {
+    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) {
-            try {
-                updateStatus.accept(OnOffType.from(configuration.invert != gpio.getValue()));
-            } catch (PigpioException e) {
-                logger.warn("Unknown pigpio exception while handling Refresh", e);
+            lupdateStatus.accept(OnOffType.from(configuration.invert != lgpio.getValue()));
+        }
+    }
+
+    @Override
+    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);
+                        }
+                    }
+                }
             }
         }
     }
index a48874c18ff951c12869bedef86907b21fc658a4..cb1dd34bab796ed0c9450e851834c79b31634d10 100644 (file)
  */
 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.");
+            }
         }
-        this.gpio = new GPIO(jPigpio, gpioId, JPigpio.PI_OUTPUT);
     }
 
+    /**
+     * 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) {
-        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 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);
+                }
             }
         }
-        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 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);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+        synchronized (handleLock) {
+            Future<?> job = this.pulseJob;
+
+            if (job != null) {
+                job.cancel(true);
             }
+            this.updateStatus = null;
+            this.gpio = null;
         }
     }
 }
index 3f561cd7449ac057a2ac86b08ad7e3853f5953aa..f2ccd8fc846bb3e2bcfc3cce67432e9f77684f22 100644 (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();
     }
 }
index 564f6dc4fac8807b875e67222032a9f4a0d23653..207beccff84f1c7926295070acde0b5603510af6 100644 (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
index f71d13ca6665305b45bc47b4dbb1485f0031aedf..8e17e0e559add85e41843c42a5beb56657feff97 100644 (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>
                                <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>
 
                <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>
                                <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>
 
                        <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>