2 * Copyright (c) 2010-2024 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.smaenergymeter.internal.packet;
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;
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;
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.
34 * @author Ćukasz Dywicki - Initial contribution
38 public class PacketListener {
40 private final DefaultPacketListenerRegistry registry;
41 private final List<PayloadHandler> handlers = new CopyOnWriteArrayList<>();
43 private String multicastGroup;
46 public static final String DEFAULT_MCAST_GRP = "239.12.255.254";
47 public static final int DEFAULT_MCAST_PORT = 9522;
49 private @Nullable MulticastSocket socket;
50 private @Nullable ScheduledFuture<?> future;
52 public PacketListener(DefaultPacketListenerRegistry registry, String multicastGroup, int port) {
53 this.registry = registry;
54 this.multicastGroup = multicastGroup;
58 public void addPayloadHandler(PayloadHandler handler) {
59 if (handlers.isEmpty()) {
62 handlers.add(handler);
65 public void removePayloadHandler(PayloadHandler handler) {
66 handlers.remove(handler);
68 if (handlers.isEmpty()) {
69 registry.close(multicastGroup, port);
73 public boolean isOpen() {
74 MulticastSocket socket = this.socket;
75 return socket != null && socket.isConnected();
80 // no need to bind socket second time
84 MulticastSocket socket = new MulticastSocket(port);
85 socket.setSoTimeout(5000);
86 InetAddress address = InetAddress.getByName(multicastGroup);
87 socket.joinGroup(address);
89 future = registry.addTask(new ReceivingTask(socket, multicastGroup + ":" + port, handlers));
91 } catch (IOException e) {
92 throw new RuntimeException("Could not open socket", e);
96 void close() throws IOException {
97 ScheduledFuture<?> future = this.future;
103 InetAddress address = InetAddress.getByName(multicastGroup);
104 MulticastSocket socket = this.socket;
105 if (socket != null) {
106 socket.leaveGroup(address);
112 public void request() {
113 MulticastSocket socket = this.socket;
114 if (socket != null) {
115 registry.execute(new ReceivingTask(socket, multicastGroup + ":" + port, handlers));
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;
125 ReceivingTask(DatagramSocket socket, String group, List<PayloadHandler> handlers) {
126 this.socket = socket;
128 this.handlers = handlers;
132 byte[] bytes = new byte[608];
133 DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
134 DatagramSocket socket = this.socket;
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();
145 for (PayloadHandler handler : handlers) {
146 handler.handle(meter);
148 } while (msgPacket.getLength() == 608);
149 } catch (IOException e) {
150 logger.debug("Unexpected payload received for group {}", group, e);