Skip to content
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

[Bug]: cannot connect more than 1 controller #135

Open
davidos81 opened this issue Jan 5, 2025 · 9 comments
Open

[Bug]: cannot connect more than 1 controller #135

davidos81 opened this issue Jan 5, 2025 · 9 comments
Assignees
Labels
bug Something isn't working

Comments

@davidos81
Copy link

What happened?

I have a few different bluetooth controllers , a wii controller , ps4 copy controller and a game sir nova lite , all of them work 100% fine when paired , however if I try to connect more than 1 then the secound one will not pair

So I thought maybe they don't connect because of different manufacturers so I bought a 2nd gamesir nova lite , and that too works but only on its own

How do i get the example arduino project to work with more than 1 controller ?

thanks in advance , and well done on such a brilliant project!

Bluepad32 Version

Other

Bluepad32 version custom

bluepad32 4.1.0

Bluepad32 Platform

Arduino IDE

Platform version

arduino 2.34

Controller

issues

Microcontroller

ESP32

Microcontroller board

esp32 dev module

OS

Windows

Relevant log output

No response

Relevant sketch

#include <Bluepad32.h>

ControllerPtr myControllers[BP32_MAX_GAMEPADS];

// This callback gets called any time a new gamepad is connected.
// Up to 4 gamepads can be connected at the same time.
void onConnectedController(ControllerPtr ctl) {
    bool foundEmptySlot = false;
    for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == nullptr) {
            Serial.printf("CALLBACK: Controller is connected, index=%d\n", i);
            // Additionally, you can get certain gamepad properties like:
            // Model, VID, PID, BTAddr, flags, etc.
            ControllerProperties properties = ctl->getProperties();
            Serial.printf("Controller model: %s, VID=0x%04x, PID=0x%04x\n", ctl->getModelName().c_str(), properties.vendor_id,
                           properties.product_id);
            myControllers[i] = ctl;
            foundEmptySlot = true;
            break;
        }
    }
    if (!foundEmptySlot) {
        Serial.println("CALLBACK: Controller connected, but could not found empty slot");
    }
}

void onDisconnectedController(ControllerPtr ctl) {
    bool foundController = false;

    for (int i = 0; i < BP32_MAX_GAMEPADS; i++) {
        if (myControllers[i] == ctl) {
            Serial.printf("CALLBACK: Controller disconnected from index=%d\n", i);
            myControllers[i] = nullptr;
            foundController = true;
            break;
        }
    }

    if (!foundController) {
        Serial.println("CALLBACK: Controller disconnected, but not found in myControllers");
    }
}

void dumpGamepad(ControllerPtr ctl) {
    Serial.printf(
        "idx=%d, dpad: 0x%02x, buttons: 0x%04x, axis L: %4d, %4d, axis R: %4d, %4d, brake: %4d, throttle: %4d, "
        "misc: 0x%02x, gyro x:%6d y:%6d z:%6d, accel x:%6d y:%6d z:%6d\n",
        ctl->index(),        // Controller Index
        ctl->dpad(),         // D-pad
        ctl->buttons(),      // bitmask of pressed buttons
        ctl->axisX(),        // (-511 - 512) left X Axis
        ctl->axisY(),        // (-511 - 512) left Y axis
        ctl->axisRX(),       // (-511 - 512) right X axis
        ctl->axisRY(),       // (-511 - 512) right Y axis
        ctl->brake(),        // (0 - 1023): brake button
        ctl->throttle(),     // (0 - 1023): throttle (AKA gas) button
        ctl->miscButtons(),  // bitmask of pressed "misc" buttons
        ctl->gyroX(),        // Gyro X
        ctl->gyroY(),        // Gyro Y
        ctl->gyroZ(),        // Gyro Z
        ctl->accelX(),       // Accelerometer X
        ctl->accelY(),       // Accelerometer Y
        ctl->accelZ()        // Accelerometer Z
    );
}

