]> git.basschouten.com Git - openhab-addons.git/blob
ffda46abbb5cb0219b49640e347eee670fae444f
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.flicbutton.internal.handler;
14
15 import static org.openhab.binding.flicbutton.internal.FlicButtonBindingConstants.*;
16
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;
22
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;
35
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;
41
42 /**
43  * The {@link FlicButtonHandler} is responsible for initializing the online status of Flic Buttons
44  * and trigger channel events when they're used.
45  *
46  * @author Patrick Fink - Initial contribution
47  */
48 @NonNullByDefault
49 public class FlicButtonHandler extends ChildThingHandler<FlicDaemonBridgeHandler> {
50
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;
58
59     public FlicButtonHandler(Thing thing) {
60         super(thing);
61     }
62
63     public @Nullable Bdaddr getBdaddr() {
64         return bdaddr;
65     }
66
67     @Override
68     public void handleCommand(ChannelUID channelUID, Command command) {
69         // Pure sensor -> no commands have to be handled
70     }
71
72     @Override
73     public void initialize() {
74         super.initialize();
75         bdaddr = new Bdaddr((String) this.getThing().getConfiguration().get(CONFIG_ADDRESS));
76         if (bridgeValid) {
77             initializationTask = scheduler.submit(this::initializeThing);
78         }
79     }
80
81     @Override
82     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
83         super.bridgeStatusChanged(bridgeStatusInfo);
84         if (getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE && bridgeValid) {
85             dispose();
86             initializationTask = scheduler.submit(this::initializeThing);
87         }
88     }
89
90     private void initializeThing() {
91         try {
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");
98             }
99         } catch (IOException | InterruptedException e) {
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
101                     "Connection setup failed: {}" + e.getMessage());
102         }
103     }
104
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;
110     }
111
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);
118     }
119
120     @Override
121     public void dispose() {
122         cancelDelayedDisconnectTask();
123         cancelInitializationTask();
124         try {
125             if (eventConnection != null) {
126                 bridgeHandler.getFlicClient().removeConnectionChannel(eventConnection);
127             }
128             if (batteryConnection != null) {
129                 bridgeHandler.getFlicClient().removeBatteryStatusListener(this.batteryConnection);
130             }
131         } catch (IOException e) {
132             logger.warn("Button channel could not be properly removed", e);
133         }
134
135         super.dispose();
136     }
137
138     void initializeStatus(ConnectionStatus connectionStatus) {
139         if (connectionStatus == ConnectionStatus.Disconnected) {
140             setOffline();
141         } else {
142             setOnline();
143         }
144     }
145
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();
152         } else {
153             setOnline();
154         }
155     }
156
157     private void scheduleStatusChangeToOffline() {
158         if (delayedDisconnectTask == null) {
159             delayedDisconnectTask = scheduler.schedule(this::setOffline, BUTTON_OFFLINE_GRACE_PERIOD_SECONDS,
160                     TimeUnit.SECONDS);
161         }
162     }
163
164     protected void setOnline() {
165         updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
166     }
167
168     protected void setOffline() {
169         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.NONE,
170                 "Disconnect Reason: " + Objects.toString(latestDisconnectReason));
171     }
172
173     // Cleanup delayedDisconnect on status change to online
174     @Override
175     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
176         if (status == ThingStatus.ONLINE) {
177             cancelDelayedDisconnectTask();
178         }
179         super.updateStatus(status, statusDetail, description);
180     }
181
182     private void cancelInitializationTask() {
183         if (initializationTask != null) {
184             initializationTask.cancel(true);
185             initializationTask = null;
186         }
187     }
188
189     private void cancelDelayedDisconnectTask() {
190         if (delayedDisconnectTask != null) {
191             delayedDisconnectTask.cancel(false);
192             delayedDisconnectTask = null;
193         }
194     }
195
196     void updateBatteryChannel(int percent) {
197         DecimalType batteryLevel = new DecimalType(percent);
198         updateState(CHANNEL_ID_BATTERY_LEVEL, batteryLevel);
199     }
200
201     void flicButtonRemoved() {
202         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.GONE,
203                 "Button was removed/detached from flicd (e.g. by simpleclient).");
204     }
205
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);
212     }
213 }