/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.souliss.internal.network.udp; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.util.ArrayList; import org.openhab.binding.souliss.internal.network.typicals.SoulissGenericTypical; import org.openhab.binding.souliss.internal.network.typicals.SoulissNetworkParameter; import org.openhab.binding.souliss.internal.network.typicals.SoulissTypicals; import org.openhab.binding.souliss.internal.network.typicals.StateTraslator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class provide to take packet, and send it to regular interval to Souliss * Network * * @author Tonino Fazio * @since 1.7.0 */ public class SendDispatcher { public static ArrayList<SocketAndPacket> packetsList = new ArrayList<SocketAndPacket>(); protected boolean bExit = false; static int iDelay = 0; // equal to 0 if array is empty int SEND_DELAY; int SEND_MIN_DELAY; static boolean bPopSuspend = false; private static Logger logger = LoggerFactory.getLogger(SendDispatcher.class); private static SoulissTypicals soulissTypicalsRecipients; private long start_time = System.currentTimeMillis(); public SendDispatcher(SoulissTypicals soulissTypicalsRecip, int SEND_DELAY, int SEND_MIN_DELAY) { // this("SendDispatcher"); this.SEND_DELAY = SEND_DELAY; this.SEND_MIN_DELAY = SEND_MIN_DELAY; logger.info("Start SendDispatcherThread"); soulissTypicalsRecipients = soulissTypicalsRecip; } /** * Put packet to send in ArrayList PacketList */ public synchronized static void put(DatagramSocket socket, DatagramPacket packetToPUT) { bPopSuspend = true; boolean bPacchettoGestito = false; // estraggo il nodo indirizzato dal pacchetto in ingresso // restituisce -1 se il node non è del tipo Souliss_UDP_function_force int node = getNode(packetToPUT); logger.debug("Push"); if (packetsList.size() == 0 || node < 0) { bPacchettoGestito = false; } else { // OTTIMIZZATORE // scansione lista paccetti da inviare for (int i = 0; i < packetsList.size(); i++) { if (node >= 0 && getNode(packetsList.get(i).packet) == node && !packetsList.get(i).isSent()) { // frame per lo stesso nodo già presente in lista logger.debug("Frame UPD per nodo {} già presente il lista. Esecuzione ottimizzazione.", node); bPacchettoGestito = true; // se il pacchetto da inserire è più corto (o uguale) di // quello in lista allora sovrascrivo i byte del pacchetto // presente in lista if (packetToPUT.getData().length <= packetsList.get(i).packet.getData().length) { // scorre i byte di comando e se il byte è diverso da // zero sovrascrive il byte presente nel pacchetto in // lista logger.debug("Optimizer. Packet to push: " + MaCacoToString(packetToPUT.getData())); logger.debug("Optimizer. Previous frame: " + MaCacoToString(packetsList.get(i).packet.getData())); // i valori dei tipici partono dal byte 12 in poi for (int j = 12; j < packetToPUT.getData().length; j++) { // se il j-esimo byte è diverso da zero allora lo // sovrascrivo al byte del pacchetto già presente if (packetToPUT.getData()[j] != 0) { packetsList.get(i).packet.getData()[j] = packetToPUT.getData()[j]; } } logger.debug("Optimizer. Previous frame modified to: " + MaCacoToString(packetsList.get(i).packet.getData())); } else { // se il pacchetto da inserire è più lungo di quello in // lista allora sovrascrivo i byte del pacchetto da // inserire, poi elimino quello in lista ed inserisco // quello nuovo if (packetToPUT.getData().length > packetsList.get(i).packet.getData().length) { for (int j = 12; j < packetsList.get(i).packet.getData().length; j++) { // se il j-esimo byte è diverso da zero allora // lo sovrascrivo al byte del pacchetto gi� // presente if (packetsList.get(i).packet.getData()[j] != 0) { if (packetToPUT.getData()[j] == 0) { // sovrascrive i byte dell'ultimo frame // soltanto se il byte è uguale a zero. // Se è diverso da zero l'ultimo frame // ha la precedenza e deve sovrascrivere packetToPUT.getData()[j] = packetsList.get(i).packet.getData()[j]; } } } // rimuove il pacchetto logger.debug( "Optimizer. Remove frame: " + MaCacoToString(packetsList.get(i).packet.getData())); packetsList.remove(i); // inserisce il nuovo logger.debug("Optimizer. Add frame: " + MaCacoToString(packetToPUT.getData())); packetsList.add(new SocketAndPacket(socket, packetToPUT)); } } } } } if (!bPacchettoGestito) { logger.debug("Add frame: " + MaCacoToString(packetToPUT.getData())); packetsList.add(new SocketAndPacket(socket, packetToPUT)); } bPopSuspend = false; } /** * Get node number from packet */ private static int getNode(DatagramPacket packet) { // 7 è il byte del frame VNet al quale trovo il codice comando // 10 è il byte del frame VNet al quale trovo l'ID del nodo if (packet.getData()[7] == ConstantsUDP.Souliss_UDP_function_force) { return packet.getData()[10]; } return -1; } long t, t_prec = 0; /** * Pop SocketAndPacket from ArrayList PacketList */ private synchronized SocketAndPacket pop() { synchronized (this) { // non esegue il pop se bPopSuspend=true // bPopSuspend è impostato dal metodo put if (!bPopSuspend) { t = System.currentTimeMillis(); // riporta l'intervallo al minimo solo se: // - la lista è minore o uguale a 1; // - se è trascorso il tempo SEND_DELAY. if (packetsList.size() <= 1) { iDelay = SEND_MIN_DELAY; } else { iDelay = SEND_DELAY; } int iPacket = 0; boolean bFlagWhile = true; // scarta i pacchetti già inviati while (!(iPacket >= packetsList.size()) && bFlagWhile) { if (packetsList.get(iPacket).sent) { iPacket++; } else { bFlagWhile = false; } } boolean tFlag = (t - t_prec) >= SEND_DELAY; // se siamo arrivati alla fine della lista e quindi tutti i // pacchetti sono già stati inviati allora pongo anche il tFlag // a false (come se il timeout non fosse ancora trascorso) if (iPacket >= packetsList.size()) { tFlag = false; } if (packetsList.size() > 0 && tFlag) { t_prec = System.currentTimeMillis(); // SocketAndPacket sp = packetsList.remove(0); // estratto il primo elemento della lista SocketAndPacket sp = packetsList.get(iPacket); // GESTIONE PACCHETTO: eliminato dalla lista oppure // contrassegnato come inviato se è un FORCE if (packetsList.get(iPacket).packet.getData()[7] == ConstantsUDP.Souliss_UDP_function_force) { // flag inviato a true packetsList.get(iPacket).setSent(true); // imposto time packetsList.get(iPacket).setTime(System.currentTimeMillis()); } else { packetsList.remove(iPacket); } logger.debug("POP: " + packetsList.size() + " packets in memory"); if (logger.isDebugEnabled()) { int iPacketSentCounter = 0; int i = 0; while (!(i >= packetsList.size())) { if (packetsList.get(i).sent) { iPacketSentCounter++; } i++; } logger.debug("POP: " + (iPacketSentCounter) + " force frame sent"); } logger.debug("Pop frame {} - Delay for 'SendDispatcherThread' setted to {} mills.", MaCacoToString(sp.packet.getData()), iDelay); return sp; } } } return null; } /** * Sleep for iDelay Get and send packet */ public void tick() { // while (!bExit) { try { // wait while (!checkTime()) { ; } resetTime(); SocketAndPacket sp = pop(); if (sp != null) { logger.debug( "SendDispatcherThread - Functional Code 0x{} - Packet: {} - Elementi rimanenti in lista: {}", Integer.toHexString(sp.packet.getData()[7]), MaCacoToString(sp.packet.getData()), packetsList.size()); sp.socket.send(sp.packet); } // confronta gli stati in memoria con i frame inviati. Se // corrispondono cancella il frame dalla lista inviati SendDispatcher.safeSendCheck(); } catch (IOException e) { logger.warn(e.getMessage()); } catch (Exception e) { logger.warn(e.getMessage()); } } private void resetTime() { start_time = System.currentTimeMillis(); } private boolean checkTime() { return start_time < (System.currentTimeMillis() - iDelay); } private static String MaCacoToString(byte[] frame2) { byte[] frame = frame2.clone(); StringBuilder sb = new StringBuilder(); sb.append("HEX: ["); for (byte b : frame) { sb.append(String.format("%02X ", b)); } sb.append("]"); return sb.toString(); } /** * check frame updates with packetList, where flag "sent" is true. If all * commands was executed there delete packet in list. confronta gli * aggiornamenti ricevuti con i frame inviati. Se corrispondono cancella il * frame nella lista inviati . */ public static void safeSendCheck() { // short sVal = getByteAtSlot(macacoFrame, slot); // scansione lista paccetti inviati for (int i = 0; i < packetsList.size(); i++) { if (packetsList.get(i).isSent()) { int node = getNode(packetsList.get(i).packet); int iSlot = 0; for (int j = 12; j < packetsList.get(i).packet.getData().length; j++) { // controllo lo slot solo se il comando è diverso da ZERO if (packetsList.get(i).packet.getData()[j] != 0) { // recupero tipico dalla memoria SoulissGenericTypical typ = soulissTypicalsRecipients.getTypicalFromAddress(node, iSlot, 0); // traduce il comando inviato con lo stato previsto e // poi fa il confronto con lo stato attuale if (logger.isDebugEnabled() && typ != null) { String s1 = Integer.toHexString((int) typ.getState()); String sStateMemoria = s1.length() < 2 ? "0x0" + s1.toUpperCase() : "0x" + s1.toUpperCase(); String sCmd = Integer.toHexString(packetsList.get(i).packet.getData()[j]); sCmd = sCmd.length() < 2 ? "0x0" + sCmd.toUpperCase() : "0x" + sCmd.toUpperCase(); logger.debug( "Compare. Node: {} Slot: {} Typical: {} Command: {} EXPECTED: {} - IN MEMORY: {}", node, iSlot, Integer.toHexString(typ.getType()), sCmd, expectedState(typ.getType(), packetsList.get(i).packet.getData()[j]), sStateMemoria); } if (typ != null && checkExpectedState((int) typ.getState(), expectedState(typ.getType(), packetsList.get(i).packet.getData()[j]))) { // se il valore del tipico coincide con il valore // trasmesso allora pongo il byte a zero. // quando tutti i byte saranno uguale a zero allora // si // cancella il frame packetsList.get(i).packet.getData()[j] = 0; logger.debug("T{} Node: {} Slot: {} - OK Expected State", Integer.toHexString(typ.getType()), node, iSlot); } else if (typ == null) { // se allo slot j non esiste un tipico allora vuol dire che si tratta di uno slot collegato // al precedente (es: RGB, T31,...) // allora se lo slot j-1=0 allora anche j può essere messo a 0 if (packetsList.get(i).packet.getData()[j - 1] == 0) { packetsList.get(i).packet.getData()[j] = 0; } } } iSlot++; } if (checkAllsSlotZero(packetsList.get(i).packet)) { logger.debug("Command packet executed - Removed"); packetsList.remove(i); } else { // se il frame non è uguale a zero controllo il TIMEOUT e se // è scaduto allora pongo il flag SENT a false long t = System.currentTimeMillis(); if (SoulissNetworkParameter.SECURE_SEND_TIMEOUT_TO_REQUEUE < t - packetsList.get(i).getTime()) { if (SoulissNetworkParameter.SECURE_SEND_TIMEOUT_TO_REMOVE_PACKET < t - packetsList.get(i).getTime()) { logger.info("Packet Execution timeout - Removed"); packetsList.remove(i); } else { logger.info("Packet Execution timeout - Requeued"); packetsList.get(i).setSent(false); } } } } } } private static boolean checkExpectedState(int state, String expectedState) { // if expected state is null than return true. The frame will not requeued if (expectedState == null) { return true; } String s1 = String.valueOf(state); String sState = s1.length() < 2 ? "0x0" + s1.toUpperCase() : "0x" + s1.toUpperCase(); return sState.equals(expectedState); } private static String expectedState(short soulissType, byte command) { return StateTraslator.translateCommandsToExpectedStates(soulissType, command); } private static boolean checkAllsSlotZero(DatagramPacket packet) { boolean bflag = true; for (int j = 12; j < packet.getData().length; j++) { if (!(packet.getData()[j] == 0)) { bflag = false; } } return bflag; } }