Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@
#define AUTO_ADD_ROOM_SERVER (1 << 3) // 0x08 - auto-add Room Server (ADV_TYPE_ROOM)
#define AUTO_ADD_SENSOR (1 << 4) // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR)

#define BLE_ADV_UPDATE_INTERVAL_MS 30000 // Update BLE advertising every 30 seconds

void MyMesh::writeOKFrame() {
uint8_t buf[1];
buf[0] = RESP_CODE_OK;
Expand Down Expand Up @@ -237,6 +239,8 @@ void MyMesh::addToOfflineQueue(const uint8_t frame[], int len) {
memcpy(offline_queue[offline_queue_len].buf, frame, len);
offline_queue_len++;
}

updateBLEUnreadCount();
}

int MyMesh::getFromOfflineQueue(uint8_t frame[]) {
Expand All @@ -248,11 +252,19 @@ int MyMesh::getFromOfflineQueue(uint8_t frame[]) {
for (int i = 0; i < offline_queue_len; i++) { // delete top item from queue
offline_queue[i] = offline_queue[i + 1];
}

updateBLEUnreadCount();

return len;
}
return 0; // queue is empty
}

void MyMesh::updateBLEUnreadCount() {
if (!_serial) return;
_serial->setUnreadCount((uint8_t)min(offline_queue_len, 255));
}

float MyMesh::getAirtimeBudgetFactor() const {
return _prefs.airtime_factor;
}
Expand Down Expand Up @@ -2158,6 +2170,8 @@ void MyMesh::checkSerialInterface() {
}

void MyMesh::loop() {
static unsigned long last_adv_update = 0;

BaseChatMesh::loop();

if (_cli_rescue) {
Expand All @@ -2172,6 +2186,14 @@ void MyMesh::loop() {
dirty_contacts_expiry = 0;
}

// Periodically update battery level in BLE advertising
if (millis() - last_adv_update >= BLE_ADV_UPDATE_INTERVAL_MS) {
if (_serial) {
_serial->setBatteryMilliVolts(board.getBattMilliVolts());
}
last_adv_update = millis();
}

#ifdef DISPLAY_CLASS
if (_ui) _ui->setHasConnection(_serial->isConnected());
#endif
Expand Down
2 changes: 2 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
void loop();
void handleCmdFrame(size_t len);
bool advert();

void updateBLEUnreadCount();
void enterCLIRescue();

int getRecentlyHeard(AdvertPath dest[], int max_num);
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/BaseSerialInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ class BaseSerialInterface {
virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
virtual size_t checkRecvFrame(uint8_t dest[]) = 0;

virtual void setUnreadCount(uint8_t count) {}
virtual void setBatteryMilliVolts(uint16_t millivolts) {}
};
86 changes: 77 additions & 9 deletions src/helpers/esp32/SerialBLEInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_WRITE_ENC_MITM);
pRxCharacteristic->setCallbacks(this);

pServer->getAdvertising()->addServiceUUID(SERVICE_UUID);
// Setup advertising with manufacturer data
pAdvertising = pServer->getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
applyAdvertisingData();
}

// -------- BLESecurityCallbacks methods
Expand Down Expand Up @@ -140,10 +143,10 @@ void SerialBLEInterface::enable() {

// Start advertising

//pServer->getAdvertising()->setMinInterval(500);
//pServer->getAdvertising()->setMaxInterval(1000);
//pAdvertising->setMinInterval(500);
//pAdvertising->setMaxInterval(1000);

pServer->getAdvertising()->start();
pAdvertising->start();
adv_restart_time = 0;
}

Expand All @@ -152,7 +155,7 @@ void SerialBLEInterface::disable() {

BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");

pServer->getAdvertising()->stop();
pAdvertising->stop();
pServer->disconnect(last_conn_id);
pService->stop();
oldDeviceConnected = deviceConnected = false;
Expand Down Expand Up @@ -223,16 +226,16 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {

BLE_DEBUG_PRINTLN("SerialBLEInterface -> disconnecting...");

//pServer->getAdvertising()->setMinInterval(500);
//pServer->getAdvertising()->setMaxInterval(1000);
//pAdvertising->setMinInterval(500);
//pAdvertising->setMaxInterval(1000);

adv_restart_time = millis() + ADVERT_RESTART_DELAY;
} else {
BLE_DEBUG_PRINTLN("SerialBLEInterface -> stopping advertising");
BLE_DEBUG_PRINTLN("SerialBLEInterface -> connecting...");
// connecting
// do stuff here on connecting
pServer->getAdvertising()->stop();
pAdvertising->stop();
adv_restart_time = 0;
}
oldDeviceConnected = deviceConnected;
Expand All @@ -241,13 +244,78 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
if (adv_restart_time && millis() >= adv_restart_time) {
if (pServer->getConnectedCount() == 0) {
BLE_DEBUG_PRINTLN("SerialBLEInterface -> re-starting advertising");
pServer->getAdvertising()->start(); // re-Start advertising
pAdvertising->start(); // re-Start advertising
}
adv_restart_time = 0;
}

// Apply pending advertising data changes when not connected
if (_advDataDirty && !deviceConnected) {
applyAdvertisingData();
_advDataDirty = false;
}

return 0;
}