void dumpMouse(ControllerPtr ctl) {
    Serial.printf("idx=%d, buttons: 0x%04x, scrollWheel=0x%04x, delta X: %4d, delta Y: %4d\n",
                   ctl->index(),        // Controller Index
                   ctl->buttons(),      // bitmask of pressed buttons
                   ctl->scrollWheel(),  // Scroll Wheel
                   ctl->deltaX(),       // (-511 - 512) left X Axis
                   ctl->deltaY()        // (-511 - 512) left Y axis
    );
}

void dumpKeyboard(ControllerPtr ctl) {
    static const char* key_names[] = {
        // clang-format off
        // To avoid having too much noise in this file, only a few keys are mapped to strings.
        // Starts with "A", which is offset 4.
        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
        "W", "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0",
        // Special keys
        "Enter", "Escape", "Backspace", "Tab", "Spacebar", "Underscore", "Equal", "OpenBracket", "CloseBracket",
        "Backslash", "Tilde", "SemiColon", "Quote", "GraveAccent", "Comma", "Dot", "Slash", "CapsLock",
        // Function keys
        "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
        // Cursors and others
        "PrintScreen", "ScrollLock", "Pause", "Insert", "Home", "PageUp", "Delete", "End", "PageDown",
        "RightArrow", "LeftArrow", "DownArrow", "UpArrow",
        // clang-format on
    };
    static const char* modifier_names[] = {
        // clang-format off
        // From 0xe0 to 0xe7
        "Left Control", "Left Shift", "Left Alt", "Left Meta",
        "Right Control", "Right Shift", "Right Alt", "Right Meta",
        // clang-format on
    };
    Serial.printf("idx=%d, Pressed keys: ", ctl->index());
    for (int key = Keyboard_A; key <= Keyboard_UpArrow; key++) {
        if (ctl->isKeyPressed(static_cast<KeyboardKey>(key))) {
            const char* keyName = key_names[key-4];
            Serial.printf("%s,", keyName);
       }
    }
    for (int key = Keyboard_LeftControl; key <= Keyboard_RightMeta; key++) {
        if (ctl->isKeyPressed(static_cast<KeyboardKey>(key))) {
            const char* keyName = modifier_names[key-0xe0];
            Serial.printf("%s,", keyName);
        }
    }
    Console.printf("\n");
}

void dumpBalanceBoard(ControllerPtr ctl) {
    Serial.printf("idx=%d,  TL=%u, TR=%u, BL=%u, BR=%u, temperature=%d\n",
                   ctl->index(),        // Controller Index
                   ctl->topLeft(),      // top-left scale
                   ctl->topRight(),     // top-right scale
                   ctl->bottomLeft(),   // bottom-left scale
                   ctl->bottomRight(),  // bottom-right scale
                   ctl->temperature()   // temperature: used to adjust the scale value's precision
    );
}

void processGamepad(ControllerPtr ctl) {
    // There are different ways to query whether a button is pressed.
    // By query each button individually:
    //  a(), b(), x(), y(), l1(), etc...
    if (ctl->a()) {
        static int colorIdx = 0;
        // Some gamepads like DS4 and DualSense support changing the color LED.
        // It is possible to change it by calling:
        switch (colorIdx % 3) {
            case 0:
                // Red
                ctl->setColorLED(255, 0, 0);
                break;
            case 1:
                // Green
                ctl->setColorLED(0, 255, 0);
                break;
            case 2:
                // Blue
                ctl->setColorLED(0, 0, 255);
                break;
        }
        colorIdx++;
    }

    if (ctl->b()) {
        // Turn on the 4 LED. Each bit represents one LED.
        static int led = 0;
        led++;
        // Some gamepads like the DS3, DualSense, Nintendo Wii, Nintendo Switch
        // support changing the "Player LEDs": those 4 LEDs that usually indicate
        // the "gamepad seat".
        // It is possible to change them by calling:
        ctl->setPlayerLEDs(led & 0x0f);
    }

    if (ctl->x()) {
        // Some gamepads like DS3, DS4, DualSense, Switch, Xbox One S, Stadia support rumble.
        // It is possible to set it by calling:
        // Some controllers have two motors: "strong motor", "weak motor".
        // It is possible to control them independently.
         ctl->playDualRumble(0 /* delayedStartMs */, 250 /* durationMs */, 0x80 /* weakMagnitude */,
                           0x40 /* strongMagnitude */);
    }

    // Another way to query controller data is by getting the buttons() function.
    // See how the different "dump*" functions dump the Controller info.
    dumpGamepad(ctl);
}

