]> git.basschouten.com Git - openhab-addons.git/blob
271fe803af941b3975e5458cc145f1caa17e3691
[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.doorbird.internal.listener;
14
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;
22
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;
28
29 /**
30  * The {@link DoorbirdUdpListener} is responsible for receiving
31  * UDP braodcasts from the Doorbird doorbell.
32  *
33  * @author Mark Hilbush - Initial contribution
34  */
35 @NonNullByDefault
36 public class DoorbirdUdpListener extends Thread {
37     // Doorbird devices report status on a UDP port
38     private static final int UDP_PORT = 6524;
39
40     // How long to wait in milliseconds for a UDP packet
41     private static final int SOCKET_TIMEOUT_MILLISECONDS = 3000;
42
43     private static final int BUFFER_SIZE = 80;
44
45     private final Logger logger = LoggerFactory.getLogger(DoorbirdUdpListener.class);
46
47     private final DoorbirdEvent event = new DoorbirdEvent();
48
49     // Used for callbacks to handler
50     private final DoorbellHandler thingHandler;
51
52     // UDP socket used to receive status events from doorbell
53     private @Nullable DatagramSocket socket;
54
55     private byte @Nullable [] lastData;
56     private int lastDataLength;
57     private long lastDataTime;
58
59     public DoorbirdUdpListener(DoorbellHandler thingHandler) {
60         this.thingHandler = thingHandler;
61     }
62
63     @Override
64     public void run() {
65         receivePackets();
66     }
67
68     public void shutdown() {
69         if (socket != null) {
70             socket.close();
71             logger.debug("Listener closing listener socket");
72             socket = null;
73         }
74     }
75
76     private void receivePackets() {
77         try {
78             DatagramSocket s = new DatagramSocket(null);
79             s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
80             s.setReuseAddress(true);
81             InetSocketAddress address = new InetSocketAddress(UDP_PORT);
82             s.bind(address);
83             socket = s;
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);
87             socket = null;
88             return;
89         }
90
91         DatagramPacket packet = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
92         while (socket != null) {
93             try {
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());
100                 socket = null;
101             }
102         }
103         logger.debug("Listener exiting");
104     }
105
106     private void processPacket(DatagramPacket packet) {
107         logger.trace("Got datagram of length {} from {}", packet.getLength(), packet.getAddress().getHostAddress());
108
109         // Check for duplicate packet
110         if (isDuplicate(packet)) {
111             logger.trace("Dropping duplicate packet");
112             return;
113         }
114
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");
119             return;
120         }
121         try {
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);
126             return;
127         }
128
129         if (event.isDoorbellEvent()) {
130             if ("motion".equalsIgnoreCase(event.getEventId())) {
131                 thingHandler.updateMotionChannel(event.getTimestamp());
132             } else {
133                 String intercomId = event.getIntercomId();
134                 if (intercomId != null && userId.toLowerCase().startsWith(intercomId.toLowerCase())) {
135                     thingHandler.updateDoorbellChannel(event.getTimestamp());
136                 } else {
137                     logger.info("Received doorbell event for unknown device: {}", event.getIntercomId());
138                 }
139             }
140         }
141     }
142
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;
151                 }
152             }
153         }
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;
159     }
160 }