/*
 *****************************************************************************
 *                                                                           *
 *              IMPINJ CONFIDENTIAL AND PROPRIETARY                          *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc. Reproduction or     *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2014. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/

package com.impinj.stp;

import com.google.protobuf.*;
import com.impinj.stp.StpApi.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;

public class Device {
    private Thread reportsThread;
    private Response response = null;
    private final Object lock = new Object();
    private final int MAX_READER_WAIT_MS = 5000;
    private final int BLOCKING_QUEUE_SIZE = 2048;
    private boolean debug = false;
    private BlockingQueue<Report> reportsQueue;
    private TcpIpCommunication comm;
    public final static String JUNIT_TEST_TAG = "_JUNIT_TEST_TAG";
    protected static int PORT = 0xC0DA;
    protected static byte[] FRAME_MARKER_BYTES = new byte[] { 0x69, 0x6d, 0x70, 0x21 };
    protected static byte VERSION_BYTE = 0x01;
    protected static byte FORMATTING_LENGTH = 12;
    protected static int MAX_SENSIBLE_FRAME_LENGTH = 65536;
    final String API_VERSION_STR = "1";
    
    //Autogen
    
    private List<OperationSummaryReportListener> operationSummaryReportListeners;
    private List<TagOperationReportListener> tagOperationReportListeners;
    private List<DeviceEventReportListener> deviceEventReportListeners;
    private List<CartonContentsReportListener> cartonContentsReportListeners;
    
    /**
    * Provides the application a communication interface with the Device
    *
    * @param  ipOrHost of the Speedway Device. Can be an IP address or host name.
    * @param  timeoutMS Connection timeout in milliseconds
    * @throws IOException  TCP/IP communication failure
    */
    public Device(String ipOrHost, int timeoutMS)  throws IOException {
        // TCP/IP socket interface
        try {
           comm = new TcpIpCommunication(ipOrHost, timeoutMS);
           if (!comm.isjUnitTest()) {
                reportsQueue = new ArrayBlockingQueue<Report>(BLOCKING_QUEUE_SIZE);
                reportsThread = new Thread(new ReportsConsumer());
                reportsThread.start();
                new Thread(new DeviceReader()).start();
           }
           //Autogen
           
           operationSummaryReportListeners = Collections.synchronizedList(new ArrayList<OperationSummaryReportListener>());
           tagOperationReportListeners = Collections.synchronizedList(new ArrayList<TagOperationReportListener>());
           deviceEventReportListeners = Collections.synchronizedList(new ArrayList<DeviceEventReportListener>());
           cartonContentsReportListeners = Collections.synchronizedList(new ArrayList<CartonContentsReportListener>());
        } catch (IOException e) {
           e.printStackTrace();
           throw e;
        }
    }
  /**
    * Provides the application a communication interface with the Device
    *
    * @param  ipOrHost of the Speedway Device. Can be an IP address or host name. Default timeout 1 second.
    * @throws IOException  TCP/IP communication failure
    */
    public Device(String ipOrHost)  throws IOException {
        this(ipOrHost, 1000);
    }
    public static byte[] buildPayloadBuffer(byte payloadBytes[]) {
         int payloadLength = payloadBytes.length;
         byte[] lengthBytes = new byte[]
            {
                (byte)((payloadLength >> 24) & 0xff),
                (byte)((payloadLength >> 16) & 0xff),
                (byte)((payloadLength >>  8) & 0xff),
                (byte)((payloadLength      ) & 0xff)
            };
            byte lengthChecksumByte = Helpers.calculateChecksum(lengthBytes);
            int crc = Helpers.calculateCrc(payloadBytes);
            byte[] crcBytes = new byte[]
            {
                (byte)((crc >> 8) & 0xff),
                (byte)((crc     ) & 0xff),
            };
            byte[] frame = new byte[payloadBytes.length + FORMATTING_LENGTH];
            int index = 0;
            for (int i=0;i<FRAME_MARKER_BYTES.length;i++)
                frame[index++]=FRAME_MARKER_BYTES[i];
            frame[index++]=VERSION_BYTE;
            for (int i=0;i<lengthBytes.length;i++)
                frame[index++]=lengthBytes[i];
            frame[index++]=lengthChecksumByte;
            for (int i=0;i<payloadBytes.length;i++)
                frame[index++]=payloadBytes[i];
            for (int i=0;i<crcBytes.length;i++)
                frame[index++]=crcBytes[i];
            return frame;
     }
     /*
      * Used for JUnit Testing
      *     
      * @throws IOException  TCP/IP communication failure
      */
     protected void txToReader(Command command) throws IOException
     {
         Packet packet = Packet.newBuilder().setCommand(command).build();
         comm.write( buildPayloadBuffer(packet.toByteArray()));
     }
     // Response
  /**
    *  Used for jUnit tests so it is public
    *
    * @throws BadBytesReceivedException an error in CRC or reading from the data stream
    * @throws IOException  TCP/IP communication failure
    * @return byte[] byte array containing command return data or report data
    */
     protected byte[] getNextToHost() throws BadBytesReceivedException, IOException
     {
         byte[] toHost = null;
         byte markerBytes[] = comm.read(4);
         for (int i = 0; i < 4; i++)
         {
                if (markerBytes[i] != FRAME_MARKER_BYTES[i]) {
                     throw new BadBytesReceivedException("GetNextToHost: FRAME_MARKER_BYTES error");
                }
         }
         byte versionByte[] = comm.read(1);
         if (versionByte[0] != VERSION_BYTE) {
                    throw new BadBytesReceivedException("GetNextToHost: VERSION_BYTE error");
         }
         byte lengthBytes[] = comm.read(4);
         int length = (
                    (lengthBytes[0] & 0xFF) * 256 * 256 * 256 +
                    (lengthBytes[1] & 0xFF) * 256 * 256 +
                    (lengthBytes[2] & 0xFF) * 256  +
                    (lengthBytes[3] & 0xFF));
         if (length > MAX_SENSIBLE_FRAME_LENGTH) {
            throw new BadBytesReceivedException("GetNextToHost: MAX_SENSIBLE_FRAME_LENGTH error");
         }
         byte lengthChecksum[] = comm.read(1);
         if (lengthChecksum[0] != Helpers.calculateChecksum(lengthBytes)) {
             throw new BadBytesReceivedException("GetNextToHost: Checksum error");
         }
         byte payloadBytes[] = comm.read(length);
         
         byte crcBytes[] = comm.read(2);
         int crc = (crcBytes[0] & 0xFF) * 256 + (crcBytes[1] & 0xFF);          
         if (crc != Helpers.calculateCrc(payloadBytes)) {
             throw new BadBytesReceivedException("GetNextToHost: CRC Error");
         }
         try { toHost = payloadBytes;  }
         catch (Exception e) {
            throw new BadBytesReceivedException(e.getMessage());
         }
         return toHost;
    }
    private Response sendCommandWaitForResponse(Command command) throws DeviceException {
            Response ret = null;
            synchronized(lock) {
                try { 
                    txToReader(command);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    lock.wait(MAX_READER_WAIT_MS);
                    ret = response;  // make copy to return, null class variable
                    response = null;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (ret == null)
                throw new DeviceException("Error: Command repsonse is null.", command.toString());
            // Autogen code
            
            if (ret.hasConnect() && ret.getConnect().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getConnect().getReply().getErrorString(), command.toString());
            if (ret.hasGetVersionsReader() && ret.getGetVersionsReader().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getGetVersionsReader().getReply().getErrorString(), command.toString());
            if (ret.hasStart() && ret.getStart().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getStart().getReply().getErrorString(), command.toString());
            if (ret.hasStop() && ret.getStop().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getStop().getReply().getErrorString(), command.toString());
            if (ret.hasSetDeviceConfig() && ret.getSetDeviceConfig().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getSetDeviceConfig().getReply().getErrorString(), command.toString());
            if (ret.hasGetDeviceConfig() && ret.getGetDeviceConfig().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getGetDeviceConfig().getReply().getErrorString(), command.toString());
            if (ret.hasSetAntennaConfig() && ret.getSetAntennaConfig().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getSetAntennaConfig().getReply().getErrorString(), command.toString());
            if (ret.hasGetAntennaConfig() && ret.getGetAntennaConfig().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getGetAntennaConfig().getReply().getErrorString(), command.toString());
            if (ret.hasEngage() && ret.getEngage().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getEngage().getReply().getErrorString(), command.toString());
            if (ret.hasDisengage() && ret.getDisengage().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getDisengage().getReply().getErrorString(), command.toString());
            if (ret.hasSetOperationResultGpo() && ret.getSetOperationResultGpo().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getSetOperationResultGpo().getReply().getErrorString(), command.toString());
            if (ret.hasAddSerializationJob() && ret.getAddSerializationJob().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getAddSerializationJob().getReply().getErrorString(), command.toString());
            if (ret.hasRemoveSerializationJob() && ret.getRemoveSerializationJob().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getRemoveSerializationJob().getReply().getErrorString(), command.toString());
            if (ret.hasAddTidTableEntries() && ret.getAddTidTableEntries().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getAddTidTableEntries().getReply().getErrorString(), command.toString());
            if (ret.hasAddOrderedListEntries() && ret.getAddOrderedListEntries().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getAddOrderedListEntries().getReply().getErrorString(), command.toString());
            if (ret.hasSetInventoryJob() && ret.getSetInventoryJob().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getSetInventoryJob().getReply().getErrorString(), command.toString());
            if (ret.hasSetCartonContentsJob() && ret.getSetCartonContentsJob().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getSetCartonContentsJob().getReply().getErrorString(), command.toString());
            if (ret.hasGetOperationData() && ret.getGetOperationData().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getGetOperationData().getReply().getErrorString(), command.toString());
            if (ret.hasAddLicenseKey() && ret.getAddLicenseKey().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getAddLicenseKey().getReply().getErrorString(), command.toString());
            if (ret.hasGetDeviceState() && ret.getGetDeviceState().getReply().getErrorCode() != ErrorCode.ERROR_CODE_SUCCESS)
                 throw new DeviceException(ret.getGetDeviceState().getReply().getErrorString(), command.toString());

            return ret;
        }
      /**
        * Sends debug messages to standard output.
        *
        * @param  on sends debug messages if true (Default: false)
        */
        public void setDebug(boolean on) {
            debug = on;
        }
      /**
        * Closes connection between Speedway and the STP application
        */
       public void stpClose() {
           reportsThread.interrupt();
           try { reportsThread.join(); } catch(InterruptedException  ex) {}
           Helpers.sleepMS(200);  // allow time for report thread to exit before close.
           comm.Close();
       }
        // Exception classes
        private class BadBytesReceivedException extends Exception {
             BadBytesReceivedException(String s) {
              super(s);
             }
         }
       /** General exception while communication with the Speedway
         */       
         public class DeviceException extends Exception {
            DeviceException(String error, String cmd) {
                super("Device Exception: " + error + "\r\ncommand="+ cmd);
            }
         }
         
      /**
        * Runs a handshake command
        *
        * @return  ConnectResp which contain the handshake object with version information.
        * @throws DeviceException when command fails
        */
         public ConnectResp handshake() throws DeviceException {
            return sendCommandWaitForResponse( 
                Command.newBuilder()
                     .setConnect(
                         ConnectCommand.newBuilder()
                            .setHandShake(
                                HandShake.newBuilder()
                                    .setAPIName("STP")
                                    .setAPIVersion(1)
                            )
                        )
                .build()).getConnect();
        }
        /**  Sends GetVersions command
         * 
         * @throws DeviceException when command fails 
         * @return List of Version for each software interface
         */
        public List<Version> getVersions() throws DeviceException {
            Versions versions =  sendCommandWaitForResponse( Command.newBuilder()
                     .setGetVersionsReader(
                        GetVersionsReaderCommand.newBuilder()
                            .setReserved(true)
                   ).build()).getGetVersionsReader().getVersions(); 
            List<Version> vlist = new ArrayList<Version>();
            for (Version v : versions.getVersionListList())
                 vlist.add(v);
            // Now add the library stuff
            vlist.add(buildVersionFromString(kdkVersion(), VersionType.VERSION_TYPE_KODIAK_HOST, "Comp=impinj_api"));
            vlist.add(buildVersionFromString(API_VERSION_STR, VersionType.VERSION_TYPE_API_HOST, "Comp=impinj_api"));
            return vlist;
        }
        private Version buildVersionFromString(String versionString, VersionType versionType, String identData) {
            String ident = identData;
            String identParsing[] = versionString.split("-");
            if (identParsing.length == 2) {
                ident = (identData + ";Label=" + identParsing[1]);
            }
            String versionParsing[] = identParsing[0].split("\\.");  // . is a regular expression, \\ is needed
            int build=0, maintenance=0, minor=0, major=0;
            switch(versionParsing.length)
            {
                case 4:
                    build = Integer.parseInt(versionParsing[3]);
                case 3:
                    maintenance = Integer.parseInt(versionParsing[2]);
                case 2:
                    minor = Integer.parseInt(versionParsing[1]);
                case 1:
                    major = Integer.parseInt(versionParsing[0]);
                    break;
            }
            return Version.newBuilder()
                        .setIdent(ident)
                        .setBuild(build)
                        .setMaintenance(maintenance)
                        .setMajor(major)
                        .setMajor(major)
                        .setVersionID(versionType)
                   .build();
        }
         
     /** Puts Speedway into Start mode.
       * @throws DeviceException when command fails 
       * @return StartResp command repose
       */
         public StartResp start() throws DeviceException {
            return runStartCommand(StartCommand.newBuilder().build());
        }
      /** Takes Speedway out of Start mode
       * @throws DeviceException when command fails
       * @return StopResp command repose 
       */
        public StopResp stop() throws DeviceException {
             return runStopCommand(StopCommand.newBuilder().build());
        }
      /** Puts Speedway into Engage mode.
       * @throws DeviceException when command fails
       * @return EngageResp command repose
       */
        public EngageResp engage() throws DeviceException {
             return runEngageCommand(EngageCommand.newBuilder().build());
        }
      /** Takes Speedway out of Engage mode
       * @throws DeviceException when command fails
       * @return DisengageResp command repose
       */
        public DisengageResp disengage() throws DeviceException {
             return runDisengageCommand(DisengageCommand.newBuilder().build());
        }
        // Autogen Factories
             
        /** Runs  Connect proto buffer command
          * @param cmd Builder object for ConnectCommand
          * @return ConnectResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public ConnectResp runConnectCommand(ConnectCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setConnect(cmd).build()).getConnect();
         }    
        /** Runs  GetVersionsReader proto buffer command
          * @param cmd Builder object for GetVersionsReaderCommand
          * @return GetVersionsReaderResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public GetVersionsReaderResp runGetVersionsReaderCommand(GetVersionsReaderCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setGetVersionsReader(cmd).build()).getGetVersionsReader();
         }    
        /** Runs  Start proto buffer command
          * @param cmd Builder object for StartCommand
          * @return StartResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public StartResp runStartCommand(StartCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setStart(cmd).build()).getStart();
         }    
        /** Runs  Stop proto buffer command
          * @param cmd Builder object for StopCommand
          * @return StopResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public StopResp runStopCommand(StopCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setStop(cmd).build()).getStop();
         }    
        /** Runs  SetDeviceConfig proto buffer command
          * @param cmd Builder object for SetDeviceConfigCommand
          * @return SetDeviceConfigResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public SetDeviceConfigResp runSetDeviceConfigCommand(SetDeviceConfigCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setSetDeviceConfig(cmd).build()).getSetDeviceConfig();
         }    
        /** Runs  GetDeviceConfig proto buffer command
          * @param cmd Builder object for GetDeviceConfigCommand
          * @return GetDeviceConfigResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public GetDeviceConfigResp runGetDeviceConfigCommand(GetDeviceConfigCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setGetDeviceConfig(cmd).build()).getGetDeviceConfig();
         }    
        /** Runs  SetAntennaConfig proto buffer command
          * @param cmd Builder object for SetAntennaConfigCommand
          * @return SetAntennaConfigResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public SetAntennaConfigResp runSetAntennaConfigCommand(SetAntennaConfigCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setSetAntennaConfig(cmd).build()).getSetAntennaConfig();
         }    
        /** Runs  GetAntennaConfig proto buffer command
          * @param cmd Builder object for GetAntennaConfigCommand
          * @return GetAntennaConfigResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public GetAntennaConfigResp runGetAntennaConfigCommand(GetAntennaConfigCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setGetAntennaConfig(cmd).build()).getGetAntennaConfig();
         }    
        /** Runs  Engage proto buffer command
          * @param cmd Builder object for EngageCommand
          * @return EngageResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public EngageResp runEngageCommand(EngageCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setEngage(cmd).build()).getEngage();
         }    
        /** Runs  Disengage proto buffer command
          * @param cmd Builder object for DisengageCommand
          * @return DisengageResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public DisengageResp runDisengageCommand(DisengageCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setDisengage(cmd).build()).getDisengage();
         }    
        /** Runs  SetOperationResultGpo proto buffer command
          * @param cmd Builder object for SetOperationResultGPOCommand
          * @return SetOperationResultGPOResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public SetOperationResultGPOResp runSetOperationResultGPOCommand(SetOperationResultGPOCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setSetOperationResultGpo(cmd).build()).getSetOperationResultGpo();
         }    
        /** Runs  AddSerializationJob proto buffer command
          * @param cmd Builder object for AddSerializationJobCommand
          * @return AddSerializationJobResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public AddSerializationJobResp runAddSerializationJobCommand(AddSerializationJobCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setAddSerializationJob(cmd).build()).getAddSerializationJob();
         }    
        /** Runs  RemoveSerializationJob proto buffer command
          * @param cmd Builder object for RemoveSerializationJobCommand
          * @return RemoveSerializationJobResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public RemoveSerializationJobResp runRemoveSerializationJobCommand(RemoveSerializationJobCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setRemoveSerializationJob(cmd).build()).getRemoveSerializationJob();
         }    
        /** Runs  AddTidTableEntries proto buffer command
          * @param cmd Builder object for AddTIDTableEntriesCommand
          * @return AddTIDTableEntriesResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public AddTIDTableEntriesResp runAddTIDTableEntriesCommand(AddTIDTableEntriesCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setAddTidTableEntries(cmd).build()).getAddTidTableEntries();
         }    
        /** Runs  AddOrderedListEntries proto buffer command
          * @param cmd Builder object for AddOrderedListEntriesCommand
          * @return AddOrderedListEntriesResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public AddOrderedListEntriesResp runAddOrderedListEntriesCommand(AddOrderedListEntriesCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setAddOrderedListEntries(cmd).build()).getAddOrderedListEntries();
         }    
        /** Runs  SetInventoryJob proto buffer command
          * @param cmd Builder object for SetInventoryJobCommand
          * @return SetInventoryJobResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public SetInventoryJobResp runSetInventoryJobCommand(SetInventoryJobCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setSetInventoryJob(cmd).build()).getSetInventoryJob();
         }    
        /** Runs  SetCartonContentsJob proto buffer command
          * @param cmd Builder object for SetCartonContentsJobCommand
          * @return SetCartonContentsJobResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public SetCartonContentsJobResp runSetCartonContentsJobCommand(SetCartonContentsJobCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setSetCartonContentsJob(cmd).build()).getSetCartonContentsJob();
         }    
        /** Runs  GetOperationData proto buffer command
          * @param cmd Builder object for GetOperationDataCommand
          * @return GetOperationDataResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public GetOperationDataResp runGetOperationDataCommand(GetOperationDataCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setGetOperationData(cmd).build()).getGetOperationData();
         }    
        /** Runs  AddLicenseKey proto buffer command
          * @param cmd Builder object for AddLicenseKeyCommand
          * @return AddLicenseKeyResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public AddLicenseKeyResp runAddLicenseKeyCommand(AddLicenseKeyCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setAddLicenseKey(cmd).build()).getAddLicenseKey();
         }    
        /** Runs  GetDeviceState proto buffer command
          * @param cmd Builder object for GetDeviceStateCommand
          * @return GetDeviceStateResp contains results from command
          * @throws Device.DeviceException when command fails (See error Message)
          */
         public GetDeviceStateResp runGetDeviceStateCommand(GetDeviceStateCommand cmd) throws Device.DeviceException {
            return sendCommandWaitForResponse(Command.newBuilder().setGetDeviceState(cmd).build()).getGetDeviceState();
         }
           
        // Thread classes
        private class DeviceReader implements Runnable {
            public DeviceReader() {  }
            public void run() { 
                try {
                    try {
                         while(!Thread.currentThread().isInterrupted()){
                            Packet packet=null;
                            try { 
                                 packet = Packet.getDefaultInstance().getParserForType().parsePartialFrom(getNextToHost());
                                 if (packet.hasResponse()) {                            
                                    synchronized(lock) {
                                       response = packet.getResponse();
                                       lock.notifyAll();
                                       if (debug)
                                           System.out.println("Response: "+response);
                                   }
                                } else if (packet.hasReport()) {
                                    try { reportsQueue.put(packet.getReport()); } catch(InterruptedException ex) {ex.printStackTrace();}
                                } else
                                    System.out.println("ERROR: neither response nor report received!");
                            } catch(InvalidProtocolBufferException ex) {
                                System.out.println("DeviceReader InvalidProtocolBufferException: "+ex.getMessage());
                            }

                        }  // while
                    } catch(BadBytesReceivedException ex) {
                        System.out.println("Fatal Error BadBytesReceivedException: "+ex.getMessage());
                    }
                } catch(IOException ex) {
                    if (comm.getSocket() != null)  // If main thread closed socket then normal exit
                        System.out.println("DeviceReader IOException: "+ex.getMessage());
                }
            } // run
        }      
        private class ReportsConsumer implements Runnable {
            public ReportsConsumer() {  }
            public void run() {
              try {
                while (true) { consume((Report)reportsQueue.take()); }
              } catch (InterruptedException e) { }  // interrupt is used to for exit so don't print error message.
            }
            void consume(Report report) {
                if (debug)
                    System.out.println("Report: "+report);
                // Autogen
                
                if (report.hasOperationSummaryReport()) {
                    synchronized ( operationSummaryReportListeners ) {
                        for (OperationSummaryReportListener operationSummaryReport : operationSummaryReportListeners)
                            operationSummaryReport.reportEvent(report.getOperationSummaryReport());
                    }
                }
                if (report.hasTagOperationReport()) {
                    synchronized ( tagOperationReportListeners ) {
                        for (TagOperationReportListener tagOperationReport : tagOperationReportListeners)
                            tagOperationReport.reportEvent(report.getTagOperationReport());
                    }
                }
                if (report.hasDeviceEventReport()) {
                    synchronized ( deviceEventReportListeners ) {
                        for (DeviceEventReportListener deviceEventReport : deviceEventReportListeners)
                            deviceEventReport.reportEvent(report.getDeviceEventReport());
                    }
                }
                if (report.hasCartonContentsReport()) {
                    synchronized ( cartonContentsReportListeners ) {
                        for (CartonContentsReportListener cartonContentsReport : cartonContentsReportListeners)
                            cartonContentsReport.reportEvent(report.getCartonContentsReport());
                    }
                }   
            }
        }
    // Autogen
    
    /** Adds a OperationSummaryReport listener
     * @param report OperationSummaryReport Listener to be added 
     */
    public void addOperationSummaryReportListener(OperationSummaryReportListener report) {
        operationSummaryReportListeners.add(report); 
    }
    /** Removes a specified OperationSummaryReport listener
     * @param report OperationSummaryReport Listener to be removed
     */
    public void removeOperationSummaryReportListener(OperationSummaryReportListener report) {
        operationSummaryReportListeners.remove(report);
    }
    /** Defines the Interface for the OperationSummaryReport Listener
     */
    public interface OperationSummaryReportListener {
        public void reportEvent(OperationSummaryReport report);
    }
    /** Adds a TagOperationReport listener
     * @param report TagOperationReport Listener to be added 
     */
    public void addTagOperationReportListener(TagOperationReportListener report) {
        tagOperationReportListeners.add(report); 
    }
    /** Removes a specified TagOperationReport listener
     * @param report TagOperationReport Listener to be removed
     */
    public void removeTagOperationReportListener(TagOperationReportListener report) {
        tagOperationReportListeners.remove(report);
    }
    /** Defines the Interface for the TagOperationReport Listener
     */
    public interface TagOperationReportListener {
        public void reportEvent(TagOperationReport report);
    }
    /** Adds a DeviceEventReport listener
     * @param report DeviceEventReport Listener to be added 
     */
    public void addDeviceEventReportListener(DeviceEventReportListener report) {
        deviceEventReportListeners.add(report); 
    }
    /** Removes a specified DeviceEventReport listener
     * @param report DeviceEventReport Listener to be removed
     */
    public void removeDeviceEventReportListener(DeviceEventReportListener report) {
        deviceEventReportListeners.remove(report);
    }
    /** Defines the Interface for the DeviceEventReport Listener
     */
    public interface DeviceEventReportListener {
        public void reportEvent(DeviceEventReport report);
    }
    /** Adds a CartonContentsReport listener
     * @param report CartonContentsReport Listener to be added 
     */
    public void addCartonContentsReportListener(CartonContentsReportListener report) {
        cartonContentsReportListeners.add(report); 
    }
    /** Removes a specified CartonContentsReport listener
     * @param report CartonContentsReport Listener to be removed
     */
    public void removeCartonContentsReportListener(CartonContentsReportListener report) {
        cartonContentsReportListeners.remove(report);
    }
    /** Defines the Interface for the CartonContentsReport Listener
     */
    public interface CartonContentsReportListener {
        public void reportEvent(CartonContentsReport report);
    }
    
    /** Gets version String. 
     *  @return String version string
     */ 
    public String kdkVersion() {
        return "2.7.11.240";
    }
}