void processMouse(ControllerPtr ctl) {
    // This is just an example.
    if (ctl->scrollWheel() > 0) {
        // Do Something
    } else if (ctl->scrollWheel() < 0) {
        // Do something else
    }

    // See "dumpMouse" for possible things to query.
    dumpMouse(ctl);
}

void processKeyboard(ControllerPtr ctl) {
    if (!ctl->isAnyKeyPressed())
        return;

    // This is just an example.
    if (ctl->isKeyPressed(Keyboard_A)) {
        // Do Something
        Serial.println("Key 'A' pressed");
    }

    // Don't do "else" here.
    // Multiple keys can be pressed at the same time.
    if (ctl->isKeyPressed(Keyboard_LeftShift)) {
        // Do something else
        Serial.println("Key 'LEFT SHIFT' pressed");
    }

    // Don't do "else" here.
    // Multiple keys can be pressed at the same time.
    if (ctl->isKeyPressed(Keyboard_LeftArrow)) {
        // Do something else
        Serial.println("Key 'Left Arrow' pressed");
    }

    // See "dumpKeyboard" for possible things to query.
    dumpKeyboard(ctl);
}

void processBalanceBoard(ControllerPtr ctl) {
    // This is just an example.
    if (ctl->topLeft() > 10000) {
        // Do Something
    }

    // See "dumpBalanceBoard" for possible things to query.
    dumpBalanceBoard(ctl);
}

void processControllers() {
    for (auto myController : myControllers) {
        if (myController && myController->isConnected() && myController->hasData()) {
            if (myController->isGamepad()) {
                processGamepad(myController);
            } else if (myController->isMouse()) {
                processMouse(myController);
            } else if (myController->isKeyboard()) {
                processKeyboard(myController);
            } else if (myController->isBalanceBoard()) {
                processBalanceBoard(myController);
            } else {
                Serial.println("Unsupported controller");
            }
        }
    }
}

