]> git.basschouten.com Git - openhab-addons.git/blob
f917aaab7055b469cd6b98e51bcbc278cd3e2852
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.max.internal.command;
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.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;
25 import java.util.Map;
26
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;
32
33 /**
34  * The {@link UdpCubeCommand} is responsible for sending UDP commands to the MAX!
35  * Cube LAN gateway.
36  *
37  * @author Marcel Verpaalen - Initial contribution
38  */
39 @NonNullByDefault
40 public class UdpCubeCommand {
41
42     private static final String MAXCUBE_COMMAND_STRING = "eQ3Max*\0";
43
44     private final Logger logger = LoggerFactory.getLogger(UdpCubeCommand.class);
45
46     protected static boolean commandRunning;
47
48     private final UdpCommandType commandType;
49     private final String serialNumber;
50     private Map<String, String> commandResponse = new HashMap<>();
51     @Nullable
52     private String ipAddress;
53
54     public UdpCubeCommand(UdpCommandType commandType, @Nullable String serialNumber) {
55         this.commandType = commandType;
56         if (serialNumber == null || serialNumber.isEmpty()) {
57             this.serialNumber = "**********";
58         } else {
59             this.serialNumber = serialNumber;
60         }
61     }
62
63     /**
64      * UDP command types
65      * REBOOT - R Reboot
66      * DISCOVERY - I Identify
67      * NETWORK - N Get network address
68      * URL - h get URL information
69      * DEFAULTNET - c get network default info
70      */
71     public enum UdpCommandType {
72         REBOOT,
73         DISCOVERY,
74         NETWORK,
75         URL,
76         DEFAULTNET
77     }
78
79     /**
80      * Executes the composed {@link UdpCubeCommand} command
81      */
82     public synchronized boolean send() {
83         String commandString;
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";
94         } else {
95             logger.debug("Unknown Command {}", commandType);
96             return false;
97         }
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.");
104         return true;
105     }
106
107     private void receiveUdpCommandResponse() {
108         commandRunning = true;
109
110         try (DatagramSocket bcReceipt = new DatagramSocket(23272)) {
111             bcReceipt.setReuseAddress(true);
112             bcReceipt.setSoTimeout(5000);
113
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);
119
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(),
125                             message);
126                 }
127
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);
136
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(" ", "."));
143                     } else {
144                         // TODO: Further parsing of the other message types
145                         commandResponse.put("messageResponse",
146                                 Utils.getHex(message.substring(20).getBytes(StandardCharsets.UTF_8)));
147                     }
148
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()));
154                         }
155                         logger.debug("MAX! UDP response {}", builder);
156                     }
157                 }
158             }
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;
165         }
166     }
167
168     /**
169      * Send broadcast message over all active interfaces
170      *
171      * @param commandString string to be used for the discovery
172      * @param ipAddress IP address of the MAX! Cube
173      *
174      */
175     private void sendUdpCommand(String commandString, @Nullable String ipAddress) {
176         DatagramSocket bcSend = null;
177         try {
178             bcSend = new DatagramSocket();
179             bcSend.setBroadcast(true);
180
181             byte[] sendData = commandString.getBytes(StandardCharsets.UTF_8);
182
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()) {
188                     continue;
189                 }
190                 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
191                     InetAddress[] broadcast = new InetAddress[3];
192                     if (ipAddress != null && !ipAddress.isEmpty()) {
193                         broadcast[0] = InetAddress.getByName(ipAddress);
194                     } else {
195                         broadcast[0] = InetAddress.getByName("224.0.0.1");
196                         broadcast[1] = InetAddress.getByName("255.255.255.255");
197                         broadcast[2] = interfaceAddress.getBroadcast();
198                     }
199                     for (InetAddress bc : broadcast) {
200                         // Send the broadcast package!
201                         if (bc != null) {
202                             try {
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);
209                             }
210                             logger.trace("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
211                                     networkInterface.getDisplayName());
212                         }
213                     }
214                 }
215             }
216             logger.trace("Done looping over all network interfaces. Now waiting for a reply!");
217
218         } catch (IOException e) {
219             logger.debug("IO error during MAX! Cube UDP command sending: {}", e.getMessage());
220         } finally {
221             try {
222                 if (bcSend != null) {
223                     bcSend.close();
224                 }
225             } catch (Exception e) {
226                 // Ignore
227             }
228         }
229     }
230
231     /**
232      * Set the IP address to send the command to. The command will be send to the address and broadcasted over all
233      * active interfaces
234      *
235      * @param ipAddress IP address of the MAX! Cube
236      *
237      */
238     public void setIpAddress(String ipAddress) {
239         this.ipAddress = ipAddress;
240     }
241
242     /**
243      * Response of the MAX! Cube on the
244      *
245      */
246     public Map<String, String> getCommandResponse() {
247         return commandResponse;
248     }
249 }