2 * Copyright (c) 2010-2023 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.max.internal.command;
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.InterfaceAddress;
20 import java.net.NetworkInterface;
21 import java.net.SocketTimeoutException;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Enumeration;
24 import java.util.HashMap;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.max.internal.Utils;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * The {@link UdpCubeCommand} is responsible for sending UDP commands to the MAX!
37 * @author Marcel Verpaalen - Initial contribution
40 public class UdpCubeCommand {
42 private static final String MAXCUBE_COMMAND_STRING = "eQ3Max*\0";
44 private final Logger logger = LoggerFactory.getLogger(UdpCubeCommand.class);
46 protected static boolean commandRunning;
48 private final UdpCommandType commandType;
49 private final String serialNumber;
50 private Map<String, String> commandResponse = new HashMap<>();
52 private String ipAddress;
54 public UdpCubeCommand(UdpCommandType commandType, @Nullable String serialNumber) {
55 this.commandType = commandType;
56 if (serialNumber == null || serialNumber.isEmpty()) {
57 this.serialNumber = "**********";
59 this.serialNumber = serialNumber;
66 * DISCOVERY - I Identify
67 * NETWORK - N Get network address
68 * URL - h get URL information
69 * DEFAULTNET - c get network default info
71 public enum UdpCommandType {
80 * Executes the composed {@link UdpCubeCommand} command
82 public synchronized boolean send() {
84 if (commandType.equals(UdpCommandType.REBOOT)) {
85 commandString = MAXCUBE_COMMAND_STRING + serialNumber + "R";
86 } else if (commandType.equals(UdpCommandType.DISCOVERY)) {
87 commandString = MAXCUBE_COMMAND_STRING + serialNumber + "I";
88 } else if (commandType.equals(UdpCommandType.NETWORK)) {
89 commandString = MAXCUBE_COMMAND_STRING + serialNumber + "N";
90 } else if (commandType.equals(UdpCommandType.URL)) {
91 commandString = MAXCUBE_COMMAND_STRING + serialNumber + "h";
92 } else if (commandType.equals(UdpCommandType.DEFAULTNET)) {
93 commandString = MAXCUBE_COMMAND_STRING + serialNumber + "c";
95 logger.debug("Unknown Command {}", commandType);
98 commandResponse.clear();
99 logger.debug("Send {} command to MAX! Cube {}", commandType, serialNumber);
100 sendUdpCommand(commandString, ipAddress);
101 logger.trace("Done sending command.");
102 receiveUdpCommandResponse();
103 logger.debug("Done receiving response.");
107 private void receiveUdpCommandResponse() {
108 commandRunning = true;
110 try (DatagramSocket bcReceipt = new DatagramSocket(23272)) {
111 bcReceipt.setReuseAddress(true);
112 bcReceipt.setSoTimeout(5000);
114 while (commandRunning) {
115 // Wait for a response
116 byte[] recvBuf = new byte[1500];
117 DatagramPacket receivePacket = new DatagramPacket(recvBuf, recvBuf.length);
118 bcReceipt.receive(receivePacket);
120 // We have a response
121 String message = new String(receivePacket.getData(), receivePacket.getOffset(),
122 receivePacket.getLength(), StandardCharsets.UTF_8);
123 if (logger.isDebugEnabled()) {
124 logger.debug("Broadcast response from {} : {} '{}'", receivePacket.getAddress(), message.length(),
128 // Check if the message is correct
129 if (message.startsWith("eQ3Max") && !message.equals(MAXCUBE_COMMAND_STRING)) {
130 commandResponse.put("maxCubeIP", receivePacket.getAddress().getHostAddress().toString());
131 commandResponse.put("maxCubeState", message.substring(0, 8));
132 commandResponse.put("serialNumber", message.substring(8, 18));
133 commandResponse.put("msgValidid", message.substring(18, 19));
134 String requestType = message.substring(19, 20);
135 commandResponse.put("requestType", requestType);
137 if (requestType.equals("I")) {
138 commandResponse.put("rfAddress",
139 Utils.getHex(message.substring(21, 24).getBytes(StandardCharsets.UTF_8))
140 .replace(" ", "").toLowerCase());
141 commandResponse.put("firmwareVersion", Utils
142 .getHex(message.substring(24, 26).getBytes(StandardCharsets.UTF_8)).replace(" ", "."));
144 // TODO: Further parsing of the other message types
145 commandResponse.put("messageResponse",
146 Utils.getHex(message.substring(20).getBytes(StandardCharsets.UTF_8)));
149 commandRunning = false;
150 if (logger.isDebugEnabled()) {
151 final StringBuilder builder = new StringBuilder();
152 for (final Map.Entry<String, String> entry : commandResponse.entrySet()) {
153 builder.append(String.format("%s: %s\n", entry.getKey(), entry.getValue()));
155 logger.debug("MAX! UDP response {}", builder);
159 } catch (SocketTimeoutException e) {
160 logger.trace("No further response");
161 commandRunning = false;
162 } catch (IOException e) {
163 logger.debug("IO error during MAX! Cube response: {}", e.getMessage());
164 commandRunning = false;
169 * Send broadcast message over all active interfaces
171 * @param commandString string to be used for the discovery
172 * @param ipAddress IP address of the MAX! Cube
175 private void sendUdpCommand(String commandString, @Nullable String ipAddress) {
176 DatagramSocket bcSend = null;
178 bcSend = new DatagramSocket();
179 bcSend.setBroadcast(true);
181 byte[] sendData = commandString.getBytes(StandardCharsets.UTF_8);
183 // Broadcast the message over all the network interfaces
184 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
185 while (interfaces.hasMoreElements()) {
186 NetworkInterface networkInterface = interfaces.nextElement();
187 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
190 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
191 InetAddress[] broadcast = new InetAddress[3];
192 if (ipAddress != null && !ipAddress.isEmpty()) {
193 broadcast[0] = InetAddress.getByName(ipAddress);
195 broadcast[0] = InetAddress.getByName("224.0.0.1");
196 broadcast[1] = InetAddress.getByName("255.255.255.255");
197 broadcast[2] = interfaceAddress.getBroadcast();
199 for (InetAddress bc : broadcast) {
200 // Send the broadcast package!
203 DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, bc, 23272);
204 bcSend.send(sendPacket);
205 } catch (IOException e) {
206 logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
207 } catch (Exception e) {
208 logger.debug("{}", e.getMessage(), e);
210 logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
211 networkInterface.getDisplayName());
216 logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
218 } catch (IOException e) {
219 logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
222 if (bcSend != null) {
225 } catch (Exception e) {
232 * Set the IP address to send the command to. The command will be send to the address and broadcasted over all
235 * @param ipAddress IP address of the MAX! Cube
238 public void setIpAddress(String ipAddress) {
239 this.ipAddress = ipAddress;
243 * Response of the MAX! Cube on the
246 public Map<String, String> getCommandResponse() {
247 return commandResponse;