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.net.DatagramPacket;
16 import java.net.DatagramSocket;
17 import java.net.InetSocketAddress;
18 import java.nio.channels.DatagramChannel;
19 import java.util.ArrayList;
20 import java.util.Iterator;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.souliss.internal.SoulissBindingConstants;
25 import org.openhab.binding.souliss.internal.SoulissUDPConstants;
26 import org.openhab.binding.souliss.internal.handler.SoulissGatewayHandler;
27 import org.openhab.binding.souliss.internal.handler.SoulissGenericHandler;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.Thing;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * This class provide to take packet, and send it to regular interval to Souliss
37 * @author Tonino Fazio - Initial contribution
38 * @author Luca Calcaterra - Refactor for OH3
41 public class SendDispatcherRunnable implements Runnable {
43 private final Logger logger = LoggerFactory.getLogger(SendDispatcherRunnable.class);
45 private @Nullable SoulissGatewayHandler gwHandler;
46 static boolean bPopSuspend = false;
47 protected static ArrayList<PacketStruct> packetsList = new ArrayList<>();
48 private long startTime = System.currentTimeMillis();
49 static int iDelay = 0; // equal to 0 if array is empty
50 static int sendMinDelay = 0;
52 public SendDispatcherRunnable(Bridge bridge) {
53 this.gwHandler = (SoulissGatewayHandler) bridge.getHandler();
57 * Put packet to send in ArrayList PacketList
59 public static synchronized void put(DatagramPacket packetToPUT, Logger logger) {
61 var bPacchettoGestito = false;
62 // I extract the node addressed by the incoming packet. returns -1 if the package is not of the
63 // SOULISS_UDP_FUNCTION_FORCE type
64 int node = getNode(packetToPUT);
66 logger.debug("Push packet in queue - Node {}", node);
69 if (packetsList.isEmpty() || node < 0) {
70 bPacchettoGestito = false;
73 // scan packets list to sent
74 for (var i = 0; i < packetsList.size(); i++) {
75 if (node >= 0 && getNode(packetsList.get(i).getPacket()) == node && !packetsList.get(i).getSent()) {
76 // frame for the same node already present in the list
77 logger.debug("Frame UPD per nodo {} già presente in coda. Esecuzione ottimizzazione.", node);
78 bPacchettoGestito = true;
79 // if the packet to be inserted is shorter (or equal) than the one in the queue
80 // then I overwrite the bytes of the packet present in the queue
81 if (packetToPUT.getData().length <= packetsList.get(i).getPacket().getData().length) {
82 // it scrolls the command bytes and if the byte is non-zero overwrites the byte present in the
84 logger.trace("Optimizer. Packet to push: {}", macacoToString(packetToPUT.getData()));
85 logger.trace("Optimizer. Previous frame: {}",
86 macacoToString(packetsList.get(i).getPacket().getData()));
87 // typical values start from byte 12 onwards
88 for (var j = 12; j < packetToPUT.getData().length; j++) {
89 // if the j-th byte is different from zero then
90 // I overwrite it with the byte of the packet already present
91 if (packetToPUT.getData()[j] != 0) {
92 packetsList.get(i).getPacket().getData()[j] = packetToPUT.getData()[j];
95 logger.debug("Optimizer. Previous frame modified to: {}",
96 macacoToString(packetsList.get(i).getPacket().getData()));
98 // if the packet to be inserted is longer than the one in the list then
99 // I overwrite the bytes of the packet to be inserted, then I delete the one in the list
100 // and insert the new one
101 if (packetToPUT.getData().length > packetsList.get(i).getPacket().getData().length) {
102 for (var j = 12; j < packetsList.get(i).getPacket().getData().length; j++) {
103 // if the j-th byte is different from zero then I overwrite it with the byte of the
104 // packet already present
105 if ((packetsList.get(i).getPacket().getData()[j] != 0)
106 && (packetToPUT.getData()[j] == 0)) {
107 // overwrite the bytes of the last frame
108 // only if the byte equals zero.
109 // If the last frame is nonzero
110 // takes precedence and must override
111 packetToPUT.getData()[j] = packetsList.get(i).getPacket().getData()[j];
115 // removes the packet
116 logger.debug("Optimizer. Remove frame: {}",
117 macacoToString(packetsList.get(i).getPacket().getData()));
118 packetsList.remove(i);
120 logger.debug("Optimizer. Add frame: {}", macacoToString(packetToPUT.getData()));
121 packetsList.add(new PacketStruct(packetToPUT));
128 if (!bPacchettoGestito) {
129 logger.debug("Add packet: {}", macacoToString(packetToPUT.getData()));
130 packetsList.add(new PacketStruct(packetToPUT));
137 DatagramSocket sender = null;
139 try (var channel = DatagramChannel.open();) {
141 PacketStruct sp = pop();
144 "SendDispatcherJob - Functional Code 0x{} - Packet: {} - Elementi rimanenti in lista: {}",
145 Integer.toHexString(sp.getPacket().getData()[7]), macacoToString(sp.getPacket().getData()),
148 sender = channel.socket();
149 sender.setReuseAddress(true);
150 sender.setBroadcast(true);
152 var localGwHandler = this.gwHandler;
153 if (localGwHandler != null) {
154 var sa = new InetSocketAddress(localGwHandler.getGwConfig().preferredLocalPortNumber);
156 sender.send(sp.getPacket());
160 // compare the states in memory with the frames sent.
161 // If match deletes the frame from the sent list
166 } catch (Exception e) {
167 logger.warn("{}", e.getMessage());
169 if (sender != null && !sender.isClosed()) {
176 * Get node number from packet
178 private static int getNode(DatagramPacket packet) {
179 // 7 is the byte of the VNet frame at which I find the command code
180 // 10 is the byte of the VNet frame at which I find the node ID
181 if (packet.getData()[7] == SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE) {
182 return packet.getData()[10];
187 private static String macacoToString(byte[] frame2) {
188 byte[] frame = frame2.clone();
189 var sb = new StringBuilder();
191 for (byte b : frame) {
192 sb.append(String.format("%02X ", b));
195 return sb.toString();
199 * check frame updates with packetList, where flag "sent" is true. If all
200 * commands was executed there delete packet in list.
202 public void safeSendCheck() {
205 SoulissGenericHandler localTyp;
211 // scan of the sent packets list
212 for (var i = 0; i < packetsList.size(); i++) {
214 if (packetsList.get(i).getSent()) {
215 node = getNode(packetsList.get(i).getPacket());
217 for (var j = 12; j < packetsList.get(i).getPacket().getData().length; j++) {
218 // I check the slot only if the command is different from ZERO
219 if ((packetsList.get(i).getPacket().getData()[j] != 0) && (this.gwHandler != null)) {
220 localTyp = getHandler(node, iSlot, this.logger);
222 if (localTyp != null) {
223 bExpected = localTyp.getExpectedRawState(packetsList.get(i).getPacket().getData()[j]);
225 // if the expected value of the typical is -1 then it means that the typical does not
233 // translate the command sent with the expected state e
234 // then compare with the current state
235 if (logger.isDebugEnabled() && localTyp != null) {
236 sCmd = Integer.toHexString(packetsList.get(i).getPacket().getData()[j]);
238 sCmd = sCmd.length() < 2 ? "0x0" + sCmd.toUpperCase() : "0x" + sCmd.toUpperCase();
239 sExpected = Integer.toHexString(bExpected);
240 sExpected = sExpected.length() < 2 ? "0x0" + sExpected.toUpperCase()
241 : "0x" + sExpected.toUpperCase();
243 "Compare. Node: {} Slot: {} Node Name: {} Command: {} Expected Souliss State: {} - Actual OH item State: {}",
244 node, iSlot, localTyp.getLabel(), sCmd, sExpected, localTyp.getRawState());
247 if (localTyp != null && checkExpectedState(localTyp.getRawState(), bExpected)) {
248 // if the value of the typical matches the value
249 // transmitted then I set the byte to zero.
250 // when all bytes are equal to zero then
252 packetsList.get(i).getPacket().getData()[j] = 0;
253 logger.debug("{} Node: {} Slot: {} - OK Expected State", localTyp.getLabel(), node,
255 } else if (localTyp == null) {
257 // if the typical is not managed then I set the byte of the relative slot to zero
258 packetsList.get(i).getPacket().getData()[j] = 0;
260 // if there is no typical at slot j then it means that it is one
263 // to the previous one (ex: RGB, T31, ...)
264 // then if slot j-1 = 0 then j can also be set to 0
265 if (packetsList.get(i).getPacket().getData()[j - 1] == 0) {
266 packetsList.get(i).getPacket().getData()[j] = 0;
276 // if the value of all bytes that make up the packet is 0 then I remove the packet from
278 // also if the timout has elapsed then I set the packet to be resent
279 if (checkAllsSlotZero(packetsList.get(i).getPacket())) {
280 logger.debug("Command packet executed - Removed");
281 packetsList.remove(i);
283 // if the frame is not equal to zero I check the TIMEOUT and if
284 // it has expired so I set the SENT flag to false
285 long time = System.currentTimeMillis();
287 SoulissGatewayHandler localGwHandler = this.gwHandler;
288 if (localGwHandler != null) {
289 if ((localGwHandler.getGwConfig().timeoutToRequeue < time - packetsList.get(i).getTime())
290 && (localGwHandler.getGwConfig().timeoutToRemovePacket < time
291 - packetsList.get(i).getTime())) {
292 logger.debug("Packet Execution timeout - Removed");
293 packetsList.remove(i);
295 logger.debug("Packet Execution timeout - Requeued");
296 packetsList.get(i).setSent(false);
305 private @Nullable SoulissGenericHandler getHandler(int node, int slot, Logger logger) {
306 SoulissGatewayHandler localGwHandler = this.gwHandler;
308 Iterator<Thing> thingsIterator;
309 if (localGwHandler != null) {
310 thingsIterator = localGwHandler.getThing().getThings().iterator();
312 while (thingsIterator.hasNext()) {
313 typ = thingsIterator.next();
314 if (typ.getThingTypeUID().equals(SoulissBindingConstants.TOPICS_THING_TYPE)) {
317 SoulissGenericHandler handler = (SoulissGenericHandler) typ.getHandler();
319 // execute it only if binding is Souliss and update is for my
321 if ((handler != null) && (handler.getNode() == node && handler.getSlot() == slot)) {
329 private static boolean checkExpectedState(byte itemState, byte expectedState) {
330 // if expected state is null than return true. The frame will not requeued
331 if (expectedState <= -1) {
334 return itemState == expectedState;
337 private static boolean checkAllsSlotZero(DatagramPacket packet) {
339 for (var j = 12; j < packet.getData().length; j++) {
340 if ((packet.getData()[j] != 0)) {
351 * Pop SocketAndPacket from ArrayList PacketList
354 private synchronized PacketStruct pop() {
355 synchronized (this) {
356 SoulissGatewayHandler localGwHandler = this.gwHandler;
358 // don't pop if bPopSuspend = true
359 // bPopSuspend is set by the put method
360 if ((localGwHandler != null) && (!bPopSuspend)) {
361 t = System.currentTimeMillis();
363 // brings the interval to the minimum only if:
364 // the length of the tail less than or equal to 1;
365 // if the SEND_DELAY time has elapsed.
367 if (packetsList.size() <= 1) {
368 iDelay = sendMinDelay;
370 iDelay = localGwHandler.getGwConfig().sendInterval;
375 var bFlagWhile = true;
376 // discard packages already sent
377 while ((iPacket < packetsList.size()) && bFlagWhile) {
378 if (packetsList.get(iPacket).getSent()) {
385 boolean tFlag = (t - tPrec) >= localGwHandler.getGwConfig().sendInterval;
387 // if we have reached the end of the list and then all
388 // packets have already been sent so I also place the tFlag
389 // to false (as if the timeout hasn't elapsed yet)
390 if (iPacket >= packetsList.size()) {
394 if ((!packetsList.isEmpty()) && tFlag) {
395 tPrec = System.currentTimeMillis();
397 // extract the first element of the list
398 PacketStruct sp = packetsList.get(iPacket);
400 // PACKAGE MANAGEMENT: deleted from the list or
401 // marked as sent if it is a FORCE
402 if (packetsList.get(iPacket).getPacket()
403 .getData()[7] == SoulissUDPConstants.SOULISS_UDP_FUNCTION_FORCE) {
404 // flag sent set to true
405 packetsList.get(iPacket).setSent(true);
407 packetsList.get(iPacket).setTime(System.currentTimeMillis());
409 packetsList.remove(iPacket);
412 logger.debug("POP: {} packets in memory", packetsList.size());
413 if (logger.isDebugEnabled()) {
414 var iPacketSentCounter = 0;
416 while ((i < packetsList.size())) {
417 if (packetsList.get(i).getSent()) {
418 iPacketSentCounter++;
422 logger.debug("POP: {} force frame sent", iPacketSentCounter);
425 logger.debug("Pop frame {} - Delay for 'SendDispatcherThread' setted to {} mills.",
426 macacoToString(sp.getPacket().getData()), iDelay);
435 private void resetTime() {
436 startTime = System.currentTimeMillis();
439 private boolean checkTime() {
440 return startTime < (System.currentTimeMillis() - iDelay);