]> git.basschouten.com Git - openhab-addons.git/blob
eb59497eae92c854e9dd8f08d230843d1593bb24
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.Optional;
22 import java.util.stream.Collectors;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.ekey.internal.EkeyConfiguration;
27 import org.openhab.binding.ekey.internal.EkeyDynamicStateDescriptionProvider;
28 import org.openhab.binding.ekey.internal.api.EkeyPacketListener;
29 import org.openhab.binding.ekey.internal.api.EkeyUdpPacketReceiver;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.thing.binding.builder.ChannelBuilder;
39 import org.openhab.core.thing.binding.builder.ThingBuilder;
40 import org.openhab.core.thing.type.ChannelKind;
41 import org.openhab.core.thing.type.ChannelTypeUID;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.State;
44 import org.openhab.core.types.StateOption;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 /**
49  * The {@link EkeyHandler} is responsible for handling commands, which are
50  * sent to one of the channels.
51  *
52  * @author Hans-Jörg Merk - Initial contribution
53  * @author Robert Delbrück - Add natIp
54  */
55 @NonNullByDefault
56 public class EkeyHandler extends BaseThingHandler implements EkeyPacketListener {
57
58     private final Logger logger = LoggerFactory.getLogger(EkeyHandler.class);
59
60     private final EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider;
61
62     private EkeyConfiguration config = new EkeyConfiguration();
63     private @Nullable EkeyUdpPacketReceiver receiver;
64
65     public EkeyHandler(Thing thing, EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider) {
66         super(thing);
67         this.ekeyStateDescriptionProvider = ekeyStateDescriptionProvider;
68     }
69
70     @Override
71     public void handleCommand(ChannelUID channelUID, Command command) {
72         // The binding does not handle any command
73     }
74
75     @Override
76     public void initialize() {
77         logger.debug("ekey handler initializing");
78         config = getConfigAs(EkeyConfiguration.class);
79
80         if (!config.ipAddress.isEmpty() && config.port != 0) {
81             updateStatus(ThingStatus.UNKNOWN);
82
83             scheduler.submit(() -> {
84                 populateChannels(config.protocol);
85                 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
86
87                 EkeyUdpPacketReceiver localReceiver = receiver = new EkeyUdpPacketReceiver(
88                         Optional.ofNullable(config.natIp).orElse(config.ipAddress), config.port, readerThreadName);
89                 localReceiver.addEkeyPacketListener(this);
90                 try {
91                     localReceiver.openConnection();
92                 } catch (IOException e) {
93                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot open connection)");
94                 }
95                 updateStatus(ThingStatus.ONLINE);
96             });
97         } else {
98             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)");
99         }
100     }
101
102     @Override
103     public void dispose() {
104         EkeyUdpPacketReceiver localReceiver = this.receiver;
105
106         if (localReceiver != null) {
107             localReceiver.closeConnection();
108             localReceiver.removeEkeyPacketListener(this);
109         }
110         super.dispose();
111     }
112
113     @Override
114     public void messageReceived(byte[] message) {
115         logger.debug("messageReceived() : {}", message);
116         config = getConfigAs(EkeyConfiguration.class);
117         String delimiter = config.delimiter;
118
119         switch (config.protocol) {
120             case "RARE":
121                 parseRare(message, delimiter);
122                 break;
123             case "MULTI":
124                 parseMulti(message, delimiter);
125                 break;
126             case "HOME":
127                 parseHome(message, delimiter);
128                 break;
129         }
130     }
131
132     @Override
133     public void connectionStatusChanged(ThingStatus status, byte @Nullable [] message) {
134         if (message != null) {
135             this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, null);
136         }
137         this.updateStatus(status);
138     }
139
140     public void parseRare(byte[] message, String delimiter) {
141         logger.debug("parse RARE packet");
142         if (message.length >= 72) {
143             byte[] newMessage = Arrays.copyOf(message, 72);
144             String messageString = new String(newMessage);
145             long action = getIntValueFrom(newMessage, 4, 4);
146             long terminalid = getIntValueFrom(newMessage, 8, 4);
147             String terminalSerial = getStringValueFrom(newMessage, 12, 14);
148             long relayid = getIntValueFrom(newMessage, 26, 1);
149             long userid = getIntValueFrom(newMessage, 28, 4);
150             long fingerid = getIntValueFrom(newMessage, 32, 4);
151             int serial = reconstructFsSerial(terminalid);
152             if (logger.isTraceEnabled()) {
153                 logger.trace("messageString received() : {}", messageString);
154                 logger.trace("AKTION           : {}", action);
155                 logger.trace("TERMINAL SERIAL  : {}", terminalSerial);
156                 logger.trace("RESERVED         : {}", getStringValueFrom(newMessage, 27, 1));
157                 logger.trace("RELAY ID         : {}", relayid);
158                 logger.trace("USER ID          : {}", userid);
159                 logger.trace("FINGER ID        : {}", fingerid);
160                 logger.trace("EVENT            : {}", getStringValueFrom(newMessage, 36, 16));
161                 logger.trace("FS SERIAL        : {}", serial);
162             }
163             updateState(CHANNEL_TYPE_ACTION, new DecimalType(action));
164             if (!terminalSerial.isEmpty()) {
165                 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf(terminalSerial));
166             } else {
167                 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf("-1"));
168             }
169             updateState(CHANNEL_TYPE_RESERVED, StringType.valueOf(getStringValueFrom(newMessage, 27, 1)));
170             updateState(CHANNEL_TYPE_RELAYID, new DecimalType(relayid));
171             updateState(CHANNEL_TYPE_USERID, new DecimalType(userid));
172             updateState(CHANNEL_TYPE_FINGERID, new DecimalType(fingerid));
173             updateState(CHANNEL_TYPE_EVENT, StringType.valueOf(getStringValueFrom(newMessage, 36, 16)));
174             updateState(CHANNEL_TYPE_FSSERIAL, new DecimalType(serial));
175
176         }
177     }
178
179     public void parseMulti(byte[] message, String delimiter) {
180         logger.debug("parse MULTI packet");
181         if (message.length >= 46) {
182             byte[] newMessage = Arrays.copyOf(message, 46);
183             String messageString = new String(newMessage);
184             String[] array = messageString.split(delimiter);
185             if (logger.isTraceEnabled()) {
186                 logger.trace("messageString received() : {}", messageString);
187                 logger.trace("USER ID     : {}", array[1]);
188                 logger.trace("USER ID     : {}", array[1]);
189                 logger.trace("USER NAME   : {}", array[2]);
190                 logger.trace("USER STATUS : {}", array[3]);
191                 logger.trace("FINGER ID   : {}", array[4]);
192                 logger.trace("KEY ID      : {}", array[5]);
193                 logger.trace("SERIENNR FS : {}", array[6]);
194                 logger.trace("NAME FS     : {}", new String(array[7]).replace("-", ""));
195                 logger.trace("AKTION      : {}", array[8]);
196                 logger.trace("INPUT ID    : {}", array[9]);
197
198             }
199             if (!"-".equals(array[1])) {
200                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
201             } else {
202                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
203             }
204             String userName = (array[2]).toString();
205             if (!userName.isEmpty()) {
206                 userName = userName.replace("-", "");
207                 userName = userName.replace(" ", "");
208                 updateState(CHANNEL_TYPE_USERNAME, StringType.valueOf(userName));
209             }
210             if (!"-".equals(array[3])) {
211                 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf((array[3])));
212             } else {
213                 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf("-1"));
214             }
215             if (!"-".equals(array[4])) {
216                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[4])));
217             } else {
218                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
219             }
220             if (!"-".equals(array[5])) {
221                 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf((array[5])));
222             } else {
223                 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf("-1"));
224             }
225             updateState(CHANNEL_TYPE_FSSERIAL, DecimalType.valueOf((array[6])));
226             updateState(CHANNEL_TYPE_FSNAME, new StringType(new String(array[7]).replace("-", "")));
227             updateState(CHANNEL_TYPE_ACTION, DecimalType.valueOf((array[8])));
228             if (!"-".equals(array[9])) {
229                 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf((array[9])));
230             } else {
231                 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf("-1"));
232             }
233         } else {
234             logger.trace("received packet is to short : {}", message);
235         }
236     }
237
238     public void parseHome(byte[] message, String delimiter) {
239         logger.debug("parse HOME packet");
240         if (message.length >= 27) {
241             byte[] newMessage = Arrays.copyOf(message, 27);
242             String messageString = new String(newMessage);
243             String[] array = messageString.split(delimiter);
244             if (logger.isTraceEnabled()) {
245                 logger.trace("messageString received() : {}", messageString);
246                 logger.trace("USER ID     : {}", array[1]);
247                 logger.trace("FINGER ID   : {}", array[2]);
248                 logger.trace("SERIENNR FS : {}", array[3]);
249                 logger.trace("AKTION      : {}", array[4]);
250                 logger.trace("RELAY ID    : {}", array[5]);
251             }
252             if (!"-".equals(array[1])) {
253                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
254             } else {
255                 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
256             }
257             if (!"-".equals(array[2])) {
258                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[2])));
259             } else {
260                 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
261             }
262             updateState(CHANNEL_TYPE_FSSERIAL, DecimalType.valueOf((array[3])));
263             updateState(CHANNEL_TYPE_ACTION, DecimalType.valueOf((array[4])));
264             if (!"-".equals(array[5])) {
265                 State relayId = DecimalType.valueOf((array[5]));
266                 updateState(CHANNEL_TYPE_RELAYID, relayId);
267             } else {
268                 updateState(CHANNEL_TYPE_RELAYID, DecimalType.valueOf("-1"));
269             }
270         } else {
271             logger.trace("received packet is to short : {}", message);
272         }
273     }
274
275     public void addChannel(String channelId, String itemType, @Nullable final Collection<String> options) {
276         if (thing.getChannel(channelId) == null) {
277             logger.debug("Channel '{}' for UID to be added", channelId);
278             ThingBuilder thingBuilder = editThing();
279             final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelId);
280             Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelId), itemType)
281                     .withType(channelTypeUID).withKind(ChannelKind.STATE).build();
282             thingBuilder.withChannel(channel);
283             updateThing(thingBuilder.build());
284         }
285         if (options != null) {
286             final List<StateOption> stateOptions = options.stream()
287                     .map(e -> new StateOption(e, e.substring(0, 1) + e.substring(1).toLowerCase()))
288                     .collect(Collectors.toList());
289             logger.debug("StateOptions : '{}'", stateOptions);
290             ekeyStateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId), stateOptions);
291         }
292     }
293
294     public void populateChannels(String protocol) {
295         String channelId = "";
296         String itemType = "Number";
297
298         switch (protocol) {
299             case "HOME":
300                 channelId = CHANNEL_TYPE_RELAYID;
301                 addChannel(channelId, itemType, null);
302                 break;
303             case "MULTI":
304                 channelId = CHANNEL_TYPE_USERNAME;
305                 addChannel(channelId, "String", null);
306                 channelId = CHANNEL_TYPE_USERSTATUS;
307                 addChannel(channelId, itemType, null);
308                 channelId = CHANNEL_TYPE_KEYID;
309                 addChannel(channelId, itemType, null);
310                 channelId = CHANNEL_TYPE_FSNAME;
311                 addChannel(channelId, "String", null);
312                 channelId = CHANNEL_TYPE_INPUTID;
313                 addChannel(channelId, itemType, null);
314                 break;
315             case "RARE":
316                 channelId = CHANNEL_TYPE_TERMID;
317                 addChannel(channelId, itemType, null);
318                 channelId = CHANNEL_TYPE_RELAYID;
319                 addChannel(channelId, itemType, null);
320                 channelId = CHANNEL_TYPE_RESERVED;
321                 addChannel(channelId, "String", null);
322                 channelId = CHANNEL_TYPE_EVENT;
323                 addChannel(channelId, "String", null);
324                 break;
325         }
326     }
327
328     private long getIntValueFrom(byte[] bytes, int start, int length) {
329         if (start + length > bytes.length) {
330             return -1;
331         }
332         long value = 0;
333         int bits = 0;
334         for (int i = start; i < start + length; i++) {
335             value |= (bytes[i] & 0xFF) << bits;
336             bits += 8;
337         }
338         return value;
339     }
340
341     private String getStringValueFrom(byte[] bytes, int start, int length) {
342         if (start + length > bytes.length) {
343             logger.debug("Could not get String from bytes");
344             return "";
345         }
346         StringBuffer value = new StringBuffer();
347         for (int i = start + length - 1; i >= start; i--) {
348             if (bytes[i] > (byte) ' ' && bytes[i] < (byte) '~') {
349                 value.append((char) bytes[i]);
350             }
351         }
352         return value.toString();
353     }
354
355     private int reconstructFsSerial(long termID) {
356         long s = termID;
357         s ^= 0x70000000;
358         int ssss = (int) (s & 0xFFFF);
359         s >>= 16;
360         int yy = (int) s % 53;
361         int ww = (int) s / 53;
362         yy *= 1000000;
363         ww *= 10000;
364         return ww + yy + ssss;
365     }
366 }