2 * Copyright (c) 2010-2024 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.Optional;
22 import java.util.stream.Collectors;
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;
49 * The {@link EkeyHandler} is responsible for handling commands, which are
50 * sent to one of the channels.
52 * @author Hans-Jörg Merk - Initial contribution
53 * @author Robert Delbrück - Add natIp
56 public class EkeyHandler extends BaseThingHandler implements EkeyPacketListener {
58 private final Logger logger = LoggerFactory.getLogger(EkeyHandler.class);
60 private final EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider;
62 private EkeyConfiguration config = new EkeyConfiguration();
63 private @Nullable EkeyUdpPacketReceiver receiver;
65 public EkeyHandler(Thing thing, EkeyDynamicStateDescriptionProvider ekeyStateDescriptionProvider) {
67 this.ekeyStateDescriptionProvider = ekeyStateDescriptionProvider;
71 public void handleCommand(ChannelUID channelUID, Command command) {
72 // The binding does not handle any command
76 public void initialize() {
77 logger.debug("ekey handler initializing");
78 config = getConfigAs(EkeyConfiguration.class);
80 if (!config.ipAddress.isEmpty() && config.port != 0) {
81 updateStatus(ThingStatus.UNKNOWN);
83 scheduler.submit(() -> {
84 populateChannels(config.protocol);
85 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
87 EkeyUdpPacketReceiver localReceiver = receiver = new EkeyUdpPacketReceiver(
88 Optional.ofNullable(config.natIp).orElse(config.ipAddress), config.port, readerThreadName);
89 localReceiver.addEkeyPacketListener(this);
91 localReceiver.openConnection();
92 } catch (IOException e) {
93 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Cannot open connection)");
95 updateStatus(ThingStatus.ONLINE);
98 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No IP address specified)");
103 public void dispose() {
104 EkeyUdpPacketReceiver localReceiver = this.receiver;
106 if (localReceiver != null) {
107 localReceiver.closeConnection();
108 localReceiver.removeEkeyPacketListener(this);
114 public void messageReceived(byte[] message) {
115 logger.debug("messageReceived() : {}", message);
116 config = getConfigAs(EkeyConfiguration.class);
117 String delimiter = config.delimiter;
119 switch (config.protocol) {
121 parseRare(message, delimiter);
124 parseMulti(message, delimiter);
127 parseHome(message, delimiter);
133 public void connectionStatusChanged(ThingStatus status, byte @Nullable [] message) {
134 if (message != null) {
135 this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, null);
137 this.updateStatus(status);
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);
163 updateState(CHANNEL_TYPE_ACTION, new DecimalType(action));
164 if (!terminalSerial.isEmpty()) {
165 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf(terminalSerial));
167 updateState(CHANNEL_TYPE_TERMID, DecimalType.valueOf("-1"));
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));
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]);
199 if (!"-".equals(array[1])) {
200 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
202 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
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));
210 if (!"-".equals(array[3])) {
211 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf((array[3])));
213 updateState(CHANNEL_TYPE_USERSTATUS, DecimalType.valueOf("-1"));
215 if (!"-".equals(array[4])) {
216 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[4])));
218 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
220 if (!"-".equals(array[5])) {
221 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf((array[5])));
223 updateState(CHANNEL_TYPE_KEYID, DecimalType.valueOf("-1"));
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])));
231 updateState(CHANNEL_TYPE_INPUTID, DecimalType.valueOf("-1"));
234 logger.trace("received packet is to short : {}", message);
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]);
252 if (!"-".equals(array[1])) {
253 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf((array[1])));
255 updateState(CHANNEL_TYPE_USERID, DecimalType.valueOf("-1"));
257 if (!"-".equals(array[2])) {
258 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf((array[2])));
260 updateState(CHANNEL_TYPE_FINGERID, DecimalType.valueOf("-1"));
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);
268 updateState(CHANNEL_TYPE_RELAYID, DecimalType.valueOf("-1"));
271 logger.trace("received packet is to short : {}", message);
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());
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);
294 public void populateChannels(String protocol) {
295 String channelId = "";
296 String itemType = "Number";
300 channelId = CHANNEL_TYPE_RELAYID;
301 addChannel(channelId, itemType, null);
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);
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);
328 private long getIntValueFrom(byte[] bytes, int start, int length) {
329 if (start + length > bytes.length) {
334 for (int i = start; i < start + length; i++) {
335 value |= (bytes[i] & 0xFF) << bits;
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");
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]);
352 return value.toString();
355 private int reconstructFsSerial(long termID) {
358 int ssss = (int) (s & 0xFFFF);
360 int yy = (int) s % 53;
361 int ww = (int) s / 53;
364 return ww + yy + ssss;