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

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

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

Workaround #11039
Fixes #11038
Fixes #13376

Signed-off-by: Jeremy Rumpf <rumpf.99@gmail.com>
This commit is contained in:
Jeremy Rumpf
2023-10-30 16:20:42 -04:00
committed by GitHub
parent 597f01efe1
commit b24f3a2feb
14 changed files with 1159 additions and 166 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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