]> git.basschouten.com Git - openhab-addons.git/blob
49a4a911dc2be1aff056362d2e20f21a0068871d
[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.smaenergymeter.internal.packet;
14
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.MulticastSocket;
20 import java.util.List;
21 import java.util.concurrent.CopyOnWriteArrayList;
22 import java.util.concurrent.ScheduledFuture;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.smaenergymeter.internal.handler.EnergyMeter;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * The {@link PacketListener} class is responsible for communication with the SMA devices.
32  * It handles udp/multicast traffic and broadcast received data to subsequent payload handlers.
33  *
34  * @author Ćukasz Dywicki - Initial contribution
35  */
36
37 @NonNullByDefault
38 public class PacketListener {
39
40     private final DefaultPacketListenerRegistry registry;
41     private final List<PayloadHandler> handlers = new CopyOnWriteArrayList<>();
42
43     private String multicastGroup;
44     private int port;
45
46     public static final String DEFAULT_MCAST_GRP = "239.12.255.254";
47     public static final int DEFAULT_MCAST_PORT = 9522;
48
49     private @Nullable MulticastSocket socket;
50     private @Nullable ScheduledFuture<?> future;
51
52     public PacketListener(DefaultPacketListenerRegistry registry, String multicastGroup, int port) {
53         this.registry = registry;
54         this.multicastGroup = multicastGroup;
55         this.port = port;
56     }
57
58     public void addPayloadHandler(PayloadHandler handler) {
59         if (handlers.isEmpty()) {
60             open();
61         }
62         handlers.add(handler);
63     }
64
65     public void removePayloadHandler(PayloadHandler handler) {
66         handlers.remove(handler);
67
68         if (handlers.isEmpty()) {
69             registry.close(multicastGroup, port);
70         }
71     }
72
73     public boolean isOpen() {
74         MulticastSocket socket = this.socket;
75         return socket != null && socket.isConnected();
76     }
77
78     private void open() {
79         if (isOpen()) {
80             // no need to bind socket second time
81             return;
82         }
83         try {
84             MulticastSocket socket = new MulticastSocket(port);
85             socket.setSoTimeout(5000);
86             InetAddress address = InetAddress.getByName(multicastGroup);
87             socket.joinGroup(address);
88
89             future = registry.addTask(new ReceivingTask(socket, multicastGroup + ":" + port, handlers));
90             this.socket = socket;
91         } catch (IOException e) {
92             throw new RuntimeException("Could not open socket", e);
93         }
94     }
95
96     void close() throws IOException {
97         ScheduledFuture<?> future = this.future;
98         if (future != null) {
99             future.cancel(true);
100             this.future = null;
101         }
102
103         InetAddress address = InetAddress.getByName(multicastGroup);
104         MulticastSocket socket = this.socket;
105         if (socket != null) {
106             socket.leaveGroup(address);
107             socket.close();
108             this.socket = null;
109         }
110     }
111
112     public void request() {
113         MulticastSocket socket = this.socket;
114         if (socket != null) {
115             registry.execute(new ReceivingTask(socket, multicastGroup + ":" + port, handlers));
116         }
117     }
118
119     static class ReceivingTask implements Runnable {
120         private final Logger logger = LoggerFactory.getLogger(ReceivingTask.class);
121         private final DatagramSocket socket;
122         private final String group;
123         private final List<PayloadHandler> handlers;
124
125         ReceivingTask(DatagramSocket socket, String group, List<PayloadHandler> handlers) {
126             this.socket = socket;
127             this.group = group;
128             this.handlers = handlers;
129         }
130
131         public void run() {
132             byte[] bytes = new byte[608];
133             DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
134             DatagramSocket socket = this.socket;
135
136             try {
137                 do {
138                     // this loop is intended to receive all packets queued on the socket,
139                     // having a receive() call without loop causes packets to get queued over time,
140                     // if more than one meter present because we consume one packet per second
141                     socket.receive(msgPacket);
142                     EnergyMeter meter = new EnergyMeter();
143                     meter.parse(bytes);
144
145                     for (PayloadHandler handler : handlers) {
146                         handler.handle(meter);
147                     }
148                 } while (msgPacket.getLength() == 608);
149             } catch (IOException e) {
150                 logger.debug("Unexpected payload received for group {}", group, e);
151             }
152         }
153     }
154 }