2 * Copyright (c) 2010-2022 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.ekey.internal.handler;
15 import static org.openhab.binding.ekey.internal.EkeyBindingConstants.*;
17 import java.io.IOException;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.stream.Collectors;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.ekey.internal.EkeyConfiguration;
26 import org.openhab.binding.ekey.internal.EkeyDynamicStateDescriptionProvider;
27 import org.openhab.binding.ekey.internal.api.EkeyPacketListener;
28 import org.openhab.binding.ekey.internal.api.EkeyUdpPacketReceiver;
29 import org.openhab.core.library.types.DecimalType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.thing.binding.builder.ChannelBuilder;
38 import org.openhab.core.thing.binding.builder.ThingBuilder;
39 import org.openhab.core.thing.type.ChannelKind;
40 import org.openhab.core.thing.type.ChannelTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.State;
43 import org.openhab.core.types.StateOption;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link EkeyHandler} is responsible for handling commands, which are
49 * sent to one of the channels.
51 * @author Hans-Jörg Merk - Initial contribution
54 public class EkeyHandler extends BaseThingHandler implements EkeyPacketListener {
56 private final Logger logger = LoggerFactory.getLogger(EkeyHandler.class);
58 private final EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider;
60 private EkeyConfiguration config = new EkeyConfiguration();
61 private @Nullable EkeyUdpPacketReceiver receiver;
63 public EkeyHandler(Thing thing, EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider) {
65 this.ekeyStateDescriptionProvider = ekeyStateDescriptionProvider;
69 public void handleCommand(ChannelUID channelUID, Command command) {
70 // The binding does not handle any command
74 public void initialize() {
75 logger.debug("ekey handler initializing");
76 config = getConfigAs(EkeyConfiguration.class);
78 if (!config.ipAddress.isEmpty() && config.port != 0) {
79 updateStatus(ThingStatus.UNKNOWN);
81 scheduler.submit(() -> {
82 populateChannels(config.protocol);
83 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
85 EkeyUdpPacketReceiver localReceiver = receiver = new EkeyUdpPacketReceiver(config.ipAddress,
86 config.port, readerThreadName);
87 localReceiver.addEkeyPacketListener(this);
89 localReceiver.openConnection();
90 } catch (IOException e) {
91 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot open connection)");
93 updateStatus(ThingStatus.ONLINE);
96 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)");
101 public void dispose() {
102 EkeyUdpPacketReceiver localReceiver = this.receiver;
104 if (localReceiver != null) {
105 localReceiver.closeConnection();
106 localReceiver.removeEkeyPacketListener(this);
112 public void messageReceived(byte[] message) {
113 logger.debug("messageReceived() : {}", message);
114 config = getConfigAs(EkeyConfiguration.class);
115 String delimiter = config.delimiter;
117 switch (config.protocol) {
119 parseRare(message, delimiter);
122 parseMulti(message, delimiter);
125 parseHome(message, delimiter);
131 public void connectionStatusChanged(ThingStatus status, byte @Nullable [] message) {
132 if (message != null) {
133 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, null);
135 this.updateStatus(status);
138 public void parseRare(byte[] message, String delimiter) {
139 logger.debug("parse RARE packet");
140 if (message.length >= 72) {
141 byte[] newMessage = Arrays.copyOf(message, 72);
142 String messageString = new String(newMessage);
143 long action = getIntValueFrom(newMessage, 4, 4);
144 long terminalid = getIntValueFrom(newMessage, 8, 4);
145 String terminalSerial = getStringValueFrom(newMessage, 12, 14);
146 long relayid = getIntValueFrom(newMessage, 26, 1);
147 long userid = getIntValueFrom(newMessage, 28, 4);
148 long fingerid = getIntValueFrom(newMessage, 32, 4);
149 int serial = reconstructFsSerial(terminalid);
150 if (logger.isTraceEnabled()) {
151 logger.trace("messageString received() : {}", messageString);
152 logger.trace("AKTION : {}", action);
153 logger.trace("TERMINAL SERIAL : {}", terminalSerial);
154 logger.trace("RESERVED : {}", getStringValueFrom(newMessage, 27, 1));
155 logger.trace("RELAY ID : {}", relayid);
156 logger.trace("USER ID : {}", userid);
157 logger.trace("FINGER ID : {}", fingerid);
158 logger.trace("EVENT : {}", getStringValueFrom(newMessage, 36, 16));
159 logger.trace("FS SERIAL : {}", serial);
161 updateState(CHANNEL_TYPE_ACTION, new DecimalType(action));
162 if (!terminalSerial.isEmpty()) {
163 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf(terminalSerial));
165 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf("-1"));
167 updateState(CHANNEL_TYPE_RESERVED, StringType.valueOf(getStringValueFrom(newMessage, 27, 1)));
168 updateState(CHANNEL_TYPE_RELAYID, new DecimalType(relayid));
169 updateState(CHANNEL_TYPE_USERID, new DecimalType(userid));
170 updateState(CHANNEL_TYPE_FINGERID, new DecimalType(fingerid));
171 updateState(CHANNEL_TYPE_EVENT, StringType.valueOf(getStringValueFrom(newMessage, 36, 16)));
172 updateState(CHANNEL_TYPE_FSSERIAL, new DecimalType(serial));
177 public void parseMulti(byte[] message, String delimiter) {
178 logger.debug("parse MULTI packet");
179 if (message.length >= 46) {
180 byte[] newMessage = Arrays.copyOf(message, 46);
181 String messageString = new String(newMessage);
182 String[] array = messageString.split(delimiter);
183 if (logger.isTraceEnabled()) {
184 logger.trace("messageString received() : {}", messageString);
185 logger.trace("USER ID : {}", array[1]);
186 logger.trace("USER ID : {}", array[1]);
187 logger.trace("USER NAME : {}", array[2]);
188 logger.trace("USER STATUS : {}", array[3]);
189 logger.trace("FINGER ID : {}", array[4]);
190 logger.trace("KEY ID : {}", array[5]);
191 logger.trace("SERIENNR FS : {}", array[6]);
192 logger.trace("NAME FS : {}", new String(array[7]).replace("-", ""));
193 logger.trace("AKTION : {}", array[8]);
194 logger.trace("INPUT ID : {}", array[9]);
197 if (!"-".equals(array[1])) {
198 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
200 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
202 String userName = (array[2]).toString();
203 if (!userName.isEmpty()) {
204 userName = userName.replace("-", "");
205 userName = userName.replace(" ", "");
206 updateState(CHANNEL_TYPE_USERNAME, StringType.valueOf(userName));
208 if (!"-".equals(array[3])) {
209 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf((array[3])));
211 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf("-1"));
213 if (!"-".equals(array[4])) {
214 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[4])));
216 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
218 if (!"-".equals(array[5])) {
219 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf((array[5])));
221 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf("-1"));
223 updateState(CHANNEL_TYPE_FSSERIAL, DecimalType.valueOf((array[6])));
224 updateState(CHANNEL_TYPE_FSNAME, new StringType(new String(array[7]).replace("-", "")));
225 updateState(CHANNEL_TYPE_ACTION, DecimalType.valueOf((array[8])));
226 if (!"-".equals(array[9])) {
227 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf((array[9])));
229 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf("-1"));
232 logger.trace("received packet is to short : {}", message);
236 public void parseHome(byte[] message, String delimiter) {
237 logger.debug("parse HOME packet");
238 if (message.length >= 27) {
239 byte[] newMessage = Arrays.copyOf(message, 27);
240 String messageString = new String(newMessage);
241 String[] array = messageString.split(delimiter);
242 if (logger.isTraceEnabled()) {
243 logger.trace("messageString received() : {}", messageString);
244 logger.trace("USER ID : {}", array[1]);
245 logger.trace("FINGER ID : {}", array[2]);
246 logger.trace("SERIENNR FS : {}", array[3]);
247 logger.trace("AKTION : {}", array[4]);
248 logger.trace("RELAY ID : {}", array[5]);
250 if (!"-".equals(array[1])) {
251 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
253 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
255 if (!"-".equals(array[2])) {
256 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[2])));
258 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
260 updateState(CHANNEL_TYPE_FSSERIAL, DecimalType.valueOf((array[3])));
261 updateState(CHANNEL_TYPE_ACTION, DecimalType.valueOf((array[4])));
262 if (!"-".equals(array[5])) {
263 State relayId = DecimalType.valueOf((array[5]));
264 updateState(CHANNEL_TYPE_RELAYID, relayId);
266 updateState(CHANNEL_TYPE_RELAYID, DecimalType.valueOf("-1"));
269 logger.trace("received packet is to short : {}", message);
273 public void addChannel(String channelId, String itemType, @Nullable final Collection<String> options) {
274 if (thing.getChannel(channelId) == null) {
275 logger.debug("Channel '{}' for UID to be added", channelId);
276 ThingBuilder thingBuilder = editThing();
277 final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelId);
278 Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelId), itemType)
279 .withType(channelTypeUID).withKind(ChannelKind.STATE).build();
280 thingBuilder.withChannel(channel);
281 updateThing(thingBuilder.build());
283 if (options != null) {
284 final List<StateOption> stateOptions = options.stream()
285 .map(e -> new StateOption(e, e.substring(0, 1) + e.substring(1).toLowerCase()))
286 .collect(Collectors.toList());
287 logger.debug("StateOptions : '{}'", stateOptions);
288 ekeyStateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId), stateOptions);
292 public void populateChannels(String protocol) {
293 String channelId = "";
294 String itemType = "Number";
298 channelId = CHANNEL_TYPE_RELAYID;
299 addChannel(channelId, itemType, null);
302 channelId = CHANNEL_TYPE_USERNAME;
303 addChannel(channelId, "String", null);
304 channelId = CHANNEL_TYPE_USERSTATUS;
305 addChannel(channelId, itemType, null);
306 channelId = CHANNEL_TYPE_KEYID;
307 addChannel(channelId, itemType, null);
308 channelId = CHANNEL_TYPE_FSNAME;
309 addChannel(channelId, "String", null);
310 channelId = CHANNEL_TYPE_INPUTID;
311 addChannel(channelId, itemType, null);
314 channelId = CHANNEL_TYPE_TERMID;
315 addChannel(channelId, itemType, null);
316 channelId = CHANNEL_TYPE_RELAYID;
317 addChannel(channelId, itemType, null);
318 channelId = CHANNEL_TYPE_RESERVED;
319 addChannel(channelId, "String", null);
320 channelId = CHANNEL_TYPE_EVENT;
321 addChannel(channelId, "String", null);
326 private long getIntValueFrom(byte[] bytes, int start, int length) {
327 if (start + length > bytes.length) {
332 for (int i = start; i < start + length; i++) {
333 value |= (bytes[i] & 0xFF) << bits;
339 private String getStringValueFrom(byte[] bytes, int start, int length) {
340 if (start + length > bytes.length) {
341 logger.debug("Could not get String from bytes");
344 StringBuffer value = new StringBuffer();
345 for (int i = start + length - 1; i >= start; i--) {
346 if (bytes[i] > (byte) ' ' && bytes[i] < (byte) '~') {
347 value.append((char) bytes[i]);
350 return value.toString();
353 private int reconstructFsSerial(long termID) {
356 int ssss = (int) (s & 0xFFFF);
358 int yy = (int) s % 53;
359 int ww = (int) s / 53;
362 return ww + yy + ssss;