private static void sendRequest(final short register) {

    final long currentTimeInMillis = System.currentTimeMillis();
    final long elapsed = currentTimeInMillis - lastUpdateInMillis;
    totalTimeMillis += elapsed;
    updatesReceived++;

    clearDataBuffer();

    short length;

    switch (register) {
      case REGISTER_4096_PLUS_SEVEN:
        length = 8;
        break;
      case REGISTER_4140_PLUS_SIX:
        length = 7;
        break;
      default:
        length = 16;
        break;
    }

    if (connected != null & connected.isAlive()) {
      connected.write(ModbusRTU.getRegister(SLAVE_ADDRESS, HOLDING_REGISTER, register, length));

      lastUpdateInMillis = currentTimeInMillis;
      lastRegister = register;
    }
  }
  private static void process4140Response(final String data) {

    final String[] buf = data.substring(data.indexOf(SEVEN_REGISTERS), data.length()).split(" ");

    if (ModbusRTU.validCRC(buf, REGISTER_4140_LENGTH)) {
      imgStatus.setBackgroundColor(Color.GREEN);

      setLearningFlags(new String[] {"0", "0", "0", buf[15], buf[16]});
      targetAFR = Integer.parseInt(buf[3] + buf[4], 16) / 10f;
      closedLoop = (getBit(Integer.parseInt(buf[9] + buf[10], 16), 8) > 0);

      if (closedLoop) {
        txtFuelLearn.setBackgroundColor(Color.GREEN);
      } else {
        txtFuelLearn.setBackgroundColor(Color.TRANSPARENT);
      }

      txtData.setText(
          String.format("AVG: %d ms - TPS: %d%%", (int) totalTimeMillis / updatesReceived, tps));

      if (DEBUG_MODE) Log.d(TAG, "Processed " + lastRegister + " response: " + data);
      sendRequest(REGISTER_4096_PLUS_SEVEN);
      return;

    } else {
      if (DEBUG_MODE) Log.d(TAG, "bad CRC for " + lastRegister + ": " + data);
      imgStatus.setBackgroundColor(Color.RED);
      sendRequest();
    }
  }
  private static void process4096Response(final String data) {

    final String[] buf = data.substring(data.indexOf(EIGHT_REGISTERS), data.length()).split(" ");

    if (ModbusRTU.validCRC(buf, REGISTER_4096_LENGTH)) {
      imgStatus.setBackgroundColor(Color.GREEN);
      dataArray.clear();

      final int rpm = Integer.parseInt(buf[3] + buf[4], 16);
      final int map = Integer.parseInt(buf[5] + buf[6], 16);
      final int mat = getTemperatureValue(buf[7] + buf[8]);
      final int wat = getTemperatureValue(buf[9] + buf[10]);

      final float afr = Integer.parseInt(buf[14], 16) / 10f;
      final float referenceAfr = Integer.parseInt(buf[13], 16) / 10f;

      tps = Integer.parseInt(buf[17] + buf[18], 16);

      iatNeedle.setValue(mat);
      waterNeedle.setValue(Integer.parseInt(buf[9] + buf[10], 16) * 9 / 5 + 32);
      mapNeedle.setValue(map);

      {
        float afrVal = afr * 100;
        float targetAfrVal = targetAFR * 100;

        if (afrVal > AFR_MAX) afrVal = AFR_MAX;
        if (afrVal < AFR_MIN) afrVal = AFR_MIN;

        if (targetAfrVal > AFR_MAX) targetAfrVal = AFR_MAX;
        if (targetAfrVal < AFR_MIN) targetAfrVal = AFR_MIN;

        afrNeedle.setValue(AFR_MAX - afrVal + AFR_MIN);
        targetAfrNeedle.setValue(AFR_MAX - targetAfrVal + AFR_MIN);
      }

      if (rpm >= 200) lastRPM = rpm;
      dataArray.add(String.format("RPM\n%d", lastRPM));
      rpmNeedle.setValue(lastRPM);

      dataArray.add(String.format("MAP\n%d kPa", map));
      dataArray.add(String.format("MAT\n%d\u00B0 %s", mat, getTemperatureSymbol()));
      dataArray.add(String.format("AFR\n%.1f (%.1f)", afr, referenceAfr));
      dataArray.add("TAFR\n" + (targetAFR != 0f ? String.format("%.1f", targetAFR) : "--.-"));
      dataArray.add(String.format("WAT\n%d\u00B0 %s", wat, getTemperatureSymbol()));

      // alarm stuff
      if (gridData.getChildAt(3) != null && gridData.getChildAt(5) != null) {
        // water temperature
        gridData.getChildAt(5).setBackgroundColor(Color.TRANSPARENT);
        if (waterTempPref) {
          if (wat < minimumWaterTemp) gridData.getChildAt(5).setBackgroundColor(Color.BLUE);
          if (wat > maximumWaterTemp) gridData.getChildAt(5).setBackgroundColor(Color.RED);
        }

        // afr vs target alarm
        gridData.getChildAt(3).setBackgroundColor(Color.TRANSPARENT);
        if (afrNotEqualTargetPref) {
          final float threshold = targetAFR * (afrNotEqualTargetTolerance * .01f);
          if (Math.abs(targetAFR - afr) >= threshold) {
            if (afr > targetAFR) {
              gridData.getChildAt(3).setBackgroundColor(Color.RED);
            } else {
              gridData.getChildAt(3).setBackgroundColor(Color.BLUE);
            }

            if (afrAlarmLogging) {
              LogItem newItem = afrAlarmLogItems.newLogItem();
              newItem.setAfr(afr);
              newItem.setReferenceAfr(referenceAfr);
              newItem.setMap(map);
              newItem.setMat(mat);
              newItem.setRpm(rpm);
              newItem.setTargetAfr(targetAFR);
              newItem.setWat(wat);

              afrAlarmLogItems.getItems().add(newItem);

              if (!menuShareLog.isVisible()) {
                menuShareLog.setVisible(true);
              }
            }
          }
        }
      }

      if (DEBUG_MODE) Log.d(TAG, "Processed " + lastRegister + " response: " + data);
      sendRequest(REGISTER_4140_PLUS_SIX);
      return;

    } else {
      if (DEBUG_MODE) Log.d(TAG, "bad CRC for " + lastRegister + ": " + data);
      imgStatus.setBackgroundColor(Color.RED);
      sendRequest();
    }
  }
    @Override
    public void handleMessage(Message message) {

      switch (message.getData().getShort("handle")) {
        case CONNECTED:
          if (progress != null && progress.isShowing()) progress.dismiss();
          menuConnect.setTitle(R.string.menu_disconnect);
          sendRequest(REGISTER_4096_PLUS_SEVEN);
          totalTimeMillis = 0;
          updatesReceived = 0;

          refreshHandler.postDelayed(RefreshRunnable, LONG_PAUSE);

          Editor edit = prefs.edit();
          edit.putString("prefs_remote_name", message.getData().getString("name"));
          edit.putString("prefs_remote_mac", message.getData().getString("addr"));
          edit.commit();

          break;

        case CONNECTION_ERROR:
          if (progress != null && progress.isShowing()) progress.dismiss();

          disconnect();

          AlertDialog alert = new AlertDialog.Builder(ctx).create();
          alert.setTitle(message.getData().getString("title"));
          alert.setMessage("\n" + message.getData().getString("message") + "\n");
          alert.setButton(
              DialogInterface.BUTTON_NEUTRAL,
              "OK",
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  dialog.dismiss();
                }
              });
          alert.show();

          break;

        case DATA_READY:
          byte[] msg = message.getData().getByteArray("data");
          int msglength = message.getData().getInt("length");

          if (msglength > 0) {
            final String data = setDataBuffer(msg, msglength).toString();

            // this could be a lot more efficient -- rethink all the data type conversions
            final int datalength = data.trim().split(" ").length;

            // first check that we've got the right device and mode
            if (datalength > 2 && data.contains(DEVICE_ADDR_AND_MODE_HEADER)) {

              if (mapMode) {
                progress.setMessage(String.format("Reading map values %d/512...", mapOffset));

                if (data.contains(SIXTEEN_REGISTERS)
                    && datalength >= 37
                    && ModbusRTU.validCRC(data.trim().split(" "), 37)) {
                  populateAndPlotFuelMap(data);
                }

                break;
              }

              // do we have a bad message?
              if (!(data.contains(EIGHT_REGISTERS) || data.contains(SEVEN_REGISTERS))) {
                if (DEBUG_MODE) Log.d(TAG, lastRegister + " response discarded: " + data);
                imgStatus.setBackgroundColor(Color.RED);
                sendRequest();
                break;
              }

              // RPM, MAP, MAT, WAT, AUXT, & AFR - 6 16 bit integers (twelve bytes)
              if (data.contains(EIGHT_REGISTERS) && datalength >= REGISTER_4096_LENGTH) {
                process4096Response(data);
              } else {
                if (data.contains(SEVEN_REGISTERS) && datalength >= REGISTER_4140_LENGTH) {
                  process4140Response(data);
                }
              }
            }
          }
      }
    }