🧐 Round Display w/TouchRing BLE Xiao C6 & S3-Plus

TouchRing_BLE_S3 (Master)
In this demonstration,
I begin by powering on the TouchRing_BLE_S3 Peripheral—based on the Seeed Studio Xiao ESP32S3-Plus with a 1.28" round GC9A01 display and CST816S touch controller.
The startup screen briefly shows the MCU model and firmware revision, followed by the animated Touch Ring Meter interface with BLE advertising active. I launch nRF Connect for Desktop and scan for devices. The Peripheral appears as “TouchRingBLE” with a strong signal.
After connecting, we see the custom 128-bit BLE service with two characteristics: one for notifications and another for writable commands. Tapping on the Notify characteristic activates real-time updates from the device—each touch gesture changes the meter’s value and sends it over BLE, appearing instantly in the nRF Connect log.
When we write a new value (e.g., ‘36 36’) into the writable RX characteristic, the Peripheral responds immediately—updating the ring meter display and printing the new value to the Serial Monitor. The BLE indicator on the device turns blue when connected, and red when disconnected. This confirms full bidirectional communication before pairing it with a Remote TouchRing device.

TouchRing_Re_C6 (slave)
After confirming the Peripheral is working, we now power on the TouchRing_RE_C6, running on the Seeed Studio Xiao ESP32C6 with the same 1.28" GC9A01 round display and CST816S touch. At boot, the display shows the familiar startup info screen, followed by the full-color animated ring meter UI. If the “TouchRingBLE” Peripheral is active, the Central scans, connects automatically, and reads the initial meter value, syncing it instantly. A blue “BLE” indicator confirms the live connection. From this point on, both devices stay synchronized — when we adjust the value on the S3 Peripheral, the RE_C6 reflects the change in real-time via BLE Notify. Likewise, adjusting the value on the RE_C6 via up/down touch gestures sends a Write command to the S3, updating its display and Serial log.
Tapping the center of the RE_C6 toggles BLE On or Off — when Off, the red BLE indicator appears, and both devices operate independently in standalone mode.
This dual-mode behavior demonstrates the flexibility of the system: interactive sync when connected, and smooth local control when not.

What follows is Skinny display version…

Introducing the Skinny TouchRing — all the features, none of the carbs. It’s lean, mean, BLE-synced machine. While the Phat sister’s flexin’ her curves, Skinny’s out here precision-tapping like a runway model with a master’s in microcontrollers. Slim screen, sharp skills — choose your fighter. :sunglasses::sparkles::calling:

Don’t let the skinny frame fool you — she’s got the same smart moves under the hood all the brains with half the bezel. The Slim TouchRing delivers full BLE swagger in a sleeker package. Whether you’re team “Phat Curve” or “Lean Queen,” this ring’s got range. :sunglasses::ring:

Note: I’ve included in the attached ZIP file for each the pre-compiled BIN file and .INO file so you can compile your own, or Use the Flash tool and write the Bin file directly to test your hardware. (don’t forget the address) :+1:
Touch_ring_BLE_S3.zip (605.7 KB)

TouchRing_RE_C6_BLE.zip (665.4 KB)

Skinney_C6_TouchRing_Re_BLE.zip (664.3 KB)

HTH
AS you were…

GL :slight_smile: PJ :v:

Link to Original building block basic display w/TouchScreen action

Hi there,

Something for the C & P cowboys out there…:v:

This is the S3 Plus (server) peripheral end…

