]> git.basschouten.com Git - openhab-addons.git/blob
38da8a39fa6ba8985d4d96d82483bab796551b2e
[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.souliss.internal.protocol;
14
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;
21
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;
32
33 /**
34  * This class provide to take packet, and send it to regular interval to Souliss
35  * Network
36  *
37  * @author Tonino Fazio - Initial contribution
38  * @author Luca Calcaterra - Refactor for OH3
39  */
40 @NonNullByDefault
41 public class SendDispatcherRunnable implements Runnable {
42
43     private final Logger logger = LoggerFactory.getLogger(SendDispatcherRunnable.class);
44
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;
51
52     public SendDispatcherRunnable(Bridge bridge) {
53         this.gwHandler = (SoulissGatewayHandler) bridge.getHandler();
54     }
55
56     /**
57      * Put packet to send in ArrayList PacketList
58      */
59     public static synchronized void put(DatagramPacket packetToPUT, Logger logger) {
60         bPopSuspend = true;
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);
65         if (node >= 0) {
66             logger.debug("Push packet in queue - Node {}", node);
67         }
68
69         if (packetsList.isEmpty() || node < 0) {
70             bPacchettoGestito = false;
71         } else {
72             // OPTIMIZER
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
83                         // queued packet
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];
93                             }
94                         }
95                         logger.debug("Optimizer. Previous frame modified to: {}",
96                                 macacoToString(packetsList.get(i).getPacket().getData()));
97                     } else {
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];
112
113                                 }
114                             }
115                             // removes the packet
116                             logger.debug("Optimizer. Remove frame: {}",
117                                     macacoToString(packetsList.get(i).getPacket().getData()));
118                             packetsList.remove(i);
119                             // inserts the new
120                             logger.debug("Optimizer. Add frame: {}", macacoToString(packetToPUT.getData()));
121                             packetsList.add(new PacketStruct(packetToPUT));
122                         }
123                     }
124                 }
125             }
126         }
127
128         if (!bPacchettoGestito) {
129             logger.debug("Add packet: {}", macacoToString(packetToPUT.getData()));
130             packetsList.add(new PacketStruct(packetToPUT));
131         }
132         bPopSuspend = false;
133     }
134
135     @Override
136     public void run() {
137         DatagramSocket sender = null;
138
139         try (var channel = DatagramChannel.open();) {
140             if (checkTime()) {
141                 PacketStruct sp = pop();
142                 if (sp != null) {
143                     logger.debug(
144                             "SendDispatcherJob - Functional Code 0x{} - Packet: {} - Elementi rimanenti in lista: {}",
145                             Integer.toHexString(sp.getPacket().getData()[7]), macacoToString(sp.getPacket().getData()),
146                             packetsList.size());
147
148                     sender = channel.socket();
149                     sender.setReuseAddress(true);
150                     sender.setBroadcast(true);
151
152                     var localGwHandler = this.gwHandler;
153                     if (localGwHandler != null) {
154                         var sa = new InetSocketAddress(localGwHandler.getGwConfig().preferredLocalPortNumber);
155                         sender.bind(sa);
156                         sender.send(sp.getPacket());
157                     }
158                 }
159
160                 // compare the states in memory with the frames sent.
161                 // If match deletes the frame from the sent list
162                 safeSendCheck();
163
164                 resetTime();
165             }
166         } catch (Exception e) {
167             logger.warn("{}", e.getMessage());
168         } finally {
169             if (sender != null && !sender.isClosed()) {
170                 sender.close();
171             }
172         }
173     }
174
175     /**
176      * Get node number from packet
177      */
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];
183         }
184         return -1;
185     }
186
187     private static String macacoToString(byte[] frame2) {
188         byte[] frame = frame2.clone();
189         var sb = new StringBuilder();
190         sb.append("HEX: [");
191         for (byte b : frame) {
192             sb.append(String.format("%02X ", b));
193         }
194         sb.append("]");
195         return sb.toString();
196     }
197
198     /**
199      * check frame updates with packetList, where flag "sent" is true. If all
200      * commands was executed there delete packet in list.
201      */
202     public void safeSendCheck() {
203         int node;
204         int iSlot;
205         SoulissGenericHandler localTyp;
206         var sCmd = "";
207         byte bExpected;
208
209         var sExpected = "";
210
211         // scan of the sent packets list
212         for (var i = 0; i < packetsList.size(); i++) {
213
214             if (packetsList.get(i).getSent()) {
215                 node = getNode(packetsList.get(i).getPacket());
216                 iSlot = 0;
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);
221
222                         if (localTyp != null) {
223                             bExpected = localTyp.getExpectedRawState(packetsList.get(i).getPacket().getData()[j]);
224
225                             // if the expected value of the typical is -1 then it means that the typical does not
226                             // support the
227                             // function
228                             // secureSend
229                             if (bExpected < 0) {
230                                 localTyp = null;
231                             }
232
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]);
237                                 // command sent
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();
242                                 logger.debug(
243                                         "Compare. Node: {} Slot: {} Node Name: {} Command: {} Expected Souliss State: {} - Actual OH item State: {}",
244                                         node, iSlot, localTyp.getLabel(), sCmd, sExpected, localTyp.getRawState());
245                             }
246
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
251                                 // delete the frame
252                                 packetsList.get(i).getPacket().getData()[j] = 0;
253                                 logger.debug("{} Node: {} Slot: {} - OK Expected State", localTyp.getLabel(), node,
254                                         iSlot);
255                             } else if (localTyp == null) {
256                                 if (bExpected < 0) {
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;
259                                 } else {
260                                     // if there is no typical at slot j then it means that it is one
261                                     // slot
262                                     // connected
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;
267                                     }
268                                 }
269
270                             }
271                         }
272                     }
273                     iSlot++;
274                 }
275
276                 // if the value of all bytes that make up the packet is 0 then I remove the packet from
277                 // list
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);
282                 } else {
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();
286
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);
294                         } else {
295                             logger.debug("Packet Execution timeout - Requeued");
296                             packetsList.get(i).setSent(false);
297                         }
298
299                     }
300                 }
301             }
302         }
303     }
304
305     private @Nullable SoulissGenericHandler getHandler(int node, int slot, Logger logger) {
306         SoulissGatewayHandler localGwHandler = this.gwHandler;
307
308         Iterator<Thing> thingsIterator;
309         if (localGwHandler != null) {
310             thingsIterator = localGwHandler.getThing().getThings().iterator();
311             Thing typ = null;
312             while (thingsIterator.hasNext()) {
313                 typ = thingsIterator.next();
314                 if (typ.getThingTypeUID().equals(SoulissBindingConstants.TOPICS_THING_TYPE)) {
315                     continue;
316                 }
317                 SoulissGenericHandler handler = (SoulissGenericHandler) typ.getHandler();
318
319                 // execute it only if binding is Souliss and update is for my
320                 // Gateway
321                 if ((handler != null) && (handler.getNode() == node && handler.getSlot() == slot)) {
322                     return handler;
323                 }
324             }
325         }
326         return null;
327     }
328
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) {
332             return true;
333         }
334         return itemState == expectedState;
335     }
336
337     private static boolean checkAllsSlotZero(DatagramPacket packet) {
338         var bflag = true;
339         for (var j = 12; j < packet.getData().length; j++) {
340             if ((packet.getData()[j] != 0)) {
341                 bflag = false;
342             }
343         }
344         return bflag;
345     }
346
347     long t = 0;
348     long tPrec = 0;
349
350     /**
351      * Pop SocketAndPacket from ArrayList PacketList
352      */
353     @Nullable
354     private synchronized PacketStruct pop() {
355         synchronized (this) {
356             SoulissGatewayHandler localGwHandler = this.gwHandler;
357
358             // don't pop if bPopSuspend = true
359             // bPopSuspend is set by the put method
360             if ((localGwHandler != null) && (!bPopSuspend)) {
361                 t = System.currentTimeMillis();
362
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.
366
367                 if (packetsList.size() <= 1) {
368                     iDelay = sendMinDelay;
369                 } else {
370                     iDelay = localGwHandler.getGwConfig().sendInterval;
371
372                 }
373
374                 var iPacket = 0;
375                 var bFlagWhile = true;
376                 // discard packages already sent
377                 while ((iPacket < packetsList.size()) && bFlagWhile) {
378                     if (packetsList.get(iPacket).getSent()) {
379                         iPacket++;
380                     } else {
381                         bFlagWhile = false;
382                     }
383                 }
384
385                 boolean tFlag = (t - tPrec) >= localGwHandler.getGwConfig().sendInterval;
386
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()) {
391                     tFlag = false;
392                 }
393
394                 if ((!packetsList.isEmpty()) && tFlag) {
395                     tPrec = System.currentTimeMillis();
396
397                     // extract the first element of the list
398                     PacketStruct sp = packetsList.get(iPacket);
399
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);
406                         // set time
407                         packetsList.get(iPacket).setTime(System.currentTimeMillis());
408                     } else {
409                         packetsList.remove(iPacket);
410                     }
411
412                     logger.debug("POP: {} packets in memory", packetsList.size());
413                     if (logger.isDebugEnabled()) {
414                         var iPacketSentCounter = 0;
415                         var i = 0;
416                         while ((i < packetsList.size())) {
417                             if (packetsList.get(i).getSent()) {
418                                 iPacketSentCounter++;
419                             }
420                             i++;
421                         }
422                         logger.debug("POP: {}  force frame sent", iPacketSentCounter);
423                     }
424
425                     logger.debug("Pop frame {} - Delay for 'SendDispatcherThread' setted to {} mills.",
426                             macacoToString(sp.getPacket().getData()), iDelay);
427                     return sp;
428                 }
429             }
430
431         }
432         return null;
433     }
434
435     private void resetTime() {
436         startTime = System.currentTimeMillis();
437     }
438
439     private boolean checkTime() {
440         return startTime < (System.currentTimeMillis() - iDelay);
441     }
442 }