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

Sending data from PC to my ESP32 via BLE #195

Open
akutterxressential opened this issue Oct 17, 2023 · 24 comments
Open

Sending data from PC to my ESP32 via BLE #195

akutterxressential opened this issue Oct 17, 2023 · 24 comments

Comments

@akutterxressential
Copy link

Ladies and gentlemen,

this is my first time writing here. I created a simple gamepad to send a few buttons and three axis via BLE to the PC. Works like a charm. But is it possible to send infos from my Unity application to the ESP32? Like a float value, an int value or at least a bool? I would like to use it for example to trigger the vibration strenght or colorize an RGB LED. Or at least switch the vibration or the LED on and off with a bool.

I am not very experienced tbh, so looking forward to your help!

@LeeNX
Copy link
Contributor

LeeNX commented Oct 18, 2023

Congratulations.

Similar question has been asked a few times in different ways.
#165
#99
#43

My own work in this area paused at https://github.com/LeeNX/ESP32-BLE-Gamepad/tree/test-led, which could be further basically tested with https://nondebug.github.io/webhid-explorer/ but using Chrome.

My research has me thinking there is no real standard way to do this from the OS. I was thinking getting a working tech demo and patch SDL to support this, but I have not had time to go further yet. Plus the branch is a little old.

Hope this helps and we all make some progress.

@akutterxressential
Copy link
Author

Thank you @LeeNX very much appreciated.

Ok I understand the issue. I will dig a bit deeper into this, let's see what my simple developer mind can achieve :-D

@AlEnterni
Copy link

Hello,
I am reaching out to inquire about the feasibility of retrieving data from a characteristic created in a similar fashion to the following code snippet:

pCharacteristic_FFB = pService->createCharacteristic( NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE );

This characteristic is intended to be utilized within a function, and the goal is to write to this characteristic from Python or any other program using a BLE library.
I am not well-versed in Bluetooth and BLE, and I'm curious to know if this approach is viable. Any insights or guidance on how this can be achieved would be greatly appreciated.
Thank you for your time and assistance.

Best regards,

@LeeNX
Copy link
Contributor

LeeNX commented Nov 22, 2023

I believe it should be possible, but I currently have not got a working example yet. I think the HID/report interface might be easier, like using the webhid-explorer using Chrome.

My PlayerLEDs branch might help, commit LeeNX@8096d8e has a start

I also think you need to add Characteristic to a Service, plus if you want to write into it, you would need a callBack, which is in my branch.

Let us know how you go with this.

@AlEnterni
Copy link

I'm encountering difficulties with Nimble, and I'm seeking assistance in resolving the issue. I've followed the examples provided by the library, but I'm still unable to write to characteristics that should be writable.

To provide a bit more context, I'm using nRF Connect on my mobile device for the connection. While I can successfully read data from the characteristics, writing to them seems to be problematic. I'm wondering if anyone else has faced a similar issue or if there are insights into what might be causing this inconvenience.

@LeeNX
Copy link
Contributor

LeeNX commented Nov 25, 2023

@AlEnterni are you having issues with the NimBLE library or the ESP32-BLE-Gamepad library?

Currently ESP32-BLE-Gamepad library does not have write support.

If you have an example or PR to extending the ESP32-BLE-Gamepad that would be awesome.

@EmanresuEmanreztuneb
Copy link

I'll make it short:

If this is still active, im willing to support/donate with more then just a smile!

@lemmingDev
Copy link
Owner

Might be something usable here
https://github.com/afpineda/Nus-NimBLE-Serial

@LeeNX
Copy link
Contributor

LeeNX commented Feb 26, 2024

Thanks @lemmingDev , I was looking at adding the option for NUS type service, but I could just not get it added as an option in the ESP32-BLE-Gamepad library. This is more a limitation of my C skills, than any other problem.

Thou the linked library looks to do way more than I was thinking of adding.

If only there was an easy way to add BLE services without them been all tied together.

@AlEnterni
Copy link

Hello everyone,

