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() {
71 logger.debug("Listener closing listener socket");
76 private void receivePackets() {
78 DatagramSocket s = new DatagramSocket(null);
79 s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
80 s.setReuseAddress(true);
81 InetSocketAddress address = new InetSocketAddress(UDP_PORT);
84 logger.debug("Listener got UDP socket on port {} with timeout {}", UDP_PORT, SOCKET_TIMEOUT_MILLISECONDS);
85 } catch (SocketException e) {
86 logger.debug("Listener got SocketException: {}", e.getMessage(), e);
91 DatagramPacket packet = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
92 while (socket != null) {
94 socket.receive(packet);
95 processPacket(packet);
96 } catch (SocketTimeoutException e) {
97 // Nothing to do on socket timeout
98 } catch (IOException e) {
99 logger.debug("Listener got IOException waiting for datagram: {}", e.getMessage());
103 logger.debug("Listener exiting");
106 private void processPacket(DatagramPacket packet) {
107 logger.trace("Got datagram of length {} from {}", packet.getLength(), packet.getAddress().getHostAddress());
109 // Check for duplicate packet
110 if (isDuplicate(packet)) {
111 logger.trace("Dropping duplicate packet");
115 String userId = thingHandler.getUserId();
116 String userPassword = thingHandler.getUserPassword();
117 if (userId == null || userPassword == null) {
118 logger.info("Doorbird user id and/or password is not set in configuration");
122 event.decrypt(packet, userPassword);
123 } catch (RuntimeException e) {
124 // The libsodium library might generate a runtime exception if the packet is malformed
125 logger.info("DoorbirdEvent got unhandled exception: {}", e.getMessage(), e);
129 if (event.isDoorbellEvent()) {
130 if ("motion".equalsIgnoreCase(event.getEventId())) {
131 thingHandler.updateMotionChannel(event.getTimestamp());
133 String intercomId = event.getIntercomId();
134 if (intercomId != null && userId.toLowerCase().startsWith(intercomId.toLowerCase())) {
135 thingHandler.updateDoorbellChannel(event.getTimestamp());
137 logger.info("Received doorbell event for unknown device: {}", event.getIntercomId());
143 private boolean isDuplicate(DatagramPacket packet) {
144 boolean packetIsDuplicate = false;
145 if (lastData != null && lastDataLength == packet.getLength()) {
146 // Packet must be received within 750 ms of previous packet to be considered a duplicate
147 if ((System.currentTimeMillis() - lastDataTime) < 750) {
148 // Compare packets byte-for-byte
149 if (Arrays.equals(lastData, Arrays.copyOf(packet.getData(), packet.getLength()))) {
150 packetIsDuplicate = true;
154 // Remember this packet for duplicate check
155 lastDataLength = packet.getLength();
156 lastData = Arrays.copyOf(packet.getData(), lastDataLength);
157 lastDataTime = System.currentTimeMillis();
158 return packetIsDuplicate;