KVMote/S3/KVMote_ESP32S3/KVMote_ESP32S3.ino

264 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
KVMote — ESP32-S3 N16R8
BLE NUS (Nordic UART Service) → USB HID nativo
Substitui: Arduino Leonardo + HC-06 + LED RGB externo
LED: WS2812B embutido na placa (GPIO 48 na maioria das DevKit-C1).
Se a sua placa usar outro pino, altere LED_PIN abaixo.
Protocolo binário: idêntico ao KVMote.ino (Leonardo).
Mouse move → 'M' dx(int8) dy(int8) 3 bytes
Mouse wheel → 'W' delta(int8) 2 bytes
Tecla write → 'K' char 2 bytes
Clique → 'C' 'L'|'R' 2 bytes
Tecla press → 'P' keycode 2 bytes
Tecla release → 'U' keycode 2 bytes
ReleaseAll → 'A' 1 byte
Mouse press → 'D' 'L'|'R' 2 bytes
Mouse release → 'E' 'L'|'R' 2 bytes
LED cliente → 'O' (magenta)
LED host ok → 'H' (azul)
LED sem host → 'G' (verde)
Ping/Pong → '~' → responde [PONG] 1 byte
Dependências (instale pela Library Manager do Arduino IDE):
- Adafruit NeoPixel (by Adafruit)
Já incluídas no core ESP32:
- USB / USBHIDKeyboard / USBHIDMouse
- BLEDevice / BLEServer / BLE2902
Board: "ESP32S3 Dev Module"
USB Mode → Hardware CDC and JTAG ← mantém JTAG para upload via porta COM
USB CDC On Boot → Disabled ← CRÍTICO: libera USB nativo para HID
Upload Mode → Internal USB (ou USB-OTG CDC)
PSRAM → OPI PSRAM (para N16R8)
Flash Size → 16MB
Conexões:
Porta USB (nativa OTG) → PC cliente (aparece como teclado+mouse HID)
Porta COM (CH343) → PC de desenvolvimento (upload de firmware)
BLE → Host PC (sem fio, KVMote.exe)
*/
#include "USB.h"
#include "USBHIDKeyboard.h"
#include "USBHIDMouse.h"
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Adafruit_NeoPixel.h>
// ── NUS UUIDs ─────────────────────────────────────────────────────────────────
#define NUS_SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define NUS_RX_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" // PC escreve aqui
#define NUS_TX_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" // ESP32 notifica (PONG)
// ── LED WS2812B embutido ──────────────────────────────────────────────────────
#define LED_PIN 48 // GPIO 48 — ESP32-S3-DevKitC-1; altere se necessário
#define LED_COUNT 1
#define LED_BRIGHTNESS 80 // 0255 (80 ≈ 30%, evita ofuscar)
// ── Objetos USB HID ───────────────────────────────────────────────────────────
USBHIDKeyboard Keyboard;
USBHIDMouse Mouse;
// ── NeoPixel ──────────────────────────────────────────────────────────────────
Adafruit_NeoPixel pixel(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
void ledCor(uint8_t r, uint8_t g, uint8_t b) {
pixel.setPixelColor(0, pixel.Color(r, g, b));
pixel.show();
}
// ── BLE ───────────────────────────────────────────────────────────────────────
BLEServer* pServer = nullptr;
BLECharacteristic* pTxChar = nullptr;
bool bleConn = false;
// ── Fila BLE → HID (desacopla callback BLE do USB TinyUSB) ───────────────────
// O callback BLE roda numa task FreeRTOS separada. Chamar Mouse.move() /
// Keyboard.press() de lá bloqueia a task BLE esperando o USB. A fila resolve:
// callback só enfileira bytes, loop() drena e chama o HID.
static QueueHandle_t rxQueue;
// ── Máquina de estados (idêntica ao Leonardo) ─────────────────────────────────
enum Estado : uint8_t {
AGUARDA_CMD,
AGUARDA_MOUSE_DX,
AGUARDA_MOUSE_DY,
AGUARDA_MOUSE_WHEEL,
AGUARDA_TECLA,
AGUARDA_CLIQUE,
AGUARDA_PRESS_KEY,
AGUARDA_RELEASE_KEY,
AGUARDA_MOUSE_PRESS,
AGUARDA_MOUSE_RELEASE
};
Estado estado = AGUARDA_CMD;
int8_t pendingDX = 0;
// ── Processa um byte do protocolo ─────────────────────────────────────────────
// Chamada diretamente do callback BLE (task separada do FreeRTOS).
// As funções HID do ESP32 são thread-safe.
void processaByte(uint8_t b) {
switch (estado) {
case AGUARDA_CMD:
if (b == 'M') estado = AGUARDA_MOUSE_DX;
else if (b == 'W') estado = AGUARDA_MOUSE_WHEEL;
else if (b == 'K') estado = AGUARDA_TECLA;
else if (b == 'C') estado = AGUARDA_CLIQUE;
else if (b == 'P') estado = AGUARDA_PRESS_KEY;
else if (b == 'U') estado = AGUARDA_RELEASE_KEY;
else if (b == 'D') estado = AGUARDA_MOUSE_PRESS;
else if (b == 'E') estado = AGUARDA_MOUSE_RELEASE;
else if (b == 'A') { Keyboard.releaseAll(); }
else if (b == 'O') { ledCor(255, 0, 255); } // magenta — mouse no cliente
else if (b == 'H') { ledCor( 0, 0, 255); } // azul — host conectado
else if (b == 'G') { ledCor( 0, 255, 0); } // verde — host desconectado
else if (b == '~') {
if (pTxChar && bleConn) {
pTxChar->setValue((uint8_t*)"[PONG]", 6);
pTxChar->notify();
}
}
break;
case AGUARDA_MOUSE_DX:
pendingDX = (int8_t)b;
estado = AGUARDA_MOUSE_DY;
break;
case AGUARDA_MOUSE_DY:
Mouse.move(pendingDX, (int8_t)b, 0);
estado = AGUARDA_CMD;
break;
case AGUARDA_MOUSE_WHEEL:
Mouse.move(0, 0, (int8_t)b);
estado = AGUARDA_CMD;
break;
case AGUARDA_TECLA:
Keyboard.write(b); // keycodes >= 0x80 seguem a mesma convenção do Arduino HID
estado = AGUARDA_CMD;
break;
case AGUARDA_CLIQUE:
if (b == 'L') Mouse.click(MOUSE_LEFT);
if (b == 'R') Mouse.click(MOUSE_RIGHT);
estado = AGUARDA_CMD;
break;
case AGUARDA_PRESS_KEY:
Keyboard.press(b);
estado = AGUARDA_CMD;
break;
case AGUARDA_RELEASE_KEY:
Keyboard.release(b);
estado = AGUARDA_CMD;
break;
case AGUARDA_MOUSE_PRESS:
if (b == 'L') Mouse.press(MOUSE_LEFT);
if (b == 'R') Mouse.press(MOUSE_RIGHT);
estado = AGUARDA_CMD;
break;
case AGUARDA_MOUSE_RELEASE:
if (b == 'L') Mouse.release(MOUSE_LEFT);
if (b == 'R') Mouse.release(MOUSE_RIGHT);
estado = AGUARDA_CMD;
break;
}
}
// ── Callback: chegada de dados pelo BLE (PC → ESP32) ─────────────────────────
// Apenas enfileira — não chama HID aqui para não bloquear a task BLE.
class RxCallback : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pChar) override {
String val = pChar->getValue();
for (int i = 0; i < val.length(); i++) {
uint8_t b = (uint8_t)val[i];
xQueueSend(rxQueue, &b, 0); // não bloqueia se a fila estiver cheia
}
}
};
// ── Callbacks de conexão / desconexão BLE ─────────────────────────────────────
class ServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer*) override {
bleConn = true;
// LED permanece verde até o host enviar 'H'
}
void onDisconnect(BLEServer*) override {
bleConn = false;
estado = AGUARDA_CMD;
Keyboard.releaseAll();
ledCor(0, 255, 0); // verde — aguardando host
BLEDevice::startAdvertising(); // permite reconexão imediata
}
};
// ── Setup ─────────────────────────────────────────────────────────────────────
void setup() {
// LED
pixel.begin();
pixel.setBrightness(LED_BRIGHTNESS);
ledCor(0, 255, 0); // verde — aguardando conexão
// USB HID (TinyUSB via USB OTG)
USB.productName("KVMote");
USB.manufacturerName("KVMote");
USB.begin();
Keyboard.begin();
Mouse.begin();
// BLE — NUS
BLEDevice::init("KVMote");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
BLEService* pService = pServer->createService(NUS_SERVICE_UUID);
// RX: aceita Write e Write Without Response — sem criptografia (evita AccessDenied no Windows)
BLECharacteristic* pRxChar = pService->createCharacteristic(
NUS_RX_UUID,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR
);
pRxChar->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
pRxChar->setCallbacks(new RxCallback());
// TX: apenas Notify (para [PONG]) — sem criptografia
pTxChar = pService->createCharacteristic(
NUS_TX_UUID,
BLECharacteristic::PROPERTY_NOTIFY
);
pTxChar->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
BLE2902* cccd = new BLE2902();
cccd->setAccessPermissions(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE);
pTxChar->addDescriptor(cccd);
pService->start();
rxQueue = xQueueCreate(256, sizeof(uint8_t));
BLEAdvertising* pAdv = BLEDevice::getAdvertising();
pAdv->addServiceUUID(NUS_SERVICE_UUID);
pAdv->setScanResponse(true);
pAdv->setMinPreferred(0x06); // melhora compatibilidade com iOS/Windows
BLEDevice::startAdvertising();
}
// ── Loop ──────────────────────────────────────────────────────────────────────
void loop() {
// Drena a fila e processa no contexto do loop (seguro para TinyUSB HID)
uint8_t b;
while (xQueueReceive(rxQueue, &b, 0) == pdTRUE)
processaByte(b);
delay(1);
}