// =======================================================================================
// Project:     TouchRing_BLE_S3 – BLE Peripheral Touch Ring Meter
// Platform:    Seeed Studio XIAO ESP32S3-Plus + 1.28" GC9A01 Round Display + CST816S Touch
// BLE Role:    Peripheral / Server
// BLE Service: 128-bit UUID: 00002B01-0000-1000-8000-00805F9B34FB
// BLE TX Char: Notify   – UUID: 00002B01-0000-1000-8000-00805F9B34FC
// BLE RX Char: Writable – UUID: 00002B01-0000-1000-8000-00805F9B34FD
// Features:
//   - Smooth, colorful ring meter UI with touch up/down gesture control
//   - Center-tap toggles BLE advertising ON/OFF
//   - Sends value changes via Notify
//   - Accepts remote value writes via BLE Write
//   - BLE status shown as BLUE (connected) or RED (off)
//   - Start-up info screen with MCU and version
// Libraries:   TFT_eSPI, lv_xiao_round_screen, BLEDevice (ESP32 BLE)
// Notes:       Compatible with TouchRing_RE_C6 (ESP32C6 Central)
// Revision:    v1.4 // 2025-06-12 11:47 PM
// Authors  : Pj Glasso, & Embedded AI Assistant (ESP32 Expert Mode) , https://wiki.seeedstudio.com/
// Code generated by AI_Code_Gen (LLM-based embedded assistant)
// Finalized with real-time hardware validation
// =======================================================================================
#include <TFT_eSPI.h>
#include <SPI.h>
#define USE_TFT_ESPI_LIBRARY
#define CST816S_NO_TOUCH_PIN_CHECK
#include "lv_xiao_round_screen.h"
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// UUIDs for BLE Percentage8 Control
#define SERVICE_UUID            "00002B01-0000-1000-8000-00805F9B34FB"    // this is a more open Service than Volume Control " 2A07"
#define CHARACTERISTIC_UUID_TX  "00002B01-0000-1000-8000-00805F9B34FC"    // I wanted one that allowed Full duplex , P8 does that automatically 
#define CHARACTERISTIC_UUID_RX  "00002B01-0000-1000-8000-00805F9B34FD"    // with TX and RX in the same Char. 

// for just a simple BLE volume control you can use the Service for that too. This Tech is more for control and display(indicator) Hense
// under BLE details it reports as " Luminous Intensity " LOL go figure.

#define RADIUS 120
#define SEEED_GREEN 0x07E0        // this allows a color pallete of your choosing
#define SEEED_BLUE  0x001F
#define YELLOW      0xFFE0
#define ORANGE      0xFD20
#define RED         0xF800
#define GREY        0x2108
#define WHITE       0xFFFF
#define BLACK       0x0000
#define SKY_BLUE    0x867D  // Nice blue shade for BLE indicator

int currentValue = 50;
lv_coord_t touchX, touchY;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
int ringValue = 50;  // or whatever your default value is

// Forward declarations , are exactly as required when the callbacks are before the function call, what a thing LOL
int ringMeter(int value, int vmin, int vmax, int x, int y, int r);
void drawText(String text, int x, int y, int value);
void drawBLEIndicator(bool connected);

// -----------------------------------------------------------------------------------
class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    deviceConnected = true;
    Serial.println("BLE device connected");
    drawBLEIndicator(true);
  }

  void onDisconnect(BLEServer* pServer) {
    deviceConnected = false;
    Serial.println("BLE device disconnected");
    drawBLEIndicator(false);
    BLEDevice::startAdvertising();
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* pChar) {
    String rxVal = pChar->getValue().c_str();
    if (rxVal.length() > 0) {
      int newValue = atoi(rxVal.c_str());
      if (newValue >= 0 && newValue <= 99) {
        currentValue = newValue;
        ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
        drawText(String(currentValue), 120, 125, currentValue);
        Serial.printf("BLE> %d\n", currentValue);
      }
    }
  }
};

// -----------------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println();
Serial.println("=== Touch_Ring_BLE Demo ===");
Serial.println("Rev: v1.4");
Serial.println("MCU: ESP32-S3-Plus");
Serial.println("===========================");


  pinMode(TOUCH_INT, INPUT_PULLUP);
  Wire.begin(); // SDA=D4, SCL=D5

  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextColor(WHITE, BLACK);
  showMainScreen();
  delay(500);
  tft.fillScreen(TFT_BLACK);

  drawPlusMinus();
  ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
  drawText(String(currentValue), 120, 125, currentValue);
  drawBLEIndicator(false); // Start disconnected

  // BLE setup
  BLEDevice::init("TouchRingBLE");
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  BLEService *pService = pServer->createService(SERVICE_UUID);

  pTxCharacteristic = pService->createCharacteristic(
  CHARACTERISTIC_UUID_TX,
  BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_READ
);
  // Set initial value (important!)