I've been working on modifying a BLE library for my project. My goal is to write a value to a characteristic and create a function to retrieve that value. However, due to my limited programming knowledge, I'm encountering an error that I can't seem to resolve. Here's the code snippet along with the error message:

---Blegamepad.h---



class BleGamepad
{
private:
    ...
    NimBLECharacteristic *pBeefCharacteristic;
    ...

public:
    ...
    void WriteData();
    ...
};

---Blegamepad.cpp---


...
void BleGamepad::taskServer(void *pvParameter)
    ...
    NimBLEService* pDeadService = pServer->createService("DEAD");
    NimBLECharacteristic* pBeefCharacteristic = pDeadService->createCharacteristic(
                                               "BEEF",
                                               NIMBLE_PROPERTY::READ |
                                               NIMBLE_PROPERTY::WRITE |
                                              );

    pBeefCharacteristic->setValue("Burger");

    ...

void BleGamepad::WriteData( )
{
    Serial.println((this->pBeefCharacteristic->getValue().c_str()));
}

When I try to run this code on my Arduino, I encounter the following error:


rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1344
load:0x40078000,len:13964
load:0x40080400,len:3600
entry 0x400805f0
Starting BLE work!

abort() was called at PC 0x400d4f3c on core 0

Backtrace: 0x40083949:0x3ffce0b0 0x40093515:0x3ffce0d0 0x40098a29:0x3ffce0f0 0x400d4f3c:0x3ffce170 0x400d31aa:0x3ffce1b0 0x400d2c62:0x3ffce1e0

ELF file SHA256: fab548ad5f8ea5c0

I would greatly appreciate any assistance or guidance on how to resolve this issue and successfully modify the code as intended. Thank you in advance for your help!

Best regards,

@LeeNX
Copy link
Contributor

LeeNX commented Mar 19, 2024

I ran into similar issues, but my C skills are so little, maybe @lemmingDev could help make my branch work - https://github.com/LeeNX/ESP32-BLE-Gamepad/tree/leet-uart

The last three commits is where I tried to add NUS

@Talha-Babar
Copy link

Hi @LeeNX & @akuttervrinsightde ,

I am here for the same reason, i am developing the gamepad using esp32 with multiple buttons and axis. The gamepad is working fine and now i need a some variables from unreal engine for vibration and a value for actuators (motor) that will provide a jerk or elevation etc.

Is there something related to this you guys found and would you help me how can i achieve this.

Thanks and regards

@LeeNX
Copy link
Contributor

LeeNX commented Aug 12, 2024

Sorry @Talha-Babar , not made any progress myself. Mostly because there is no standard way that I have found, that is not mimicking XBox controller (XInput) or Playstation interface, that is not really a standard - https://github.com/Mystfit/ESP32-BLE-CompositeHID/blob/master/examples/GamepadExamples/XboxXInputController/XboxXInputController.ino

But maybe ESP32-BLE-CompositeHID will do what you need and don't mind mimicking the XBox controller.

I was looking at the features of BLE, but been thinking about how would a device be shared between the Host OS and the Application and if it would be an all or nothing approach. ie: if you application opens the via HID, you would need full controller and process all of the HID, not just the feature set for like feedback.

Hope that helps.

@LeeNX
Copy link
Contributor

LeeNX commented Nov 20, 2024

I was able to expose the NUS service in a test sketch last night, going to add to my gamepad repo and then drop the link here.

While testing, I was wonder if this is really going to be useful. The reason I say this, was I was looking for a way to debug and possible tweak my gamepad while running.

But in practice, I am thinking this might not really be possible, ie: gamepad connected to host OS, the host OS would also be able to open a NUS service to the gamepad and read debugging info and possible send commands or something. Capturing the streaming debugging messages to a file might have some use, if the host OS is a multi-tasking OS like Linux.

I think some people are hoping this might be a stepping stone to ForceFeedBack/Haptic interface, which could be used in custom setup, but I don't think it's a general function.

#include <Arduino.h>
#include <NimBLEDevice.h>
#include <BleGamepad.h>

