]> git.basschouten.com Git - openhab-addons.git/blob
01e37007373a5e413c0ba6abd064abf94e29428b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.linuxinput.internal;
14
15 import static org.openhab.binding.linuxinput.internal.LinuxInputBindingConstants.*;
16
17 import java.io.IOException;
18 import java.nio.channels.SelectionKey;
19 import java.nio.channels.Selector;
20 import java.util.*;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.linuxinput.internal.evdev4j.EvdevDevice;
25 import org.openhab.binding.linuxinput.internal.evdev4j.jnr.EvdevLibrary;
26 import org.openhab.core.library.CoreItemFactory;
27 import org.openhab.core.library.types.OpenClosedType;
28 import org.openhab.core.library.types.StringType;
29 import org.openhab.core.thing.*;
30 import org.openhab.core.thing.binding.builder.ChannelBuilder;
31 import org.openhab.core.thing.binding.builder.ThingBuilder;
32 import org.openhab.core.types.Command;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * Handler for Linux Input devices.
38  *
39  * @author Thomas Weißschuh - Initial contribution
40  */
41 @NonNullByDefault
42 public final class LinuxInputHandler extends DeviceReadingHandler {
43
44     private final Logger logger = LoggerFactory.getLogger(LinuxInputHandler.class);
45
46     private final Map<Integer, Channel> channels;
47     private final Channel keyChannel;
48     private @Nullable EvdevDevice device;
49     private final @Nullable String defaultLabel;
50
51     private @NonNullByDefault({}) LinuxInputConfiguration config;
52
53     public LinuxInputHandler(Thing thing, @Nullable String defaultLabel) {
54         super(thing);
55         this.defaultLabel = defaultLabel;
56
57         keyChannel = ChannelBuilder.create(new ChannelUID(thing.getUID(), "key"), CoreItemFactory.STRING)
58                 .withType(CHANNEL_TYPE_KEY).build();
59         channels = Collections.synchronizedMap(new HashMap<>());
60     }
61
62     @Override
63     public void handleCommand(ChannelUID channelUID, Command command) {
64         /* no commands to handle */
65     }
66
67     @Override
68     boolean immediateSetup() {
69         config = getConfigAs(LinuxInputConfiguration.class);
70         channels.clear();
71         String statusDesc = null;
72         if (!config.enable) {
73             statusDesc = "Administratively disabled";
74         }
75         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, statusDesc);
76         return true;
77     }
78
79     @Override
80     boolean delayedSetup() throws IOException {
81         ThingBuilder customizer = editThing();
82         List<Channel> newChannels = new ArrayList<>();
83         newChannels.add(keyChannel);
84         EvdevDevice newDevice = new EvdevDevice(config.path);
85         for (EvdevDevice.Key o : newDevice.enumerateKeys()) {
86             String name = o.getName();
87             Channel channel = ChannelBuilder
88                     .create(new ChannelUID(thing.getUID(), CHANNEL_GROUP_KEYPRESSES_ID, name), CoreItemFactory.CONTACT)
89                     .withLabel(name).withType(CHANNEL_TYPE_KEY_PRESS).build();
90             channels.put(o.getCode(), channel);
91             newChannels.add(channel);
92         }
93         if (Objects.equals(defaultLabel, thing.getLabel())) {
94             customizer.withLabel(newDevice.getName());
95         }
96         customizer.withChannels(newChannels);
97         Map<String, String> props = getProperties(Objects.requireNonNull(newDevice));
98         customizer.withProperties(props);
99         updateThing(customizer.build());
100         for (Channel channel : newChannels) {
101             updateState(channel.getUID(), OpenClosedType.OPEN);
102         }
103         if (config.enable) {
104             updateStatus(ThingStatus.ONLINE);
105         }
106         device = newDevice;
107         return config.enable;
108     }
109
110     @Override
111     protected void closeDevice() throws IOException {
112         @Nullable
113         EvdevDevice currentDevice = device;
114         device = null;
115
116         if (currentDevice != null) {
117             currentDevice.close();
118         }
119         logger.debug("Device {} closed", this);
120     }
121
122     @Override
123     String getInstanceName() {
124         LinuxInputConfiguration c = config;
125         if (c == null || c.path == null) {
126             return "unknown";
127         }
128         return c.path;
129     }
130
131     @Override
132     void handleEventsInThread() throws IOException {
133         try (Selector selector = EvdevDevice.openSelector()) {
134             @Nullable
135             EvdevDevice currentDevice = device;
136             if (currentDevice == null) {
137                 throw new IOException("trying to handle events without an device");
138             }
139             SelectionKey evdevReady = currentDevice.register(selector);
140
141             logger.debug("Grabbing device {}", currentDevice);
142             currentDevice.grab(); // ungrab will happen implicitly at device.close()
143
144             while (true) {
145                 if (Thread.currentThread().isInterrupted()) {
146                     logger.debug("Thread interrupted, exiting");
147                     break;
148                 }
149                 logger.trace("Waiting for event");
150                 selector.select(20_000);
151                 if (selector.selectedKeys().remove(evdevReady)) {
152                     while (true) {
153                         Optional<EvdevDevice.InputEvent> ev = currentDevice.nextEvent();
154                         if (!ev.isPresent()) {
155                             break;
156                         }
157                         handleEvent(ev.get());
158                     }
159                 }
160             }
161         }
162     }
163
164     private void handleEvent(EvdevDevice.InputEvent event) {
165         if (event.type() != EvdevLibrary.Type.KEY) {
166             return;
167         }
168         @Nullable
169         Channel channel = channels.get(event.getCode());
170         if (channel == null) {
171             String msg = "Could not find channel for code {}";
172             if (isInitialized()) {
173                 logger.warn(msg, event.getCode());
174             } else {
175                 logger.debug(msg, event.getCode());
176             }
177             return;
178         }
179         logger.debug("Got event: {}", event);
180         // Documented in README.md
181         int eventValue = event.getValue();
182         switch (eventValue) {
183             case EvdevLibrary.KeyEventValue.DOWN:
184                 String keyCode = channel.getUID().getIdWithoutGroup();
185                 updateState(keyChannel.getUID(), new StringType(keyCode));
186                 updateState(channel.getUID(), OpenClosedType.CLOSED);
187                 triggerChannel(keyChannel.getUID(), keyCode);
188                 triggerChannel(channel.getUID(), CommonTriggerEvents.PRESSED);
189                 updateState(keyChannel.getUID(), new StringType());
190                 break;
191             case EvdevLibrary.KeyEventValue.UP:
192                 updateState(channel.getUID(), OpenClosedType.OPEN);
193                 triggerChannel(channel.getUID(), CommonTriggerEvents.RELEASED);
194                 break;
195             case EvdevLibrary.KeyEventValue.REPEAT:
196                 /* Ignored */
197                 break;
198             default:
199                 logger.debug("Unexpected event value for channel {}: {}", channel, eventValue);
200                 break;
201         }
202     }
203
204     private static Map<String, String> getProperties(EvdevDevice device) {
205         Map<String, String> properties = new HashMap<>();
206         properties.put("physicalLocation", device.getPhys());
207         properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getUniq());
208         properties.put(Thing.PROPERTY_MODEL_ID, hex(device.getProdutId()));
209         properties.put(Thing.PROPERTY_VENDOR, hex(device.getVendorId()));
210         properties.put("busType", device.getBusType().map(Object::toString).orElseGet(() -> hex(device.getBusId())));
211         properties.put(Thing.PROPERTY_FIRMWARE_VERSION, hex(device.getVersionId()));
212         properties.put("driverVersion", hex(device.getDriverVersion()));
213         return properties;
214     }
215
216     private static String hex(int i) {
217         return String.format("%04x", i);
218     }
219 }