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.leapmotion.internal.handler;
15 import static org.openhab.binding.leapmotion.internal.LeapMotionBindingConstants.*;
17 import java.util.concurrent.TimeUnit;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.openhab.core.thing.ChannelUID;
22 import org.openhab.core.thing.Thing;
23 import org.openhab.core.thing.ThingStatus;
24 import org.openhab.core.thing.binding.BaseThingHandler;
25 import org.openhab.core.types.Command;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
29 import com.leapmotion.leap.CircleGesture;
30 import com.leapmotion.leap.Controller;
31 import com.leapmotion.leap.FingerList;
32 import com.leapmotion.leap.Frame;
33 import com.leapmotion.leap.Gesture;
34 import com.leapmotion.leap.GestureList;
35 import com.leapmotion.leap.Hand;
36 import com.leapmotion.leap.Listener;
39 * The {@link LeapMotionHandler} is responsible for handling commands, which are
40 * sent to one of the channels.
42 * @author Kai Kreuzer - Initial contribution
43 * @author Thomas Eichstädt-Engelen - Initial version of listener logic
47 public class LeapMotionHandler extends BaseThingHandler {
49 private final Logger logger = LoggerFactory.getLogger(LeapMotionHandler.class);
51 private @NonNullByDefault({}) LeapMotionListener listener;
52 private @NonNullByDefault({}) Controller leapController;
54 public LeapMotionHandler(Thing thing) {
59 public void handleCommand(ChannelUID channelUID, Command command) {
63 public void initialize() {
64 this.listener = new LeapMotionListener();
66 leapController = new Controller();
67 leapController.setPolicyFlags(Controller.PolicyFlag.POLICY_BACKGROUND_FRAMES);
68 leapController.addListener(this.listener);
69 if (leapController.isConnected()) {
70 updateStatus(ThingStatus.ONLINE);
72 updateStatus(ThingStatus.OFFLINE);
77 public void dispose() {
78 leapController.removeListener(this.listener);
80 leapController.delete();
81 leapController = null;
84 private class LeapMotionListener extends Listener {
86 private static final int RATE_LIMIT_IN_MS = 200;
87 private long lastEvent = 0;
88 private boolean noHand;
90 public LeapMotionListener() {
94 public void onConnect(@Nullable Controller controller) {
95 if (controller != null) {
96 controller.enableGesture(Gesture.Type.TYPE_KEY_TAP);
97 controller.enableGesture(Gesture.Type.TYPE_CIRCLE);
99 updateStatus(ThingStatus.ONLINE);
103 public void onDisconnect(@Nullable Controller controller) {
104 updateStatus(ThingStatus.OFFLINE);
108 public void onFrame(@Nullable Controller controller) {
109 if (controller == null) {
112 Frame frame = controller.frame();
114 GestureList gestures = frame.gestures();
115 for (int i = 0; i < gestures.count(); i++) {
116 Gesture gesture = gestures.get(i);
117 switch (gesture.type()) {
119 logger.debug("Key tap");
120 triggerChannel(CHANNEL_GESTURE, GESTURE_TAP);
123 CircleGesture circle = new CircleGesture(gesture);
124 // Calculate clock direction using the angle between circle normal and pointable
125 boolean clockwiseness;
126 if (circle.pointable().direction().angleTo(circle.normal()) <= Math.PI / 4) {
127 // Clockwise if angle is less than 90 degrees
128 clockwiseness = true;
130 clockwiseness = false;
133 // Calculate angle swept since last frame
134 if (circle.state() == com.leapmotion.leap.Gesture.State.STATE_UPDATE) {
135 if (System.nanoTime() > lastEvent + TimeUnit.MILLISECONDS.toNanos(RATE_LIMIT_IN_MS)) {
136 logger.debug("Circle (clockwise={})", clockwiseness);
138 triggerChannel(CHANNEL_GESTURE, GESTURE_CLOCKWISE);
140 triggerChannel(CHANNEL_GESTURE, GESTURE_ANTICLOCKWISE);
142 lastEvent = System.nanoTime();
147 logger.debug("Unknown gesture type.");
152 if (!frame.hands().isEmpty()) {
154 // Get the first hand
155 Hand hand = frame.hands().get(0);
156 // Check if the hand has any fingers
157 FingerList fingers = hand.fingers();
158 if (System.nanoTime() > lastEvent + TimeUnit.MILLISECONDS.toNanos(RATE_LIMIT_IN_MS)) {
159 int height = (int) hand.palmPosition().getY();
160 logger.debug("Fingers shown {} @ {}", fingers.count(), height);
161 triggerChannel(CHANNEL_GESTURE, GESTURE_FINGERS + fingers.count() + "_" + height);
162 lastEvent = System.nanoTime();
167 logger.debug("No hand");
168 triggerChannel(CHANNEL_GESTURE, GESTURE_NOHAND);