//const int LED_PIN = LED_BUILTIN;
const int LED_PIN = 8;
unsigned long previousMillisLED = 0; // Stores the last time the LED was updated
unsigned long previousMillisTX = 0; // Stores the last time the LED was updated
const long intervalLED = 500;        // Interval at which to blink (in milliseconds)
const long intervalTX = 500;        // Interval at which to blink (in milliseconds)

bool ledState = LOW; // Stores the current state of the LED

// Initialize the BLE Gamepad object
BleGamepad bleGamepad("leet-Gamepad", "leenx", 100);

// BLE Serial setup
NimBLEServer *pServer = nullptr;
NimBLECharacteristic *pTxCharacteristic = nullptr;
std::string rxBuffer;

// UUIDs for the Serial Service
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

// Serial RX Callback
class MyCallbacks : public NimBLECharacteristicCallbacks {
  void onWrite(NimBLECharacteristic *pCharacteristic) {
    rxBuffer = pCharacteristic->getValue();
    if (!rxBuffer.empty()) {
      Serial.print("Received over BLE: ");
      Serial.println(rxBuffer.c_str());
      
      if (rxBuffer == "press") {
        bleGamepad.press(BUTTON_1); // Simulate button press
        Serial.println("Button 1 pressed");
      } else if (rxBuffer == "release") {
        bleGamepad.release(BUTTON_1); // Simulate button release
        Serial.println("Button 1 released");
      }
    } else {
      Serial.println("Received empty or invalid data.");
    }
  }
};

// Server connection callback
class MyServerCallbacks : public NimBLEServerCallbacks {
  void onConnect(NimBLEDevice* device) {
    Serial.println("Device connected, sending 'Connected' message!");
    
    // Send a 'Connected' message to the client
    std::string connectedMessage = "Connected to BLE Gamepad!";
    pTxCharacteristic->setValue(connectedMessage);  // Set the value of the TX characteristic
    pTxCharacteristic->notify();  // Notify the client that the TX value has changed
  }

  void onDisconnect(NimBLEDevice* device) {
    Serial.println("Device disconnected.");
  }
};

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_PIN, OUTPUT);

  // Initialize Serial communication at 115200 baud
  Serial.begin(115200);
  Serial.println("BLE Gamepad started. Waiting for connection...");

  // Initialize NimBLE stack
  NimBLEDevice::init("ESP32 Combo Device");

  // Create NimBLE Server
  pServer = NimBLEDevice::createServer();

  // Attach connection callbacks
  pServer->setCallbacks(new MyServerCallbacks());
  
  // Create BLE Serial Service
  NimBLEService *pService = pServer->createService(SERVICE_UUID);
  
  pTxCharacteristic = pService->createCharacteristic(
      CHARACTERISTIC_UUID_TX,
      NIMBLE_PROPERTY::NOTIFY
  );
  
  // NimBLE adds 2902 Descriptors to Characteristic that had notify
  //pTxCharacteristic->addDescriptor(new NimBLE2902());
  
  NimBLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
      CHARACTERISTIC_UUID_RX,
      NIMBLE_PROPERTY::WRITE
  );
  pRxCharacteristic->setCallbacks(new MyCallbacks());

  pService->start();

  // Start the BLE Gamepad
  bleGamepad.begin();

  // Start BLE advertising
  NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->start();

  Serial.println("BLE setup complete. Waiting for connection...");
}

