]> git.basschouten.com Git - openhab-addons.git/blob
3d7ebfa5e8e432a7aab996225c61875ebe2e42e9
[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.ekey.internal.handler;
14
15 import static org.openhab.binding.ekey.internal.EkeyBindingConstants.*;
16
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;
22
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;
46
47 /**
48  * The {@link EkeyHandler} is responsible for handling commands, which are
49  * sent to one of the channels.
50  *
51  * @author Hans-Jörg Merk - Initial contribution
52  */
53 @NonNullByDefault
54 public class EkeyHandler extends BaseThingHandler implements EkeyPacketListener {
55
56     private final Logger logger = LoggerFactory.getLogger(EkeyHandler.class);
57
58     private final EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider;
59
60     private EkeyConfiguration config = new EkeyConfiguration();
61     private @Nullable EkeyUdpPacketReceiver receiver;
62
63     public EkeyHandler(Thing thing, EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider) {
64         super(thing);
65         this.ekeyStateDescriptionProvider = ekeyStateDescriptionProvider;
66     }
67
68     @Override
69     public void handleCommand(ChannelUID channelUID, Command command) {
70         // The binding does not handle any command
71     }
72
73     @Override
74     public void initialize() {
75         logger.debug("ekey handler initializing");
76         config = getConfigAs(EkeyConfiguration.class);
77
78         if (!config.ipAddress.isEmpty() && config.port != 0) {
79             updateStatus(ThingStatus.UNKNOWN);
80
81             scheduler.submit(() -> {
82                 populateChannels(config.protocol);
83                 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
84
85                 EkeyUdpPacketReceiver localReceiver = receiver = new EkeyUdpPacketReceiver(config.ipAddress,
86                         config.port, readerThreadName);
87                 localReceiver.addEkeyPacketListener(this);
88                 try {
89                     localReceiver.openConnection();
90                 } catch (IOException e) {
91                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot open connection)");
92                 }
93                 updateStatus(ThingStatus.ONLINE);
94             });
95         } else {
96             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)");
97         }
98     }
99
100     @Override
101     public void dispose() {
102         EkeyUdpPacketReceiver localReceiver = this.receiver;
103
104         if (localReceiver != null) {
105             localReceiver.closeConnection();
106             localReceiver.removeEkeyPacketListener(this);
107         }
108         super.dispose();
109     }
110
111     @Override
112     public void messageReceived(byte[] message) {
113         logger.debug("messageReceived() : {}", message);
114         config = getConfigAs(EkeyConfiguration.class);
115         String delimiter = config.delimiter;
116
117         switch (config.protocol) {
118             case "RARE":
119                 parseRare(message, delimiter);
120                 break;
121             case "MULTI":
122                 parseMulti(message, delimiter);
123                 break;
124             case "HOME":
125                 parseHome(message, delimiter);
126                 break;
127         }
128     }
129
130     @Override
131     public void connectionStatusChanged(ThingStatus status, byte @Nullable [] message) {
132         if (message != null) {
133             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, null);
134         }
135         this.updateStatus(status);
136     }
137
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);
160             }
161             updateState(CHANNEL_TYPE_ACTION, new DecimalType(action));
162             if (!terminalSerial.isEmpty()) {
163                 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf(terminalSerial));
164             } else {
165                 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf("-1"));
166             }
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));
173
174         }
175     }
176
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]);
195
196             }
197             if (!"-".equals(array[1])) {
198                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
199             } else {
200                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
201             }
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));
207             }
208             if (!"-".equals(array[3])) {
209                 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf((array[3])));
210             } else {
211                 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf("-1"));
212             }
213             if (!"-".equals(array[4])) {
214                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[4])));
215             } else {
216                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
217             }
218             if (!"-".equals(array[5])) {
219                 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf((array[5])));
220             } else {
221                 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf("-1"));
222             }
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])));
228             } else {
229                 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf("-1"));
230             }
231         } else {
232             logger.trace("received packet is to short : {}", message);
233         }
234     }
235
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]);
249             }
250             if (!"-".equals(array[1])) {
251                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
252             } else {
253                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
254             }
255             if (!"-".equals(array[2])) {
256                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[2])));
257             } else {
258                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
259             }
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);
265             } else {
266                 updateState(CHANNEL_TYPE_RELAYID, DecimalType.valueOf("-1"));
267             }
268         } else {
269             logger.trace("received packet is to short : {}", message);
270         }
271     }
272
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());
282         }
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);
289         }
290     }
291
292     public void populateChannels(String protocol) {
293         String channelId = "";
294         String itemType = "Number";
295
296         switch (protocol) {
297             case "HOME":
298                 channelId = CHANNEL_TYPE_RELAYID;
299                 addChannel(channelId, itemType, null);
300                 break;
301             case "MULTI":
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);
312                 break;
313             case "RARE":
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);
322                 break;
323         }
324     }
325
326     private long getIntValueFrom(byte[] bytes, int start, int length) {
327         if (start + length > bytes.length) {
328             return -1;
329         }
330         long value = 0;
331         int bits = 0;
332         for (int i = start; i < start + length; i++) {
333             value |= (bytes[i] & 0xFF) << bits;
334             bits += 8;
335         }
336         return value;
337     }
338
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");
342             return "";
343         }
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]);
348             }
349         }
350         return value.toString();
351     }
352
353     private int reconstructFsSerial(long termID) {
354         long s = termID;
355         s ^= 0x70000000;
356         int ssss = (int) (s & 0xFFFF);
357         s >>= 16;
358         int yy = (int) s % 53;
359         int ww = (int) s / 53;
360         yy *= 1000000;
361         ww *= 10000;
362         return ww + yy + ssss;
363     }
364 }