Hi there,
Something for the C & P cowboys out thereā¦
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 PJ