// the loop function runs over and over again forever
void loop() {
  // Get the current time
  unsigned long currentMillis = millis();

  // Check if the interval has passed
  if (currentMillis - previousMillisLED >= intervalLED) {
    previousMillisLED = currentMillis; // Save the last time the LED was updated

    // Toggle the LED state
    ledState = !ledState;
    // Set the LED
    digitalWrite(LED_PIN, ledState);

    // Send feedback to the Serial Monitor
    Serial.print("LED is now ");
    Serial.print(ledState ? "ON" : "OFF");
    Serial.print(" | Current time: ");
    Serial.print(currentMillis);
    Serial.println(" ms");
  }

  if (bleGamepad.isConnected()) {
    Serial.println("Gamepad connected!");
    delay(100);
  } else {
    Serial.println("Gamepad not connected.");
    delay(100);
  }

  // Notify Serial Clients
  if (pTxCharacteristic) {
    if (currentMillis - previousMillisTX >= intervalTX) {
      previousMillisTX = currentMillis; // Save the last time the LED was updated

      // Send a message periodically if connected
      // Create a message with the current time in milliseconds
      std::string periodicMessage = "Sending periodic message at " + std::to_string(currentMillis) + " ms";
      pTxCharacteristic->setValue(periodicMessage);  // Set the value of the TX characteristic
      pTxCharacteristic->notify();  // Notify the client that the TX value has changed

    delay(100);    
    }
  }
}

@lemmingDev
Copy link
Owner

I was able to expose the NUS service in a test sketch last night, going to add to my gamepad repo and then drop the link here.

While testing, I was wonder if this is really going to be useful. The reason I say this, was I was looking for a way to debug and possible tweak my gamepad while running.

But in practice, I am thinking this might not really be possible, ie: gamepad connected to host OS, the host OS would also be able to open a NUS service to the gamepad and read debugging info and possible send commands or something. Capturing the streaming debugging messages to a file might have some use, if the host OS is a multi-tasking OS like Linux.

I think some people are hoping this might be a stepping stone to ForceFeedBack/Haptic interface, which could be used in custom setup, but I don't think it's a general function.

An underrated post that didn't really click as being extremely cool at the time

I notice this doesn't work with latest ESP32-BLE-Gamepad and NimBLE-Arduino 2.2.1 etc as I guess it was targeted towards NimBLE-Arduino 1.4

Anyway, I'm working on adding the support within the library itself, and actually have it working

Just tweaking a few things and getting some examples done

@lemmingDev
Copy link
Owner

@LeeNX

Ok, so I think I'm done with adding the Nordic UART Service

Can you test with the attached test library?

ESP32-BLE-Gamepad NUS Test.zip

Your sketch would become

#include <Arduino.h>
#include <BleGamepad.h>

//const int LED_PIN = LED_BUILTIN;
const int LED_PIN = 8;
unsigned long previousMillisLED = 0; // Stores the last time the LED was updated
unsigned long previousMillisTX = 0; // Stores the last time the LED was updated
const long intervalLED = 500;        // Interval at which to blink (in milliseconds)
const long intervalTX = 500;        // Interval at which to blink (in milliseconds)

bool ledState = LOW; // Stores the current state of the LED

// Initialize the BLE Gamepad object
BleGamepad bleGamepad("leet-Gamepad", "leenx", 100, true);  // Delay server advertising start until after NuS service added

std::string rxBuffer;

void onUartDataReceived(const uint8_t *data, size_t length) {
  rxBuffer.assign(reinterpret_cast<const char*>(data), length-2); // remove the new line
  
  if (!rxBuffer.empty()) {
    Serial.print("Received over BLE: ");
    Serial.println(rxBuffer.c_str());
    
    if (rxBuffer == "press") {
      bleGamepad.press(BUTTON_1); // Simulate button press
      Serial.println("Button 1 pressed");
    } else if (rxBuffer == "release") {
      bleGamepad.release(BUTTON_1); // Simulate button release
      Serial.println("Button 1 released");
    }
  } else {
    Serial.println("Received empty or invalid data.");
  }
}

// The setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_PIN, OUTPUT);

  // Initialize Serial communication at 115200 baud
  Serial.begin(115200);
  Serial.println("BLE Gamepad started. Waiting for connection...");

  // Start the BLE Gamepad
  bleGamepad.begin();
  bleGamepad.beginNUS();  // Initialise Nordic UART service
  bleGamepad.setNUSDataReceivedCallback(onUartDataReceived);

  Serial.println("BLE setup complete. Waiting for connection...");
}