pTxCharacteristic->setValue(String(ringValue).c_str());

  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID_RX,
    BLECharacteristic::PROPERTY_WRITE
  );

  pRxCharacteristic->setCallbacks(new MyCallbacks());
  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();
  Serial.println("BLE advertising started as TouchRingBLE...");
}

// -----------------------------------------------------------------------------------
void loop() {                     // Basically it's maintaining the display's Value, and Touch interrupts while monitoring the connection if any.
  static int lastValue = -1;
  if (chsc6x_is_pressed()) {
    chsc6x_get_xy(&touchX, &touchY);
    int dx = touchX - 120;
    int dy = touchY - 120;
    float dist = sqrt(dx * dx + dy * dy);

    if (dist > 20) {
      if (abs(dy) > abs(dx)) {
        if (dy < -10 && currentValue < 99) currentValue++;
        if (dy > 10 && currentValue > 0) currentValue--;
      }
    }

    if (currentValue != lastValue) {
      ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
      drawText(String(currentValue), 120, 125, currentValue);
      Serial.printf("%d\n", currentValue);

      if (deviceConnected) {
        char buf[4];
        snprintf(buf, sizeof(buf), "%d", currentValue);
        pTxCharacteristic->setValue((uint8_t*)buf, strlen(buf));
        pTxCharacteristic->notify();
      }

      lastValue = currentValue;
    }
  }

  delay(30);
}

// -----------------------------------------------------------------------------------
int ringMeter(int value, int vmin, int vmax, int x, int y, int r) {
  x += r;
  y += r;
  int w = r / 3;
  int angle = 150;
  int v = map(value, vmin, vmax, -angle, angle);
  byte seg = 3;
  byte inc = 6;

  for (int i = -angle + inc / 2; i < angle - inc / 2; i += inc) {
    float sx = cos((i - 90) * DEG_TO_RAD);
    float sy = sin((i - 90) * DEG_TO_RAD);
    uint16_t x0 = sx * (r - w) + x;
    uint16_t y0 = sy * (r - w) + y;
    uint16_t x1 = sx * r + x;
    uint16_t y1 = sy * r + y;

    float sx2 = cos((i + seg - 90) * DEG_TO_RAD);
    float sy2 = sin((i + seg - 90) * DEG_TO_RAD);
    int x2 = sx2 * (r - w) + x;
    int y2 = sy2 * (r - w) + y;
    int x3 = sx2 * r + x;
    int y3 = sy2 * r + y;

    uint16_t color = GREY;
    if (i < v) {
      int mapped = map(i, -angle, angle, 0, 99);
      if (mapped <= 24) color = SEEED_GREEN;
      else if (mapped <= 50) color = SEEED_BLUE;
      else if (mapped <= 75) color = YELLOW;
      else if (mapped <= 86) color = ORANGE;
      else color = RED;
    }

    tft.fillTriangle(x0, y0, x1, y1, x2, y2, color);
    tft.fillTriangle(x1, y1, x2, y2, x3, y3, color);
  }

  return x + r;
}

