Hi there,
Ok, so here is the OTHER end (Client) or Central…
The magic in all of this is the BLE…IMO.

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
PJ 
// =======================================================================================
// 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();
}
}
}