001/*
002 *  $URL: svn://fred.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/lednet/LedNetProxy.java $
003 *  $Author: tgutwin $
004 *  $Revision: 1302 $
005 *  $Date: 2019-11-03 17:19:03 -0800 (Sun, 03 Nov 2019) $
006 */
007/*
008 *
009 *  Written by Tom Gutwin - WebARTS Design.
010 *  Copyright (C) 2016-2017 WebARTS Design, North Vancouver Canada
011 *  http://www.webarts.bc.ca
012 *
013 *  This program is free software; you can redistribute it and/or modify
014 *  it under the terms of the GNU General Public License as published by
015 *  the Free Software Foundation; either version 2 of the License, or
016 *  (at your option) any later version.
017 *
018 *  This program is distributed in the hope that it will be useful,
019 *  but WITHOUT ANY WARRANTY; without_ even the implied warranty of
020 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
021 *  GNU General Public License for more details.
022 *
023 *  You should have received a copy of the GNU General Public License
024 *  along with this program; if not, write to the Free Software
025 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
026 */
027
028package ca.bc.webarts.tools.lednet;
029
030import java.io.*;
031import java.net.*;
032import java.util.Arrays;
033import java.util.concurrent.atomic.DoubleAdder;
034import java.util.HashMap;
035import java.util.TreeSet;
036import java.util.Iterator;
037import java.util.Vector;
038
039/**
040 *  A class that wraps the comunication to LedNet devices using the
041 *  reverse engineered Protocol. Thanks go to the contributors on the Universal Device
042 * <a href="http://forum.universal-devices.com/topic/19913-magic-ufo-controller-for-rgbw" > Forum POST</a>.
043 * They wireSharked the message format.
044 *  <br />
045 *  The TCP packets look like:<br />
046 *  &nbsp;&nbsp;On: 113;35;15;163;129;138;139;150<br />
047 *  &nbsp;&nbsp;Off: 113;36;15;164;129;138;139;150<br />
048 *  <br />
049 *  &nbsp;&nbsp;Red:   49;255;0;0;0;0;0;15;63;129;138;139;150<br />
050 *  &nbsp;&nbsp;Green: 49;0;255;0;0;0;0;15;63;129;138;139;150<br />
051 *  &nbsp;&nbsp;Blue:  49;0;0;255;0;0;0;15;63;129;138;139;150<br />
052 *  &nbsp;&nbsp;White: 49;0;0;0;255;0;0;15;63;129;138;139;150<br />
053 *  &nbsp;&nbsp;Cyan:  49;0;255;255;0;0;0;15;62;129;138;139;150<br />
054 *<br />
055 * You need to send the TCP packet to the LedNet IP at Port 5577.
056 *<br />
057 * The bytes after the 15 (63) are stated as being checksum value of the first eight values and have to change for each code you send.<br />
058 * Notice how the ON/Off commands use 163, 164? IT would seem the byte after the 15 (packet terminator) is always the sum of the digits from the first until the 15 (inclusive)<br />
059 * In a normal protocol if the checksum didn't add up correctly the packet would be ignored as noise.<br />
060 *<br />
061 * Having said all that, the first single (primary = not cyan) colour packets you sent did add up to the 63 checksum and should have worked.
062
063 *  <br /> See also <a href="http://forum.universal-devices.com/topic/19913-magic-ufo-controller-for-rgbw" > UDI Forum</a> writeup.
064 *
065 * @author     Tom Gutwin P.Eng
066 */
067public class LedNetProxy
068{
069  /**  A holder for this clients System File Separator.  */
070  public final static String SYSTEM_FILE_SEPERATOR = File.separator;
071
072  /**  A holder for this clients System line termination separator.  */
073  public final static String SYSTEM_LINE_SEPERATOR =
074                                           System.getProperty("line.separator");
075
076  /**  The VM classpath (used in some methods)..  */
077  public static String CLASSPATH = System.getProperty("class.path");
078
079  /**  The users home ditrectory.  */
080  public static String USERHOME = System.getProperty("user.home");
081
082  /**  The users pwd ditrectory.  */
083  public static String USERDIR = System.getProperty("user.dir");
084
085  /**  A holder This classes name (used when logging).  */
086  private static String CLASSNAME = "ca.bc.webarts.tools.lednet.LedNetProxy";
087
088  /**  Class flag signifying if the initUtil method has been called  */
089  private static boolean classInit = false;
090
091  /**  Class flag signifying if debugging_ messages are ptinted */
092  private static boolean debugging_ = false;
093
094  /** default receiver IP Address. **/
095  public static final String DEFAULT_LEDNET_IP = "10.0.0.40";
096  /** Instantiated class IP for the receiver to communicate with. **/
097  private String receiverIP_ = DEFAULT_LEDNET_IP;
098
099  /** default LEDNET port. **/
100  public static final int DEFAULT_LEDNET_PORT = 5577;
101  /** Instantiated class Port for the receiver to communicate with. **/
102  private int receiverPort_ = DEFAULT_LEDNET_PORT;
103
104  /** the socket for communication - the protocol spec says to use one socket connection AND HOLD ONTO IT for re-use. **/
105  private static Socket lednetSocket_ = null;
106  /** the timeout in ms for socket reads. **/
107  private static int socketTimeOut_ = 500;
108  private static ObjectOutputStream out_ = null;
109  private static DataInputStream in_ = null;
110  private static boolean connected_ = false;
111
112  private static StringBuffer helpMsg_ = new StringBuffer(SYSTEM_LINE_SEPERATOR);
113
114  private  HashMap<Integer, Integer> levels_ = null;
115  private  HashMap<Integer, Integer> reverseLevels_ = null;
116  private int levelCount = 0;
117  public enum FadeSpeed
118  {
119      INSTANT (1),  //calls constructor with value 1 step
120      VERYFAST (2),  //calls constructor with value 2
121      FAST (4),       //calls constructor with value 4
122      MEDIUM (8),     //calls constructor with value 8
123      SLOW (16),       //calls constructor with value 16
124      VERYSLOW (32),    //calls constructor with value 32
125      SMOOTH (64),    //calls constructor with value 64
126      MOLASSES (128),    //calls constructor with value 128
127      BYONES (255)    //calls constructor with value 128
128      ; // semicolon needed when fields / methods follow
129      private final int levelSteps;
130      private FadeSpeed(int levelSteps)
131      {
132          this.levelSteps = levelSteps;
133      }
134      public int getLevelSteps()
135      {
136        return this.levelSteps;
137      }
138  }
139
140
141  /** Simple class Constructor (using deafult IP and port) that gets all the class command constants
142   * set-up.
143   **/
144  public LedNetProxy()
145  {
146    initLevelsMap();
147  }
148
149
150  /** Constructor that takes your receivers ip and default port, gets all the class command
151   * constants set-up .
152   **/
153  public LedNetProxy(String ip)
154  {
155    initLevelsMap();
156    if (ip==null || ip.equals(""))
157      receiverIP_=DEFAULT_LEDNET_IP;
158    else
159      receiverIP_=ip;
160    receiverPort_=DEFAULT_LEDNET_PORT;
161  }
162
163
164  /** Constructor that takes your receivers ip and port,  gets all the class command
165   * constants set-up .
166   **/
167  public LedNetProxy(String ip, int ipPort)
168  {
169    initLevelsMap();
170    if (ip==null || ip.equals(""))
171      receiverIP_=DEFAULT_LEDNET_IP;
172    else
173      receiverIP_=ip;
174    if (ipPort<1 )
175      receiverPort_=DEFAULT_LEDNET_PORT;
176    else
177      receiverPort_=ipPort;
178  }
179
180
181  /** Makes Chocolate glazed doughnuts. **/
182  public void setReceiverIP( String ip) { receiverIP_ = ip;}
183  /** Makes Sprinkle doughnuts. **/
184  public String getReceiverIP() {return receiverIP_;}
185  /** Makes mini doughnuts. **/
186  public void setReceiverPort( int port) { receiverPort_ = port;}
187  /** Makes glazed doughnuts. **/
188  public int getReceiverPort() {return receiverPort_;}
189
190  /**
191   * Connects to the receiver by opening a socket connection through the DEFaULT IP and DEFAULT eISCP port.
192   **/
193   public boolean connectSocket() { return connectSocket(null, -1);}
194
195
196  /**
197   * Connects to the receiver by opening a socket connection through the DEFAULT LedNet Receiver Port.
198   **/
199   public boolean connectSocket(String ip) { return connectSocket(ip,-1);}
200
201
202  /**
203   * Connects to the receiver by opening a socket connection through the passed in port (or default LedNet Receiver Port).
204   **/
205  public boolean connectSocket(String ip, int ipPort)
206  {
207    if (ip==null || ip.equals("")) ip=receiverIP_;
208    if (ipPort<1 ) ipPort=receiverPort_;
209
210    if (lednetSocket_==null || !connected_ || !lednetSocket_.isConnected())
211    try
212    {
213      //1. creating a socket to connect to the server
214      lednetSocket_ = new Socket(ip, ipPort);
215      System.out.println("Connected to LedNetProxy at "+ip+" on port "+ipPort);
216      //2. get Input and Output streams
217      out_ = new ObjectOutputStream(lednetSocket_.getOutputStream());
218      in_ = new DataInputStream(lednetSocket_.getInputStream());
219
220      //System.out.println("out_Init");
221      out_.flush();
222      // System.out.println("inInit");
223      connected_ = true;
224    }
225    catch(UnknownHostException unknownHost)
226    {
227      System.err.println("You are trying to connect to an unknown host!");
228    }
229    catch(IOException ioException)
230    {
231      System.err.println("Can't Connect: "+ioException.getMessage());
232    }
233    return connected_;
234  }
235
236
237  /**
238   * Tests the Connection to the receiver by opening a socket connection through the DEFaULT IP and DEFAULT eISCP port.
239   * @return true if already connected or can connect, and false if can't connect
240   **/
241   public boolean testConnection() { return testConnection(DEFAULT_LEDNET_IP,DEFAULT_LEDNET_PORT);}
242
243
244  /**
245   * Tests the Connection to the receiver by opening a socket connection through the specified IP and DEFAULT eISCP port.
246   * @param ip is the ip address (as a String) of the AV receiver to connect
247   * @return true if already connected or can connect, and false if can't connect
248   **/
249   public boolean testConnection(String ip) { return testConnection(DEFAULT_LEDNET_IP,DEFAULT_LEDNET_PORT);}
250
251
252  /**
253   * test the connection to the receiver by opening a socket connection AND THEN CLOSES it if it was not already open.
254   * this method can be used when you need to specificly specify the IP and PORT. If the default port is used then you could also use the
255   * {@link #testConnection(String) testConnection} method (that used the default port) or the {@link #testConnection() testConnection}
256   * method (that used the default IP and port).
257   *
258   * @param ip is the ip address (as a String) of the AV receiver to connect
259   * @param ipPort is the IP Port of the AV receiver to connect with (Onkyo's default is 60128)
260   * @return true if already connected or can connect, and false if can't connect
261   **/
262  public boolean testConnection(String ip, int ipPort)
263  {
264    boolean retVal = false;
265    if (ip==null || ip.equals("")) ip=DEFAULT_LEDNET_IP;
266    if (ipPort==0 ) ipPort=DEFAULT_LEDNET_PORT;
267
268    if (connected_)
269    {
270      // test existing connection
271      if (lednetSocket_.isConnected()) retVal = true;
272    }
273    else
274    {
275      // test a new connection
276      try
277      {
278        //1. creating a socket to connect to the server
279        lednetSocket_ = new Socket(ip, ipPort);
280        if (lednetSocket_!=null) lednetSocket_.close();
281        retVal = true;
282      }
283      catch(UnknownHostException unknownHost)
284      {
285        System.err.println("You are trying to connect to an unknown host!");
286      }
287      catch(IOException ioException)
288      {
289        System.err.println("Can't Connect: "+ioException.getMessage());
290      }
291    }
292    return retVal;
293  }
294
295
296  /**
297   * Closes the socket connection.
298   * @return true if the closed succesfully
299   **/
300  public boolean closeSocket()
301  {
302    //4: Closing connection
303    try
304    {
305      boolean acted = false;
306      if (in_!=null) {in_.close();in_=null;acted = true;}
307      if (out_!=null) {out_.close();out_=null;acted = true;}
308      if (lednetSocket_!=null) {lednetSocket_.close();lednetSocket_=null;acted = true;}
309      if (acted) System.out.println("closed connections");
310      connected_ = false;
311    }
312    catch(IOException ioException)
313    {
314      ioException.printStackTrace();
315    }
316    return connected_;
317  }
318
319
320  public static String convertAsciiToBase10(String str)  {return convertAsciiToBase10( str, false);}
321  public static String convertAsciiToBase10(String str,  boolean dumpOut)
322  {
323    char[] chars = str.toCharArray();
324    StringBuffer base10 = new StringBuffer();
325    if (dumpOut) System.out.print(" Base10: ");
326    for(int i = 0; i < chars.length; i++)
327    {
328      base10.append( (int)chars[i]);
329      if(i+1<chars.length) base10.append( ";" );
330      if (dumpOut) System.out.print("  "+((""+(int)chars[i]).length()==1?"0":"")+(int)chars[i] + " ");
331    }
332   if (dumpOut) System.out.println("");
333    return base10.toString();
334  }
335
336
337  /** Converts an ascii decimal String to a hex  String.
338   * @param str holding the string to convert to HEX
339   * @return a string holding the HEX representation of the passed in decimal str.
340   **/
341  public static String convertStringToHex(String str)
342  {
343     return convertStringToHex( str, false);
344  }
345
346
347  /** Converts an ascii decimal String to a hex  String.
348   * @param str holding the string to convert to HEX
349   * @param dumpOut flag to turn some debug output on/off
350   * @return a string holding the HEX representation of the passed in str.
351   **/
352  public static String convertStringToHex(String str,  boolean dumpOut)
353  {
354    char[] chars = str.toCharArray();
355    String out_put = "";
356
357    if (dumpOut) System.out.println("    Ascii: "+str);
358    if (dumpOut) System.out.print("    Hex: ");
359    StringBuffer hex = new StringBuffer();
360    for(int i = 0; i < chars.length; i++)
361    {
362      out_put = Integer.toHexString((int)chars[i]);
363      if (out_put.length()==1) hex.append("0");
364      hex.append(out_put);
365      if (dumpOut) System.out.print("0x"+(out_put.length()==1?"0":"")+ out_put+" ");
366    }
367    if (dumpOut) System.out.println("");
368    if (dumpOut) convertAsciiToBase10(str,dumpOut);
369    /*
370    if (dumpOut) System.out.print(" Base10: ");
371    for(int i = 0; i < chars.length; i++)
372    {
373      System.out.print("   "+convertHexNumberStringToDecimal(chars[i]));
374    }
375    if (dumpOut) System.out.println("");
376    */
377
378    return hex.toString();
379  }
380
381
382  public static final byte[] intToByteArray(int value)
383  {
384    return new byte[] {
385            (byte)(value >>> 24),
386            (byte)(value >>> 16),
387            (byte)(value >>> 8),
388            (byte)value};
389  }
390
391  /** Converts an HEX number String to its decimal equivalent.
392   * @param str holding the Hex Number string to convert to decimal
393   * @return an int holding the decimal equivalent of the passed in HEX numberStr.
394   **/
395  public static int convertHexNumberStringToDecimal(String str)
396  {
397    return convertHexNumberStringToDecimal(str, false);
398  }
399
400
401  /** Converts an HEX number String to its decimal equivalent.
402   * @param str holding the Hex Number string to convert to decimal
403   * @param dumpOut boolean flag to turn some debug output on/off
404   * @return an int holding the decimal equivalent of the passed in HEX numberStr.
405   **/
406  public static int convertHexNumberStringToDecimal(String str,  boolean dumpOut)
407  {
408    char[] chars = str.toCharArray();
409    String out_put = "";
410
411    if (dumpOut) System.out.println("\n      AsciiHex: 0x"+str);
412    if (dumpOut) System.out.print(  "       Decimal: ");
413
414    StringBuffer hex = new StringBuffer();
415    String hexInt = new String();
416    for(int i = 0; i < chars.length; i++)
417    {
418      out_put = Integer.toHexString((int)chars[i]);
419      if (out_put.length()==1) hex.append("0");
420      hex.append(out_put);
421      if (dumpOut) System.out.print((out_put.length()==1?"0":"")+ out_put);
422    }
423    hexInt = ""+(Integer.parseInt( hex.toString(), 16));
424    if (dumpOut) System.out.println("");
425    if (dumpOut) System.out.println( "      Decimal: "+hexInt.toString());
426
427    return Integer.parseInt(hexInt.toString());
428
429    //return Integer.decode("0x"+str);
430  }
431
432
433  /** Converts a hex byte to an ascii String.
434   * @param hex byte holding the HEX string to convert back to decimal
435   * @return a string holding the HEX representation of the passed in str.
436   **/
437  public static String convertHexToString(byte hex)
438  {
439    byte [] bytes = {hex};
440    return convertHexToString( new String(bytes), false);
441  }
442
443
444  /** Converts a hex String to an ascii String.
445   * @param hex the HEX string to convert back to decimal
446   * @return a string holding the HEX representation of the passed in str.
447   **/
448  public static String convertHexToString(String hex)
449  {
450    return convertHexToString( hex, false);
451  }
452
453
454  /** Converts a hex String to an ascii String.
455   * @param hex the HEX string to convert backk to decimal
456   * @param dumpOut boolean flag to turn some debug output on/off
457   * @return a string holding the HEX representation of the passed in str.
458   **/
459  public static String convertHexToString(String hex,  boolean dumpOut)
460  {
461
462    StringBuilder sb = new StringBuilder();
463    StringBuilder temp = new StringBuilder();
464    String out_put = "";
465
466    if (dumpOut) System.out.print("    Hex: ");
467    //49204c6f7665204a617661 split into two characters 49, 20, 4c...
468    for( int i=0; i<hex.length()-1; i+=2 ){
469
470        //grab the hex in pairs
471        out_put = hex.substring(i, (i + 2));
472        if (dumpOut) System.out.print("0x"+out_put+" ");
473        //convert hex to decimal
474        int decimal = Integer.parseInt(out_put, 16);
475        //convert the decimal to character
476        sb.append((char)decimal);
477
478        temp.append(decimal);
479    }
480    if (dumpOut) System.out.println("    Decimal : " + temp.toString());
481
482    return temp.toString();
483  }
484
485
486  /**
487    * Wraps a command in a colour data message (data characters).
488    *  <br />
489    *           49, r, g, b, ww, cw, 0, 15<br />
490    *  &nbsp;&nbsp;<span style="color: #FF0000">Red</span>:   49;255;0;0;0;0;0;15;63<br />
491    *  &nbsp;&nbsp;<span style="color: #00FF00">Green</span>: 49;0;255;0;0;0;0;15;63<br />
492    *  &nbsp;&nbsp;<span style="color: #0000FF">Blue</span>:  49;0;0;255;0;0;0;15;63<br />
493    *  &nbsp;&nbsp;<span style="color: #FFFFFF; background: #333333">ColdWhite</span>: 49;0;0;0;0;255;0;15;63 (isy networkResource command = 49;0;0;0;0;255;255;15;62;129;138;139;150)<br />
494    *  &nbsp;&nbsp;<span style="color: #00FFFF">Cyan</span>:  49;0;255;255;0;0;0;15;62;129;138;139;150<br />
495    *<br />
496    * The bytes after the 15 (63) are stated as being checksum value of the first eight values and have to change for each code you send.<br />
497    * Notice how the ON/Off commands use 163, 164? IT would seem the byte after the 15 (packet terminator) is always the sum of the digits from the first until the 15 (inclusive)<br />
498    * In a normal protocol if the checksum didn't add up correctly the packet would be ignored as noise.<br />
499    *  <br /> Thanks go to the contributors at the <a href="http://forum.universal-devices.com/topic/19913-magic-ufo-controller-for-rgbw" > UDI Forum POST</a> writeup.
500    *
501    * @param rgbw must be one of the 0-255 representing a decimal val for the colour
502    * @return StringBuffer holing the full message packet
503    **/
504  public StringBuilder getLedNetColourMessage(int r, int g, int b, int w)
505  {
506    String cmdStr = "";
507    if(r<0)r=0;if(r>255)r=255;
508    if(g<0)g=0;if(g>255)g=255;
509    if(b<0)b=0;if(b>255)b=255;
510    if(w<0)w=0;if(w>255)w=255;
511
512    int chkSum = 0;  //49+r+g+b+w+15;   //+240;
513
514    StringBuilder sb = new StringBuilder();
515
516    /* This is where I construct the entire message character by character. */
517     /*
518       0x31     command of setting color and color temperature
519       Send      [0X31] + [8bit red data ] + [8bit green data] + [8bit blue data] + [8bit warm white data] + [8bit cold white data]
520             + [8bit status sign] + [0xF0 remote,0x0F local] + [check digit] (length of command:9)
521
522       Return   Local(0x0F):no return
523       Remote(0xF0): [0xF0 remote] +  [0X31] + [0x00] + [check digit]
524       Status sign: [0XF0]  means changing RGB, [0X0F] means W
525       Note:phone send commands which control static color.Range of static color value is 00-0xff.When value is 0,PWM is 0%;when value is 0XFF,PWM is 100%;
526
527       The ISY msg for 25% BLU = 49; 0;0;64; 0;0; 0; 15; 128;129;138;139;150
528              dido for 100% CW = 49; 0;0;0;0;255;255;15;  62;129;138;139;150
529      */
530    sb.append((char)Integer.parseInt("31", 16));  //0x31  = 49
531    chkSum += 49;
532
533    // char for R G B wW cW
534    sb.append((char)Integer.parseInt(""+r, 10));
535    chkSum += Integer.parseInt(""+r, 10);
536    sb.append((char)Integer.parseInt(""+g, 10));
537    chkSum += Integer.parseInt(""+g, 10);
538    sb.append((char)Integer.parseInt(""+b, 10));
539    chkSum += Integer.parseInt(""+b, 10);
540    sb.append((char)Integer.parseInt("00", 16));// 1 char for Warm White (mine is not connected)
541    chkSum += Integer.parseInt(""+0, 10);
542    sb.append((char)Integer.parseInt(""+w, 10));// 1 char for Cold White
543    chkSum += Integer.parseInt(""+w, 10);
544
545    //8bit status sign: [0XF0] means RGB, [0X0F] means W, [0xff] means both
546    /*
547    if( (r+g+b)>0 && w==0)
548      sb.append((char)Integer.parseInt("F0", 16));
549    else if( (r+g+b)==0 && w>0)
550      sb.append((char)Integer.parseInt("0F", 16));
551    else if( (r+g+b)>0 && w>0)
552      sb.append((char)Integer.parseInt("FF", 16));
553    else if( (r+g+b)==0 && w==0)
554      sb.append((char)Integer.parseInt("FF", 16));
555    */
556
557    if( (r+g+b)>0 && w==0)  // [0X00] means RGB
558    {
559      sb.append((char)Integer.parseInt("00", 16));
560      chkSum += Integer.parseInt("00", 16);
561    }
562    else if( (r+g+b)==0 && w>0)  // [0XFF] means W
563    {
564      sb.append((char)Integer.parseInt("FF", 16));
565      chkSum += Integer.parseInt("FF", 16);
566    }
567    else if( (r+g+b)>0 && w>0)
568    {
569      sb.append((char)Integer.parseInt("00", 16));
570      chkSum += Integer.parseInt("00", 16);
571    }
572    else if( (r+g+b)==0 && w==0)
573    {
574      sb.append((char)Integer.parseInt("00", 16));
575      chkSum += Integer.parseInt("00", 16);
576    }
577
578    sb.append((char)Integer.parseInt("0F", 16));  //0xF0 remote WIFI, 0x0F local / direct WIFI (0x0f=15)
579    chkSum += Integer.parseInt("0F", 16);
580
581    // keep the ckecksum below 256
582    if(chkSum>(5*256))
583      chkSum-=(5*256);
584    else if(chkSum>(4*256))
585      chkSum-=(4*256);
586    else if(chkSum>(3*256))
587      chkSum-=(3*256);
588    else if(chkSum>(2*256))
589      chkSum-=(2*256);
590    else if(chkSum>(256))
591      chkSum-=(256);
592
593    // 1 char for checkSum
594    sb.append((char)Integer.parseInt(""+chkSum, 10));
595
596    // msg end
597    sb.append((char)Integer.parseInt(""+129, 10));
598    sb.append((char)Integer.parseInt(""+138, 10));
599    sb.append((char)Integer.parseInt(""+139, 10));
600    sb.append((char)Integer.parseInt(""+150, 10));
601
602    return sb;
603  }  //getLedNetColourMessage
604
605
606  /**
607    * This implements the custom message format - NOT DONE YET.
608    **/
609  public StringBuilder getCustomMessage(int r, int g, int b, int w, int r2, int g2, int b2, int w2, int speed)
610  {
611    String cmdStr = "";
612    w=0; w2=0;  // ignore for now
613    if(r<0)r=0;if(r>255)r=255;
614    if(g<0)g=0;if(g>255)g=255;
615    if(b<0)b=0;if(b>255)b=255;
616    if(w<0)w=0;if(w>255)w=255;
617
618    int chkSum = Integer.parseInt("51", 16)+
619                 r+g+b+w+r2+g2+b2+w2+
620                 speed+
621                 Integer.parseInt("60", 16)+Integer.parseInt("FF", 16)+Integer.parseInt("0F", 16);
622
623    if(chkSum>(13*256))
624      chkSum-=(13*256);
625    else if(chkSum>(12*256))
626      chkSum-=(12*256);
627    else if(chkSum>(11*256))
628      chkSum-=(11*256);
629    else if(chkSum>(10*256))
630      chkSum-=(10*256);
631    else if(chkSum>(9*256))
632      chkSum-=(9*256);
633    else if(chkSum>(8*256))
634      chkSum-=(8*256);
635    else if(chkSum>(7*256))
636      chkSum-=(7*256);
637    else if(chkSum>(6*256))
638      chkSum-=(6*256);
639    else if(chkSum>(5*256))
640      chkSum-=(5*256);
641    else if(chkSum>(4*256))
642      chkSum-=(4*256);
643    else if(chkSum>(3*256))
644      chkSum-=(3*256);
645    else if(chkSum>(2*256))
646      chkSum-=(2*256);
647    else if(chkSum>(256))
648      chkSum-=(256);
649
650    StringBuilder sb = new StringBuilder();
651    String zeroColour5ByteStr = ""+(char)Integer.parseInt("00", 16)+(char)Integer.parseInt("00", 16)+(char)Integer.parseInt("00", 16);
652    int numColours = 0;
653    //int speed = 50;  // 0 - 100
654
655    /* This is where I construct the entire message character by character. */
656    /*
657      0x51      command of setting command of custom mode
658      Send       [0X51] + [First point 32bit color value] + [Second point 32bit color value] + [Third point 32bit color value] +
659                 [Fourth point 32bit color value] + [Fifth point 32bit color value] + [Sixth point 32bit color value] +
660                 [Seventh point 32bit color value] + [Eighth point 32bit color value] + [Ninth point 32bit color value] +
661                 [Tenth point 32bit color value] + [Eleventh point 32bit color value] + [Twelfth point 32bit color value] +
662                 [Thirteenth point 32bit color value] + [Fourteenth point 32bit color value] + [Fifteenth point 32bit color value] +
663                 [Sixteenth point 32bit color value] + [8bit speed value] + [8bit mode value] +
664                 [0XFF] + [0xF0 remote,0x0F local] + [check digit] (length of command:55)"
665
666      Return     If command is local(0x0F):no return
667             If command is remote (0xF0): [0xF0 remote] +  [0X51] + [0x00] + [check digit]
668
669        Note:In command of custom mode,value of RGB cannot be 0x01,0x02,0x03.Otherwise,RGB is over.
670             mode: Custome mode is 0x60
671
672       32bit colour: ARGB values are typically expressed using 8 hexadecimal digits, with each pair of the hexadecimal digits
673                     representing the values of the Alpha, Red, Green and Blue channel, respectively.
674                     For example, 80FFFF00 represents 50.2% opaque (non-premultiplied) yellow.
675                     SEE: https://en.wikipedia.org/wiki/RGBA_color_space#RGBA_hexadecimal_.28word-order.29
676    */
677    sb.append((char)Integer.parseInt("51", 16));  //0x51
678    // char for R G B wW cW
679    //int first32BitColour = Integer.parseInt(""+r, 10)+Integer.parseInt(""+g, 10)+Integer.parseInt(""+b, 10)+Integer.parseInt(""+w, 10);
680    String first32BitColourHexStr = convertStringToHex(""+r,true)+convertStringToHex(""+g,true)+convertStringToHex(""+b,true)+convertStringToHex(""+w,true);
681    byte [] bytes1 = intToByteArray(Integer.parseInt(first32BitColourHexStr, 16));
682    if (debugging_) System.out.println("32bit Colour1: "+Arrays.toString(bytes1));
683    String second32BitColourHexStr = convertStringToHex(""+r2,true)+convertStringToHex(""+g2,true)+convertStringToHex(""+b2,true)+convertStringToHex(""+w2,true);
684    byte [] bytes2 = intToByteArray(Integer.parseInt(second32BitColourHexStr, 16));
685    if (debugging_) System.out.println("32bit Colour2: "+Arrays.toString(bytes2));
686
687    for (int i=0;i<bytes1.length;i++) sb.append((char)bytes1[i]);
688    numColours++;
689
690    for (int i=0;i<bytes2.length;i++) sb.append((char)bytes2[i]);
691    numColours++;
692
693    for (int i=0; i< 16-numColours;i++) sb.append(zeroColour5ByteStr);
694
695    sb.append((char)Integer.parseInt(""+speed, 10));
696    sb.append((char)Integer.parseInt("60", 16)); // mode: custom mode is 0x60
697
698    sb.append((char)Integer.parseInt("FF", 16));
699    sb.append((char)Integer.parseInt("0F", 16)); //0xF0 remote WIFI, 0x0F local / direct WIFI
700
701    // 1 char for checkSum
702    sb.append((char)Integer.parseInt(""+chkSum, 10));
703
704    // msg end
705    sb.append((char)Integer.parseInt(""+129, 10));
706    sb.append((char)Integer.parseInt(""+138, 10));
707    sb.append((char)Integer.parseInt(""+139, 10));
708    sb.append((char)Integer.parseInt(""+150, 10));
709
710    return sb;
711  }  //getLedNetColourMessage
712
713
714  /** Override helper method to redirect to getLedNetOnOffMessage true. **/
715  public StringBuilder getLedNetOnMessage(){ return getLedNetOnOffMessage(true);}
716  /** Override helper method to redirect to getLedNetOnOffMessage false. **/
717  public StringBuilder getLedNetOffMessage(){ return getLedNetOnOffMessage(false);}
718
719
720  /**
721    * Wraps an On or Off command in a  data message (data characters).
722    *  <br />
723    *  On: 113;35;15;163;129;138;139;150<br />
724    *  Off: 113;36;15;164;129;138;139;150<br />
725    *<br />
726    * The bytes after the 15 (163) are stated as being checksum value of the first eight values and have to change for each code you send.<br />
727    * Notice how the ON/Off commands use 163, 164? IT would seem the byte after the 15 (packet terminator) is always the sum of the digits from the first until the 15 (inclusive)<br />
728    * In a normal protocol if the checksum didn't add up correctly the packet would be ignored as noise.<br />    *
729    * @param on must be true to send on command, or false to send OFF command
730    * @return StringBuffer holing the full message packet
731   **/
732  public StringBuilder getLedNetOnOffMessage(boolean on)
733  {
734    String cmdStr = "";
735    int chkSum = (on?35:36)+113+15;
736    StringBuilder sb = new StringBuilder();
737
738    /* This is where I construct the entire message
739        character by character. */
740    sb.append((char)Integer.parseInt("113", 10));
741
742    // 1 char for On/Off
743    sb.append((char)Integer.parseInt(""+(on?35:36), 10));
744    sb.append((char)Integer.parseInt(""+15, 10));
745
746    // 1 char for checkSum
747    sb.append((char)Integer.parseInt(""+chkSum, 10));
748
749    // msg end
750    sb.append((char)Integer.parseInt(""+129, 10));
751    sb.append((char)Integer.parseInt(""+138, 10));
752    sb.append((char)Integer.parseInt(""+139, 10));
753    sb.append((char)Integer.parseInt(""+150, 10));
754
755    return sb;
756  }  //getLedNetOnOffMessage
757
758
759  /** Sends the passed in on or off command to LedNet and leaves the socket open.
760    *
761    * @param on true or falso to send On or Off.
762  **/
763  public String sendOnOffCommand(boolean on){return sendOnOffCommand(on, false);}
764
765
766  /**
767    * Sends to command to the receiver and does not wait for a reply.
768    *
769    * @param on true or falso to send On or Off.
770    * @param closeSocket flag to close the connection when done or leave it open.
771    **/
772  public String sendOnOffCommand(boolean on, boolean closeSocket)
773  {
774    String retVal = "";
775
776    StringBuilder sb = getLedNetOnOffMessage(on);
777    if(connectSocket())
778    {
779      try
780      {
781        if (debugging_) System.out.print("  sending "+sb.length() +" chars: ");
782        //out_.writeObject(sb.toString());
783        //out_.writeChars(sb.toString());
784        out_.writeBytes(sb.toString());  // <--- This is the one that works
785        //out_.writeBytes(convertStringToHex(sb.toString(), false));
786        //out_.writeChars(convertStringToHex(sb.toString(), false));
787        out_.flush();
788        if (debugging_)  System.out.println((on?" ON ":" OFF ")+"sent!" );
789      }
790      catch(IOException ioException)
791      {
792        ioException.printStackTrace();
793      }
794    }
795    if (closeSocket) closeSocket();
796
797    return retVal;
798  }
799
800
801  /**
802    * Sends to command to the receiver and does not wait for a reply AND leaves the socket open.
803    *
804    * @param rgbw are the colour levels (0-255)
805    **/
806  public String sendColourCommand(int r, int g, int b, int w){return sendColourCommand(r, g, b, w, false, false);}
807
808
809  /**
810    * Sends to command to the receiver and does not wait for a reply.
811    *
812    * @param rgbw are the colour levels (0-255)
813    * @param closeSocket flag to close the connection when done or leave it open.
814    **/
815  public String sendColourCommand(int r, int g, int b, int w, boolean waitForResponse, boolean closeSocket)
816  {
817    String retVal = "";
818    StringBuilder sb = getLedNetColourMessage(r,g,b,w);
819    //StringBuilder sb2 = getLedNetColourMessage(0,128,0,0);
820    if(connectSocket())
821    {
822      try
823      {
824        if(debugging_)System.out.print("  sending "+sb.toString().getBytes("UNICODE").length +" chars (bytes): ");
825
826        //convertStringToHex(sb.toString(), true);
827        //out_.writeObject(sb.toString());
828        //out_.writeChars(sb.toString());
829
830        if(debugging_)System.out.println(sb.toString().getBytes("UNICODE"));
831        out_.writeBytes(sb.toString());  // <--- This is the one that works
832
833        //if(debugging_)System.out.println(convertStringToHex(sb.toString(),false));
834        //out_.writeBytes(convertStringToHex(sb.toString(), false));
835        //out_.writeChars(convertStringToHex(sb.toString(), false));
836        out_.flush();
837
838        //out_.writeBytes(sb2.toString());
839        //out_.flush();
840
841        if(debugging_)System.out.println("sent!" );
842        if(waitForResponse)
843        {
844          /* now listen for the response. */
845          Vector <String> rv = null;
846          rv = readQueryResponses();
847        }
848      }
849      catch(IOException ioException)
850      {
851        ioException.printStackTrace();
852      }
853    }
854    if (closeSocket) closeSocket();
855
856    return retVal;
857  }
858
859
860  /**
861    * Sends to command to the receiver and does not wait for a reply AND leaves the socket open.
862    *
863    * @param rgbw are the colour levels (0-255)
864    **/
865  public void sendCustomCommand(int r, int g, int b, int w, int r2, int g2, int b2, int w2, int speed){sendCustomCommand(r, g, b, w, r2, g2, b2, w2, speed, false);}
866
867
868  /**
869    * Sends to command to the receiver and does not wait for a reply.
870    *
871    * @param rgbw are the colour levels (0-255)
872    * @param closeSocket flag to close the connection when done or leave it open.
873    **/
874  public void sendCustomCommand(int r, int g, int b, int w, int r2, int g2, int b2, int w2, int speed, boolean closeSocket)
875  {
876    boolean waitForResponse = false;
877    StringBuilder sb = getCustomMessage(r,g,b,w,r2,g2,b2,w2,speed);
878    StringBuilder sb2 = getLedNetColourMessage(0,128,0,0);
879    if(connectSocket())
880    {
881      try
882      {
883        if(debugging_)System.out.println("  sending "+sb.length() +" chars: ");
884        if(debugging_)System.out.println(convertStringToHex(sb.toString(),true));
885        //convertStringToHex(sb.toString(), true);
886        //out_.writeObject(sb.toString());
887        //out_.writeChars(sb.toString());
888        out_.writeBytes(sb.toString());  // <--- This is the one that works
889        //out_.writeBytes(convertStringToHex(sb.toString(), false));
890        //out_.writeChars(convertStringToHex(sb.toString(), false));
891        out_.flush();
892
893        //out_.writeBytes(sb2.toString());
894        //out_.flush();
895
896        if(debugging_)System.out.println("sent!" );
897        if(waitForResponse)
898        {
899          /* now listen for the response. */
900          Vector <String> rv = null;
901          rv = readQueryResponses();
902        }
903
904      }
905      catch(IOException ioException)
906      {
907        ioException.printStackTrace();
908      }
909    }
910    if (closeSocket) closeSocket();
911  }
912
913
914  /** A method to wrap a number of colour change calls (just for fun).
915  *
916  * @param loopTime is how long to loop the fun OR -1 to run forever.
917  **/
918  public void fun(int loopTime){ fun(loopTime,0);}
919  /** A method to wrap a number of colour change calls (just for fun).
920  *
921  * @param loopTime is how long to loop the fun OR -1 to run forever.
922  * @param pause is the delay between colour change calls in the loop
923  **/
924  public void fun(int loopTime, long pause)
925  {
926    int r = 10; int g = 0; int b = 0; int w = 0;
927    long looping = 0;
928    long msgTime = 505;
929    int count = 0;
930    System.out.println("Fun "+(loopTime!=-1?" for "+loopTime +"ms.":" Indefinately."));
931    String t1 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
932    int []reds = {15,15, 0, 0, 0, 5};
933    int []grns = { 0,15,15,15, 0, 0};
934    int []blus = { 0, 0, 0, 5,15,15};
935    int []whts = { 0, 0, 0, 0, 0, 0};
936    while(loopTime==-1||looping<loopTime)
937    {
938      for(int i=0; i< reds.length;i++)
939      {
940        r = reds[i]; g = grns[i]; b = blus[i]; w = whts[i];
941        sendColourCommand(r,g,b,w); sleep(pause);
942      }
943
944      if(loopTime!=-1) {looping+=6*(pause+msgTime);count++;}
945    }
946    String t2 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
947    double duration = secondsBetween(t1,t2); // in ms
948    System.out.println("duration="+duration);
949    System.out.println("count="+count);
950    System.out.println("msgDuration="+(duration*1000/count/6 - pause) );
951  }
952
953
954  /** A method to wrap a number of colour change calls (just for fun).
955  *
956  * @param loopTime is how long to loop the fun OR -1 to run forever.
957  **/
958  public void christmasFun(int loopTime){ fun(loopTime,0);}
959  /** A method to wrap a number of colour change calls (just for fun).
960  *
961  * @param loopTime is how long to loop the fun OR -1 to run forever.
962  * @param pause is the delay between colour change calls in the loop
963  **/
964  public void christmasFun(int loopTime, long pause)
965  {
966    boolean dim = true;
967    int r = 10; int g = 0; int b = 0; int w = 0;
968    long looping = 0;
969    long msgTime = 505;
970    int count = 0;
971    System.out.println("Xmas Fun "+(loopTime!=-1?" for "+loopTime +"ms.":" Indefinately."));
972    String t1 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
973    int []reds1 = {60,45,60,30,30,20,15, 8, 8, 8, 8, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
974    int []grns1 = { 0, 0, 0, 0, 0, 0, 1, 2, 4, 8,15,15,15,15,15,20,20,30,30,60,45,60};
975    int []blus1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
976    int []whts1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
977
978    int []reds2 = { 8, 8, 7, 6, 5, 4, 4, 3, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
979    int []grns2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 8};
980    int []blus2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
981    int []whts2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
982
983    int []reds = reds1;
984    int []grns = grns1;
985    int []blus = blus1;
986    int []whts = whts1;
987
988    if (dim)
989    {
990      reds = reds2;
991      grns = grns2;
992      blus = blus2;
993      whts = whts2;
994    }
995
996    while(loopTime==-1||looping<loopTime)
997    {
998      for(int i=0; i< reds.length;i++)
999      {
1000        r = reds[i]; g = grns[i]; b = 0; w = 0;
1001        sendColourCommand(r,g,b,w); sleep(pause);
1002      }
1003      for(int i=reds.length-1; i>=0 ;i--)
1004      {
1005        r = reds[i]; g = grns[i]; b = 0; w = 0;
1006        sendColourCommand(r,g,b,w); sleep(pause);
1007      }
1008
1009      if(loopTime!=-1) {looping+=6*(pause+msgTime);count++;}
1010    }
1011    String t2 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
1012    double duration = secondsBetween(t1,t2); // in ms
1013    System.out.println("duration="+duration);
1014    System.out.println("count="+count);
1015    System.out.println("msgDuration="+(duration*1000/count/6 - pause) );
1016  }
1017
1018
1019  /** A method to wrap a number of colour change calls (just for fun).
1020  *
1021  * @param loopTime is how long to loop the fun OR -1 to run forever.
1022  **/
1023  public void christmasFade(){ christmasFade(64,-1,0);}
1024  /** A method to wrap a number of colour change calls (just for fun).
1025  *
1026  * @param loopTime is how long to loop the fun OR -1 to run forever.
1027  **/
1028  public void christmasFade(int loopTime){ christmasFade(64, loopTime,0);}
1029  /** A method to wrap a number of colour change calls (just for fun).
1030  *
1031  * @param loopTime is how long to loop the fun OR -1 to run forever.
1032  * @param pause is the delay between colour change calls in the loop
1033  **/
1034  public void christmasFade(int toLevel, int loopTime, long pause)
1035  {
1036    long looping = 0;
1037    //int loopTime = -1;
1038    //long pause = 250;
1039    long msgTime = 505;
1040    int count = 0;
1041
1042    // Turn On
1043    sendColourCommand(0,0,0,0);
1044    String queryResponse =  sendQueryOnOffCommand(true, false);
1045
1046    // Fade
1047    while(loopTime==-1||looping<loopTime)
1048    {
1049
1050      fadeBetweenCommand( 0,1,0,0, 0,toLevel,0,0 ,  FadeSpeed.SLOW); // green
1051      fadeBetweenCommand( 0,toLevel,0,0, 0,2,0,0,   FadeSpeed.SLOW);  // dim the green
1052      fadeBetweenCommand( 0,2,0,0, 2,2,0,0,   FadeSpeed.SLOW);  // red
1053      fadeBetweenCommand( 2,2,0,0, toLevel,0,0,0,   FadeSpeed.SLOW);  // red
1054      sleep(1000);
1055      sendColourCommand(4,0,0,0);
1056      sleep(250);
1057      sendColourCommand(toLevel,0,0,0);
1058      sleep(250);
1059      sendColourCommand(4,0,0,0);
1060      sleep(250);
1061      //sendColourCommand(toLevel,0,0,0);
1062      fadeBetweenCommand( toLevel,0,0,0 ,2,2,0,0,   FadeSpeed.SLOW);  // dim the red
1063      fadeBetweenCommand( 2,2,0,0, 0,2,0,0,    FadeSpeed.SLOW);  // green
1064      sleep(1000);
1065      sendColourCommand(0,4,0,0);
1066      sleep(250);
1067      sendColourCommand(0,toLevel,0,0);
1068      sleep(250);
1069      sendColourCommand(0,4,0,0);
1070      sleep(250);
1071      if(loopTime!=-1) {looping+=6*(pause+msgTime);count++;}
1072    }
1073
1074    //Turn OFF
1075    sendQueryOnOffCommand(false, true);
1076  }
1077
1078
1079  public void slowBurn(int toLevel, int loopTime, long pause)
1080  {
1081    long looping = 0;
1082    //int loopTime = -1;
1083    //long pause = 250;
1084    long msgTime = 505;
1085    int count = 0;
1086
1087    // Turn On
1088    sendColourCommand(0,0,0,0);
1089    String queryResponse =  sendQueryOnOffCommand(true, false);
1090
1091    // Fade
1092    boolean waitDelay = true;
1093    int waitDelayMs = 750;
1094    while(loopTime==-1||looping<loopTime)
1095    {
1096      //fadeBetweenCommand(int r, int g, int b, int w, int r2, int g2, int b2, int w2, FadeSpeed speed, boolean nonLinear)
1097      sendColourCommand( 6,0,0,0, waitDelay, false);sleep(waitDelayMs);
1098      sendColourCommand( 6,0,0,0, waitDelay, false);sleep(waitDelayMs);
1099      sendColourCommand( 5,0,1,0, waitDelay, false);sleep(waitDelayMs);
1100      sendColourCommand( 5,0,1,0, waitDelay, false);sleep(waitDelayMs);
1101      sendColourCommand( 4,0,2,0, waitDelay, false);sleep(waitDelayMs);
1102      sendColourCommand( 3,0,3,0, waitDelay, false);sleep(waitDelayMs);
1103      sendColourCommand( 2,0,4,0, waitDelay, false);sleep(waitDelayMs);
1104      sendColourCommand( 2,0,5,0, waitDelay, false);sleep(waitDelayMs);
1105      sendColourCommand( 2,0,5,0, waitDelay, false);sleep(waitDelayMs);
1106      sendColourCommand( 2,0,6,0, waitDelay, false);sleep(waitDelayMs);
1107      sendColourCommand( 1,0,6,0, waitDelay, false);sleep(waitDelayMs);
1108      sendColourCommand( 1,0,7,0, waitDelay, false);sleep(waitDelayMs);
1109      sendColourCommand( 1,0,7,0, waitDelay, false);sleep(waitDelayMs);
1110      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1111      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1112      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1113      //sendColourCommand( 0,0,10,0, waitDelay, false);sleep(waitDelayMs);
1114      //sendColourCommand( 0,0,10,0, waitDelay, false);sleep(waitDelayMs);
1115      //sendColourCommand( 0,0,11,0, waitDelay, false);sleep(waitDelayMs);
1116      //sendColourCommand( 0,0,11,0, waitDelay, false);sleep(waitDelayMs);
1117      //sendColourCommand( 0,0,12,0, waitDelay, false);sleep(waitDelayMs);
1118      //sendColourCommand( 0,0,12,0, waitDelay, false);sleep(waitDelayMs);
1119      sleep(1500);
1120      //sendColourCommand( 0,0,11,0, waitDelay, false);sleep(waitDelayMs);
1121      //sendColourCommand( 0,0,11,0, waitDelay, false);sleep(waitDelayMs);
1122      //sendColourCommand( 0,0,10,0, waitDelay, false);sleep(waitDelayMs);
1123      //sendColourCommand( 0,0,10,0, waitDelay, false);sleep(waitDelayMs);
1124      //sendColourCommand( 0,0,9,0, waitDelay, false);sleep(waitDelayMs);
1125      //sendColourCommand( 0,0,9,0, waitDelay, false);sleep(waitDelayMs);
1126      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1127      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1128      sendColourCommand( 0,0,7,0, waitDelay, false);sleep(waitDelayMs);
1129      sendColourCommand( 1,0,7,0, waitDelay, false);sleep(waitDelayMs);
1130      sendColourCommand( 1,0,7,0, waitDelay, false);sleep(waitDelayMs);
1131      sendColourCommand( 2,0,7,0, waitDelay, false);sleep(waitDelayMs);
1132      sendColourCommand( 2,0,6,0, waitDelay, false);sleep(waitDelayMs);
1133      sendColourCommand( 3,0,6,0, waitDelay, false);sleep(waitDelayMs);
1134      sendColourCommand( 3,0,5,0, waitDelay, false);sleep(waitDelayMs);
1135      sendColourCommand( 4,0,5,0, waitDelay, false);sleep(waitDelayMs);
1136      sendColourCommand( 4,0,4,0, waitDelay, false);sleep(waitDelayMs);
1137      sendColourCommand( 4,0,3,0, waitDelay, false);sleep(waitDelayMs);
1138      sendColourCommand( 4,0,2,0, waitDelay, false);sleep(waitDelayMs);
1139      sendColourCommand( 4,0,1,0, waitDelay, false);sleep(waitDelayMs);
1140      sendColourCommand( 4,0,1,0, waitDelay, false);sleep(waitDelayMs);
1141      sendColourCommand( 4,0,0,0, waitDelay, false);sleep(waitDelayMs);
1142      sendColourCommand( 4,0,0,0, waitDelay, false);sleep(waitDelayMs);
1143      sleep(1500);
1144      if(loopTime!=-1) {looping+=6*(pause+msgTime);count++;}
1145    }
1146
1147    //Turn OFF
1148    sendQueryOnOffCommand(false, true);
1149  }
1150
1151
1152  /**
1153    * linear Steps the rgbw  between the passed values to provide a fade. It will NOT turn the LedNet ON if not already on.
1154    *
1155    * @param r starting red value
1156    * @param r2 ending red value
1157    **/
1158  public void stepBetween(int r, int g, int b, int w, int r2, int g2, int b2, int w2)
1159  {
1160    int maxD = 0;
1161    int rD = r2-r;
1162    if(java.lang.Math.abs(rD)>maxD) maxD=java.lang.Math.abs(rD);
1163    int gD = g2-g;
1164    if(java.lang.Math.abs(gD)>maxD) maxD=java.lang.Math.abs(gD);
1165    int bD = b2-b;
1166    if(java.lang.Math.abs(bD)>maxD) maxD=java.lang.Math.abs(bD);
1167    int wD = w2-w;
1168    if(java.lang.Math.abs(wD)>maxD) maxD=java.lang.Math.abs(wD);
1169    if(debugging_)System.out.println("\nStepping\n      rD="+rD+"   gD="+gD+"   bD="+bD+"   wD="+wD+"   maxD="+maxD);
1170    int steps = maxD;
1171    double stepsd = steps;
1172    if(debugging_)System.out.println("Stepping "+maxD+" times: r "+r+"-->"+r2+"    g "+g+"-->"+g2+"    b "+b+"-->"+b2+"    w "+w+"-->"+w2);
1173
1174    int nextR = r;
1175    int nextG = g;
1176    int nextB = b;
1177    int nextW = w;
1178    int rStepSize = (rD>0?1:(rD<0?-1:0));
1179    int gStepSize = (gD>0?1:(gD<0?-1:0));
1180    int bStepSize = (bD>0?1:(bD<0?-1:0));
1181    int wStepSize = (wD>0?1:(wD<0?-1:0));
1182    int rSteps = 0;
1183    int gSteps = 0;
1184    int bSteps = 0;
1185    int wSteps = 0;
1186    int rStepCount = 0;
1187    int gStepCount = 0;
1188    int bStepCount = 0;
1189    int wStepCount = 0;
1190
1191    if(rD!=0) rSteps = dToi(maxD / rD);
1192    if(gD!=0) gSteps = dToi(maxD / gD);
1193    if(bD!=0) bSteps = dToi(maxD / bD);
1194    if(wD!=0) wSteps = dToi(maxD / wD);
1195    if(debugging_)System.out.println("Stepping "+maxD+" times: red "+rSteps+"    green "+gSteps+"    blue "+bSteps+"    w "+wSteps);
1196
1197
1198    int i=0;
1199    for (i=0; i< steps; i++)
1200    {
1201      if(rStepCount == java.lang.Math.abs(rSteps))
1202      {
1203        nextR += rStepSize;
1204        rStepCount = 1;
1205      }
1206      else
1207      {
1208        rStepCount++;
1209      }
1210      if(gStepCount == java.lang.Math.abs(gSteps))
1211      {
1212        nextG += gStepSize;
1213        gStepCount = 1;
1214      }
1215      else
1216      {
1217        gStepCount++;
1218      }
1219      if(bStepCount == java.lang.Math.abs(bSteps))
1220      {
1221        nextB += bStepSize;
1222        bStepCount = 1;
1223      }
1224      else
1225      {
1226        bStepCount++;
1227      }
1228      if(wStepCount == java.lang.Math.abs(wSteps))
1229      {
1230        nextW += wStepSize;
1231        wStepCount = 1;
1232      }
1233      else
1234      {
1235        wStepCount++;
1236      }
1237
1238      if(debugging_) System.out.println(""+i+"/"+steps+" ]  nextR="+nextR+"  nextG="+nextG+"  nextB="+nextB+"  nextW="+nextW);
1239      sendColourCommand(nextR, nextG, nextB, nextW);
1240    }
1241    // One Last Step
1242    nextR += rStepSize;
1243    nextG += gStepSize;
1244    nextB += bStepSize;
1245    nextW += wStepSize;
1246    if(debugging_) System.out.println(""+i+"/"+steps+" ]  nextR="+nextR+"  nextG="+nextG+"  nextB="+nextB+"  nextW="+nextW);
1247    sendColourCommand(nextR, nextG, nextB, nextW);
1248  }
1249
1250
1251    /**
1252    * Steps the non-linear LedLevel rgbw between the passed values to provide a fade. It will also turn the LedNet ON if not already on.
1253    *
1254    * @param nonLinear boolean flag to choose between linear or default non-linear level lookup.
1255    **/
1256  public void fadeBetweenCommand(int r, int g, int b, int w, int r2, int g2, int b2, int w2, FadeSpeed speed)
1257  {
1258    fadeBetweenCommand(r, g, b, w, r2, g2, b2, w2, speed, true);
1259  }
1260
1261
1262  /**
1263    * Steps the rgbw (linear or non-linear LedLevel ) between the passed values to provide a fade. It will also turn the LedNet ON if not already on.
1264    *
1265    * @param nonLinear boolean flag to choose between linear or default non-linear level lookup.
1266    **/
1267  public void fadeBetweenCommand(int r, int g, int b, int w, int r2, int g2, int b2, int w2, FadeSpeed speed, boolean nonLinear)
1268  {
1269    boolean dryRun = false;
1270    int stepMultiple = 1;
1271    int fadeSteps = speed.getLevelSteps();
1272    double fadeStepsd = fadeSteps;
1273    double stepMultipled = stepMultiple;
1274
1275    if(debugging_)System.out.println((nonLinear?"NON-linear ":"")+"Fading "+fadeSteps+": r "+r+"-->"+r2+"    g "+g+"-->"+g2+"    b "+b+"-->"+b2+"    w "+w+"-->"+w2);
1276    int rD = r2-r;
1277    int gD = g2-g;
1278    int bD = b2-b;
1279    int wD = w2-w;
1280    if(debugging_)System.out.println("      rD="+rD+"   gD="+gD+"   bD="+bD+"   wD="+wD);
1281
1282    int rlD = levelsDiff(r,r2, nonLinear); // returns the abs diff between the ledLevels for r and r2
1283    int glD = levelsDiff(g,g2, nonLinear);
1284    int blD = levelsDiff(b,b2, nonLinear);
1285    int wlD = levelsDiff(w,w2, nonLinear);
1286    double rlDd = rlD;
1287    double glDd = glD;
1288    double blDd = blD;
1289    double wlDd = wlD;
1290
1291    int maxLevelDiff = Math.max(Math.max(Math.max(rlD,glD),blD),wlD);
1292
1293    int maxLevelStep = (int) Math.round((double)(maxLevelDiff/fadeSteps));
1294
1295    if(debugging_)
1296    {
1297      System.out.println("FadingLevelSteps "+fadeSteps+": r "+r+"/"+ledLevel(r, nonLinear)+"-->"+ledLevel(r2, nonLinear)+
1298                         "    g "+g+"/"+ledLevel(g, nonLinear)+"-->"+ledLevel(g2, nonLinear)+"    b "+b+"/"+ledLevel(b, nonLinear)+"-->"+ledLevel(b2, nonLinear)+"    w "+w+"/"+ledLevel(w, nonLinear)+"-->"+ledLevel(w2, nonLinear));
1299      System.out.println("    LevelDIFFs rlD="+rlD+"   glD="+glD+"   blD="+blD+"   wlD="+wlD);
1300      System.out.println("   maxLevelDiff="+maxLevelDiff);
1301    }
1302
1303    if(fadeSteps>maxLevelDiff) fadeSteps=maxLevelDiff;
1304    int levelStepSize = fadeSteps;
1305
1306    //if(stepTime < fastestStep) stepTime = fastestStep;
1307
1308    int rlStep = (rD<0?-1:1)*(rlD==0?0:rlD/fadeSteps)*stepMultiple; // these are in ledLevels
1309    int glStep = (gD<0?-1:1)*(glD==0?0:glD/fadeSteps)*stepMultiple;
1310    int blStep = (bD<0?-1:1)*(blD==0?0:blD/fadeSteps)*stepMultiple;
1311    int wlStep = (wD<0?-1:1)*(wlD==0?0:wlD/fadeSteps)*stepMultiple;
1312    double rlStepd = (rD<0?-1:1)*(rlD==0?0:rlDd/fadeStepsd)*stepMultipled; // these are in ledLevels
1313    double glStepd = (gD<0?-1:1)*(glD==0?0:glDd/fadeStepsd)*stepMultipled;
1314    double blStepd = (bD<0?-1:1)*(blD==0?0:blDd/fadeStepsd)*stepMultipled;
1315    double wlStepd = (wD<0?-1:1)*(wlD==0?0:wlDd/fadeStepsd)*stepMultipled;
1316
1317    DoubleAdder currRleveld = new DoubleAdder();
1318    DoubleAdder currGleveld = new DoubleAdder();
1319    DoubleAdder currBleveld = new DoubleAdder();
1320    DoubleAdder currWleveld = new DoubleAdder();
1321
1322    int currRlevel=ledLevel(r, nonLinear);
1323    currRleveld.add(Double.parseDouble(""+currRlevel));
1324    int currGlevel=ledLevel(g, nonLinear);
1325    currGleveld.add(Double.parseDouble(""+currGlevel));
1326    int currBlevel=ledLevel(b, nonLinear);
1327    currBleveld.add(Double.parseDouble(""+currBlevel));
1328    int currWlevel=ledLevel(w, nonLinear);
1329    currWleveld.add(Double.parseDouble(""+currWlevel));
1330    int prevRlevel=currRlevel;
1331    int prevGlevel=currGlevel;
1332    int prevBlevel=currBlevel;
1333    int prevWlevel=currWlevel;
1334    if(debugging_)
1335    {
1336      System.out.println("testFading :    rlStep="+rlStep+"   glStep="+glStep+"   blStep="+blStep+"   wlStep="+wlStep);
1337      System.out.println("testFading :    rlStepd="+rlStepd+"   glStepd="+glStepd+"   blStepd="+blStepd+"   wlStepd="+wlStepd);
1338      System.out.println("num steps : fadeSteps="+fadeSteps);
1339    }
1340    String t1 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
1341    if(!dryRun)
1342    {
1343      //sendColourCommand(r,g,b,w);
1344      //sleep(100);
1345    }
1346    String t2 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
1347    for(int i=0; i<= fadeSteps; i++)
1348    {
1349      /*System.out.println("i:"+i+"     rLevel="+currRlevel+
1350                                "     gLevel="+currGlevel+
1351                                "     bLevel="+currBlevel+
1352                                "     wLevel="+currWlevel
1353                         );*/
1354      if(debugging_)
1355      {
1356        System.out.println("i:"+i+"   rLevelda="+daToi(currRleveld)+
1357                                  "   gLevelda="+daToi(currGleveld)+
1358                                  "   bLevelda="+daToi(currBleveld)+
1359                                  "   wLevelda="+daToi(currWleveld)
1360                           );
1361      }
1362
1363      if(!dryRun)
1364      {
1365        boolean oldDebug = debugging_;
1366        debugging_=false;
1367          //sendOnOffCommand(true,false);
1368          //sendColourCommand(currRlevel,currGlevel,currBlevel,currWlevel);  // these are already ledLevel'd
1369          sendColourCommand(daToi(currRleveld),daToi(currGleveld),daToi(currBleveld),daToi(currWleveld));  // these are already ledLevel'd
1370        debugging_=oldDebug;
1371        //sleep(100);
1372      }
1373      prevRlevel=currRlevel;
1374      prevGlevel=currGlevel;
1375      prevBlevel=currBlevel;
1376      prevWlevel=currWlevel;
1377      currRlevel+=rlStep;currGlevel+=glStep;currBlevel+=blStep;currWlevel+=wlStep;
1378      currRleveld.add(rlStepd);currGleveld.add(glStepd);currBleveld.add(blStepd);currWleveld.add(wlStepd);
1379    }
1380    String t3 = ca.bc.webarts.widgets.Util.createCurrentTimeStamp();
1381    if(!debugging_)
1382    {
1383      System.out.println("t1= "+t1);
1384      System.out.println("t2= "+t2);
1385      System.out.println("t3= "+t3);
1386    }
1387  }
1388
1389
1390  /**
1391    * Function to accepts a double and returns an int primitive.  Could also get implemented as a java 8 java.util.function.DoubleToIntFunction
1392  **/
1393  private int dToi(double d) { return (new Double(d)).intValue();}
1394
1395
1396  /**
1397    * Function to accepts a java 8 doubleAdder and returns an int primitive.
1398  **/
1399  private int daToi(DoubleAdder d) { return (new Double(d.sum())).intValue();}
1400
1401
1402  public  String right(String value, int length)
1403  {
1404    // To get right characters from a string, change the begin index.
1405    return value.substring(value.length() - length);
1406  }
1407
1408
1409  /**
1410    * Calculates the seconds between the 2 passed timeStamps.
1411  **/
1412  public  double secondsBetween(String t1,String t2)
1413  {
1414    /*
1415    System.out.println("t1= "+t1);
1416    System.out.println("t2= "+t2);
1417    System.out.println("tm1= "+t1.substring((t1.length() - (t1.startsWith("0")?8:9)), 5));
1418    System.out.println("tm2= "+t2.substring((t2.length() - (t2.startsWith("0")?8:9)), 5));
1419    System.out.println("add60= "+!t2.substring((t2.length() - (t2.startsWith("0")?8:9)), 5).equals(t1.substring((t1.length() - (t1.startsWith("0")?8:9)), 5)));
1420    */
1421    double add60 = 0d;
1422    double mins = Double.parseDouble(t2.substring((t2.length() - (t2.startsWith("0")?8:9)), 5)) - Double.parseDouble(t1.substring((t1.length() - (t1.startsWith("0")?8:9)), 5));
1423    if(!t2.substring((t2.length() - (t2.startsWith("0")?8:9)), 5).equals(t1.substring((t1.length() - (t1.startsWith("0")?8:9)), 5))) add60 = 60d;
1424    //System.out.println("t2.length()= "+t2.length());
1425    //System.out.println("seconds= "+t2.substring((t2.length() - (t2.startsWith("0")?5:6)), 8)+"."+right(t2,3));
1426    return Double.parseDouble(t2.substring((t2.length() - (t2.startsWith("0")?5:6)), 8)+"."+right(t2,3))+ mins*add60 -
1427           Double.parseDouble(t1.substring((t1.length() - (t1.startsWith("0")?5:6)), 8)+"."+right(t1,3));
1428  }
1429
1430
1431  /**
1432  * LED Level lookup from the <b>non-linear</b> hashmap setup in {@link #initLevelsMap() initLevelsMap}.
1433  *
1434  * @param v is the (0-255) level to lookup the non-linear level lookup.
1435  **/
1436  private int ledLevel(int v)  { return ledLevel(v, true);}
1437
1438
1439  /**
1440  * LED Level lookup from the linear or non-linear hashmap setup in {@link #initLevelsMap() initLevelsMap}.
1441  *
1442  * @param v is the (0-255) level to lookup the non-linear level lookup.
1443  * @param nonLinear boolean flag to choose between linear or default non-linear level lookup from {@link #HashMap levels_} map.
1444  **/
1445  private int ledLevel(int v, boolean nonLinear)
1446  {
1447    int retVal = levels_.get(Integer.valueOf(v)).intValue(); // start with the default map lookup
1448    if(v<0)v=0;if(v>255)v=255;
1449
1450    boolean direct = false;
1451    boolean lookup = false;
1452    boolean useExponential = true;
1453    //(1+EXP(((A2/21)-6)*-1))*255
1454    //int form = Math.round( (int)(1/((1+Math.exp(((v/21)-6)*-1))*255)));
1455
1456    //System.out.println("v="+v+"  "+levels_.get(Integer.valueOf(v)).intValue());
1457    //System.out.println("v="+v+"  ExpLevel="+1/((1+Math.exp(((v/21)-6)*-1))*255));
1458    if (direct) retVal = v;
1459    else if (useExponential) retVal = expLedLevel(v);
1460
1461    //System.out.println("v="+v+" "+(useExponential?" ExponLevel=":"")+retVal);
1462    return retVal;
1463  }
1464
1465
1466  /**
1467  * LED Level lookup from an exponential formula.
1468  *
1469  * @param v is the (0-255) level to lookup the non-linear level lookup.
1470  **/
1471  private int expLedLevel(int v)
1472  {
1473    double vd = (double) v;
1474    if(vd<0.0)vd=0.0;if(vd>255.0)vd=255.0;
1475    //double retVal = (1/(1+Math.exp(vd*-1))*255);
1476    int retVal = (int) Math.round( 1/(1+Math.exp((((vd)/21)-6)*-1))*255+0.5);
1477    if(v==0) retVal = 0;
1478    return retVal;
1479  }
1480
1481
1482  /**
1483  * The difference between the LED Level lookup from the non-linear hashmap setup in {@link #initLevelsMap() initLevelsMap}.
1484  *
1485  * @param l1 is the (0-255) level to lookup the non-linear level lookup.
1486  * @param l2 is the (0-255) level to lookup the non-linear level lookup.
1487  **/
1488  private int levelsDiff(int l1, int l2) {return levelsDiff(l1,l2,true);}
1489  /**
1490  * The difference between the LED Level lookup from the linear or non-linear hashmap setup in {@link #initLevelsMap() initLevelsMap}.
1491  *
1492  * @param l1 is the (0-255) level to lookup the non-linear level lookup.
1493  * @param l2 is the (0-255) level to lookup the non-linear level lookup.
1494  * @param nonLinear boolean flag to choose between linear or default non-linear level lookup.
1495  **/
1496  private int levelsDiff(int l1, int l2, boolean nonLinear)
1497  {
1498    int levelDiff = l1-l2;
1499    //System.err.println("Diffing: l1="+l1+"  l2="+l2);
1500    //System.err.println("Diffing: ledLevel(l1)="+ledLevel(l1)+"  ledLevel(l2)="+ledLevel(l2));
1501    try
1502    {
1503      if (nonLinear)
1504        levelDiff = Math.abs(ledLevel(l1)-ledLevel(l2));
1505      else
1506        levelDiff = Math.abs(levelDiff);
1507    }
1508    catch (java.lang.NullPointerException npe)
1509    {
1510      System.err.println("ERROR: levelsDiff l1="+l1+"  l2="+l2);
1511      npe.printStackTrace();
1512    }
1513    return levelDiff;
1514  }
1515
1516
1517  /**
1518    * Set Method for class field 'levelCount'.
1519    *
1520    * @param levelCount is the value to set this class field to.
1521    *
1522    **/
1523  public  void setLevelCount(int levelCount)
1524  {
1525    this.levelCount = levelCount;
1526  }  // setLevelCount Method
1527
1528
1529  /**
1530    * Get Method for class field 'levelCount'.
1531    *
1532    * @return int - The value the class field 'levelCount'.
1533    *
1534    **/
1535  public int getLevelCount()
1536  {
1537    return levelCount;
1538  }  // getLevelCount Method
1539
1540
1541  /**
1542  * Lookup the linear value from the non-linear hashmap setup in {@link #initLevelsMap() initLevelsMap}.
1543  *
1544  * @param v is the non-level value to lookup the linear level .
1545  **/
1546  private int reverseLedLevel(int v)
1547  {
1548   if(v<0){v=0;}
1549   else if(v>255){v=255;}
1550   Integer ii = Integer.valueOf(v);
1551   if(debugging_) System.out.println("ii="+ii);
1552   Integer iii = reverseLevels_.get(ii);
1553   if(debugging_) System.out.println("iii="+iii);
1554   return (iii!=null?iii.intValue():-1);
1555  }
1556
1557
1558  /**
1559    * Sends to command to the receiver and then waits for the response(s). The responses often have nothing to do with the command sent
1560    * so this method can filter them to return only the responses related to the command sent.
1561    *
1562    * @param command must be one of the Command Class Constants from the eiscp.Eiscp.Command class.
1563    * @param closeSocket flag to close the connection when done or leave it open.
1564    * @param returnAll flags if all response packetMessages are returned, if no then ONLY the ones related to the command requested
1565    * @return the response to the command
1566    **/
1567  public String sendQueryOnOffCommand(boolean on,  boolean closeSocket)
1568  {
1569    String retVal = "";
1570
1571    /* Send The Command and then... */
1572    sendOnOffCommand(on,false);
1573    //sleep(50); // docs say so
1574
1575    /* now listen for the response. */
1576    Vector <String> rv = null;
1577    rv = readQueryResponses();
1578    String currResponse = "";
1579    for (int i=0; i < rv.size(); i++)
1580    {
1581      currResponse = (String) rv.elementAt(i);
1582      retVal+= currResponse+"\n";
1583    }
1584
1585    if (closeSocket) closeSocket();
1586
1587    return retVal ;
1588  }
1589
1590
1591  /**
1592   * This method reads responses (possibly more than one) after a command.
1593   * @return an array of the data portion of the response messages only - There might be more than one response message received.
1594   **/
1595  public Vector <String> readQueryResponses()
1596  {
1597    //boolean debugging = debugging_;
1598    boolean foundCommand = false;
1599    Vector <String> retVal = new Vector <String> ();
1600    byte [] responseBytes = new byte[32] ;
1601    String currResponse = "";
1602    int numBytesReceived = 0;
1603    int totBytesReceived = 0;
1604    int i=0;
1605    int packetCounter=0;
1606    int headerSizeDecimal = 0;
1607    int dataSizeDecimal = 0;
1608    char endChar1 ='!';// NR-5008 response sends 3 chars to terminate the packet - 0x1a 0x0d 0x0a
1609    char endChar2 ='!';
1610    char endChar3 ='!';
1611
1612    if(connected_)
1613    {
1614      try
1615      {
1616        if (debugging_) System.out.println("\nReading Response Packet");
1617        lednetSocket_.setSoTimeout(socketTimeOut_); // this must be set or the following read will BLOCK / hang the method when the messages are done
1618
1619        while(!foundCommand && ((numBytesReceived = in_.read(responseBytes))>0) )
1620        {
1621          totBytesReceived = 0;
1622          StringBuilder msgBuffer = new StringBuilder("");
1623          if (debugging_) System.out.println("\n*\n*\n*\n*Buffering bytes: "+numBytesReceived);
1624          if (debugging_) System.out.print( " Packet"+"["+packetCounter+"]:");
1625
1626          /* Read ALL the incoming Bytes and buffer them */
1627          // *******************************************
1628          while(numBytesReceived>0 )
1629          {
1630            totBytesReceived+=numBytesReceived;
1631            msgBuffer.append(new String(responseBytes));
1632            responseBytes = new byte[32];
1633            numBytesReceived = 0;
1634            if (in_.available()>0)
1635              numBytesReceived = in_.read(responseBytes);
1636            if (debugging_) System.out.print(" "+numBytesReceived);
1637          }
1638          if (debugging_) System.out.println();
1639          convertStringToHex(msgBuffer.toString(), debugging_);
1640
1641          /* Response is done... process it into dataMessages */
1642          // *******************************************
1643          char [] responseChars = msgBuffer.toString().toCharArray(); // use the charArray to step through
1644          msgBuffer = null;// clear for garbageCollection
1645
1646          if (debugging_) System.out.println("responseChars.length="+responseChars.length);
1647          int responseByteCnt = 0;
1648          char [] headerSizeBytes = new char[4];
1649          char [] dataSizeBytes  = new char[4];
1650          char [] dataMessage = null ; //init dynamically
1651          int dataByteCnt = 0;
1652          String dataMsgStr = "";
1653
1654          if(debugging_ && msgBuffer!=null) System.out.println("Message Received: "+msgBuffer.toString());
1655          // loop through all the chars and split out the dataMessages
1656          //while (!foundCommand && (responseByteCnt< totBytesReceived))
1657          //{
1658            // read Header
1659            // 1st 4 chars are the leadIn
1660           // responseByteCnt+=4;
1661
1662            // read headerSize
1663            //headerSizeBytes[0]=responseChars[responseByteCnt++];
1664           // headerSizeBytes[1]=responseChars[responseByteCnt++];
1665           // headerSizeBytes[2]=responseChars[responseByteCnt++];
1666           // headerSizeBytes[3]=responseChars[responseByteCnt++];
1667
1668            //packetCounter++;
1669          //}// done packet
1670
1671        } // check for more data
1672
1673      }
1674      catch( java.net.SocketTimeoutException  noMoreDataException)
1675      {
1676        if (debugging_) System.out.println("Response Done: " );
1677      }
1678      catch(EOFException  eofException)
1679      {
1680        System.out.println("received: \""+retVal+"\"" );
1681      }
1682      catch(IOException ioException)
1683      {
1684        ioException.printStackTrace();
1685      }
1686    }
1687    else
1688      System.out.println("!!Not Connected to Receive ");
1689    return retVal;
1690  }
1691
1692
1693  /**
1694   *  A method to simply abstract the Try/Catch required to put the current
1695   *  thread to sleep for the specified time in ms.
1696   *
1697   * @param  waitTime  the sleep time in milli seconds (ms).
1698   * @return           boolean value specifying if the sleep completed (true) or was interupted (false).
1699   */
1700  public boolean sleep(long waitTime)
1701  {
1702    boolean retVal = true;
1703    /*
1704     *  BLOCK for the spec'd time
1705     */
1706    if(waitTime>0)
1707    try
1708    {
1709      Thread.sleep(waitTime);
1710    }
1711    catch (InterruptedException iex)
1712    {
1713      retVal = false;
1714    }
1715    return retVal;
1716  }
1717
1718
1719  /** gets the help as a String.
1720   * @return the helpMsg in String form
1721   **/
1722  private static String getHelpMsgStr() {return getHelpMsg().toString();}
1723
1724
1725  /** initializes and gets the helpMsg_
1726  class var.
1727   * @return the class var helpMsg_
1728   **/
1729  private static StringBuffer getHelpMsg()
1730  {
1731    helpMsg_ = new StringBuffer(SYSTEM_LINE_SEPERATOR);
1732    helpMsg_.append("---  WebARTS LedNetProxy Class  -----------------------------------------------------");
1733    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1734    helpMsg_.append("---  $Revision: 1302 $ $Date: 2019-11-03 17:19:03 -0800 (Sun, 03 Nov 2019) $ ---");
1735    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1736    helpMsg_.append("-------------------------------------------------------------------------------");
1737    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1738    helpMsg_.append("WebARTS LedNetProxy Class");
1739    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1740    helpMsg_.append("SYNTAX:");
1741    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1742    helpMsg_.append("   java ");
1743    helpMsg_.append(CLASSNAME);
1744    helpMsg_.append(" [hostIP:port] command [commandArgs]");
1745    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1746    helpMsg_.append("      - hostIP:port is optional and defaults to 10.0.0.247:5577");
1747    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1748    helpMsg_.append("      - command is NOT optional");
1749    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1750    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1751    helpMsg_.append("Available Commands:");
1752    /* now add all the commands available */
1753      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1754      helpMsg_.append("-->   "+"test");
1755      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1756      helpMsg_.append("-->   "+"on");
1757      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1758      helpMsg_.append("-->   "+"off");
1759      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1760      helpMsg_.append("-->   "+"colour r g b w");
1761      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1762      helpMsg_.append("-->   "+"r");
1763      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1764      helpMsg_.append("-->   "+"g");
1765      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1766      helpMsg_.append("-->   "+"b");
1767      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1768      helpMsg_.append("-->   "+"w");
1769      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1770      helpMsg_.append("-->   "+"cw");
1771      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1772      helpMsg_.append("-->   "+"p");
1773      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1774      helpMsg_.append("-->   "+"cyan");
1775      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1776      helpMsg_.append("-->   "+"fade r g b w r2 g2 b2 w2");
1777      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1778      helpMsg_.append("         -->   "+"the from and to rgbw levels");
1779      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1780      helpMsg_.append("-->   "+"fun [loopTime-ms [pausetime-ms]]");
1781      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1782      helpMsg_.append("         -->   "+"loopTime-ms  can be -1 to loop indefinitely");
1783      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1784      helpMsg_.append("         -->   "+"pausetime-ms  is the pause between the colour changes");
1785      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1786      helpMsg_.append("-->   "+"christmasFun [loopTime-ms [pausetime-ms]]");
1787      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1788      helpMsg_.append("         -->   "+"loopTime-ms  can be -1 to loop indefinitely");
1789      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1790      helpMsg_.append("         -->   "+"pausetime-ms  is the pause between the colour changes");
1791      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1792      helpMsg_.append("-->   "+"christmasFade [loopTime-ms [pausetime-ms]]");
1793      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1794      helpMsg_.append("         -->   "+"loopTime-ms  can be -1 to loop indefinitely");
1795      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1796      helpMsg_.append("         -->   "+"pausetime-ms  is the pause between the colour changes");
1797      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1798      helpMsg_.append("-->   "+"slowBurn [loopTime-ms [pausetime-ms]]");
1799      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1800      helpMsg_.append("         -->   "+"loopTime-ms  can be -1 to loop indefinitely");
1801      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1802      helpMsg_.append("         -->   "+"pausetime-ms  is the pause between the colour changes");
1803      helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1804      helpMsg_.append("-->   "+"dumpLevels");
1805    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1806    helpMsg_.append("---------------------------------------------------------");
1807    helpMsg_.append("----------------------");
1808    helpMsg_.append(SYSTEM_LINE_SEPERATOR);
1809
1810    return helpMsg_;
1811  }
1812
1813
1814  /**
1815   * Class main commandLine entry method.
1816   **/
1817  public static void main(String [] args)
1818  {
1819    final String methodName = CLASSNAME + ": main()";
1820    LedNetProxy instance = new LedNetProxy(DEFAULT_LEDNET_IP, DEFAULT_LEDNET_PORT);
1821    int commandArg = 0;
1822
1823    // if there are more than 1 args, then the 1st is assumed to be a non-default port number
1824    if (args.length>1 && args[commandArg].contains(":"))
1825    {
1826      System.out.println("Overriding DEFAULT IP & port with: "+args[commandArg]);
1827      String [] ipPort = args[commandArg].split("\\:");
1828      instance.setReceiverIP(ipPort[0]);
1829      instance.setReceiverPort(Integer.parseInt(ipPort[1]));
1830      commandArg++;
1831    }
1832
1833    /* Simple way af parsing the args */
1834    if (args ==null || args.length<1)
1835      System.out.println(getHelpMsgStr());
1836    else
1837    {
1838      /* *********************************************** */
1839      if (args[commandArg].equalsIgnoreCase("test"))
1840      {
1841        System.out.println("Testing LedNetProxy");
1842        String queryResponse =  instance.sendQueryOnOffCommand(true, false);
1843        System.out.println(queryResponse);
1844        System.out.println();
1845
1846        int delay = 250;
1847        instance.sendColourCommand(128,0,0,0);
1848        instance.fadeBetweenCommand(255,0,0,0,  0,255,0,0, FadeSpeed.MEDIUM);
1849        instance.fadeBetweenCommand(0,255,0,0,  0,0,255,0, FadeSpeed.MEDIUM);
1850
1851        instance.sendColourCommand(0,0,0,0);
1852        instance.sendColourCommand(180,180,180,0);
1853        instance.sendColourCommand(0,0,0,180);
1854        instance.sendColourCommand(0,0,0,0);
1855        instance.sendColourCommand(0,0,0,180);
1856        instance.sendColourCommand(0,0,0,0);
1857        instance.sendColourCommand(0,0,0,180);
1858        instance.sendColourCommand(0,0,0,0);
1859        instance.sendColourCommand(0,0,0,180);
1860
1861        instance.fadeBetweenCommand(255,0,0,0,  0,255,0,0 , FadeSpeed.MEDIUM);
1862
1863        instance.sendQueryOnOffCommand(false, true);
1864      }
1865      /* *********************************************** */
1866      else if (args[commandArg].equalsIgnoreCase("test2"))
1867      {
1868        int toLevel = 255;
1869        instance.debugging_=true;
1870        instance.sendQueryOnOffCommand(true, false);
1871        // Test Fade
1872        instance.fadeBetweenCommand( toLevel,0,toLevel,0, (toLevel/20),0,(toLevel/20),0 ,  FadeSpeed.MOLASSES, true);
1873        instance.fadeBetweenCommand( (toLevel/20),0,(toLevel/20),0 , toLevel,0,toLevel,0,  FadeSpeed.MOLASSES, true);
1874
1875        //Turn OFF
1876        instance.sendQueryOnOffCommand(false, true);
1877      }
1878      /* *********************************************** */
1879      else if (args[commandArg].equalsIgnoreCase("testFade"))
1880      {
1881        int toLevel = 128;
1882        if(args.length>1) toLevel = Integer.parseInt(args[commandArg+1]);
1883        System.out.println("Testing LedNetProxy Fading to level:"+toLevel);
1884        // Turn On
1885        instance.sendColourCommand(0,0,0,0);
1886        String queryResponse =  instance.sendQueryOnOffCommand(true, false);
1887
1888        // Test Fade
1889        instance.fadeBetweenCommand( toLevel,0,toLevel,0, (toLevel/20),0,(toLevel/20),0 ,  FadeSpeed.VERYSLOW);
1890        instance.fadeBetweenCommand( (toLevel/20),0,(toLevel/20),0 , toLevel,0,toLevel,0,  FadeSpeed.VERYSLOW);
1891
1892        //Turn OFF
1893        instance.sendQueryOnOffCommand(false, true);
1894      }
1895      /* *********************************************** */
1896      else if (args[commandArg].equalsIgnoreCase("testCustom"))
1897      {
1898        //instance.sendQueryOnOffCommand(true, true);
1899        instance.sendCustomCommand(255,0,0,0,0,0,255,0,60);
1900      }
1901
1902
1903      /* *********************************************** */
1904      else if (args[commandArg].equalsIgnoreCase("on"))
1905      {
1906        //instance.sendQueryOnOffCommand(true, true);
1907        String queryResponse =  instance.sendQueryOnOffCommand(true, true);
1908      }
1909      /* *********************************************** */
1910      else if (args[commandArg].equalsIgnoreCase("off"))
1911      {
1912        //instance.sendQueryOnOffCommand(true, true);
1913        String queryResponse =  instance.sendQueryOnOffCommand(false, true);
1914      }
1915      /* *********************************************** */
1916      else if (args[commandArg].equalsIgnoreCase("r"))
1917      {
1918        //instance.sendQueryOnOffCommand(true, true);
1919        instance.sendColourCommand(255,0,0,0);
1920      }
1921      /* *********************************************** */
1922      else if (args[commandArg].equalsIgnoreCase("g"))
1923      {
1924        //instance.sendQueryOnOffCommand(true, true);
1925        instance.sendColourCommand(0,255,0,0);
1926      }
1927      /* *********************************************** */
1928      else if (args[commandArg].equalsIgnoreCase("b"))
1929      {
1930        //instance.sendQueryOnOffCommand(true, true);
1931        instance.sendColourCommand(0,0,255,0);
1932      }
1933      /* *********************************************** */
1934      else if (args[commandArg].equalsIgnoreCase("p"))
1935      {
1936        //instance.sendQueryOnOffCommand(true, true);
1937        instance.sendColourCommand(5,0,15,0);
1938      }
1939      /* *********************************************** */
1940      else if (args[commandArg].equalsIgnoreCase("y"))
1941      {
1942        //instance.sendQueryOnOffCommand(true, true);
1943        instance.sendColourCommand(255,255,0,0);
1944      }
1945      /* *********************************************** */
1946      else if (args[commandArg].equalsIgnoreCase("w"))
1947      {
1948        //instance.sendQueryOnOffCommand(true, true);
1949        instance.sendColourCommand(200,200,200,255);
1950      }
1951      /* *********************************************** */
1952      else if (args[commandArg].equalsIgnoreCase("ww"))
1953      {
1954        //instance.sendQueryOnOffCommand(true, true);
1955        instance.sendColourCommand(0,0,0,255);
1956      }
1957      /* *********************************************** */
1958      else if (args[commandArg].equalsIgnoreCase("cw"))
1959      {
1960        //instance.sendQueryOnOffCommand(true, true);
1961        instance.sendColourCommand(240,240,255,80);
1962      }
1963      /* *********************************************** */
1964      else if (args[commandArg].equalsIgnoreCase("cyan"))
1965      {
1966        //instance.sendQueryOnOffCommand(true, true);
1967        instance.sendColourCommand(0,20,10,0);
1968      }
1969      /* *********************************************** */
1970      else if (args[commandArg].equalsIgnoreCase("fade"))
1971      {
1972        if(args.length>8)
1973        {
1974          instance.sendQueryOnOffCommand(true, false);
1975          instance.fadeBetweenCommand(Integer.parseInt(args[commandArg+1]),
1976                                      Integer.parseInt(args[commandArg+2]),
1977                                      Integer.parseInt(args[commandArg+3]),
1978                                      Integer.parseInt(args[commandArg+4]),
1979                                      Integer.parseInt(args[commandArg+5]),
1980                                      Integer.parseInt(args[commandArg+6]),
1981                                      Integer.parseInt(args[commandArg+7]),
1982                                      Integer.parseInt(args[commandArg+8]),
1983                                      FadeSpeed.VERYSLOW,
1984                                      false);
1985        }
1986      }
1987      /* *********************************************** */
1988      else if (args[commandArg].equalsIgnoreCase("colour"))
1989      {
1990        //System.out.println("args.length="+args.length+"\ncommandArg="+commandArg);
1991        int r = (args.length>commandArg+1 ? Integer.parseInt(args[commandArg+1]):0);
1992        int g = (args.length>commandArg+2?Integer.parseInt(args[commandArg+2]):0);
1993        int b = (args.length>commandArg+3?Integer.parseInt(args[commandArg+3]):0);
1994        int w = (args.length>commandArg+4?Integer.parseInt(args[commandArg+4]):0);
1995        //instance.sendQueryOnOffCommand(true, true);
1996        instance.sendColourCommand(r,g,b,w);
1997      }
1998      /* *********************************************** */
1999      else if (args[commandArg].equalsIgnoreCase("dumplevels"))
2000      {
2001        //instance.sendQueryOnOffCommand(true, true);
2002        instance.dumpLevels();
2003        instance.dumpReverseLevels();
2004      }
2005      /* *********************************************** */
2006      else if (args[commandArg].equalsIgnoreCase("dumpExpLevels"))
2007      {
2008        //instance.sendQueryOnOffCommand(true, true);
2009        instance.dumpExpLevels();
2010      }
2011
2012
2013      /* *********************************************** */
2014      else if (args[commandArg].equalsIgnoreCase("fun"))
2015      {
2016        //instance.sendQueryOnOffCommand(true, true);
2017        instance.fun((args.length>1?Integer.parseInt(args[commandArg+1]):-1),
2018                     (args.length>2?Integer.parseInt(args[commandArg+2]):200));  // loopTime, pause
2019      }
2020      /* *********************************************** */
2021      else if (args[commandArg].equalsIgnoreCase("christmasFun"))
2022      {
2023        //instance.sendQueryOnOffCommand(true, true);
2024        instance.christmasFun((args.length>1?Integer.parseInt(args[commandArg+1]):-1),
2025                              (args.length>2?Integer.parseInt(args[commandArg+2]):200));  // loopTime, pause
2026      }
2027      /* *********************************************** */
2028      else if (args[commandArg].equalsIgnoreCase("christmasFade"))
2029      {
2030        int loopTime = -1;
2031        long pause = 250;
2032        int toLevel = 64;
2033        if(args.length>1) toLevel = Integer.parseInt(args[commandArg+1]);
2034        instance.christmasFade(toLevel, loopTime, pause);
2035
2036        //Turn OFF
2037        instance.sendQueryOnOffCommand(false, true);
2038      }
2039      /* *********************************************** */
2040      else if (args[commandArg].equalsIgnoreCase("slowBurn"))
2041      {
2042        int loopTime = -1;
2043        long pause = 250;
2044        int toLevel = 10;
2045        if(args.length>1) toLevel = Integer.parseInt(args[commandArg+1]);
2046        instance.slowBurn(toLevel, loopTime, pause);
2047
2048        //Turn OFF
2049        instance.sendQueryOnOffCommand(false, true);
2050      }
2051      /* *********************************************** */
2052      else if (args[commandArg].equalsIgnoreCase("nightFade"))
2053      {
2054        instance.sendQueryOnOffCommand(true, false);
2055
2056        while(true)
2057        {
2058          instance.stepBetween(1,0,1,0,   8,0,16,0);
2059          instance.stepBetween(8,0,16,0,  1,0,1,0);
2060        }
2061      }
2062      instance.closeSocket();
2063    }
2064  } // main
2065
2066
2067  /** Prints out the non-linear levels from the class var Exponential formula. **/
2068  public void dumpExpLevels()
2069  {
2070    for (int i=0; i<256; i++)
2071      System.out.println("v="+i+"  "+expLedLevel(i));
2072  }
2073
2074
2075  /** Prints out the non-linear levels from the class var levels_. **/
2076  public void dumpLevels()
2077  {
2078    for (int i=0; i<256; i++)
2079      //System.out.println("v="+i+"  "+levels_.get(Integer.valueOf(i)).intValue());
2080      System.out.println("v="+i+"  "+ledLevel(i));
2081    //System.out.println("\nLevelCount="+levelCount);
2082
2083  }
2084
2085
2086  /** Prints out the reverse non-linear lookup levels from the class var reverseLevels_. **/
2087  public void dumpReverseLevels()
2088  {
2089    System.out.println("\rreverseLevels_ size="+reverseLevels_.size());
2090    for (int i=0; i<256; i++)
2091      if (reverseLevels_.get(Integer.valueOf(i))!=null)
2092        System.out.println("v="+i+"  "+reverseLevels_.get(Integer.valueOf(i)).intValue());
2093    System.out.println("\nLevelCount="+levelCount);
2094
2095  }
2096
2097
2098  /**
2099    * Initializes the class var 'levels_' HashMap of non-linear lookup values for the LED brightness levels.
2100    **/
2101  private void initLevelsMap()
2102  {
2103    int inc = 1;
2104    int currVal = 0;
2105    levelCount = 0;
2106    int [] levelMarks = {0,16,32,64,128,256};
2107    int currMark = 0;
2108    int maxVal = 255;
2109
2110    levels_ = new HashMap<Integer, Integer>(256);
2111
2112    for (int loop=0; loop<levelMarks.length-1; loop++)
2113    {
2114      currMark = levelMarks[loop];
2115      //System.out.println("\n   initLevelsMap currMark="+currMark);
2116      // count through all numbers 0-255 and assign its lookupLevel
2117      for (int i=currMark; i<levelMarks[loop+1]; i++)
2118      {
2119        levels_.put(Integer.valueOf(i), Integer.valueOf(currVal));
2120        //System.out.println("       i="+i+"  mapped to "+currVal);
2121        currVal+=inc;
2122        if(currVal>maxVal) currVal=maxVal;
2123      }
2124      inc*=2;  // double the incrementer after every levelMark
2125      /*for (int i=currMark; i<levelMarks[loop+1]; i+=inc)
2126      {
2127        for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2128        currVal+=inc; levelCount++;
2129      }
2130      currVal+=inc; inc*=2;  // double the incrementer after every levelMark*/
2131    }
2132
2133    // manually set the levels
2134    /*
2135    for (int i=0; i<8; i++)
2136    {
2137      levels_.put(Integer.valueOf(i), Integer.valueOf(currVal));
2138      currVal+=inc;
2139      levelCount++;
2140    }
2141
2142    currVal+=inc;
2143    inc=2;
2144    for (int i=8; i<16; i+=inc)
2145    {
2146      for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2147      currVal+=inc; levelCount++;
2148    }
2149
2150    currVal+=inc;
2151    inc=4;
2152    for (int i=16; i<32; i+=inc)
2153    {
2154      for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2155      currVal+=inc; levelCount++;
2156    }
2157
2158    currVal+=inc;
2159    inc=8;
2160    for (int i=32; i<64; i+=inc)
2161    {
2162      for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2163      currVal+=inc; levelCount++;
2164    }
2165
2166    currVal+=inc;
2167    inc=16;
2168    for (int i=64; i<128; i+=inc)
2169    {
2170      for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2171      currVal+=inc; levelCount++;
2172    }
2173
2174    currVal+=inc;
2175    inc=32;
2176    for (int i=128; i<256; i+=inc)
2177    {
2178      for(int j=0; j<inc; j++)  {levels_.put(Integer.valueOf(i+j), Integer.valueOf(currVal));}
2179      currVal+=inc; levelCount++;
2180    }
2181    */
2182
2183
2184    //System.out.println("Creating  reverseLevels_  levelsCount="+levelCount);
2185    reverseLevels_ = new HashMap<Integer, Integer>(levelCount);
2186    for (int i=levelMarks[0]; i<levelMarks[levelMarks.length-1]; i++)
2187    {
2188      for(int j=levelMarks[0];j<levelMarks[levelMarks.length-1];j++)
2189      {
2190        if(levels_.get(Integer.valueOf(j))!=null)
2191        {
2192          //System.out.println(" Checking "+i+" for level "+levels_.get(Integer.valueOf(j)));
2193          if(levels_.get(Integer.valueOf(j)).intValue()==i)
2194          {
2195            //System.out.println("       ..... adding "+Integer.valueOf(j) + "  "+ Integer.valueOf(i));
2196            reverseLevels_.put(Integer.valueOf(j), Integer.valueOf(i));
2197            j=levelMarks[levelMarks.length-1];
2198          }
2199        }
2200      }
2201    }
2202    //System.out.println("reverseLevels_  size="+reverseLevels_.size());
2203
2204  }
2205
2206} // class