// -----------------------------------------------------------------------------------
void drawText(String text, int x, int y, int value) {
  uint16_t color = (value > 86) ? RED : WHITE;
  tft.setTextColor(color, BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(3);
  tft.fillRect(x - 30, y - 20, 60, 40, BLACK);  // Clear previous digits
  tft.drawString(text, x, y);
}

// -----------------------------------------------------------------------------------
void drawPlusMinus() {
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(3);
  tft.setTextColor(WHITE, BLACK);
  tft.drawString("+", 120, 30);   // Top center
  tft.drawString("-", 120, 210);  // Bottom center
}

// -----------------------------------------------------------------------------------
void showMainScreen() {
  String mcuType = "Unknown";

#if CONFIG_IDF_TARGET_ESP32C6
  mcuType = "ESP32-C6";
#elif CONFIG_IDF_TARGET_ESP32C3
  mcuType = "ESP32-C3";
#elif CONFIG_IDF_TARGET_ESP32S3
  mcuType = "ESP32-S3";
#elif CONFIG_IDF_TARGET_ESP32
  mcuType = "ESP32";
#endif

#ifdef ARDUINO_XIAO_ESP32S3
  mcuType = "ESP32-S3-Plus";
#endif

  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(SEEED_GREEN);
  tft.setTextSize(2);
  tft.setCursor(40, 40);
  tft.println("Seeed Studio");
  tft.setTextColor(WHITE);
  tft.setCursor(30, 90);
  tft.print("MCU: ");
  tft.println(mcuType);
  tft.setTextColor(SKY_BLUE);
  tft.setCursor(40, 135);
  tft.println("Touch_Ring_BLE");
  tft.setTextColor(WHITE);
  tft.setCursor(70, 170);
  tft.print("Rev: v1.4");
  delay(5000);
}

// -----------------------------------------------------------------------------------
// Creates a "BLE" indicator on the display middle dial tiny ;-p 
void drawBLEIndicator(bool connected) {
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);
  tft.setTextColor(connected ? SKY_BLUE : GREY, BLACK);
  tft.drawString("BLE", 120, 80);
}

HTH
GL :slight_smile: PJ :+1:

1 Like

Hi there,

Ok, so here is the OTHER end (Client) or Central…
The magic in all of this is the BLE…IMO. :stuck_out_tongue: :ok_hand:
most versatile tech you can use. The callbacks are what make it Go!
Services, Characteristics pretty much make it what it is.
Reading, Writing, and Notifying are the base for solid exchange of data.

HTH
GL :slight_smile: PJ :v:


// =======================================================================================
// Project:     TouchRing_RE_C6 – BLE Central Remote Touch Ring Meter
// Platform:    Seeed Studio XIAO ESP32S3-Plus + 1.28" GC9A01 Round Display + CST816S Touch
// BLE Role:    Central / Client
// BLE Service: 128-bit UUID: 00002B01-0000-1000-8000-00805F9B34FB
// BLE TX Char: Notify   – UUID: 00002B01-0000-1000-8000-00805F9B34FC
// BLE RX Char: Writable – UUID: 00002B01-0000-1000-8000-00805F9B34FD
// Features:
//   - Mirrors touch ring display and control from Peripheral
//   - Touch up/down to adjust value, sends to Peripheral
//   - Receives Notify updates from Peripheral and updates display
//   - BLE connection toggle via center tap
//   - BLE disconnect detection and red indicator when off
//   - Boot button (GPIO9) retries connection
//   - Matching visuals with color ring meter and numeric display
// Libraries:   TFT_eSPI, lv_xiao_round_screen, BLEDevice (ESP32 BLE)
// Notes:       Final version with merged visuals and BLE stability improvements
// Revision:    v1.4 // 2025-06-12 11:59 PM
// Authors  : Pj Glasso, & Embedded AI Assistant (ESP32 Expert Mode) , https://wiki.seeedstudio.com/
// Code generated by AI_Code_Gen (LLM-based embedded assistant)
// Finalized with real-time hardware validation
// =======================================================================================

#include <TFT_eSPI.h>
#include <SPI.h>
#define USE_TFT_ESPI_LIBRARY
#define CST816S_NO_TOUCH_PIN_CHECK
#include <lv_xiao_round_screen.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEClient.h>
#include <BLEAdvertisedDevice.h>

