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 handlers.add(handler);
62 public void removePayloadHandler(PayloadHandler handler) {
63 handlers.remove(handler);
65 if (handlers.isEmpty()) {
66 registry.close(multicastGroup, port);
70 public boolean isOpen() {
71 MulticastSocket socket = this.socket;
72 return socket != null && socket.isConnected();
75 public void open(int intervalSec) throws IOException {
77 // no need to bind socket second time
80 MulticastSocket socket = new MulticastSocket(port);
81 socket.setSoTimeout(5000);
82 InetAddress address = InetAddress.getByName(multicastGroup);
83 socket.joinGroup(address);
85 future = registry.addTask(new ReceivingTask(socket, multicastGroup + ":" + port, handlers), intervalSec);
89 void close() throws IOException {
90 ScheduledFuture<?> future = this.future;
96 InetAddress address = InetAddress.getByName(multicastGroup);
97 MulticastSocket socket = this.socket;
99 socket.leaveGroup(address);
105 public void request() {
106 MulticastSocket socket = this.socket;
107 if (socket != null) {
108 registry.execute(new ReceivingTask(socket, multicastGroup + ":" + port, handlers));
112 static class ReceivingTask implements Runnable {
113 private final Logger logger = LoggerFactory.getLogger(ReceivingTask.class);
114 private final DatagramSocket socket;
115 private final String group;
116 private final List<PayloadHandler> handlers;
118 ReceivingTask(DatagramSocket socket, String group, List<PayloadHandler> handlers) {
119 this.socket = socket;
121 this.handlers = handlers;
126 byte[] bytes = new byte[608];
127 DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
128 DatagramSocket socket = this.socket;
129 socket.receive(msgPacket);
132 EnergyMeter meter = new EnergyMeter();
135 for (PayloadHandler handler : handlers) {
136 handler.handle(meter);
138 } catch (IOException e) {
139 logger.debug("Unexpected payload received for group {}", group, e);
141 } catch (IOException e) {
142 logger.warn("Failed to receive data for multicast group {}", group, e);