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.souliss.internal.protocol;
15 import java.io.IOException;
16 import java.net.DatagramPacket;
17 import java.net.DatagramSocket;
18 import java.net.InetAddress;
19 import java.net.InetSocketAddress;
20 import java.net.InterfaceAddress;
21 import java.net.NetworkInterface;
22 import java.net.SocketException;
23 import java.net.UnknownHostException;
24 import java.nio.channels.DatagramChannel;
25 import java.util.ArrayList;
26 import java.util.Enumeration;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.souliss.internal.SoulissUDPConstants;
31 import org.openhab.binding.souliss.internal.config.GatewayConfig;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * This class provide to construct MaCaco and UDP frame
38 * @author Tonino Fazio - Initial contribution
39 * @author Luca Calcaterra - Refactor for OH3
40 * @author Alessandro Del Pex - Souliss App
43 public class CommonCommands {
45 private final Logger logger = LoggerFactory.getLogger(CommonCommands.class);
47 private static final String LITERAL_SEND_FRAME = "sendFORCEFrame - {}, soulissNodeIPAddressOnLAN: {}";
49 public final void sendFORCEFrame(GatewayConfig gwConfig, int idNode, int slot, byte shortCommand) {
50 sendFORCEFrame(gwConfig, idNode, slot, shortCommand, null, null, null);
54 * used for set dimmer value. It set command at first byte and dimmerVal to
57 public final void sendFORCEFrame(GatewayConfig gwConfig, int idNode, int slot, byte shortCommand, byte lDimmer) {
58 sendFORCEFrame(gwConfig, idNode, slot, shortCommand, lDimmer, null, null);
62 * send force frame with command and RGB value
64 public final void sendFORCEFrame(GatewayConfig gwConfig, int idNode, int slot, byte shortCommand,
65 @Nullable Byte byte1, @Nullable Byte byte2, @Nullable Byte byte3) {
66 ArrayList<Byte> macacoFrame = new ArrayList<>();
67 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE);
69 // PUTIN, STARTOFFEST, NUMBEROF
71 macacoFrame.add((byte) 0x0);
73 macacoFrame.add((byte) 0x0);
75 macacoFrame.add((byte) (idNode));// Start Offset
77 if (byte1 == null && byte2 == null && byte3 == null) {
79 macacoFrame.add((byte) ((byte) slot + 1));
80 } else if (byte2 == null && byte3 == null) {
81 // Number Of byte of payload= command + set byte
82 macacoFrame.add((byte) ((byte) slot + 2));
84 // Number Of byte of payload= OnOFF + Red + Green + Blu
85 macacoFrame.add((byte) ((byte) slot + 4));
88 for (var i = 0; i <= slot - 1; i++) {
89 // I set the bytes preceding the slot to be modified to zero
90 macacoFrame.add((byte) 00);
93 macacoFrame.add(shortCommand);
95 if (byte1 != null && byte2 != null && byte3 != null) {
97 macacoFrame.add(byte1);
99 macacoFrame.add(byte2);
101 macacoFrame.add(byte3);
102 } else if (byte1 != null) {
104 macacoFrame.add(byte1);
107 logger.debug(LITERAL_SEND_FRAME, macacoToString(macacoFrame), gwConfig);
108 queueToDispatcher(macacoFrame, gwConfig);
112 * T61 send frame to push the setpoint value
115 public final void sendFORCEFrameT61SetPoint(GatewayConfig gwConfig, int idNode, int slot, Byte byte1, Byte byte2) {
116 ArrayList<Byte> macacoFrame = new ArrayList<>();
117 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE);
119 // PUTIN, STARTOFFEST, NUMBEROF
121 macacoFrame.add((byte) 0x00);
123 macacoFrame.add((byte) 0x00);
125 macacoFrame.add((byte) (idNode));
126 // Number Of byte of payload= command + set byte
127 macacoFrame.add((byte) ((byte) slot + 2));
129 for (var i = 0; i <= slot - 1; i++) {
130 // I set the bytes preceding the slot to be modified to zero
131 macacoFrame.add((byte) 00);
134 // first byte Setpoint Value
135 macacoFrame.add(byte1);
136 // second byte Setpoint Value
137 macacoFrame.add(byte2);
139 logger.debug(LITERAL_SEND_FRAME, macacoToString(macacoFrame), gwConfig);
141 queueToDispatcher(macacoFrame, gwConfig);
145 * T31 send force frame with command and setpoint float
147 public final void sendFORCEFrameT31SetPoint(GatewayConfig gwConfig, int idNode, int slot, byte shortCommand,
148 Byte byte1, Byte byte2) {
149 ArrayList<Byte> macacoFrame = new ArrayList<>();
150 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE);
152 // PUTIN, STARTOFFEST, NUMBEROF
154 macacoFrame.add((byte) 0x00);
156 macacoFrame.add((byte) 0x00);
159 macacoFrame.add((byte) (idNode));
160 // Number Of byte of payload= command + set byte
161 macacoFrame.add((byte) ((byte) slot + 5));
163 for (var i = 0; i <= slot - 1; i++) {
164 // prvious byte to zero
165 macacoFrame.add((byte) 00);
166 // slot to be changed
169 macacoFrame.add(shortCommand);
171 // Empty - Temperature Measured Value
172 macacoFrame.add((byte) 0x0);
173 // Empty - Temperature Measured Value
174 macacoFrame.add((byte) 0x0);
175 // Temperature Setpoint Value
176 macacoFrame.add(byte1);
177 // Temperature Setpoint Value
178 macacoFrame.add(byte2);
180 logger.debug(LITERAL_SEND_FRAME, macacoToString(macacoFrame), gwConfig);
181 queueToDispatcher(macacoFrame, gwConfig);
184 public final void sendDBStructFrame(GatewayConfig gwConfig) {
185 ArrayList<Byte> macacoFrame = new ArrayList<>();
186 macacoFrame.add((byte) SoulissUDPConstants.SOULISS_UDP_FUNCTION_DBSTRUCT_REQ);
188 macacoFrame.add((byte) 0x0);
190 macacoFrame.add((byte) 0x0);
192 macacoFrame.add((byte) 0x0);
194 macacoFrame.add((byte) 0x0);
196 logger.debug("sendDBStructFrame - {}, soulissNodeIPAddressOnLAN: {}", macacoToString(macacoFrame), gwConfig);
197 queueToDispatcher(macacoFrame, gwConfig);
201 * Queue command to Dispatcher (for securesend retransmission)
203 private final void queueToDispatcher(ArrayList<Byte> macacoFrame, GatewayConfig gwConfig) {
204 ArrayList<Byte> buf = buildVNetFrame(macacoFrame, gwConfig.gatewayLanAddress, (byte) gwConfig.userIndex,
205 (byte) gwConfig.nodeIndex);
206 byte[] merd = toByteArray(buf);
208 InetAddress serverAddr;
210 serverAddr = gwConfig.gatewayWanAddress.isEmpty() ? InetAddress.getByName(gwConfig.gatewayLanAddress)
211 : InetAddress.getByName(gwConfig.gatewayWanAddress);
212 var packet = new DatagramPacket(merd, merd.length, serverAddr,
213 SoulissUDPConstants.SOULISS_GATEWAY_DEFAULT_PORT);
214 SendDispatcherRunnable.put(packet, logger);
215 } catch (IOException e) {
216 logger.warn("Error: {} ", e.getMessage());
221 * send broadcast UDP frame - unused in this version
223 private final void sendBroadcastNow(ArrayList<Byte> macacoFrame) {
224 byte iUserIndex = (byte) 120;
225 byte iNodeIndex = (byte) 70;
227 // Broadcast the message over all the network interfaces
228 Enumeration<@Nullable NetworkInterface> interfaces;
229 DatagramSocket sender = null;
231 interfaces = NetworkInterface.getNetworkInterfaces();
233 while (interfaces.hasMoreElements()) {
234 var networkInterface = interfaces.nextElement();
235 if (networkInterface != null) {
236 if (networkInterface.isLoopback() || !networkInterface.isUp()) {
239 for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) {
240 var broadcast = new InetAddress[3];
241 broadcast[0] = InetAddress.getByName("224.0.0.1");
242 broadcast[1] = InetAddress.getByName("255.255.255.255");
243 broadcast[2] = interfaceAddress.getBroadcast();
244 for (InetAddress bc : broadcast) {
245 // Send the broadcast package!
248 ArrayList<Byte> buf = buildVNetFrame(macacoFrame, "255.255.255.255", iUserIndex,
250 byte[] merd = toByteArray(buf);
251 var packet = new DatagramPacket(merd, merd.length, bc,
252 SoulissUDPConstants.SOULISS_GATEWAY_DEFAULT_PORT);
253 // Datagramsocket creation
254 var channel = DatagramChannel.open();
255 sender = channel.socket();
256 sender.setReuseAddress(true);
257 sender.setBroadcast(true);
259 var sa = new InetSocketAddress(230);
263 logger.debug("Request packet sent to: {} Interface: {}", bc.getHostAddress(),
264 networkInterface.getDisplayName());
266 } catch (IOException e) {
267 logger.debug("IO error: {}", e.getMessage());
268 } catch (Exception e) {
269 logger.debug("{}", e.getMessage(), e);
271 if ((sender != null) && (!sender.isClosed())) {
280 } catch (SocketException | UnknownHostException e) {
281 logger.warn("{}", e.getMessage());
288 private final ArrayList<Byte> buildVNetFrame(ArrayList<Byte> macacoFrame2, @Nullable String gatewayLanAddress,
289 byte iUserIndex, byte iNodeIndex) {
290 if (gatewayLanAddress != null) {
291 ArrayList<Byte> frame = new ArrayList<>();
294 ip = InetAddress.getByName(gatewayLanAddress);
295 } catch (UnknownHostException e) {
296 logger.warn("{}", e.getMessage());
299 byte[] dude = ip.getAddress();
302 frame.add((byte) 23);
303 // es 192.168.1.XX BOARD
304 frame.add((byte) (dude[3] & 0xFF));
306 // n broadcast : communication by Ip
307 // 255.255.255.255 to associate vNet 0xFFFF address.
308 frame.add(gatewayLanAddress.compareTo(SoulissUDPConstants.BROADCASTADDR) == 0 ? dude[2] : 0);
309 // NODE INDEX - source vNet address User Interface
310 frame.add(iNodeIndex);
311 // USER INDEX - source vNet address User Interface
312 frame.add(iUserIndex);
314 // adds the calculation in the head
316 frame.add(0, (byte) (frame.size() + macacoFrame2.size() + 1));
318 frame.add(0, (byte) (frame.size() + macacoFrame2.size() + 1));
320 frame.addAll(macacoFrame2);
323 throw new IllegalArgumentException("Cannot build VNet Frame . Null Souliss IP address");
328 * Builds old-school byte array
333 private final byte[] toByteArray(ArrayList<Byte> buf) {
334 var merd = new byte[buf.size()];
335 for (var i = 0; i < buf.size(); i++) {
336 merd[i] = buf.get(i);
342 * Build MULTICAST FORCE Frame
344 public final void sendMULTICASTFORCEFrame(GatewayConfig gwConfig, byte typical, byte shortCommand) {
345 ArrayList<Byte> macacoFrame = new ArrayList<>();
346 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE_MASSIVE);
348 // PUTIN, STARTOFFEST, NUMBEROF
350 macacoFrame.add((byte) 0x0);
352 macacoFrame.add((byte) 0x0);
354 macacoFrame.add(typical);
356 macacoFrame.add((byte) 1);
358 macacoFrame.add(shortCommand);
359 logger.debug("sendMULTICASTFORCEFrame - {}, soulissNodeIPAddressOnLAN: {}", macacoToString(macacoFrame),
361 queueToDispatcher(macacoFrame, gwConfig);
367 public final void sendPing(@Nullable GatewayConfig gwConfig) {
368 if (gwConfig != null) {
369 ArrayList<Byte> macacoFrame = new ArrayList<>();
370 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_PING_REQ);
372 // PUTIN, STARTOFFEST, NUMBEROF
374 macacoFrame.add((byte) 0x00);
376 macacoFrame.add((byte) 0x00);
378 macacoFrame.add((byte) 0x00);
380 macacoFrame.add((byte) 0x00);
381 logger.debug("sendPing - {}, IP: {} ", macacoToString(macacoFrame), gwConfig);
382 queueToDispatcher(macacoFrame, gwConfig);
384 logger.warn("Cannot send Souliss Ping - Ip null");
389 * Build BROADCAST PING Frame
391 public final void sendBroadcastGatewayDiscover() {
392 ArrayList<Byte> macacoFrame = new ArrayList<>();
393 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_DISCOVER_GW_NODE_BCAST_REQ);
395 // PUTIN, STARTOFFEST, NUMBEROF
397 macacoFrame.add((byte) 0x05);
399 macacoFrame.add((byte) 0x00);
401 macacoFrame.add((byte) 0x00);
403 macacoFrame.add((byte) 0x00);
404 logger.debug("sendBroadcastPing - {} ", macacoToString(macacoFrame));
405 sendBroadcastNow(macacoFrame);
409 * Build SUBSCRIPTION Frame
411 public final void sendSUBSCRIPTIONframe(GatewayConfig gwConfig, int iNodes) {
412 ArrayList<Byte> macacoFrame = new ArrayList<>();
413 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_SUBSCRIBE_REQ);
415 // PUTIN, STARTOFFEST, NUMBEROF
417 macacoFrame.add((byte) 0x00);
419 macacoFrame.add((byte) 0x00);
420 macacoFrame.add((byte) 0x00);
422 macacoFrame.add((byte) iNodes);
423 logger.debug("sendSUBSCRIPTIONframe - {}, IP: {} ", macacoToString(macacoFrame), gwConfig);
424 queueToDispatcher(macacoFrame, gwConfig);
428 * Build HEALTHY REQUEST Frame
430 public final void sendHealthyRequestFrame(GatewayConfig gwConfig, int iNodes) {
431 ArrayList<Byte> macacoFrame = new ArrayList<>();
432 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_HEALTHY_REQ);
434 // PUTIN, STARTOFFSET, NUMBEROF
436 macacoFrame.add((byte) 0x00);
438 macacoFrame.add((byte) 0x00);
439 macacoFrame.add((byte) 0x00);
440 macacoFrame.add((byte) iNodes);
441 logger.debug("sendHealthyRequestFrame - {}, IP: {} ", macacoToString(macacoFrame), gwConfig);
442 queueToDispatcher(macacoFrame, gwConfig);
446 * Build TYPICAL REQUEST Frame
448 public final void sendTypicalRequestFrame(GatewayConfig gwConfig, int nodes) {
449 ArrayList<Byte> macacoFrame = new ArrayList<>();
450 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_TYP_REQ);
451 // PUTIN, STARTOFFEST, NUMBEROF
453 macacoFrame.add((byte) 0x00);
455 macacoFrame.add((byte) 0x00);
457 macacoFrame.add((byte) 0x00);
459 macacoFrame.add((byte) nodes);
460 logger.debug("sendTypicalRequestFrame - {}, IP: {} ", macacoToString(macacoFrame), gwConfig.gatewayLanAddress);
461 queueToDispatcher(macacoFrame, gwConfig);
465 * Build TYPICAL REQUEST Frame with start offset
467 public final void sendTypicalRequestFrame(GatewayConfig gwConfig, int start, int nodes) {
468 ArrayList<Byte> macacoFrame = new ArrayList<>();
469 macacoFrame.add(SoulissUDPConstants.SOULISS_UDP_FUNCTION_TYP_REQ);
470 // PUTIN, STARTOFFEST, NUMBEROF
472 macacoFrame.add((byte) 0x00);
474 macacoFrame.add((byte) 0x00);
476 macacoFrame.add((byte) start);
478 macacoFrame.add((byte) nodes);
479 logger.debug("sendTypicalRequestFrame - {}, IP: {} ", macacoToString(macacoFrame), gwConfig.gatewayLanAddress);
480 queueToDispatcher(macacoFrame, gwConfig);
485 private final String macacoToString(ArrayList<Byte> mACACOframe) {
486 // I copy arrays to avoid concurrent changes
487 ArrayList<Byte> mACACOframe2 = new ArrayList<>();
488 mACACOframe2.addAll(mACACOframe);
490 var sb = new StringBuilder();
492 for (byte b : mACACOframe2) {
493 sb.append(String.format("%02X ", b));
497 return sb.toString();