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

[Request for assistance] Broadcast info with manufacturerData field #266

Open
LeeNX opened this issue Feb 9, 2025 · 18 comments
Open

[Request for assistance] Broadcast info with manufacturerData field #266

LeeNX opened this issue Feb 9, 2025 · 18 comments

Comments

@LeeNX
Copy link
Contributor

LeeNX commented Feb 9, 2025

Looking to expose some info, like battery level and possible charging status using the manufacturerData, but my limited coding skills has me stumped.

Doing something like

void setAdvertisingData() {
    // Create new advertisement data object
    NimBLEAdvertisementData newAdvertisementData;
  
    // Add battery level to Manufacturer Data
    uint8_t manufacturerData[10];
    manufacturerData[0] = 0xff; // manufacturer data value - custom
    manufacturerData[1] = 0xff;
    manufacturerData[2] = 0xa0; // begin sig
    manufacturerData[3] = 0x00;
    manufacturerData[4] = advertiseInterval;    // how often broadcast
    manufacturerData[5] = batteryLevel;         // battery level
    manufacturerData[6] = chargingState;        // charging state
    manufacturerData[7] = 0x00; // end sig
    manufacturerData[8] = 0xa0;

    newAdvertisementData.setManufacturerData(std::string((char *)manufacturerData, sizeof(manufacturerData)));

    // Apply updated advertisement data
    pAdvertising->setAdvertisementData(newAdvertisementData);
}

Stops all over the BLE advertising data, I think I am missing the ServiceUUID, which I am not able to get from BleGamepad.

Is this possible get the ServiceUUID from BLEGamepad or make it a global in setup()?

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 9, 2025

I got the idea from my Home Assistant BLE tracking.

I can hear somebody say, why would you want the data broadcast, it could be a security issue. A use case that I can think of and will use, is that your BLE Proxy devices can report the advertising to your HA install and you can track usage and possible set notifications or alerts to charge your gamepad before your next big gaming session or something like that.

This would partially resolve #182

My broken example esp32blegamepad-blink-battery-broadcast-test-01 if anybody will look it over, please, no laughing or pointing fingers, I know I am not a good programmer.

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 9, 2025

Image
Example of test idea - 0xA0000F3F0100A0
0xA000 is start of sig
0x0F is how often broadcast would happen
0x3F is batter level - 63%
0x01 is charging status - 1 = Discharging / 2 = Charging / 3 = Fully Charged
0x00A0 is end of sig
Image
Is with a BLE gamepad, but no manufacturerData

@LeeNX LeeNX changed the title [Request for an assist] Broadcast info with manufacturerData field [Request for assistance] Broadcast info with manufacturerData field Feb 9, 2025
@lemmingDev
Copy link
Owner

FYI - I'm about to add support for Characteristic 0x2A1A - Battery Power State

If it's just power stuff you're interested in for now, might be good to hang off for a short time

I've been able to attach characteristic 0x2A1A to the existing NimBLE battery instance and the new characteristic is automatically detected by some apps as Battery Power State.

I am also able to notify data through that characteristic to Android, and have that data show up

There seems to be some confusion as to what data corresponds to what though. In some documentation, I see, in an 8 bit binary value, the 4 most least significant bits are basically Booleans for 4 different battery power state flags, and the remaining 4 bits are reserved

No Android apps that I've found seem to decipher actual battery state data in this way though, although I can see the data coming through

There is one Android app however, that seems to use 2 bits at a time to decipher battery data, where the first 2 define some information, the second 2 bits another and so on, for 4 different attributes

For each 2 bits, that's four possible states, 00, 01, 10, 11, and if I adopt that standard, the information is deciphered correctly by the app as 0x2A1A - Battery Power State data and shows whether it's charging/discharging etc, and if the battery level is good or critically low, among other things

I'm looking for documentation for this second way to see whether we should adopt it, as I can only find information on the first method

@lemmingDev
Copy link
Owner

There's actually a heap of built-in battery stuff as well (that perhaps isn't exposed through NimBLE-Arduino???) here https://github.com/espressif/esp-iot-solution/blob/ce1e2dd1/components/bluetooth/ble_services/bas/include/esp_bas.h

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 14, 2025

Interesting ... Nice find! Is this not possible another SDK or something? I will add to my notes and look over and see if I can learn any more.

As for my Request for assistance, which after trying my hand at programming, is more becoming a Feature Request, as I don't see a clean way to get this to work.

As a feature, called Custom Broadcast data, I wish to get two things right. First would be to help with some debugging and later, to add to my Home Assistant setup, been able to track battery and temperature (and maybe other things I have not come up with yet) ... Also setup alerts for when my battery is running low or stuff like that.