bool SerialBLEInterface::isConnected() const {
return deviceConnected; //pServer != NULL && pServer->getConnectedCount() > 0;
}

void SerialBLEInterface::setUnreadCount(uint8_t count) {
if (_advStatus.unread_count == count) return;
_advStatus.unread_count = count;
onAdvStatusChanged();
}

void SerialBLEInterface::setBatteryMilliVolts(uint16_t millivolts) {
uint8_t encoded;
if (millivolts < 2500) {
encoded = 0;
} else if (millivolts > 5000) {
encoded = 250;
} else {
encoded = (millivolts - 2500) / 10;
}
if (_advStatus.battery_voltage == encoded) return;
_advStatus.battery_voltage = encoded;
onAdvStatusChanged();
}

void SerialBLEInterface::onAdvStatusChanged() {
if (deviceConnected) {
_advDataDirty = true;
} else if (_isEnabled) {
applyAdvertisingData();
}
}

void SerialBLEInterface::applyAdvertisingData() {
if (!pAdvertising) {
return;
}

// Build manufacturer specific data:
// Bytes 0-1: Manufacturer ID (little-endian)
// Bytes 2-3: AdvertisingStatus struct
uint8_t mfr_data[4];
mfr_data[0] = MESHCORE_MANUFACTURER_ID & 0xFF; // Manufacturer ID low byte
mfr_data[1] = (MESHCORE_MANUFACTURER_ID >> 8) & 0xFF; // Manufacturer ID high byte
mfr_data[2] = _advStatus.unread_count;
mfr_data[3] = _advStatus.battery_voltage;

// Slave Connection Interval Range (AD type 0x12)
// min=40ms (0x0020), max=80ms (0x0040)
uint8_t conn_interval[] = {0x05, 0x12, 0x20, 0x00, 0x40, 0x00};

BLEAdvertisementData advData;
advData.setFlags(ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
advData.setCompleteServices(BLEUUID(SERVICE_UUID));
advData.setManufacturerData(std::string((char*)mfr_data, sizeof(mfr_data)));
advData.addData(std::string((char*)conn_interval, sizeof(conn_interval)));

pAdvertising->setAdvertisementData(advData);

BLE_DEBUG_PRINTLN("applyAdvertisingData: unread=%d, battery_voltage=%d",
_advStatus.unread_count, _advStatus.battery_voltage);
}
29 changes: 29 additions & 0 deletions src/helpers/esp32/SerialBLEInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <BLEAdvertising.h>

// Manufacturer ID for MeshCore (using 0xFFFF for development/testing)
// In production, you should register with Bluetooth SIG for a unique ID
#define MESHCORE_MANUFACTURER_ID 0xFFFF

// Advertising data structure (packed into manufacturer specific data)
// Byte 0: unread message count (0-255)
// Byte 1: battery voltage encoded as (mV - 2500) / 10, range 0-250 (2500-5000 mV), 0xFF = unknown
struct AdvertisingStatus {
uint8_t unread_count;
uint8_t battery_voltage;
};

class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLEServerCallbacks, BLECharacteristicCallbacks {
BLEServer *pServer;
BLEService *pService;
BLECharacteristic * pTxCharacteristic;
BLEAdvertising *pAdvertising;
bool deviceConnected;
bool oldDeviceConnected;
bool _isEnabled;
Expand All @@ -18,6 +32,10 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
unsigned long _last_write;
unsigned long adv_restart_time;

// Advertising status data
AdvertisingStatus _advStatus;
bool _advDataDirty;

struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
Expand Down Expand Up @@ -52,13 +70,17 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
SerialBLEInterface() {
pServer = NULL;
pService = NULL;
pAdvertising = NULL;
deviceConnected = false;
oldDeviceConnected = false;
adv_restart_time = 0;
_isEnabled = false;
_last_write = 0;
last_conn_id = 0;
send_queue_len = recv_queue_len = 0;
_advStatus.unread_count = 0;
_advStatus.battery_voltage = 0xFF; // unknown
_advDataDirty = false;
}

/**
Expand All @@ -79,6 +101,13 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;

void setUnreadCount(uint8_t count) override;
void setBatteryMilliVolts(uint16_t millivolts) override;

private:
void onAdvStatusChanged();
void applyAdvertisingData();
};

#if BLE_DEBUG_LOGGING && ARDUINO
Expand Down