// Arduino setup function. Runs in CPU 1
void setup() {
    Serial.begin(115200);
    Serial.printf("Firmware: %s\n", BP32.firmwareVersion());
    const uint8_t* addr = BP32.localBdAddress();
    Serial.printf("BD Addr: %2X:%2X:%2X:%2X:%2X:%2X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);

    // Setup the Bluepad32 callbacks
    BP32.setup(&onConnectedController, &onDisconnectedController);

    // "forgetBluetoothKeys()" should be called when the user performs
    // a "device factory reset", or similar.
    // Calling "forgetBluetoothKeys" in setup() just as an example.
    // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect.
    // But it might also fix some connection / re-connection issues.
    BP32.forgetBluetoothKeys();

    // Enables mouse / touchpad support for gamepads that support them.
    // When enabled, controllers like DualSense and DualShock4 generate two connected devices:
    // - First one: the gamepad
    // - Second one, which is a "virtual device", is a mouse.
    // By default, it is disabled.
    BP32.enableVirtualDevice(false);
}

// Arduino loop function. Runs in CPU 1.
void loop() {
    // This call fetches all the controllers' data.
    // Call this function in your main loop.
    bool dataUpdated = BP32.update();
    if (dataUpdated)
        processControllers();

    // The main loop must have some kind of "yield to lower priority task" event.
    // Otherwise, the watchdog will get triggered.
    // If your main loop doesn't have one, just add a simple `vTaskDelay(1)`.
    // Detailed info here:
    // https://stackoverflow.com/questions/66278271/task-watchdog-got-triggered-the-tasks-did-not-reset-the-watchdog-in-time

        //  vTaskDelay(1);
    delay(150);
}
@davidos81 davidos81 added the bug Something isn't working label Jan 5, 2025
@davidos81
Copy link
Author

davidos81 commented Jan 7, 2025

forgot to add console output
I get the following line every secound as normal when 1 controller is connected
idx=0, dpad: 0x00, buttons: 0x0000, axis L: 4, 0, axis R: 4, 0, brake: 0, throttle: 0, misc: 0x00, gyro x: 0 y: 0 z: 0, etc..

but then every few secounds i get this:
the 2nd one never gets connected

idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
Failed to inquiry name for 6F:8B:AB:8B:60:9C, using a fake one
uni_hid_device_disconnect: Unknown GAP connection type: 0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     0 y:     0 z:     0, accel x:     0 y: -4095 z:     0
idx=0, dpad: 0x00, buttons: 0x0000, axis L:    4,    0, axis R:    4,    0, brake:    0, throttle:    0, misc: 0x00, gyro x:     ``

@davidos81 davidos81 reopened this Jan 7, 2025
@ricardoquesada
Copy link
Owner

weird. which board are you using ?
Do you have spare one to try on it ?

sometimes it could be a bug in esp32

@davidos81
Copy link
Author

davidos81 commented Jan 9, 2025

@ricardoquesada thankyou for your kind reply!

I have no idea whats going on , so like you said use a different board - So i used my ODROID GO ESP32 edition and that uses a WROVER esp32 - BUT - still same thing , the thing is it works flawlessly with 1 controller and I really want to use it in my up and coming commercial product but I need that additional controller support ,

Do i need a new(ish) classic esp32? or maybe arduino settings ?

thanks in advance

@ricardoquesada
Copy link
Owner

to discard possible bugs (and to have access to the console) I suggest that you try the esp32 example based on esp-idf (not arduino).
DO NO MODIFY the example. Just run it. and try again.
And if it stills fails, copy & paste the output from the console.

Example & instructions here:
https://github.com/ricardoquesada/bluepad32/tree/main/examples/esp32

@davidos81
Copy link
Author

ok and thanks for that!
I will give it a try tonight and let you know !

@davidos81
Copy link
Author

ok , so i tried the IDF example as you suggested and can confirm it did work correctly with 2 controllers !! , I still need to do stability & performance etc , but this is fantastic that it works

So that begs the question , why did this not work with ARDUINO?

But for now thanks @ricardoquesada

@ricardoquesada
Copy link
Owner

ricardoquesada commented Jan 9, 2025

Good to know.
The next step is to try the "template" which uses Arduino Core + ESP-IDF (however cannot be used with Arduino IDE )

Please follow the steps decribed here:

https://github.com/ricardoquesada/esp-idf-arduino-bluepad32-template

@davidos81
Copy link
Author

davidos81 commented Jan 10, 2025

@ricardoquesada actually that was the one I got working , am not an IDF person myself , seldom use it , but for now it works exactly as I expected (my_project example), though does mean i have to rewrite code for IDF if I want this to work inside my project - but thats another thing.

another option is to have bluepad32 on its own...
In your opinion which is the least powerful chip in the esp32 series for classic bluetooth , as am thinking of simply having bluepad32 on its own mcu and it sends commands to my esp32 s3/s4 through UART, so a single core variant will do the task and be low cost

again , superb work @ricardoquesada ,

@ricardoquesada
Copy link
Owner

I wouldn't use two microcontrollers... that will add an extra layer of complexity.

In any case, if "esp-idf" is too complex, try PlatformIO... which simplifies things a bit.

But if you really want to use two microcontrollers, get the cheapeast ESP32 that you can find. All have similar specs... what changes is the number of GPIOs and/or RAM (???).

You might want to take a look at the AirLift module and how Bluepad32 interacts with it: https://bluepad32.readthedocs.io/en/latest/plat_airlift/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants