]> git.basschouten.com Git - openhab-addons.git/blob
dcc918d6208834dfe7e3f2d579ba8357ceecd979
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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         DatagramSocket socket = this.socket;
70         if (socket != null) {
71             socket.close();
72             logger.debug("Listener closing listener socket");
73             this.socket = null;
74         }
75     }
76
77     private void receivePackets() {
78         try {
79             DatagramSocket s = new DatagramSocket(null);
80             s.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
81             s.setReuseAddress(true);
82             InetSocketAddress address = new InetSocketAddress(UDP_PORT);
83             s.bind(address);
84             socket = s;
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);
88             socket = null;
89             return;
90         }
91
92         DatagramPacket packet = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
93         while (socket != null) {
94             try {
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());
101                 socket = null;
102             }
103         }
104         logger.debug("Listener exiting");
105     }
106
107     private void processPacket(DatagramPacket packet) {
108         logger.trace("Got datagram of length {} from {}", packet.getLength(), packet.getAddress().getHostAddress());
109
110         // Check for duplicate packet
111         if (isDuplicate(packet)) {
112             logger.trace("Dropping duplicate packet");
113             return;
114         }
115
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");
120             return;
121         }
122         try {
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);
127             return;
128         }
129
130         if (event.isDoorbellEvent()) {
131             if ("motion".equalsIgnoreCase(event.getEventId())) {
132                 thingHandler.updateMotionChannel(event.getTimestamp());
133             } else {
134                 String intercomId = event.getIntercomId();
135                 if (intercomId != null && userId.toLowerCase().startsWith(intercomId.toLowerCase())) {
136                     thingHandler.updateDoorbellChannel(event.getTimestamp());
137                 } else {
138                     logger.info("Received doorbell event for unknown device: {}", event.getIntercomId());
139                 }
140             }
141         }
142     }
143
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;
152                 }
153             }
154         }
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;
160     }
161 }