// The loop function runs over and over again forever
void loop() {
  // Get the current time
  unsigned long currentMillis = millis();

  // Check if the interval has passed
  if (currentMillis - previousMillisLED >= intervalLED) {
    previousMillisLED = currentMillis; // Save the last time the LED was updated

    // Toggle the LED state
    ledState = !ledState;
    // Set the LED
    digitalWrite(LED_PIN, ledState);

    // Send feedback to the Serial Monitor
    Serial.print("LED is now ");
    Serial.print(ledState ? "ON" : "OFF");
    Serial.print(" | Current time: ");
    Serial.print(currentMillis);
    Serial.println(" ms");
  }

  if (bleGamepad.isConnected()) {
    Serial.println("Gamepad connected!");
    delay(100);
  } else {
    Serial.println("Gamepad not connected.");
    delay(100);
  }

  // Notify Serial Clients
  if (currentMillis - previousMillisTX >= intervalTX) {
    previousMillisTX = currentMillis; // Save the last time the LED was updated

    // Send a message periodically if connected
    // Create a message with the current time in milliseconds
    std::string periodicMessage = "Sending periodic message at " + std::to_string(currentMillis) + " ms\n";
    bleGamepad.sendDataOverNUS((const uint8_t *)periodicMessage.c_str(), periodicMessage.length());
    delay(1000);    
  }
}

@lemmingDev
Copy link
Owner

lemmingDev commented Feb 19, 2025

A blank sketch still looks like:

#include <Arduino.h>
#include <BleGamepad.h>

BleGamepad bleGamepad("Gamepad", "lemmingDev", 100);

// The setup function runs once when you press reset or power the board
void setup() {
  bleGamepad.begin();
}

// The loop function runs over and over again forever
void loop() {
  if (bleGamepad.isConnected()) {
    // Do something
  }
}

but a blank sketch with Nordic UART Service would look like

#include <Arduino.h>
#include <BleGamepad.h>

BleGamepad bleGamepad("Gamepad NUS", "lemmingDev", 100, true);

void onUartDataReceived(const uint8_t *data, size_t length) {
  // Do something with NuS data
}

// The setup function runs once when you press reset or power the board
void setup() {
  bleGamepad.begin();
  bleGamepad.beginNUS();  // Initialise Nordic UART service
  bleGamepad.setNUSDataReceivedCallback(onUartDataReceived);
}

// The loop function runs over and over again forever
void loop() {
  if (bleGamepad.isConnected()) {
    // Do something
  }
}

Or if you want to create an instance of BleNUS, you can also access it with code such as in the example below, where it creates a NuS passthrough with the serial, added to the single button example

#include <Arduino.h>
#include <NimBLEDevice.h>  // Ensure NimBLE is included
#include <BleGamepad.h>
#include <BleNUS.h>

#define DELAY_ADVERTISING true

// Pin definitions
#define BUTTONPIN 0 // Pin button is attached to


// This 4th constructor value needs to be set to true for Nordic UART Service to work correctly
// It tells the server not to start advertising until after the NuS service is added correctly 
BleGamepad bleGamepad("Gampad with NuS", "lemmingDev", 100, DELAY_ADVERTISING);

BleNUS* bleNUS = nullptr;  // Create pointer to new null instance of BleNUS

int previousButton1State = HIGH;

// Callback function for receiving UART data over BLE
void onUartDataReceived(const uint8_t *data, size_t length) {
    Serial.print("Received from BLE: ");
    Serial.write(data, length);  // Send received BLE data to hardware Serial

    // Instead of simply passing through the NuS data to the serial monitor,
    // you could check the data and, for example, press/release gamepad buttons
}

// Function to read serial buffer and return it as a String
String readSerialBuffer() {
    String input = "";  // Initialize an empty string to store the input data

    while (Serial.available() > 0) {
        char incomingByte = Serial.read();  // Read one byte at a time
        input += incomingByte;  // Append the byte to the string
    }

    return input;  // Return the accumulated data as a String
}

