2 * Copyright (c) 2010-2023 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.doorbird.internal.listener;
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetSocketAddress;
19 import java.net.SocketException;
20 import java.net.SocketTimeoutException;
21 import java.util.Arrays;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.doorbird.internal.handler.DoorbellHandler;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * The {@link DoorbirdUdpListener} is responsible for receiving
31 * UDP braodcasts from the Doorbird doorbell.
33 * @author Mark Hilbush - Initial contribution
36 public class DoorbirdUdpListener extends Thread {
37 // Doorbird devices report status on a UDP port
38 private static final int UDP_PORT = 6524;
40 // How long to wait in milliseconds for a UDP packet
41 private static final int SOCKET_TIMEOUT_MILLISECONDS = 3000;
43 private static final int BUFFER_SIZE = 80;
45 private final Logger logger = LoggerFactory.getLogger(DoorbirdUdpListener.class);
47 private final DoorbirdEvent event = new DoorbirdEvent();
49 // Used for callbacks to handler
50 private final DoorbellHandler thingHandler;
52 // UDP socket used to receive status events from doorbell
53 private @Nullable DatagramSocket socket;
55 private byte @Nullable [] lastData;
56 private int lastDataLength;
57 private long lastDataTime;
59 public DoorbirdUdpListener(DoorbellHandler thingHandler) {
60 this.thingHandler = thingHandler;
68 public void shutdown() {
69 DatagramSocket socket = this.socket;
72 logger.debug("Listener closing listener socket");
77 private void receivePackets() {
79 DatagramSocket s = new DatagramSocket(null);
80 s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
81 s.setReuseAddress(true);
82 InetSocketAddress address = new InetSocketAddress(UDP_PORT);
85 logger.debug("Listener got UDP socket on port {} with timeout {}", UDP_PORT, SOCKET_TIMEOUT_MILLISECONDS);
86 } catch (SocketException e) {
87 logger.debug("Listener got SocketException: {}", e.getMessage(), e);
92 DatagramPacket packet = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
93 while (socket != null) {
95 socket.receive(packet);
96 processPacket(packet);
97 } catch (SocketTimeoutException e) {
98 // Nothing to do on socket timeout
99 } catch (IOException e) {
100 logger.debug("Listener got IOException waiting for datagram: {}", e.getMessage());
104 logger.debug("Listener exiting");
107 private void processPacket(DatagramPacket packet) {
108 logger.trace("Got datagram of length {} from {}", packet.getLength(), packet.getAddress().getHostAddress());
110 // Check for duplicate packet
111 if (isDuplicate(packet)) {
112 logger.trace("Dropping duplicate packet");
116 String userId = thingHandler.getUserId();
117 String userPassword = thingHandler.getUserPassword();
118 if (userId == null || userPassword == null) {
119 logger.info("Doorbird user id and/or password is not set in configuration");
123 event.decrypt(packet, userPassword);
124 } catch (RuntimeException e) {
125 // The libsodium library might generate a runtime exception if the packet is malformed
126 logger.info("DoorbirdEvent got unhandled exception: {}", e.getMessage(), e);
130 if (event.isDoorbellEvent()) {
131 if ("motion".equalsIgnoreCase(event.getEventId())) {
132 thingHandler.updateMotionChannel(event.getTimestamp());
134 String intercomId = event.getIntercomId();
135 if (intercomId != null && userId.toLowerCase().startsWith(intercomId.toLowerCase())) {
136 thingHandler.updateDoorbellChannel(event.getTimestamp());
138 logger.info("Received doorbell event for unknown device: {}", event.getIntercomId());
144 private boolean isDuplicate(DatagramPacket packet) {
145 boolean packetIsDuplicate = false;
146 if (lastData != null && lastDataLength == packet.getLength()) {
147 // Packet must be received within 750 ms of previous packet to be considered a duplicate
148 if ((System.currentTimeMillis() - lastDataTime) < 750) {
149 // Compare packets byte-for-byte
150 if (Arrays.equals(lastData, Arrays.copyOf(packet.getData(), packet.getLength()))) {
151 packetIsDuplicate = true;
155 // Remember this packet for duplicate check
156 lastDataLength = packet.getLength();
157 lastData = Arrays.copyOf(packet.getData(), lastDataLength);
158 lastDataTime = System.currentTimeMillis();
159 return packetIsDuplicate;