private int getEncoderValue(int channel) {
   // use latest tachoMonitor value for the motor if available
   if (tachoMonitorAlive()) {
     return tachoMonitor.getTachoCount(channel);
   }
   // .. otherwise, query the controller to get it
   getData(REGISTER_MAP[REG_IDX_ENCODER_CURRENT][channel], buf, 4);
   return EndianTools.decodeIntBE(buf, 0);
 }
  /**
   * Execute a command. designed to never block because it is shared across two motors. The rotate
   * WAITS are done in the TetrixEncoderMotor class
   *
   * @param command the command
   * @param operand the value from the caller. Mostly not used and set to 0
   * @param channel the channel: MOTOR_1, MOTOR_2
   * @return a value depending on the command
   */
  synchronized int doCommand(int command, int operand, int channel) {
    byte workingByte = 0;
    int commandRetVal = 0;

    switch (command) {
      case CMD_FORWARD:
        if (motorState[channel] == STATE_RUNNING_FWD) break;
        if (motorType[channel] == MOTTYPE_REGULATED) {
          if (motorState[channel] != STATE_STOPPED) {
            ((TetrixRegulatedMotor) motors[channel])
                .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_STOP);
          }
          ((TetrixRegulatedMotor) motors[channel])
              .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_START);
        }
        motorGo(channel, command);
        break;
      case CMD_BACKWARD:
        if (motorState[channel] == STATE_RUNNING_BKWD) break;
        if (motorType[channel] == MOTTYPE_REGULATED) {
          if (motorState[channel] != STATE_STOPPED) {
            ((TetrixRegulatedMotor) motors[channel])
                .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_STOP);
          }
          ((TetrixRegulatedMotor) motors[channel])
              .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_START);
        }
        motorGo(channel, command);
        break;
      case CMD_FLT:
        workingByte = -128;
      case CMD_STOP:
        if (command == CMD_STOP) workingByte = 0;
        sendData(REGISTER_MAP[REG_IDX_POWER][channel], workingByte);
        Delay.msDelay(50);
        if (motorType[channel] == MOTTYPE_REGULATED
            && motorState[channel] != STATE_STOPPED
            && motorState[channel] != STATE_ROTATE_TO) {
          ((TetrixRegulatedMotor) motors[channel])
              .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_STOP);
        }
        motorState[channel] = STATE_STOPPED;
        break;
      case CMD_SETPOWER:
        motorParams[MOTPARAM_POWER][channel] = operand;
        // if not running, exit
        if (motorState[channel] == STATE_STOPPED) break;

        // set the power if running to take effect immediately
        workingByte = (byte) motorParams[MOTPARAM_POWER][channel];
        if (motorState[channel] == STATE_RUNNING_BKWD) {
          workingByte *= -1;
        }
        sendData(REGISTER_MAP[REG_IDX_POWER][channel], workingByte);
        break;
      case CMD_ROTATE:
      case CMD_ROTATE_TO:
        if (motorType[channel] == MOTTYPE_REGULATED) {
          if (motorState[channel] != STATE_STOPPED) {
            ((TetrixRegulatedMotor) motors[channel])
                .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_STOP);
          }
          ((TetrixRegulatedMotor) motors[channel])
              .doListenerState(TetrixRegulatedMotor.LISTENERSTATE_START);
        }
        rotate(channel, operand, command);
        break;
      case CMD_GETPOWER:
        commandRetVal = motorParams[MOTPARAM_POWER][channel];
        break;
      case CMD_GETTACHO:
        commandRetVal = getEncoderValue(channel);
        break;
      case CMD_RESETTACHO:
        // reset encoder/tacho
        setMode(channel, true);
        Delay.msDelay(30); // small delay to allow encoder value reset in controller to happen
        motorState[channel] = STATE_STOPPED;
        break;
      case CMD_SETREVERSE:
        motorParams[MOTPARAM_REVERSED][channel] = operand;
        setMode(channel, false);
        break;
      case CMD_ISMOVING:
        commandRetVal = MOTPARAM_OP_TRUE;
        if (motorState[channel] == STATE_STOPPED) commandRetVal = MOTPARAM_OP_FALSE;
        // over-ride previous decision if regulated motor and it is moving
        if (tachoMonitorAlive()) {
          commandRetVal = tachoMonitor.isMoving(channel) ? MOTPARAM_OP_TRUE : MOTPARAM_OP_FALSE;
        }
        break;
      case CMD_SETREGULATE:
        motorParams[MOTPARAM_REGULATED][channel] = operand; // 1=true, 0=false
        break;
      case CMD_GETSPEED:
        commandRetVal = 0;
        if (tachoMonitorAlive()) {
          commandRetVal = tachoMonitor.getSpeed(channel);
        }
        break;
      case CMD_GETLIMITANGLE:
        commandRetVal = limitangle[channel];
        break;
      default:
        throw new IllegalArgumentException("Invalid Command");
    }
    return commandRetVal;
  }
 boolean tachoMonitorAlive() {
   return tachoMonitor != null && tachoMonitor.isAlive();
 }