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.flicbutton.internal.handler;
15 import static org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants.*;
17 import java.io.IOException;
18 import java.util.Objects;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.thing.ChannelUID;
27 import org.openhab.core.thing.CommonTriggerEvents;
28 import org.openhab.core.thing.Thing;
29 import org.openhab.core.thing.ThingStatus;
30 import org.openhab.core.thing.ThingStatusDetail;
31 import org.openhab.core.thing.ThingStatusInfo;
32 import org.openhab.core.types.Command;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
36 import io.flic.fliclib.javaclient.BatteryStatusListener;
37 import io.flic.fliclib.javaclient.Bdaddr;
38 import io.flic.fliclib.javaclient.ButtonConnectionChannel;
39 import io.flic.fliclib.javaclient.enums.ConnectionStatus;
40 import io.flic.fliclib.javaclient.enums.DisconnectReason;
43 * The {@link FlicButtonHandler} is responsible for initializing the online status of Flic Buttons
44 * and trigger channel events when they're used.
46 * @author Patrick Fink - Initial contribution
49 public class FlicButtonHandler extends ChildThingHandler<FlicDaemonBridgeHandler> {
51 private Logger logger = LoggerFactory.getLogger(FlicButtonHandler.class);
52 private @Nullable ScheduledFuture<?> delayedDisconnectTask;
53 private @Nullable Future<?> initializationTask;
54 private @Nullable DisconnectReason latestDisconnectReason;
55 private @Nullable ButtonConnectionChannel eventConnection;
56 private @Nullable Bdaddr bdaddr;
57 private @Nullable BatteryStatusListener batteryConnection;
59 public FlicButtonHandler(Thing thing) {
63 public @Nullable Bdaddr getBdaddr() {
68 public void handleCommand(ChannelUID channelUID, Command command) {
69 // Pure sensor -> no commands have to be handled
73 public void initialize() {
75 bdaddr = new Bdaddr((String) this.getThing().getConfiguration().get(CONFIG_ADDRESS));
77 initializationTask = scheduler.submit(this::initializeThing);
82 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
83 super.bridgeStatusChanged(bridgeStatusInfo);
84 if (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE && bridgeValid) {
86 initializationTask = scheduler.submit(this::initializeThing);
90 private void initializeThing() {
92 initializeBatteryListener();
93 initializeEventListener();
94 // EventListener calls initializeStatus() before releasing so that ThingStatus should be set at this point
95 if (this.getThing().getStatus().equals(ThingStatus.INITIALIZING)) {
96 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
97 "Got no response by eventListener");
99 } catch (IOException | InterruptedException e) {
100 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
101 "Connection setup failed: {}" + e.getMessage());
105 private void initializeBatteryListener() throws IOException {
106 FlicButtonBatteryLevelListener batteryListener = new FlicButtonBatteryLevelListener(this);
107 BatteryStatusListener batteryConnection = new BatteryStatusListener(getBdaddr(), batteryListener);
108 bridgeHandler.getFlicClient().addBatteryStatusListener(batteryConnection);
109 this.batteryConnection = batteryConnection;
112 public void initializeEventListener() throws IOException, InterruptedException {
113 FlicButtonEventListener eventListener = new FlicButtonEventListener(this);
114 ButtonConnectionChannel eventConnection = new ButtonConnectionChannel(getBdaddr(), eventListener);
115 bridgeHandler.getFlicClient().addConnectionChannel(eventConnection);
116 this.eventConnection = eventConnection;
117 eventListener.getChannelResponseSemaphore().tryAcquire(5, TimeUnit.SECONDS);
121 public void dispose() {
122 cancelDelayedDisconnectTask();
123 cancelInitializationTask();
125 if (eventConnection != null) {
126 bridgeHandler.getFlicClient().removeConnectionChannel(eventConnection);
128 if (batteryConnection != null) {
129 bridgeHandler.getFlicClient().removeBatteryStatusListener(this.batteryConnection);
131 } catch (IOException e) {
132 logger.warn("Button channel could not be properly removed", e);
138 void initializeStatus(ConnectionStatus connectionStatus) {
139 if (connectionStatus == ConnectionStatus.Disconnected) {
146 void connectionStatusChanged(ConnectionStatus connectionStatus, @Nullable DisconnectReason disconnectReason) {
147 latestDisconnectReason = disconnectReason;
148 if (connectionStatus == ConnectionStatus.Disconnected) {
149 // Status change to offline have to be scheduled to improve stability,
150 // see https://github.com/pfink/openhab2-flicbutton/issues/2
151 scheduleStatusChangeToOffline();
157 private void scheduleStatusChangeToOffline() {
158 if (delayedDisconnectTask == null) {
159 delayedDisconnectTask = scheduler.schedule(this::setOffline, BUTTON_OFFLINE_GRACE_PERIOD_SECONDS,
164 protected void setOnline() {
165 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
168 protected void setOffline() {
169 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
170 "Disconnect Reason: " + Objects.toString(latestDisconnectReason));
173 // Cleanup delayedDisconnect on status change to online
175 protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
176 if (status == ThingStatus.ONLINE) {
177 cancelDelayedDisconnectTask();
179 super.updateStatus(status, statusDetail, description);
182 private void cancelInitializationTask() {
183 if (initializationTask != null) {
184 initializationTask.cancel(true);
185 initializationTask = null;
189 private void cancelDelayedDisconnectTask() {
190 if (delayedDisconnectTask != null) {
191 delayedDisconnectTask.cancel(false);
192 delayedDisconnectTask = null;
196 void updateBatteryChannel(int percent) {
197 DecimalType batteryLevel = new DecimalType(percent);
198 updateState(CHANNEL_ID_BATTERY_LEVEL, batteryLevel);
201 void flicButtonRemoved() {
202 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE,
203 "Button was removed/detached from flicd (e.g. by simpleclient).");
206 void fireTriggerEvent(String event) {
207 String channelID = event.equals(CommonTriggerEvents.PRESSED) || event.equals(CommonTriggerEvents.RELEASED)
208 ? CHANNEL_ID_RAWBUTTON_EVENTS
209 : CHANNEL_ID_BUTTON_EVENTS;
210 updateStatus(ThingStatus.ONLINE);
211 triggerChannel(channelID, event);