Really needs to be a broadcast, because I think (might be wrong), we can keep the broadcast running all the time, without having to pair and we can track this info from more than just one Host OS, like a bunch of BlueProxy devices.

I got to the point of write a basic gamepad sketch for testing, but after chatting with h2zero, I believe that we need to rebuild the Advertising each time we might change it - h2zero/NimBLE-Arduino#894

I was thinking we could expose the base/template Advertising data, and just update the setManufacturerData, but my limited C skill has me stumped. Welcome to look at my sketch esp32base-test

Come to learn that the advertising data is limited to 31 char, which might need some sort of warning for device names for the advertising. Does not effect the Device name Characteristic. Not sure how that would effect pairing, if they not the same.

@lemmingDev
Copy link
Owner

So, I ended up adding the 0x2A1A - Battery Power State characteristic, and the data can be set correctly, and read by nRF Connect on Android. In the pic below, I set the values to simulate a battery connected, charging, and at a good level. New version released...

I might circle back to this Custom Broadcast Data thing in a day or two if I get motivated
I wouldn't put my programming on a better level than yourself though

Image

@lemmingDev
Copy link
Owner

lemmingDev commented Feb 16, 2025

@LeeNX

I'm about to add temperature

Should I add it under this existing battery service 0x180F, or the existing device information service 0x180A with serial number etc?

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 16, 2025 via email

@lemmingDev
Copy link
Owner

Well, I could add it to both

For now, I'll add it to battery

Once done, I'm then going to add Nordic UART Service

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 16, 2025 via email

@lemmingDev
Copy link
Owner

Well, I'm going to try to make a NuS to COM port redirection app that will pipe info to/from a virtual com port so that it's as if the gamepad can connect to a real com port wirelessly (in addition to the wired connection if people really want)

I think all of the sim racing people have been asking for that feature for ages
If they can connect to NuS instead of COM port, maybe I won't go that far though

For Windows, this will create the needed com ports https://github.com/paulakg4/com0com

and on Linux, it seems this will work https://sourceforge.net/projects/tty0tty/

Just need a new app to forward all info between the NuS and virtual COM

I asked ChatGPT just now to write the code, and it did! I'll get the NuS working right first though

@lemmingDev
Copy link
Owner

Oh, and temp would come from some type of temp sensor I guess. Didn't they disable the ESP32 internal temp reading functionality ages ago?

@lemmingDev
Copy link
Owner

lemmingDev commented Feb 16, 2025

Actually, seems doable - though it is reading a little high. Could probably do with taking 20deg off the value
It does however respond to temp increases/decreases at an expected rate

#ifdef __cplusplus
extern "C" {
#endif
uint8_t temprature_sens_read();
#ifdef __cplusplus
}
#endif

void setup() {
  Serial.begin(115200);
}

void loop() {
  uint8_t temp = temprature_sens_read();
  float temperatureC = (temp - 32) / 1.8; // Fahrenheit to Celsius.
  Serial.print("Temperature: ");
  Serial.print(temperatureC);
  Serial.println(" °C");
  delay(1000);
}

@lemmingDev
Copy link
Owner

lemmingDev commented Feb 16, 2025

Oh - and seems the S3 and C3 have actual support

#include "driver/temp_sensor.h"

void initTempSensor(){
    temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();
    temp_sensor.dac_offset = TSENS_DAC_L2;  // TSENS_DAC_L2 is default; L4(-40°C ~ 20°C), L2(-10°C ~ 80°C), L1(20°C ~ 100°C), L0(50°C ~ 125°C)
    temp_sensor_set_config(temp_sensor);
    temp_sensor_start();
}

void setup() {
  Serial.begin(115200);
  initTempSensor();
}

void loop() {
  Serial.print("Temperature: ");
  float result = 0;
  temp_sensor_read_celsius(&result);
  Serial.print(result);
  Serial.println(" °C");
  delay(5000);
}

Can probably get some detection of what board they're using and use the correct method to get internal SoC temp