void setup() {
    Serial.begin(115200);
    Serial.println("BLE Gamepad with UART Passthrough");

    pinMode(BUTTONPIN, INPUT_PULLUP);

    bleGamepad.begin();
    bleGamepad.beginNUS();  // Initialise Nordic UART service
    bleNUS = bleGamepad.getNUS();  // Populate (pointer to) null BleNUS instance with actual BleNUS instance created through beginNUS() above

    if (bleNUS) {
        //bleNUS->setDataReceivedCallback(onUartDataReceived); // Same as below
        bleGamepad.setNUSDataReceivedCallback(onUartDataReceived);
    }
}

void loop() {
    // Gamepad button handling
    if (bleGamepad.isConnected()) {
        int currentButton1State = digitalRead(BUTTONPIN);

        if (currentButton1State != previousButton1State) {
            if (currentButton1State == LOW) {
                bleGamepad.press(BUTTON_1);
                bleGamepad.sendDataOverNUS((const uint8_t *)"Button 1 Pressed\n", 17);
            } else {
                bleGamepad.release(BUTTON_1);
                bleGamepad.sendDataOverNUS((const uint8_t *)"Button 1 Released\n", 18);
            }
        }
        previousButton1State = currentButton1State;
    }

    // Serial to BLE passthrough (without using Serial.readString())
    if (Serial.available() > 0) {
        // In testing, I found I needed to buffer the data first
        String data = readSerialBuffer();  // Call the function to read the serial buffer

        // Send the data via BLE
        //bleNUS->sendData((const uint8_t *)data.c_str(), data.length()); // Same as below
        bleGamepad.sendDataOverNUS((const uint8_t *)data.c_str(), data.length());
    }
}

BleNUS also has a heap of other functions if you check

@LeeNX
Copy link
Contributor

LeeNX commented Feb 20, 2025

Thanks @lemmingDev! Been away from my ManCave, so I have not had a chance to look at this, but oh my word, it looks awesome, just looking at the above. I will possible try this out this weekend and as I have anything to report back. Like a little kid in the candy store.

@lemmingDev
Copy link
Owner

I (mostly ChatGPT) made that Nordic UART Service to COM port passthrough software I was thinking of making too

Got it to work, but there are a few issues.

Test this new stuff out first though...

@LeeNX
Copy link
Contributor

LeeNX commented Feb 20, 2025

Noice! The UART Service tooling is for Windows? Are you going to put in github? I still need to set how Linux deals with NUS.

I was looking at the NuS-NimBLE-Serial library and some of the back issues. Some interesting things need to test, one been the disconnect of one service, might disconnect the other service. Also could you connect the gamepad service to one device and possible monitor gamepad over NUS with a device device, like a debugging machine that is the gamepad hosting device? Security could be an issue, but I think the debugging and tweaking could be so useful.

@lemmingDev
Copy link
Owner

I actually thought about making ESP32-BLE-Gamepad compatible with NuS-NimBLE-Serial. Perhaps still could...

After playing around with the ESP32-BLE-Gamepad library intensely for few weeks, I actually understand most of the NimBLE code I borrowed from the ESP32-BLE-Mouse library

I originally asked him to create a gamepad library, but he said he had no interest and suggested I make one

It all started from there...

@lemmingDev
Copy link
Owner

@LeeNX Going to publish this, unless you've found a breaking change...

@LeeNX
Copy link
Contributor

LeeNX commented Feb 24, 2025

Sorry @lemmingDev been a little under the weather and for the life of me this weekend, could not find zip. GitHub hide a bit of the discussion, which I now looked a little closer and found it.

I have not had a chance to test, but I don't see why it could not be pushed out.

Going to be a few weekends before I can spare time and poke around this. Super keen to play with it thou.

@1stage
Copy link

1stage commented Mar 2, 2025

@lemmingDev , might this be an approach for the issue I raised regarding client ESP32+NimBLE resets or device power-cycles leaving the ESP32-BLE-Gamepad host device with a false connection?

#271

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants