-
Notifications
You must be signed in to change notification settings - Fork 16
Support pairing #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support pairing #10
Conversation
By remove specified l2cap connection when got closing msg(0x06, I guess)
…ed the connection
|
This is incredible progress and confirms a lot of things I found in my own research, most of all that the PIN is the last 4 digits of the previously connected device reversed. |
|
Following up to say i got these changes working. The proof of concept is definitely there. It needs some tweaks though. Scan timeout needs to be lowered for the controller to find the ESP within a 3 second window. I used 1 second so it gets multiple tries. There's a quirk where using the Reset button on the ESP will make a reconnection but no data is actually transmitted. I saw you were working on disconnecting remotes as well and was trying to detect that edge case and automatically remove them. Waiting 10 seconds before reconnecting also makes the controller abandon the prior connection and that seemed to help. Now i'm onto the last test... this PR was really going well until I tried pairing a new second controller. That did not work. The ESP only wants to connect to the first one that completed the "auth" process. Again, this may be the fault of my demo sketch. |
|
Glad you got parts of it working, I have not tried pairing two controllers at once, so may very well be that does not work. I'm a bit preoccupied with work this week, but will take a look this weekend and see if I can get it working. You are definitely correct about scan interfering with reconnect though, so a better demo example is needed. Perhaps we need to add something similar to the "red sync"-button on the esp, which stops scanning after about 20 seconds. |
|
ESP32 can only connect to one device at a time, but the case I'm testing with 2 devices is making sure that this code is willing to overwrite previously stored mac adrress / PIN for a new device. Right now it seems like the answer is no. I can now ONLY connect to the controller I paired with the first time the auth function ran successfully. I can press red pair buttons on other controllers all I like and it will refuse to talk to them. I'm going to take a third controller out this afternoon after work just to make absolutely sure this is what's happening. |
|
Some discoveries after testing:
Here's the code I used for the above: #include <Arduino.h>
#include <esp32_wiimote/Wiimote.h>
#include <vector>
Wiimote wiimote;
uint64_t scan_timeout{0};
std::vector<uint16_t> connections(4, 0);
uint8_t connection_count{0};
inline void scan(uint32_t seconds) {
if (seconds > 0) {
log_i("SCAN enabled for %d seconds", seconds);
scan_timeout = millis() + seconds * 1'000;
wiimote.scan(true);
} else {
log_i("SCAN halted");
scan_timeout = 0;
wiimote.scan(false);
}
}
void wiimote_callback(wiimote_event_type_t event_type, uint16_t handle, uint8_t *data, size_t len) {
if (event_type == WIIMOTE_EVENT_INITIALIZE) {
// Scan for 5 minutes, or until a device connects
scan(300);
} else if (event_type == WIIMOTE_EVENT_DATA) {
bool wiimote_button_B = (data[3] & 0x04) != 0;
bool wiimote_button_A = (data[3] & 0x08) != 0;
if (wiimote_button_A) {
log_i("A:BUTTON Disconnecting wiimote %02X", handle);
wiimote.disconnect(handle);
} else if (wiimote_button_B) {
log_i("B:BUTTON wiimote %02X", handle);
}
} else if (event_type == WIIMOTE_EVENT_NEW) {
log_i("AUTH Wiimote %02X", handle);
wiimote.initiate_auth(handle);
} else if (event_type == WIIMOTE_EVENT_CONNECT) {
log_i("CONNECT Handle: %02X", handle);
// Find an available LED
auto itr = std::find_if(connections.begin(), connections.end(), [](uint16_t val) { return val == 0; });
if (itr != connections.end()) {
*itr = handle;
log_i("LED %d Handle: %02X", (1 + itr - connections.begin()), handle);
wiimote.set_led(handle, 1 + itr - connections.begin());
}
// Stop scanning
scan(0);
connection_count++;
} else if (event_type == WIIMOTE_EVENT_DISCONNECT) {
log_i("DISCONNECT Handle: %02X", handle);
// Free LED if in use by this wiimote
auto itr = std::find_if(connections.begin(), connections.end(), [&handle](uint16_t val) { return val == handle; });
if (itr != connections.end()) {
*itr = 0;
}
connection_count--;
if (connection_count == 0) {
// Add devices disconnected, scan for another 60 seconds.
scan(60);
}
} else if (event_type == WIIMOTE_EVENT_SCAN_STOP) {
if (millis() < scan_timeout) {
wiimote.scan(true);
}
}
}
void setup()
{
Serial.begin(115200);
wiimote.init(wiimote_callback);
}
void loop() {
wiimote.handle();
if (scan_timeout != 0 && millis() > scan_timeout) {
scan(0);
}
}Note, when using the A button to disconnect (esp32 initiates the disconnect), be sure to hold it in until the wiimote disconnects, otherwise the wiimote will try reconnect. Alternatively, you can disconnect by holding in the power button on the wiimote. |
|
On the topic of multiple devices, this commit from AqeeAqee seems relevant: |
Since you added the disconnect method in this branch, if you know the solution to fixing the race condition, I think it's best to fix it for this PR. Also because I don't know how swiftly @takeru will merge this.
The fact that we can connect to multiple devices already is absolutely wild. I had no idea! I played with your demo and confirmed it works. Still some weirdness that I've been mulling on the last couple of days. Some of the terminology of the demo code and the lack of access to reconnection in the past led me to believe we always need to scan. I thought that graph LR
I[Init] --> S[🛜 Scan] --> P[🔴 Pair button] --> C[✳️ Connected] --> D[❌ Disconnect]
C --> O[🛑 Stop Scanning] -->S
Now I've learned better. Init actually enables bluetooth connections with previously vetted devices. With access to reconnection, we can bypass scanning. Instead it actually looks like this graph TD
I[🛜 Init] --> S[🫱 Scan] --> P[🔴 Pair button] --> N[❇️ New Connection] --> A[🤝 Auth] --> C[✳️ Connected]
I --> B[🔘A Button] --> T[❇️ Temp Connection] --> Q{📋 Check List} --pass--> C
Q -- fail--> D[❌ Disconnect]
A --> L[📝 Add To List]
S ---> O[🤷🏻♂️ Stop Scan]
You've done incredible work doing the auth handshake, and writing to the list of approved devices so that the reconnection flow works. It also had a side effect of exposing that this multiple connection stuff was always possible for better and for worse. I think it's worth adding a method to remove items from the auth list. As it is right now, a device can be disconnected, but it can't be stopped from reconnecting. In my own use case, I want to limit the user to one device. A workaround I found was lowering // Wiimote.cpp
#ifndef L2CAP_CONNECTION_LIST_SIZE
#define L2CAP_CONNECTION_LIST_SIZE 8
#endifI was not able to find a workaround for removing a device though. For installation use this could be a nightmare. Pressing the A button will connect to any ESP running the firmware that previously passed auth at random, and rebooting the ESP will only cause the remote/board to reconnect in an instant. Another side-effect is that the temporary connection to verify PIN counts as a connection that does not increment connection_count. There's no event attached to that. But there is one attached to the disconnection that follows a failure. So you can end up with a negative connection count. |
|
Here's code that demonstrates my flow chart. Attach a push button to pin 21 and ground. Pressing it will disconnect any other devices (using your disconnect function) and allow pairing with new devices. Reconnection can resume after scan stops which happens without the need for an external timer. #include <Wiimote.h>
#include <vector>
Wiimote wii;
bool is_scanning = false;
#define pair_button_gpio 21
std::vector<uint16_t> connections(4, 0);
std::vector<uint16_t> connected_wiimotes(0, 0);
void wiimote_callback(wiimote_event_type_t event_type, uint16_t wiimote, uint8_t* data, size_t len) {
printf("len:%02X ", len);
if (wiimote != 0) {
printf("Wiimote:%04X ", wiimote);
}
if (event_type == WIIMOTE_EVENT_DATA) {
if (data[1] == 0x32) {
printf("🪇 ");
for (int i = 0; i < 4; i++) {
printf("%02X ", data[i]);
}
// http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck
uint8_t* ext = data + 4;
printf(" ... Nunchuk: sx=%3d sy=%3d c=%d z=%d\n",
ext[0],
ext[1],
0 == (ext[5] & 0x02),
0 == (ext[5] & 0x01));
} else if (data[1] == 0x34) {
printf("👟 ");
for (int i = 0; i < 4; i++) {
printf("%02X ", data[i]);
}
// https://wiibrew.org/wiki/Wii_Balance_Board#Data_Format
uint8_t* ext = data + 4;
/*printf(" ... Wii Balance Board: TopRight=%d BottomRight=%d TopLeft=%d BottomLeft=%d Temperature=%d BatteryLevel=0x%02x\n",
ext[0] * 256 + ext[1],
ext[2] * 256 + ext[3],
ext[4] * 256 + ext[5],
ext[6] * 256 + ext[7],
ext[8],
ext[10]
);*/
float weight[4];
wii.get_balance_weight(data, weight);
printf(" ... Wii Balance Board: TopRight=%f BottomRight=%f TopLeft=%f BottomLeft=%f\n",
weight[BALANCE_POSITION_TOP_RIGHT],
weight[BALANCE_POSITION_BOTTOM_RIGHT],
weight[BALANCE_POSITION_TOP_LEFT],
weight[BALANCE_POSITION_BOTTOM_LEFT]);
} else {
printf("🪄 ");
for (int i = 0; i < len; i++) {
printf("%02X ", data[i]);
}
printf("\n");
}
bool wiimote_button_down = (data[2] & 0x01) != 0;
bool wiimote_button_up = (data[2] & 0x02) != 0;
bool wiimote_button_right = (data[2] & 0x04) != 0;
bool wiimote_button_left = (data[2] & 0x08) != 0;
bool wiimote_button_plus = (data[2] & 0x10) != 0;
bool wiimote_button_2 = (data[3] & 0x01) != 0;
bool wiimote_button_1 = (data[3] & 0x02) != 0;
bool wiimote_button_B = (data[3] & 0x04) != 0;
bool wiimote_button_A = (data[3] & 0x08) != 0;
bool wiimote_button_minus = (data[3] & 0x10) != 0;
bool wiimote_button_home = (data[3] & 0x80) != 0;
static bool rumble = false;
if (wiimote_button_plus && !rumble) {
wii.set_rumble(wiimote, true);
rumble = true;
}
if (wiimote_button_minus && rumble) {
wii.set_rumble(wiimote, false);
rumble = false;
}
} else if (event_type == WIIMOTE_EVENT_INITIALIZE) {
printf("🛜 INITIALIZE Bluetooth\n");
} else if (event_type == WIIMOTE_EVENT_SCAN_START) {
printf("🫱 Scan started. Accepting new devices.\n", wiimote);
} else if (event_type == WIIMOTE_EVENT_NEW) {
printf("🤝 Authenticating Wiimote %02X.\n", wiimote);
wii.initiate_auth(wiimote);
} else if (event_type == WIIMOTE_EVENT_CONNECT) {
// add wiimote to connected list
connected_wiimotes.push_back(wiimote);
wii.set_led(wiimote, 1);
// Stop scanning happens automatically
// wii.scan(false);
printf("✅ CONNECT(%d) Wiimote %02X.\n", connected_wiimotes.size(), wiimote);
} else if (event_type == WIIMOTE_EVENT_DISCONNECT) {
// remove wiimote from connected list
auto iterator = std::find(connected_wiimotes.begin(), connected_wiimotes.end(), wiimote);
if (iterator != connected_wiimotes.end()) {
connected_wiimotes.erase(iterator);
}
printf("❌ DISCONNECT(%d) Wiimote: %02X\n", connected_wiimotes.size(), wiimote);
} else if (event_type == WIIMOTE_EVENT_SCAN_STOP) {
// try again if nothing connected
if (connected_wiimotes.size() == 0) {
// printf("🔄 Scan Retry\n");
// wii.scan(true);
// } else {
printf("🛑 Scan Stop\n");
is_scanning = false;
}
}
}
volatile bool scan_button_pressed = false;
void IRAM_ATTR press_scan() {
scan_button_pressed = digitalRead(pair_button_gpio) == LOW;
}
void setup() {
Serial.begin(115200);
pinMode(pair_button_gpio, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
attachInterrupt(digitalPinToInterrupt(pair_button_gpio), press_scan, CHANGE);
wii.init(wiimote_callback);
}
void loop() {
wii.handle();
if (scan_button_pressed) {
scan_button_pressed = false;
is_scanning = true;
// loop through connected wiimotes and disconnect them
for (auto& wiimote : connected_wiimotes) {
wii.disconnect(wiimote);
}
wii.scan(true);
}
digitalWrite(LED_BUILTIN, is_scanning ? HIGH : LOW);
}a few notes: |
This PR adds pin exchange using the red sync button, which enables reconnect without pairing/scanning.
Depends on #8 .