Send d-pad and analog triggers in the link frame

Johnny 5 needs seven axes of control, so the frame now carries dpad and
both analog triggers alongside the sticks and button mask.
This commit is contained in:
2026-09-09 19:28:17 +10:00
parent 24f4dcc08b
commit 6622dbd05c
+45 -18
View File
@@ -5,18 +5,23 @@
* Board: "ESP32 Dev Module" listed under esp32_bluepad32
* Libraries: none (Bluepad32 ships inside the board package)
*
* Reads a Bluetooth Classic gamepad via Bluepad32 and streams the stick /
* button state out of Serial2 as plain ASCII lines. The second ESP32 (see
* ../receiver) picks those up and drives the LEGO hubs over BLE.
* Reads a Bluetooth Classic gamepad via Bluepad32 and streams the full stick,
* trigger, d-pad and button state out of Serial2 as plain ASCII lines. The
* second ESP32 (see ../receiver) picks those up and drives the LEGO hubs.
*
* This board does NOT talk to the hubs and must not have Legoino or NimBLE
* installed. Keeping BTstack and NimBLE on separate chips is the entire point.
*
* Wiring to the receiver board:
* TX GPIO17 -> RX GPIO16 on receiver
* RX GPIO16 <- TX GPIO17 on receiver (unused today, reserved for acks)
* GND -> GND (mandatory - common ground)
*
* Line format: G,<lx>,<ly>,<rx>,<ry>,<buttons>*<XX>\n
* Line format: G,<lx>,<ly>,<rx>,<ry>,<buttons>,<dpad>,<l2>,<r2>*<XX>\n
* lx/ly/rx/ry : -512..511 (Bluepad32 raw axis range)
* buttons : 16-bit mask, decimal
* dpad : 8-bit mask, decimal
* l2/r2 : analog triggers, 0..1023
* XX : XOR checksum of everything before the '*', two hex digits
*
* Created by: Jess Rogerson (yelling commands at Claude.AI)
@@ -29,16 +34,21 @@ static const int LINK_TX_PIN = 17;
static const int LINK_RX_PIN = 16;
static const long LINK_BAUD = 115200;
static const int STATUS_LED_PIN = 2;
static const unsigned long SEND_INTERVAL_MS = 20; // 50 Hz
static const unsigned long SEND_INTERVAL_MS = 20; // 50 Hz ceiling
static const unsigned long HEARTBEAT_MS = 200; // resend even if idle
// Set to 1 to echo every outgoing frame to the USB serial monitor.
#define DEBUG_FRAMES 0
// ------------------------------------------------------------------ state
ControllerPtr myControllers[BP32_MAX_CONTROLLERS];
static unsigned long lastSend = 0;
static unsigned long lastHeartbeat = 0;
static int lastLx = 0, lastLy = 0, lastRx = 0, lastRy = 0;
static int lastL2 = 0, lastR2 = 0;
static uint16_t lastButtons = 0;
static uint8_t lastDpad = 0;
// -------------------------------------------------------------- callbacks
void onConnectedController(ControllerPtr ctl) {
@@ -71,20 +81,30 @@ static uint8_t xorChecksum(const char *s) {
return c;
}
static void sendFrame(int lx, int ly, int rx, int ry, uint16_t buttons) {
char payload[64];
snprintf(payload, sizeof(payload), "G,%d,%d,%d,%d,%u", lx, ly, rx, ry, buttons);
static void sendFrame(int lx, int ly, int rx, int ry,
uint16_t buttons, uint8_t dpad, int l2, int r2) {
char payload[80];
snprintf(payload, sizeof(payload), "G,%d,%d,%d,%d,%u,%u,%d,%d",
lx, ly, rx, ry, buttons, dpad, l2, r2);
Serial2.printf("%s*%02X\n", payload, xorChecksum(payload));
#if DEBUG_FRAMES
Serial.printf("%s*%02X\n", payload, xorChecksum(payload));
#endif
}
// Only bother transmitting when something actually moved, or on heartbeat.
static bool worthSending(int lx, int ly, int rx, int ry, uint16_t buttons) {
const int DELTA = 4; // ignore stick jitter of a few counts
// Only transmit when something actually moved, or on heartbeat.
static bool worthSending(int lx, int ly, int rx, int ry,
uint16_t buttons, uint8_t dpad, int l2, int r2) {
const int DELTA = 4; // ignore stick jitter of a few counts
const int TRIG = 8; // triggers are noisier at rest
if (buttons != lastButtons) return true;
if (dpad != lastDpad) return true;
if (abs(lx - lastLx) >= DELTA) return true;
if (abs(ly - lastLy) >= DELTA) return true;
if (abs(rx - lastRx) >= DELTA) return true;
if (abs(ry - lastRy) >= DELTA) return true;
if (abs(l2 - lastL2) >= TRIG) return true;
if (abs(r2 - lastR2) >= TRIG) return true;
return (millis() - lastHeartbeat) >= HEARTBEAT_MS;
}
@@ -100,12 +120,12 @@ void setup() {
BP32.setup(&onConnectedController, &onDisconnectedController);
// Clear stored pairings so a controller that was last paired to a phone
// will bind here. Comment this out once you are happy with the pairing,
// otherwise you re-pair on every boot.
// Bluepad32 keeps pairing keys in NVS and reflashing does NOT clear them.
// Leave this in for the first pairing, then comment it out - otherwise you
// re-pair the pad on every boot.
BP32.forgetBluetoothKeys();
// We only care about real gamepads, not the mouse/keyboard virtual device.
// We only want real gamepads, not the mouse/keyboard virtual device.
BP32.enableVirtualDevice(false);
Serial.println("Ready - put the PS4 pad in pairing mode (SHARE + PS)");
@@ -130,10 +150,12 @@ void loop() {
if (gp == nullptr) {
// Nothing connected: keep sending neutral so the receiver's failsafe
// never has to guess.
sendFrame(0, 0, 0, 0, 0);
sendFrame(0, 0, 0, 0, 0, 0, 0, 0);
lastHeartbeat = now;
lastLx = lastLy = lastRx = lastRy = 0;
lastL2 = lastR2 = 0;
lastButtons = 0;
lastDpad = 0;
return;
}
@@ -141,12 +163,17 @@ void loop() {
int ly = gp->axisY();
int rx = gp->axisRX();
int ry = gp->axisRY();
int l2 = gp->brake();
int r2 = gp->throttle();
uint16_t buttons = gp->buttons();
uint8_t dpad = gp->dpad();
if (dataUpdated || worthSending(lx, ly, rx, ry, buttons)) {
sendFrame(lx, ly, rx, ry, buttons);
if (dataUpdated || worthSending(lx, ly, rx, ry, buttons, dpad, l2, r2)) {
sendFrame(lx, ly, rx, ry, buttons, dpad, l2, r2);
lastLx = lx; lastLy = ly; lastRx = rx; lastRy = ry;
lastL2 = l2; lastR2 = r2;
lastButtons = buttons;
lastDpad = dpad;
lastHeartbeat = now;
}
}