[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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user