Add Bluepad32 PS4 transmitter sketch
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* transmitter.ino -- PS4 gamepad -> UART bridge (ESP32 "A")
|
||||
*
|
||||
* Board package: esp32_bluepad32 (NOT the plain esp32 package)
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
* lx/ly/rx/ry : -512..511 (Bluepad32 raw axis range)
|
||||
* buttons : 16-bit mask, decimal
|
||||
* XX : XOR checksum of everything before the '*', two hex digits
|
||||
*
|
||||
* Created by: Jess Rogerson (yelling commands at Claude.AI)
|
||||
*/
|
||||
|
||||
#include <Bluepad32.h>
|
||||
|
||||
// ---------------------------------------------------------------- settings
|
||||
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 HEARTBEAT_MS = 200; // resend even if idle
|
||||
|
||||
// ------------------------------------------------------------------ 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 uint16_t lastButtons = 0;
|
||||
|
||||
// -------------------------------------------------------------- callbacks
|
||||
void onConnectedController(ControllerPtr ctl) {
|
||||
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
|
||||
if (myControllers[i] == nullptr) {
|
||||
myControllers[i] = ctl;
|
||||
Serial.printf("Controller connected in slot %d\n", i);
|
||||
digitalWrite(STATUS_LED_PIN, HIGH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Serial.println("Controller connected but no free slot");
|
||||
}
|
||||
|
||||
void onDisconnectedController(ControllerPtr ctl) {
|
||||
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
|
||||
if (myControllers[i] == ctl) {
|
||||
myControllers[i] = nullptr;
|
||||
Serial.printf("Controller disconnected from slot %d\n", i);
|
||||
digitalWrite(STATUS_LED_PIN, LOW);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------- helpers
|
||||
static uint8_t xorChecksum(const char *s) {
|
||||
uint8_t c = 0;
|
||||
while (*s) c ^= (uint8_t)*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);
|
||||
Serial2.printf("%s*%02X\n", payload, xorChecksum(payload));
|
||||
}
|
||||
|
||||
// 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
|
||||
if (buttons != lastButtons) 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;
|
||||
return (millis() - lastHeartbeat) >= HEARTBEAT_MS;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- main
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial2.begin(LINK_BAUD, SERIAL_8N1, LINK_RX_PIN, LINK_TX_PIN);
|
||||
|
||||
pinMode(STATUS_LED_PIN, OUTPUT);
|
||||
digitalWrite(STATUS_LED_PIN, LOW);
|
||||
|
||||
Serial.printf("Bluepad32 firmware: %s\n", BP32.firmwareVersion());
|
||||
|
||||
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.
|
||||
BP32.forgetBluetoothKeys();
|
||||
|
||||
// We only care about real gamepads, not the mouse/keyboard virtual device.
|
||||
BP32.enableVirtualDevice(false);
|
||||
|
||||
Serial.println("Ready - put the PS4 pad in pairing mode (SHARE + PS)");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
bool dataUpdated = BP32.update();
|
||||
|
||||
ControllerPtr gp = nullptr;
|
||||
for (int i = 0; i < BP32_MAX_CONTROLLERS; i++) {
|
||||
if (myControllers[i] && myControllers[i]->isConnected() &&
|
||||
myControllers[i]->isGamepad()) {
|
||||
gp = myControllers[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
if (now - lastSend < SEND_INTERVAL_MS) return;
|
||||
lastSend = now;
|
||||
|
||||
if (gp == nullptr) {
|
||||
// Nothing connected: keep sending neutral so the receiver's failsafe
|
||||
// never has to guess.
|
||||
sendFrame(0, 0, 0, 0, 0);
|
||||
lastHeartbeat = now;
|
||||
lastLx = lastLy = lastRx = lastRy = 0;
|
||||
lastButtons = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
int lx = gp->axisX();
|
||||
int ly = gp->axisY();
|
||||
int rx = gp->axisRX();
|
||||
int ry = gp->axisRY();
|
||||
uint16_t buttons = gp->buttons();
|
||||
|
||||
if (dataUpdated || worthSending(lx, ly, rx, ry, buttons)) {
|
||||
sendFrame(lx, ly, rx, ry, buttons);
|
||||
lastLx = lx; lastLy = ly; lastRx = rx; lastRy = ry;
|
||||
lastButtons = buttons;
|
||||
lastHeartbeat = now;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user