#define SERVICE_UUID            "00002B01-0000-1000-8000-00805F9B34FB"
#define CHARACTERISTIC_UUID_TX  "00002B01-0000-1000-8000-00805F9B34FC"
#define CHARACTERISTIC_UUID_RX  "00002B01-0000-1000-8000-00805F9B34FD"

#define RADIUS 120
#define SEEED_GREEN 0x07E0
#define SEEED_BLUE  0x001F
#define YELLOW      0xFFE0
#define ORANGE      0xFD20
#define RED         0xF800
#define GREY        0x2108
#define WHITE       0xFFFF
#define BLACK       0x0000
#define SKY_BLUE    0x867D

int currentValue = 28;
int lastValue = -1;
bool bleConnected = false;
bool bleEnabled = true;

BLEClient* pClient = nullptr;
BLERemoteCharacteristic* pRemoteChar = nullptr;
BLEAdvertisedDevice* myDevice = nullptr;

lv_coord_t touchX, touchY;
unsigned long lastCheck = 0;

#define RETRY_BUTTON 9

// ========= Visuals =========

void drawText(String text, int x, int y, int value) {
  tft.setTextDatum(MC_DATUM);
  tft.setTextColor((value > 86) ? RED : WHITE, BLACK);
  tft.setTextSize(3);
  tft.fillRect(x - 30, y - 20, 60, 40, BLACK);
  tft.drawString(text, x, y);
}

void drawPlusMinus() {
  tft.setTextDatum(MC_DATUM);
  tft.setTextColor(WHITE, BLACK);
  tft.setTextSize(3);
  tft.drawString("+", 120, 30);
  tft.drawString("-", 120, 210);
}

void drawBLEIndicator(bool connected) {
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);
  tft.setTextColor(connected ? SKY_BLUE : RED, BLACK);
  tft.drawString("BLE", 120, 80);
}

void showMainScreen() {
  tft.fillScreen(BLACK);
  tft.setTextColor(SEEED_GREEN);
  tft.setTextSize(2);
  tft.setCursor(40, 40);
  tft.println("Seeed Studio");
  tft.setTextColor(WHITE);
  tft.setCursor(30, 90);
  tft.println("MCU: ESP32-C6");
  tft.setTextColor(SKY_BLUE);
  tft.setCursor(40, 135);
  tft.println("Touch_Ring_RE");
  tft.setTextColor(WHITE);
  tft.setCursor(70, 170);
  tft.print("Rev: v1.4");
  delay(5000);
}

int ringMeter(int value, int vmin, int vmax, int x, int y, int r) {
  x += r;
  y += r;
  int w = r / 3;
  int angle = 150;
  int v = map(value, vmin, vmax, -angle, angle);
  byte seg = 3;
  byte inc = 6;

  for (int i = -angle + inc / 2; i < angle - inc / 2; i += inc) {
    float sx = cos((i - 90) * DEG_TO_RAD);
    float sy = sin((i - 90) * DEG_TO_RAD);
    uint16_t x0 = sx * (r - w) + x;
    uint16_t y0 = sy * (r - w) + y;
    uint16_t x1 = sx * r + x;
    uint16_t y1 = sy * r + y;

    float sx2 = cos((i + seg - 90) * DEG_TO_RAD);
    float sy2 = sin((i + seg - 90) * DEG_TO_RAD);
    int x2 = sx2 * (r - w) + x;
    int y2 = sy2 * (r - w) + y;
    int x3 = sx2 * r + x;
    int y3 = sy2 * r + y;

    uint16_t color = GREY;
    if (i < v) {
      int mapped = map(i, -angle, angle, 0, 99);
      if (mapped <= 24) color = SEEED_GREEN;
      else if (mapped <= 50) color = SEEED_BLUE;
      else if (mapped <= 75) color = YELLOW;
      else if (mapped <= 86) color = ORANGE;
      else color = RED;
    }

    tft.fillTriangle(x0, y0, x1, y1, x2, y2, color);
    tft.fillTriangle(x1, y1, x2, y2, x3, y3, color);
  }

  return x + r;
}

