private void InitialiseRFTransmitter() throws SpiDeviceException, InterruptedException {

    System.out.println(
        "Status of Enable Data pipe register "
            + String.format("%02X ", dataPipeRegister.readFromRegister()));

    configRegister.writeToRegister((byte) 0x0C); // Set CRC encoding to 2 bytes
    System.out.println(
        "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

    configRegister.powerUp();
    System.out.println(
        "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

    dataPipeRegister.writeToRegister((byte) 0x03); // Enable data pipe 0 and 1
    System.out.println(
        "Status of Enable Data pipe register "
            + String.format("%02X ", dataPipeRegister.readFromRegister()));
    retryRegister.writeToRegister((byte) 0x1F); // wait 500uS and 15 retransmits
    rxdataloadinPipe0.writeToRegister((byte) 0x0A); // 10 byte data in pipe 0
    activateCommand();
    featureRegister.writeToRegister(
        (byte) 0x06); // Enable dynamic payload length and enable payload with ack
    dynamicRegister.writeToRegister((byte) 0x03); // Enable dynamic payload on pipe 0 and 1
    addressPipe0.writeToRegister(TxAddress);
    System.out.println(
        "Status of Address PIPE0 register "
            + String.format("%02X ", addressPipe0.readFromRegister()));

    txaddressRegister.writeToRegister(TxAddress);
  }
  private void shutDown() throws InterruptedException {
    chipSelect.setValue(false);
    chipSelect.close();
    try {
      // move this stuff into a function
      configRegister.powerDown();
      SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
      statusRegister.clearMaxRTFlag();
      activateCommand();
      System.out.println(
          "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

      System.out.println(
          "Status of Enable Data pipe register "
              + String.format("%02X ", dataPipeRegister.readFromRegister()));
      System.out.println(
          "Status of Retry register " + String.format("%02X ", retryRegister.readFromRegister()));
      System.out.println(
          "Status of Rx dataload width pipe0 register "
              + String.format("%02X ", rxdataloadinPipe0.readFromRegister()));
      System.out.println(
          "Status of Feature register "
              + String.format("%02X ", featureRegister.readFromRegister()));

      System.out.println(
          "Status of Address PIPE0 register "
              + String.format("%02X ", addressPipe0.readFromRegister()));

    } catch (SpiDeviceException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
 private void writePayLoad(byte data[]) throws SpiDeviceException {
   // use any of the instantiated registers to access this method to append command to payload data
   SPIFactory.INSTANCE.writeRead(
       configRegister.appendWriteCommandToDataArray(
           (byte) NRFCommand.WriteTxPayload.getCommand(), data));
   // send a reuse command if it is just keep alive data
   if (Arrays.equals(rfKeepAlive, data)) {
     SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.ReusePayLoad.getCommand()});
   }
 }
public class DeviceController implements Runnable, Constants {
  // private  final byte[] pipe0Address = { (byte) 0xC4,0x7B,0x4D,0x03,0x31};
  private final byte[] TxAddress = new byte[5]; // { (byte) 0xC4,0x7B,0x4D,0x03,0x31};
  private final byte[] rfKeepAlive = {0, 0, 0, 0, 1, 0, 0, 0, 0, 0};
  private ConfigRegister configRegister = ConfigRegister.getInstance();
  private EnableDataPipeRegister dataPipeRegister = EnableDataPipeRegister.getInstance();
  private SetupAutoRetryRegister retryRegister = SetupAutoRetryRegister.getInstance();
  private RxPayLoadinDataPipes rxdataloadinPipe0 =
      RxPayLoadinDataPipes.getInstance(RxDataPipes.pipe0);
  private FeatureRegister featureRegister = FeatureRegister.getInstance();
  private DynamicPayloadEnableRegister dynamicRegister = DynamicPayloadEnableRegister.getInstance();
  private RxAddressDataPipe0 addressPipe0 = RxAddressDataPipe0.getInstance();
  private TxAddressRegister txaddressRegister = TxAddressRegister.getInstance();
  private StatusRegister statusRegister = StatusRegister.getInstance();
  private FIFOStatusRegister fifoRegister = FIFOStatusRegister.getInstance();

  private OutPin chipSelect = null; // instantiate it in Initialise SPI

  private ThreadMonitor tm;

  private boolean linkEstablished = false;
  private int exceptionCounter = 0;

  // private RemoteSensorData remoteData = null;
  private final ArrayBlockingQueue<Byte> buttonQueue = new ArrayBlockingQueue<Byte>(BUFFER_SIZE);
  private final ArrayBlockingQueue<Byte[]> accQueue = new ArrayBlockingQueue<Byte[]>(BUFFER_SIZE);
  private ArrayBlockingQueue<Byte[]> txDataQueue;
  private Thread buttonProcessorThread;
  private Thread tiltProcessorThread;

  private boolean startThreads = true;
  private byte[] remoteDataPackets;
  /*
   *  Allows this class to be instantiated from any GUI class and pass the Listeners, the listeners will be updated with events
   *  as and when it happens
   *  Data populated to txData will be transmitted to the remote
   */
  public DeviceController(
      ThreadMonitor tm, RemoteAdapter rm, TiltEventListener tlt, ArrayBlockingQueue<Byte[]> txData)
      throws IOException, SpiDeviceException, InterruptedException {
    InitialiseSPI();
    InitialiseRFTransmitter();
    this.txDataQueue = txData;
    this.tm = tm;

    this.buttonProcessorThread = new Thread(new ButtonEventProcessor(buttonQueue, rm, tm));
    this.tiltProcessorThread =
        new Thread(new AxisEventProcessor(accQueue, tlt, tm, true, DEFAULT_WINDOW_SIZE));
  }

  public DeviceController(ThreadMonitor tm, ArrayList eventQueue, ArrayBlockingQueue<Byte[]> txData)
      throws IOException, SpiDeviceException, InterruptedException {
    InitialiseSPI();
    InitialiseRFTransmitter();
    this.txDataQueue = txData;
    this.tm = tm;
    this.buttonProcessorThread = new Thread(new ButtonEventProcessor(buttonQueue, tm, eventQueue));
  }

  private void InitialiseRFTransmitter() throws SpiDeviceException, InterruptedException {

    System.out.println(
        "Status of Enable Data pipe register "
            + String.format("%02X ", dataPipeRegister.readFromRegister()));

    configRegister.writeToRegister((byte) 0x0C); // Set CRC encoding to 2 bytes
    System.out.println(
        "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

    configRegister.powerUp();
    System.out.println(
        "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

    dataPipeRegister.writeToRegister((byte) 0x03); // Enable data pipe 0 and 1
    System.out.println(
        "Status of Enable Data pipe register "
            + String.format("%02X ", dataPipeRegister.readFromRegister()));
    retryRegister.writeToRegister((byte) 0x1F); // wait 500uS and 15 retransmits
    rxdataloadinPipe0.writeToRegister((byte) 0x0A); // 10 byte data in pipe 0
    activateCommand();
    featureRegister.writeToRegister(
        (byte) 0x06); // Enable dynamic payload length and enable payload with ack
    dynamicRegister.writeToRegister((byte) 0x03); // Enable dynamic payload on pipe 0 and 1
    addressPipe0.writeToRegister(TxAddress);
    System.out.println(
        "Status of Address PIPE0 register "
            + String.format("%02X ", addressPipe0.readFromRegister()));

    txaddressRegister.writeToRegister(TxAddress);
  }

  private void activateCommand() throws SpiDeviceException {

    //  this SPI Factory is abstract its concrete class will use JNI calls when using the
    // RaspberryPi
    // it will use the IOIO calls SPIWriteRead when the code runs on Android
    SPIFactory.INSTANCE.writeRead(
        new byte[] {(byte) NRFCommand.Activate.getCommand(), (byte) 0x73});
  }

  private void InitialiseSPI() throws IOException, SpiDeviceException {
    FileInputStream fr = new FileInputStream("q3remoteadd.cfg");
    fr.read(TxAddress);
    fr.close();
    SPIFactory.INSTANCE.connectSPI();
    SPIFactory.INSTANCE.setConfiguration();
    // Outpin again has multiple implementation based on whether running on Android or Pi
    chipSelect =
        new OutPin(
            0); // header 11 on Pi (Marked GPIO17) used for Chip Enable (Note: This is not CSN for
    // SPI)
  }
  // Actual Listener
  @Override
  public void run() {
    // start two threads one for button event processing and another for axis data processing
    // pass the RemoteSensorData object to it
    // write default payload to device
    try {
      // move these two lines in to a function
      writePayLoad(rfKeepAlive);
      chipSelect.setValue(true);
      System.out.println(
          "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));
      // tm is a threadMonitor  from the calling application to shutdown the threads when UI exits.
      while (!tm.getStopStatus()) {

        int status = statusRegister.readFromRegister();

        //	System.out.println("Status reg value: "+String.format("%02X ",status));
        // check whether RF link is up or down i.e whether the remote responds
        if (hasMaxRetransmitReached(status)) {
          //	System.out.println("MaxRT Flag raised");
          // reset this bit
          statusRegister.clearMaxRTFlag();
          if (linkEstablished && (exceptionCounter > 4)) {

            System.out.println("Link dropped -2");
            // previously link established
            chipSelect.setValue(false);
            SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
            writePayLoad(rfKeepAlive);
            chipSelect.setValue(true);
            exceptionCounter = 0;
          }
          if (linkEstablished) {
            exceptionCounter++;
          }

        } else if (hasAcknowledgementArrived(status)) {
          if (startThreads) {
            // start the threads to handle data received

            buttonProcessorThread.start();
            if (tiltProcessorThread != null) tiltProcessorThread.start();
            startThreads = false;
          }

          // reset NOACK counter here
          linkEstablished = true;
          exceptionCounter = 0;
          statusRegister.clearMaxRTFlag();
          // data has arrived
          // we know the data is 10 bytes long.

          // Data is read from NRF register
          // 10 bytes long -this array is of size 10
          remoteDataPackets = readDataFromFIFO(NUM_DATA_PACKETS);

          statusRegister.clearMaxRTFlag();

          if (buttonQueue.size() == BUFFER_SIZE) {
            buttonQueue.remove();
          }
          // last byte in response message is the button data
          // add it to a Queue - Button processor thread will create an event if a button
          // has been pressed or released
          buttonQueue.add(new Byte(remoteDataPackets[NUM_DATA_PACKETS]));
          if (tiltProcessorThread != null) {
            if (accQueue.size() == BUFFER_SIZE) {
              accQueue.remove();
            }

            // Accelerator data arrives in 1,2,3 positions
            // they are added to a different queue so another thread creates an event
            if (remoteDataPackets[1] != 0)
              //		accQueue.add(new Byte[]{(byte) (remoteDataPackets[1]+(byte)(0x80)),(byte)(
              // remoteDataPackets[2]-(byte)0x78) ,   (byte)(remoteDataPackets[3]+0x72)});
              accQueue.add(
                  new Byte[] {
                    (byte) remoteDataPackets[1],
                    (byte) remoteDataPackets[2],
                    (byte) remoteDataPackets[3],
                    (byte) remoteDataPackets[4],
                    (byte) remoteDataPackets[5],
                    (byte) remoteDataPackets[6]
                  });
          }

          int reuse = fifoRegister.readFromRegister();
          //	System.out.println("FIFO reg value: "+String.format("%02X ",(byte)reuse));
          // check whether there is a new transmit data is available to send or
          // or should we send default keep alive packet
          if (!(((byte) reuse & 0x40) == 0x40)) {
            //		System.out.println("Calling write payload");
            if (txDataQueue.isEmpty()) writePayLoad(rfKeepAlive);
            else {
              Byte[] dataFromuser = txDataQueue.poll();

              byte[] sendData = new byte[dataFromuser.length];
              for (int i = 0; i < sendData.length; i++) {
                sendData[i] = dataFromuser[i].byteValue();
              }
              writePayLoad(sendData);
            }
          }
        } else {
          // keep a counter here if it reaches 4 - then power down chip - reinitialise
          //	System.out.println("Neither Ack received nor Max rt occured Status reg value:
          // "+String.format("%02X ",status));

          if (linkEstablished && (exceptionCounter > 4)) {
            buttonQueue.clear();
            accQueue.clear();
            System.out.println("Link dropped");
            // previously link established
            chipSelect.setValue(false);
            SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
            writePayLoad(rfKeepAlive);
            chipSelect.setValue(true);
            exceptionCounter = 0;
          }
          if (linkEstablished) {
            exceptionCounter++;
          }
        }
        try {
          Thread.sleep(10);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }

    } catch (SpiDeviceException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      System.out.println("Shutdown command received....");
      try {
        shutDown();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      SPIFactory.INSTANCE.disconnectSPI();
      System.out.println("Device Controller thread exiting....");
    }
  }

  private void shutDown() throws InterruptedException {
    chipSelect.setValue(false);
    chipSelect.close();
    try {
      // move this stuff into a function
      configRegister.powerDown();
      SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
      statusRegister.clearMaxRTFlag();
      activateCommand();
      System.out.println(
          "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));

      System.out.println(
          "Status of Enable Data pipe register "
              + String.format("%02X ", dataPipeRegister.readFromRegister()));
      System.out.println(
          "Status of Retry register " + String.format("%02X ", retryRegister.readFromRegister()));
      System.out.println(
          "Status of Rx dataload width pipe0 register "
              + String.format("%02X ", rxdataloadinPipe0.readFromRegister()));
      System.out.println(
          "Status of Feature register "
              + String.format("%02X ", featureRegister.readFromRegister()));

      System.out.println(
          "Status of Address PIPE0 register "
              + String.format("%02X ", addressPipe0.readFromRegister()));

    } catch (SpiDeviceException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private byte[] readDataFromFIFO(int length) throws SpiDeviceException {
    byte[] data = new byte[length + 1];
    data[0] = (byte) NRFCommand.ReadRxPayload.getCommand();
    return SPIFactory.INSTANCE.writeRead(data);
  }

  private boolean hasAcknowledgementArrived(int status) {

    if (status == (byte) 0x2E
        || status == (byte) 0x60
        || status == (byte) 0x70
        || status == (byte) 0x00
        || status == (byte) 0x7E
        || status == (byte) 0x7F
        || status == (byte) 0x30) return true;

    return false;
  }

  private void writePayLoad(byte data[]) throws SpiDeviceException {
    // use any of the instantiated registers to access this method to append command to payload data
    SPIFactory.INSTANCE.writeRead(
        configRegister.appendWriteCommandToDataArray(
            (byte) NRFCommand.WriteTxPayload.getCommand(), data));
    // send a reuse command if it is just keep alive data
    if (Arrays.equals(rfKeepAlive, data)) {
      SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.ReusePayLoad.getCommand()});
    }
  }

  private boolean hasMaxRetransmitReached(int status) {

    if (status == (byte) 0x1E || status == (byte) 0x10 || status == (byte) 0x1F) return true;

    return false;
  }
}
  // Actual Listener
  @Override
  public void run() {
    // start two threads one for button event processing and another for axis data processing
    // pass the RemoteSensorData object to it
    // write default payload to device
    try {
      // move these two lines in to a function
      writePayLoad(rfKeepAlive);
      chipSelect.setValue(true);
      System.out.println(
          "Status of Config register " + String.format("%02X ", configRegister.readFromRegister()));
      // tm is a threadMonitor  from the calling application to shutdown the threads when UI exits.
      while (!tm.getStopStatus()) {

        int status = statusRegister.readFromRegister();

        //	System.out.println("Status reg value: "+String.format("%02X ",status));
        // check whether RF link is up or down i.e whether the remote responds
        if (hasMaxRetransmitReached(status)) {
          //	System.out.println("MaxRT Flag raised");
          // reset this bit
          statusRegister.clearMaxRTFlag();
          if (linkEstablished && (exceptionCounter > 4)) {

            System.out.println("Link dropped -2");
            // previously link established
            chipSelect.setValue(false);
            SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
            writePayLoad(rfKeepAlive);
            chipSelect.setValue(true);
            exceptionCounter = 0;
          }
          if (linkEstablished) {
            exceptionCounter++;
          }

        } else if (hasAcknowledgementArrived(status)) {
          if (startThreads) {
            // start the threads to handle data received

            buttonProcessorThread.start();
            if (tiltProcessorThread != null) tiltProcessorThread.start();
            startThreads = false;
          }

          // reset NOACK counter here
          linkEstablished = true;
          exceptionCounter = 0;
          statusRegister.clearMaxRTFlag();
          // data has arrived
          // we know the data is 10 bytes long.

          // Data is read from NRF register
          // 10 bytes long -this array is of size 10
          remoteDataPackets = readDataFromFIFO(NUM_DATA_PACKETS);

          statusRegister.clearMaxRTFlag();

          if (buttonQueue.size() == BUFFER_SIZE) {
            buttonQueue.remove();
          }
          // last byte in response message is the button data
          // add it to a Queue - Button processor thread will create an event if a button
          // has been pressed or released
          buttonQueue.add(new Byte(remoteDataPackets[NUM_DATA_PACKETS]));
          if (tiltProcessorThread != null) {
            if (accQueue.size() == BUFFER_SIZE) {
              accQueue.remove();
            }

            // Accelerator data arrives in 1,2,3 positions
            // they are added to a different queue so another thread creates an event
            if (remoteDataPackets[1] != 0)
              //		accQueue.add(new Byte[]{(byte) (remoteDataPackets[1]+(byte)(0x80)),(byte)(
              // remoteDataPackets[2]-(byte)0x78) ,   (byte)(remoteDataPackets[3]+0x72)});
              accQueue.add(
                  new Byte[] {
                    (byte) remoteDataPackets[1],
                    (byte) remoteDataPackets[2],
                    (byte) remoteDataPackets[3],
                    (byte) remoteDataPackets[4],
                    (byte) remoteDataPackets[5],
                    (byte) remoteDataPackets[6]
                  });
          }

          int reuse = fifoRegister.readFromRegister();
          //	System.out.println("FIFO reg value: "+String.format("%02X ",(byte)reuse));
          // check whether there is a new transmit data is available to send or
          // or should we send default keep alive packet
          if (!(((byte) reuse & 0x40) == 0x40)) {
            //		System.out.println("Calling write payload");
            if (txDataQueue.isEmpty()) writePayLoad(rfKeepAlive);
            else {
              Byte[] dataFromuser = txDataQueue.poll();

              byte[] sendData = new byte[dataFromuser.length];
              for (int i = 0; i < sendData.length; i++) {
                sendData[i] = dataFromuser[i].byteValue();
              }
              writePayLoad(sendData);
            }
          }
        } else {
          // keep a counter here if it reaches 4 - then power down chip - reinitialise
          //	System.out.println("Neither Ack received nor Max rt occured Status reg value:
          // "+String.format("%02X ",status));

          if (linkEstablished && (exceptionCounter > 4)) {
            buttonQueue.clear();
            accQueue.clear();
            System.out.println("Link dropped");
            // previously link established
            chipSelect.setValue(false);
            SPIFactory.INSTANCE.writeRead(new byte[] {(byte) NRFCommand.FlushTx.getCommand()});
            writePayLoad(rfKeepAlive);
            chipSelect.setValue(true);
            exceptionCounter = 0;
          }
          if (linkEstablished) {
            exceptionCounter++;
          }
        }
        try {
          Thread.sleep(10);
        } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      }

    } catch (SpiDeviceException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } finally {
      System.out.println("Shutdown command received....");
      try {
        shutDown();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      SPIFactory.INSTANCE.disconnectSPI();
      System.out.println("Device Controller thread exiting....");
    }
  }