2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.gpio.internal.handler;
15 import static org.openhab.binding.gpio.internal.GPIOBindingConstants.*;
17 import java.util.HashMap;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.gpio.internal.ChannelConfigurationException;
25 import org.openhab.binding.gpio.internal.configuration.GPIOInputConfiguration;
26 import org.openhab.binding.gpio.internal.configuration.GPIOOutputConfiguration;
27 import org.openhab.binding.gpio.internal.configuration.PigpioConfiguration;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.thing.type.ChannelTypeUID;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.openhab.core.types.UnDefType;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 import eu.xeli.jpigpio.JPigpio;
42 import eu.xeli.jpigpio.PigpioException;
43 import eu.xeli.jpigpio.PigpioSocket;
46 * Remote pigpio Handler
48 * This bridge is used to control remote pigpio instances.
50 * @author Nils Bauer - Initial contribution
51 * @author Jan N. Klug - Channel redesign
52 * @author Jeremy Rumpf - Improve JPigpio connection handling
55 public class PigpioRemoteHandler extends BaseThingHandler {
56 private final Logger logger = LoggerFactory.getLogger(PigpioRemoteHandler.class);
57 private final Map<ChannelUID, ChannelHandler> channelHandlers = new HashMap<>();
60 * Instantiates a new pigpio remote bridge handler.
62 * @param thing the thing
64 public PigpioRemoteHandler(Thing thing) {
69 public void handleCommand(ChannelUID channelUID, Command command) {
71 synchronized (this.connectionLock) {
72 ChannelHandler channelHandler = channelHandlers.get(channelUID);
74 if (channelHandler == null || !(ThingStatus.ONLINE.equals(thing.getStatus()))) {
75 // We raced with connectPollWorker and lost
79 if (channelHandler instanceof PigpioDigitalInputHandler inputHandler) {
81 inputHandler.handleCommand(command);
82 } catch (PigpioException pe) {
83 logger.warn("Input command exception on channel {} {}", channelUID, pe.toString());
84 if (pe.getErrorCode() == -99999999) {
85 runDisconnectActions();
86 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
87 pe.getLocalizedMessage());
90 } else if (channelHandler instanceof PigpioDigitalOutputHandler outputHandler) {
92 outputHandler.handleCommand(command);
93 } catch (PigpioException pe) {
94 logger.warn("Output command exception on channel {} {}", channelUID, pe.toString());
95 if (pe.getErrorCode() == -99999999) {
96 runDisconnectActions();
97 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
98 pe.getLocalizedMessage());
102 logger.warn("Command received for an unknown channel: {}", channelUID);
105 } catch (Exception e) {
106 logger.warn("Command exception on channel {} {}", channelUID, e.toString());
110 protected PigpioConfiguration config = new PigpioConfiguration();
111 protected @Nullable JPigpio jPigpio = null;
114 public void initialize() {
115 PigpioConfiguration lconfig = getConfigAs(PigpioConfiguration.class);
116 this.config = lconfig;
118 if (lconfig.host == null) {
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
120 "Cannot connect to PiGPIO Service on remote raspberry. IP address not set.");
123 if (lconfig.port < 1 && lconfig.port > 65535) {
124 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
125 "Cannot connect to PiGPIO Service on remote raspberry. Invalid Port.");
129 createChannelHandlers();
131 logger.debug("gpio binding initialized");
133 connectionJob = scheduler.submit(() -> {
134 connectionPollWorker();
138 protected void clearChannelHandlers() {
139 for (ChannelHandler handler : channelHandlers.values()) {
142 channelHandlers.clear();
145 protected void createChannelHandlers() {
146 clearChannelHandlers();
147 this.getThing().getChannels().forEach(channel -> {
148 ChannelUID channelUID = channel.getUID();
149 ChannelTypeUID type = channel.getChannelTypeUID();
152 if (CHANNEL_TYPE_DIGITAL_INPUT.equals(type)) {
153 GPIOInputConfiguration configuration = channel.getConfiguration().as(GPIOInputConfiguration.class);
154 this.channelHandlers.put(channelUID, new PigpioDigitalInputHandler(configuration, scheduler,
155 state -> updateState(channelUID.getId(), state)));
156 } else if (CHANNEL_TYPE_DIGITAL_OUTPUT.equals(type)) {
157 GPIOOutputConfiguration configuration = channel.getConfiguration()
158 .as(GPIOOutputConfiguration.class);
159 PigpioDigitalOutputHandler handler = new PigpioDigitalOutputHandler(configuration, scheduler,
160 state -> updateState(channelUID.getId(), state));
161 this.channelHandlers.put(channelUID, handler);
163 } catch (PigpioException e) {
164 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
165 String.format("Failed to initialize channel {} {}", channelUID, e.getLocalizedMessage()));
166 } catch (ChannelConfigurationException e) {
167 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
168 String.format("Invalid configuration for channel {} {}", channelUID, e.getLocalizedMessage()));
172 logger.debug("gpio channels initialized");
175 protected void setChannelJPigpio(@Nullable JPigpio jPigpio) throws PigpioException {
176 if (this.channelHandlers.isEmpty()) {
177 createChannelHandlers();
180 for (ChannelHandler handler : this.channelHandlers.values()) {
181 handler.listen(jPigpio);
184 logger.debug("gpio jPigpio listening");
187 private @Nullable Future<?> connectionJob = null;
189 * Syncronizes all socket related code
192 private Object connectionLock = new Object();
194 protected void killConnectionPoll() {
195 if (this.connectionJob != null) {
196 synchronized (this.connectionLock) {
197 if (this.connectionJob != null) {
198 Future<?> job = this.connectionJob;
199 this.connectionJob = null;
201 logger.debug("gpio connection poll : killing");
209 protected void connectionPollWorker() {
210 Thing thing = this.getThing();
212 synchronized (connectionLock) {
213 ThingStatus currentStatus = thing.getStatus();
214 JPigpio ljPigpio = this.jPigpio;
216 if (ThingStatus.ONLINE.equals(currentStatus) && ljPigpio != null) {
217 // We are ONLINE and jPigpio is instantiated, this is the normal path
219 logger.debug("gpio connection poll : CMD_TICK");
220 ljPigpio.getCurrentTick();
221 } catch (PigpioException e) {
222 logger.debug("gpio connection poll : disconnect");
223 runDisconnectActions();
224 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
225 e.getLocalizedMessage());
227 // We disconnected, reschedule ourselves to try a reconnect.
228 // First, try a quick reconnect if the user specified a long(ish) interval
229 int interval = this.config.heartBeatInterval;
230 if (interval > 1000) {
234 this.connectionJob = scheduler.schedule(() -> {
235 connectionPollWorker();
236 }, interval, TimeUnit.MILLISECONDS);
238 logger.warn("Pigpiod disconnected : {}", this.config.host);
243 // We are OFFLINE and jPigpio may or may not be instantiated
245 if (ljPigpio == null) {
246 // First initialization or re-initialization after dispose()
247 // jPigpio is not up and running yet.
248 logger.debug("gpio connection poll : connecting");
249 ljPigpio = new PigpioSocket(this.config.host, this.config.port);
250 this.jPigpio = ljPigpio;
251 setChannelJPigpio(ljPigpio);
252 updateStatus(ThingStatus.ONLINE);
255 // jPigpio is instantiated, but not connected.
256 // Use it's internal reconnect logic.
257 logger.debug("gpio connection poll : reconnecting");
258 ljPigpio.reconnect();
259 // jPigpio listeners are not re-established after reconnect.
260 // We need to reinject them into the channel handlers.
261 setChannelJPigpio(ljPigpio);
262 updateStatus(ThingStatus.ONLINE);
263 runReconnectActions();
266 logger.debug("Pigpiod connected : {}", this.config.host);
267 } catch (PigpioException e) {
268 logger.debug("gpio connection poll : failed, {}", e.getErrorCode());
269 if (currentStatus.equals(ThingStatus.ONLINE) || currentStatus.equals(ThingStatus.INITIALIZING)) {
270 runDisconnectActions();
271 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
272 e.getLocalizedMessage());
277 if (this.config.heartBeatInterval > 0) {
278 this.connectionJob = scheduler.schedule(() -> {
279 connectionPollWorker();
280 }, this.config.heartBeatInterval, TimeUnit.MILLISECONDS);
282 // User disabled periodic connections, one shot?
283 logger.debug("gpio connection poll : disabled");
284 this.connectionJob = null;
289 protected void runConnectActions() throws PigpioException {
290 if (this.config.inputConnectAction != null) {
291 if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
292 refreshInputChannels();
296 if (this.config.outputConnectAction != null) {
297 if (ACTION_ALL_ON.equals(this.config.outputConnectAction)) {
298 setOutputChannels(OnOffType.ON);
299 } else if (ACTION_ALL_OFF.equals(this.config.outputConnectAction)) {
300 setOutputChannels(OnOffType.OFF);
301 } else if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
302 refreshOutputChannels();
307 protected void runReconnectActions() throws PigpioException {
308 if (this.config.inputConnectAction != null) {
309 if (ACTION_REFRESH.equals(this.config.inputConnectAction)) {
310 refreshInputChannels();
314 if (this.config.outputConnectAction != null) {
315 if (ACTION_REFRESH.equals(this.config.outputConnectAction)) {
316 refreshOutputChannels();
321 protected void runDisconnectActions() {
322 if (this.config.inputDisconnectAction != null) {
323 if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
324 undefInputChannels();
328 if (this.config.outputDisconnectAction != null) {
329 if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
330 undefOutputChannels();
335 protected void refreshInputChannels() throws PigpioException {
336 logger.debug("gpio refresh input channels");
337 for (ChannelUID channelUID : channelHandlers.keySet()) {
338 ChannelHandler handler = channelHandlers.get(channelUID);
339 if (handler instanceof PigpioDigitalInputHandler) {
340 handler.handleCommand(RefreshType.REFRESH);
341 postCommand(channelUID, RefreshType.REFRESH);
346 protected void refreshOutputChannels() throws PigpioException {
347 logger.debug("gpio refresh output channels");
348 for (ChannelUID channelUID : this.channelHandlers.keySet()) {
349 ChannelHandler handler = this.channelHandlers.get(channelUID);
350 if (handler instanceof PigpioDigitalOutputHandler) {
351 handler.handleCommand(RefreshType.REFRESH);
352 postCommand(channelUID, RefreshType.REFRESH);
357 protected void undefInputChannels() {
358 logger.debug("gpio undef input channels");
359 for (ChannelUID channelUID : this.channelHandlers.keySet()) {
360 ChannelHandler handler = this.channelHandlers.get(channelUID);
361 if (handler instanceof PigpioDigitalInputHandler) {
362 updateState(channelUID, UnDefType.UNDEF);
367 protected void undefOutputChannels() {
368 logger.debug("gpio undef output channels");
369 for (ChannelUID channelUID : channelHandlers.keySet()) {
370 ChannelHandler handler = channelHandlers.get(channelUID);
371 if (handler instanceof PigpioDigitalOutputHandler) {
372 updateState(channelUID, UnDefType.UNDEF);
377 protected void setOutputChannels(OnOffType command) throws PigpioException {
378 logger.debug("gpio setting output channels: {}", command.toString());
379 for (ChannelUID channelUID : this.channelHandlers.keySet()) {
380 ChannelHandler handler = this.channelHandlers.get(channelUID);
381 if (handler instanceof PigpioDigitalOutputHandler) {
382 handler.handleCommand(command);
383 postCommand(channelUID, command);
389 public void dispose() {
391 synchronized (this.connectionLock) {
392 JPigpio ljPigpio = this.jPigpio;
394 killConnectionPoll();
396 if (ACTION_SET_UNDEF.equals(this.config.inputDisconnectAction)) {
397 undefInputChannels();
399 if (ACTION_SET_UNDEF.equals(this.config.outputDisconnectAction)) {
400 undefOutputChannels();
403 clearChannelHandlers();
405 if (ljPigpio != null) {
407 ljPigpio.gpioTerminate();
409 } catch (PigpioException e) {
410 // Best effort at a socket shutdown
414 logger.debug("gpio disposed");
415 } catch (Exception e) {
416 logger.debug("Dispose exception :", e);