#if CONFIG_IDF_TARGET_ESP32
.................
#elif CONFIG_IDF_TARGET_ESP32C3
.................
#elif CONFIG_IDF_TARGET_ESP32S3
.................
#else
'''''''''''''''
#endif

Make setTemp() use internal temp and setTemp(float temp) customisable

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 17, 2025

I just did

internalTemperature = temperatureRead();

in my testing sketch - https://gitlab.com/leet/ble-gamepad-collection/-/blame/main/platformio/esp32base-test-02/src/main.cpp?ref_type=heads#L153

Should only be needed for the example sketch, not sure it would be the libraries job to wire up the temp sensor. Nice to or even better to be able to disable or override.

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 24, 2025

So I ended up with

// Set custom advertising with HID Service UUID
void setAdvertisingData() {
    Serial.println("Clearing old advertisement data...");
    pAdvertising->clearData(); // Important: Clear old advertisement data

    NimBLEAdvertisementData advData;

    //advData.clearData();
    advData.setAppearance(HID_GAMEPAD);
    //advData.setName("lnx-gp");
    //advData.setShortName("lnx-gp");
    advData.setShortName("lgp");
    advData.addTxPower();

    advData.addServiceUUID(hidDevice->getHidService()->getUUID());

    // Add battery level to Manufacturer Data
    uint8_t manufacturerData[10];
    //uint8_t manufacturerData[11];
    //manufacturer code (0x02E5 for Espressif)
    manufacturerData[0] = 0xff; // manufacturer data value - custom
    manufacturerData[1] = 0xff;
    manufacturerData[2] = 0xa0; // begin sig
    manufacturerData[3] = 0x00;
    manufacturerData[4] = advertiseInterval;    // how often broadcast
    manufacturerData[5] = batteryLevel;         // battery level
    manufacturerData[6] = chargingState;        // charging state
    manufacturerData[7] = uint8_t(internalTempInt);  // temp of SOC
    manufacturerData[8] = uint8_t(internalTempDec);  // temp below decimal
    manufacturerData[9] = 0x00; // end sig
    //manufacturerData[10] = 0xa0;

    advData.setManufacturerData(std::string((char *)manufacturerData, sizeof(manufacturerData)));

    size_t advSize = advData.getPayload().size();
    Serial.printf("Advertisement Data Size: %d bytes (Max: 31 bytes)\n", advSize);

    if (advSize > 31) {
        Serial.println("⚠️ Warning: Advertisement data exceeds 31 bytes!");
    } else {
        Serial.println("✅ Advertisement data is within limits.");
    }

    pAdvertising->setAdvertisementData(advData);
    pAdvertising->setConnectableMode(BLE_GAP_CONN_MODE_UND);
    pAdvertising->setDiscoverableMode(BLE_GAP_DISC_MODE_GEN);
}

Runnable demo can be seen in main.cpp

I did learn a bunch about bluetooth advertising, which might be related to #265

Basically, the advertising is used by a Host Server to list devices, where the advertising packet is only 31 bytes big including the headers (control data), which my testings means there is only really 28 bytes, which includes the device name. One has to balance the number of items advertised, like TX power and name length. I don't know if we can give a warning in the library about going over this limit.

@lemmingDev
Copy link
Owner

lemmingDev commented Feb 24, 2025

Apparently, the response can be used, as well (which effectively doubles the size), as I did for adding Nordic UART Service.
h2zero/NimBLE-Arduino#135

    // Can't add Nordic UART Service UUID to the main advertisement as it makes it larger than 31 bytes
    // It's not strictly needed anyway
    //NIMBLE_LOGD(LOG_TAG, "Adding  Nordic UART Service UUID to main NimBLE server advertising");
    //pAdvertising->addServiceUUID(pService->getUUID());
    
    // Get around the above issue by adding Nordic UART Service UUID to NimBLEAdvertisementData scanResponseData;
    // Add it in a scan response instead so devices can still see it has that capability
    // https://github.com/h2zero/NimBLE-Arduino/issues/135
    NIMBLE_LOGD(LOG_TAG, "Adding  Nordic UART Service UUID to advertising scan response data");
    NimBLEAdvertisementData scanResponseData;             // Create NimBLEAdvertisementData object
    scanResponseData.addServiceUUID(pService->getUUID()); // Add UUID
    pAdvertising->setScanResponseData(scanResponseData);  // Assign the scan response data
    
    NIMBLE_LOGD(LOG_TAG, "Main NimBLE server advertising started!");
    pAdvertising->start();

@LeeNX
Copy link
Contributor Author

LeeNX commented Feb 25, 2025

I saw scanResponse as was thinking I might need to look into that (have not wrapped my head around scanResponse yet), but as long as I keep the other advertising data small (like name and manufacturerData - thinking of ways to push the data usage, compression seems over kill for 31 bytes and not human readable in LightBlue or similar BLE scanners) should be enough.

Also, while looking at scanResponse, it needs a connection for the request of scanResponse, moves away from the broadcast idea and using Home Assistant (BLE Proxy).

So @lemmingDev , big question, will you add a way to use the manufacturerData inside of ESP32-BLE-Gamepad? Or possibly show me how to get to addServiceUUID(hidDevice->getHidService()->getUUID(), so that I can re-build the advertising data? First option would be a win, but second option would work too, I think. Thanks!

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

2 participants