// ========= BLE Callbacks =========

static void notifyCallback(BLERemoteCharacteristic* pRemoteChar, uint8_t* pData, size_t length, bool isNotify) {
  int newVal = String((char*)pData).toInt();
  if (newVal >= 0 && newVal <= 99 && newVal != currentValue) {
    currentValue = newVal;
    ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
    drawText(String(currentValue), 120, 125, currentValue);
    Serial.printf("%d\n", currentValue);
  }
}

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if (advertisedDevice.getName() == "TouchRingBLE") {
      Serial.println("Found TouchRingBLE!");
      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
    }
  }
};
bool connectToPeripheral() {
  pClient = BLEDevice::createClient();
  if (!pClient->connect(myDevice)) return false;
  auto svc = pClient->getService(SERVICE_UUID);
  if (!svc) return false;

  pRemoteChar = svc->getCharacteristic(CHARACTERISTIC_UUID_RX);
  auto pNotifyChar = svc->getCharacteristic(CHARACTERISTIC_UUID_TX);
  if (pNotifyChar && pNotifyChar->canNotify()) {
    pNotifyChar->registerForNotify(notifyCallback);
    if (pNotifyChar->canRead()) {
      int initial = String(pNotifyChar->readValue().c_str()).toInt();
      if (initial >= 0 && initial <= 99) {
        currentValue = initial;
        ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
        drawText(String(currentValue), 120, 125, currentValue);
      }
    }
  }

  bleConnected = true;
  return true;
}

void disconnectBLE() {
  if (pClient && pClient->isConnected()) pClient->disconnect();
  bleConnected = false;
  drawBLEIndicator(false);
}

// ========= Setup & Loop =========

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(RETRY_BUTTON, INPUT_PULLUP);
  pinMode(TOUCH_INT, INPUT_PULLUP);

  tft.begin();
  tft.setRotation(0);
  showMainScreen();
  tft.fillScreen(BLACK);
  drawPlusMinus();
  ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
  drawText(String(currentValue), 120, 125, currentValue);
  drawBLEIndicator(false);

  BLEDevice::init("");
  BLEScan* scan = BLEDevice::getScan();
  scan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  scan->setActiveScan(true);
  scan->start(5, false);

  delay(100);
  if (myDevice && connectToPeripheral()) drawBLEIndicator(true);
}

void loop() {
  if (chsc6x_is_pressed()) {
    chsc6x_get_xy(&touchX, &touchY);
    int dx = touchX - 120;
    int dy = touchY - 120;
    float dist = sqrt(dx * dx + dy * dy);

    if (dist < 20) {
      bleEnabled = !bleEnabled;
      Serial.printf("BLE toggled %s\n", bleEnabled ? "ON" : "OFF");

      if (!bleEnabled) {
        disconnectBLE();
      } else {
        BLEScan* scan = BLEDevice::getScan();
        scan->clearResults();
        scan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
        scan->setActiveScan(true);
        scan->start(5, false);
        delay(100);
        if (myDevice && connectToPeripheral()) drawBLEIndicator(true);
      }
      delay(300);
      return;
    }

    if (abs(dy) > abs(dx)) {
      if (dy < -10 && currentValue < 99) currentValue++;
      if (dy > 10 && currentValue > 0) currentValue--;
    }

    if (currentValue != lastValue) {
      ringMeter(currentValue, 0, 99, 0, 0, RADIUS);
      drawText(String(currentValue), 120, 125, currentValue);
      lastValue = currentValue;

      if (bleConnected && pRemoteChar) {
        char buf[4];
        snprintf(buf, sizeof(buf), "%d", currentValue);
        pRemoteChar->writeValue((uint8_t*)buf, strlen(buf), true);
      }
    }
  }

  if (bleConnected && millis() - lastCheck > 1000) {
    lastCheck = millis();
    if (!pClient->isConnected()) {
      Serial.println("BLE disconnected!");
      disconnectBLE();
    }
  }
}