@Override
  public synchronized void onDataAvailable(BluetoothGattCharacteristic characteristic) {
    // UART RX
    if (characteristic.getService().getUuid().toString().equalsIgnoreCase(UUID_SERVICE)) {
      if (characteristic.getUuid().toString().equalsIgnoreCase(UUID_RX)) {
        final byte[] bytes = characteristic.getValue();
        final String data = new String(bytes, Charset.forName("UTF-8"));

        mReceivedBytes += bytes.length;

        final UartDataChunk dataChunk =
            new UartDataChunk(System.currentTimeMillis(), UartDataChunk.TRANSFERMODE_RX, data);
        mDataBuffer.add(dataChunk);
        final String formattedData = mShowDataInHexFormat ? asciiToHex(data) : data;

        runOnUiThread(
            new Runnable() {
              @Override
              public void run() {
                if (mIsTimestampDisplayMode) {
                  final String currentDateTimeString =
                      DateFormat.getTimeInstance().format(new Date(dataChunk.getTimestamp()));

                  mBufferListAdapter.add(
                      new TimestampData(
                          "[" + currentDateTimeString + "] TX: " + formattedData, mRxColor));
                  // mBufferListAdapter.add("[" + currentDateTimeString + "] RX: " + formattedData);
                  // mBufferListView.smoothScrollToPosition(mBufferListAdapter.getCount() - 1);
                  mBufferListView.setSelection(mBufferListAdapter.getCount());
                }
                updateUI();
              }
            });

        // MQTT publish to RX
        MqttSettings settings = MqttSettings.getInstance(UartActivity.this);
        if (settings.isPublishEnabled()) {
          String topic = settings.getPublishTopic(MqttUartSettingsActivity.kPublishFeed_RX);
          final int qos = settings.getPublishQos(MqttUartSettingsActivity.kPublishFeed_RX);
          mMqttManager.publish(topic, data, qos);
        }
      }
    }
  }
  private void uartSendData(String data, boolean wasReceivedFromMqtt) {
    // MQTT publish to TX
    MqttSettings settings = MqttSettings.getInstance(UartActivity.this);
    if (!wasReceivedFromMqtt) {
      if (settings.isPublishEnabled()) {
        String topic = settings.getPublishTopic(MqttUartSettingsActivity.kPublishFeed_TX);
        final int qos = settings.getPublishQos(MqttUartSettingsActivity.kPublishFeed_TX);
        mMqttManager.publish(topic, data, qos);
      }
    }

    // Add eol
    if (mIsEolEnabled) {
      // Add newline character if checked
      data += "\n";
    }

    // Send to uart
    if (!wasReceivedFromMqtt
        || settings.getSubscribeBehaviour() == MqttSettings.kSubscribeBehaviour_Transmit) {
      sendData(data);
      mSentBytes += data.length();
    }

    // Add to current buffer
    UartDataChunk dataChunk =
        new UartDataChunk(System.currentTimeMillis(), UartDataChunk.TRANSFERMODE_TX, data);
    mDataBuffer.add(dataChunk);

    final String formattedData = mShowDataInHexFormat ? asciiToHex(data) : data;
    if (mIsTimestampDisplayMode) {
      final String currentDateTimeString =
          DateFormat.getTimeInstance().format(new Date(dataChunk.getTimestamp()));
      mBufferListAdapter.add(
          new TimestampData("[" + currentDateTimeString + "] TX: " + formattedData, mTxColor));
      mBufferListView.setSelection(mBufferListAdapter.getCount());
    }

    // Update UI
    updateUI();
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_uart);

    mBleManager = BleManager.getInstance(this);
    restoreRetainedDataFragment();

    // Choose UI controls component based on available width
    /*
    if (!kShowUartControlsInTopBar)
    {
        LinearLayout headerLayout = (LinearLayout) findViewById(R.id.headerLayout);
        ViewGroup controlsLayout = (ViewGroup) getLayoutInflater().inflate(R.layout.layout_uart_singleline_controls, headerLayout, false);
        controlsLayout.measure(0, 0);
        int controlWidth = controlsLayout.getMeasuredWidth();

        Display display = getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);
        int rootWidth = size.x;

        if (controlWidth > rootWidth)       // control too big, use a smaller version
        {
            controlsLayout = (ViewGroup) getLayoutInflater().inflate(R.layout.layout_uart_multiline_controls, headerLayout, false);
        }
        //Log.d(TAG, "width: " + controlWidth + " baseWidth: " + rootWidth);

        headerLayout.addView(controlsLayout);
    }
    */

    // Get default theme colors
    TypedValue typedValue = new TypedValue();
    Resources.Theme theme = getTheme();
    theme.resolveAttribute(R.attr.colorPrimaryDark, typedValue, true);
    mTxColor = typedValue.data;
    theme.resolveAttribute(R.attr.colorControlActivated, typedValue, true);
    mRxColor = typedValue.data;

    // UI
    mBufferListView = (ListView) findViewById(R.id.bufferListView);
    mBufferListAdapter = new TimestampListAdapter(this, R.layout.layout_uart_datachunkitem);
    mBufferListView.setAdapter(mBufferListAdapter);
    mBufferListView.setDivider(null);

    mBufferTextView = (EditText) findViewById(R.id.bufferTextView);
    mBufferTextView.setKeyListener(null); // make it not editable

    mSendEditText = (EditText) findViewById(R.id.sendEditText);
    mSendEditText.setOnEditorActionListener(
        new TextView.OnEditorActionListener() {
          @Override
          public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
            if (actionId == EditorInfo.IME_ACTION_SEND) {
              onClickSend(null);
              return true;
            }

            return false;
          }
        });
    mSendEditText.setOnFocusChangeListener(
        new View.OnFocusChangeListener() {
          public void onFocusChange(View view, boolean hasFocus) {
            if (!hasFocus) {
              // Dismiss keyboard when sendEditText loses focus
              dismissKeyboard(view);
            }
          }
        });

    mSentBytesTextView = (TextView) findViewById(R.id.sentBytesTextView);
    mReceivedBytesTextView = (TextView) findViewById(R.id.receivedBytesTextView);

    // Read shared preferences
    maxPacketsToPaintAsText = PreferencesFragment.getUartTextMaxPackets(this);
    // Log.d(TAG, "maxPacketsToPaintAsText: "+maxPacketsToPaintAsText);

    // Read local preferences
    SharedPreferences preferences = getSharedPreferences(kPreferences, MODE_PRIVATE);
    mShowDataInHexFormat = !preferences.getBoolean(kPreferences_asciiMode, true);
    final boolean isTimestampDisplayMode =
        preferences.getBoolean(kPreferences_timestampDisplayMode, false);
    setDisplayFormatToTimestamp(isTimestampDisplayMode);
    mIsEchoEnabled = preferences.getBoolean(kPreferences_echo, true);
    mIsEolEnabled = preferences.getBoolean(kPreferences_eol, true);
    invalidateOptionsMenu(); // udpate options menu with current values

    /*
    if (!kShowUartControlsInTopBar) {

        Switch mEchoSwitch;
        Switch mEolSwitch;
        mEchoSwitch = (Switch) findViewById(R.id.echoSwitch);
        mEchoSwitch.setChecked(mIsEchoEnabled);
        mEolSwitch = (Switch) findViewById(R.id.eolSwitch);
        mEolSwitch.setChecked(mIsEolEnabled);

        RadioButton asciiFormatRadioButton = (RadioButton) findViewById(R.id.asciiFormatRadioButton);
        asciiFormatRadioButton.setChecked(!mShowDataInHexFormat);
        asciiFormatRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mShowDataInHexFormat = !isChecked;
            }
        });
        RadioButton hexFormatRadioButton = (RadioButton) findViewById(R.id.hexFormatRadioButton);
        hexFormatRadioButton.setChecked(mShowDataInHexFormat);
        hexFormatRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mShowDataInHexFormat = isChecked;
            }
        });

        RadioButton textDisplayFormatRadioButton = (RadioButton) findViewById(R.id.textDisplayModeRadioButton);
        textDisplayFormatRadioButton.setChecked(!mIsTimestampDisplayMode);
        textDisplayFormatRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mIsTimestampDisplayMode = !isChecked;
            }
        });


        RadioButton timestampDisplayFormatRadioButton = (RadioButton) findViewById(R.id.timestampDisplayModeRadioButton);
        timestampDisplayFormatRadioButton.setChecked(mIsTimestampDisplayMode);
        timestampDisplayFormatRadioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mIsTimestampDisplayMode = isChecked;
            }
        });

        setDisplayFormatToTimestamp(mIsTimestampDisplayMode);

    }
    */

    // Continue
    onServicesDiscovered();

    // Mqtt init
    mMqttManager = MqttManager.getInstance(this);
    if (MqttSettings.getInstance(this).isConnected()) {
      mMqttManager.connectFromSavedSettings(this);
    }
  }