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
123 changes: 123 additions & 0 deletions arch/nrf52/extra_scripts/patch_bluefruit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Patches applied:
1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size
2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect
3. bluefruit.h: Add bluefruit_blinky_cb as friend so it can access _led_conn
4. bluefruit.cpp: Guard LED toggle in blinky callback with _led_conn check

Bug description:
- When a BLE central disconnects unexpectedly (reason=8 supervision timeout),
Expand Down Expand Up @@ -133,6 +135,89 @@ def _patch_ble_connection_source(source: Path) -> bool:
return False


def _patch_bluefruit_header_led(source: Path) -> bool:
"""
Add bluefruit_blinky_cb as a friend function so it can access _led_conn.

Without this, the blink timer callback toggles LED_BLUE unconditionally,
ignoring autoConnLed(false).

Returns True if patch was applied or already applied, False on error.
"""
try:
content = source.read_text()

# Check if already patched
if "bluefruit_blinky_cb" in content:
return True # Already patched

original_pattern = ''' friend void adafruit_soc_task(void* arg);
friend class BLECentral;'''

patched_pattern = ''' friend void adafruit_soc_task(void* arg);
friend void bluefruit_blinky_cb(TimerHandle_t xTimer);
friend class BLECentral;'''

if original_pattern not in content:
print("Bluefruit patch: WARNING - bluefruit.h friend pattern not found")
return False

content = content.replace(original_pattern, patched_pattern)
source.write_text(content)

if "bluefruit_blinky_cb" not in source.read_text():
return False

return True
except Exception as e:
print(f"Bluefruit patch: ERROR patching bluefruit.h: {e}")
return False


def _patch_bluefruit_source_led(source: Path) -> bool:
"""
Guard the LED toggle in bluefruit_blinky_cb with a _led_conn check.

The blink timer can be inadvertently started by Advertising.start() ->
setConnLedInterval() -> xTimerChangePeriod() (FreeRTOS starts dormant
timers as a side effect). Without this guard, the LED toggles even when
autoConnLed(false) has been called.

Returns True if patch was applied or already applied, False on error.
"""
try:
content = source.read_text()

# Check if already patched
if "Bluefruit._led_conn" in content:
return True # Already patched

original_pattern = '''static void bluefruit_blinky_cb( TimerHandle_t xTimer )
{
(void) xTimer;
digitalToggle(LED_BLUE);'''

patched_pattern = '''void bluefruit_blinky_cb( TimerHandle_t xTimer )
{
(void) xTimer;
if ( Bluefruit._led_conn ) digitalToggle(LED_BLUE);'''

if original_pattern not in content:
print("Bluefruit patch: WARNING - bluefruit.cpp blinky_cb pattern not found")
return False

content = content.replace(original_pattern, patched_pattern)
source.write_text(content)

if "Bluefruit._led_conn" not in source.read_text():
return False

return True
except Exception as e:
print(f"Bluefruit patch: ERROR patching bluefruit.cpp: {e}")
return False


def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument
framework_path = env.get("PLATFORMFW_DIR")
if not framework_path:
Expand Down Expand Up @@ -185,6 +270,44 @@ def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-arg
print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}")
patch_failed = True

# Patch bluefruit.h - add friend declaration for blinky callback
bf_header = bluefruit_lib / "bluefruit.h"
if bf_header.exists():
before = bf_header.read_text()
success = _patch_bluefruit_header_led(bf_header)
after = bf_header.read_text()

if success:
if before != after:
print("Bluefruit patch: OK - Applied bluefruit.h fix (added blinky_cb friend)")
else:
print("Bluefruit patch: OK - bluefruit.h already patched")
else:
print("Bluefruit patch: FAILED - bluefruit.h")
patch_failed = True
else:
print(f"Bluefruit patch: ERROR - bluefruit.h not found at {bf_header}")
patch_failed = True

# Patch bluefruit.cpp - guard LED toggle with _led_conn check
bf_source = bluefruit_lib / "bluefruit.cpp"
if bf_source.exists():
before = bf_source.read_text()
success = _patch_bluefruit_source_led(bf_source)
after = bf_source.read_text()

if success:
if before != after:
print("Bluefruit patch: OK - Applied bluefruit.cpp fix (guard blinky_cb with _led_conn)")
else:
print("Bluefruit patch: OK - bluefruit.cpp already patched")
else:
print("Bluefruit patch: FAILED - bluefruit.cpp")
patch_failed = True
else:
print(f"Bluefruit patch: ERROR - bluefruit.cpp not found at {bf_source}")
patch_failed = True

if patch_failed:
print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.")
env.Exit(1)
Expand Down
4 changes: 4 additions & 0 deletions examples/companion_radio/DataStore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
file.read((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88
file.read((uint8_t *)&_prefs.led_ble_mode, sizeof(_prefs.led_ble_mode)); // 89
file.read((uint8_t *)&_prefs.led_status_mode, sizeof(_prefs.led_status_mode)); // 90

file.close();
}
Expand Down Expand Up @@ -267,6 +269,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
file.write((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88
file.write((uint8_t *)&_prefs.led_ble_mode, sizeof(_prefs.led_ble_mode)); // 89
file.write((uint8_t *)&_prefs.led_status_mode, sizeof(_prefs.led_status_mode)); // 90

file.close();
}
Expand Down
32 changes: 32 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,12 @@ void MyMesh::begin(bool has_display) {
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER);
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
_prefs.led_ble_mode = constrain(_prefs.led_ble_mode, 0, 3);
_prefs.led_status_mode = constrain(_prefs.led_status_mode, 0, 1);

// Sync LED prefs to sensor manager (exposed as custom vars)
sensors.led_ble_mode = _prefs.led_ble_mode;
sensors.led_status_mode = _prefs.led_status_mode;

#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {
Expand Down Expand Up @@ -1680,6 +1686,15 @@ void MyMesh::handleCmdFrame(size_t len) {
savePrefs();
}
#endif
// Update node preferences for LED settings
if (strcmp(sp, "led.ble") == 0) {
_prefs.led_ble_mode = sensors.led_ble_mode;
_serial->setLedBleMode(_prefs.led_ble_mode);
savePrefs();
} else if (strcmp(sp, "led.status") == 0) {
_prefs.led_status_mode = sensors.led_status_mode;
savePrefs();
}
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
Expand Down Expand Up @@ -1844,6 +1859,23 @@ void MyMesh::checkCLIRescueCmd() {
_prefs.ble_pin = atoi(&config[4]);
savePrefs();
Serial.printf(" > pin is now %06d\n", _prefs.ble_pin);
} else if (memcmp(config, "led.ble ", 8) == 0) {
if (sensors.setSettingValue("led.ble", &config[8])) {
_prefs.led_ble_mode = sensors.led_ble_mode;
_serial->setLedBleMode(_prefs.led_ble_mode);
savePrefs();
Serial.printf(" > led.ble is now %d\n", _prefs.led_ble_mode);
} else {
Serial.println(" Error: must be 0-3 (0=enabled, 1=disconn_only, 2=conn_only, 3=disabled)");
}
} else if (memcmp(config, "led.status ", 11) == 0) {
if (sensors.setSettingValue("led.status", &config[11])) {
_prefs.led_status_mode = sensors.led_status_mode;
savePrefs();
Serial.printf(" > led.status is now %d\n", _prefs.led_status_mode);
} else {
Serial.println(" Error: must be 0-1 (0=enabled, 1=disabled)");
}
} else {
Serial.printf(" Error: unknown config: %s\n", config);
}
Expand Down
10 changes: 10 additions & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
#define ADVERT_LOC_NONE 0
#define ADVERT_LOC_SHARE 1

#define LED_BLE_ENABLED 0
#define LED_BLE_DISCONN_ONLY 1
#define LED_BLE_CONN_ONLY 2
#define LED_BLE_DISABLED 3

#define LED_STATUS_ENABLED 0
#define LED_STATUS_DISABLED 1

struct NodePrefs { // persisted to file
float airtime_factor;
char node_name[32];
Expand All @@ -31,4 +39,6 @@ struct NodePrefs { // persisted to file
uint8_t client_repeat;
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
uint8_t led_ble_mode; // LED_BLE_ENABLED, LED_BLE_DISCONN_ONLY, LED_BLE_CONN_ONLY, LED_BLE_DISABLED
uint8_t led_status_mode; // LED_STATUS_ENABLED, LED_STATUS_DISABLED
};
4 changes: 2 additions & 2 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void setup() {
);

#ifdef BLE_PIN_CODE
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin(), the_mesh.getNodePrefs()->led_ble_mode);
#else
serial_interface.begin(Serial);
#endif
Expand Down Expand Up @@ -198,7 +198,7 @@ void setup() {
WiFi.begin(WIFI_SSID, WIFI_PWD);
serial_interface.begin(TCP_PORT);
#elif defined(BLE_PIN_CODE)
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin(), the_mesh.getNodePrefs()->led_ble_mode);
#elif defined(SERIAL_RX)
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
companion_serial.begin(115200);
Expand Down
24 changes: 24 additions & 0 deletions examples/companion_radio/ui-new/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,13 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i

void UITask::userLedHandler() {
#ifdef PIN_STATUS_LED
if (_node_prefs->led_status_mode == LED_STATUS_DISABLED) {
if (led_state != 0) {
led_state = 0;
digitalWrite(PIN_STATUS_LED, !LED_STATE_ON);
}
return;
}
int cur_time = millis();
if (cur_time > next_led_change) {
if (led_state == 0) {
Expand All @@ -674,6 +681,22 @@ void UITask::userLedHandler() {
#endif
}

void UITask::bleLedHandler() {
#ifdef BLE_LED_PIN
if (_node_prefs->led_ble_mode != LED_BLE_DISCONN_ONLY) return;

// Disconnected-only mode: blink when not connected
if (hasConnection()) return;

unsigned long now = millis();
if (now >= next_ble_led_change) {
ble_led_state = !ble_led_state;
digitalWrite(BLE_LED_PIN, ble_led_state ? LED_STATE_ON : !LED_STATE_ON);
next_ble_led_change = now + 250;
}
#endif
}

void UITask::setCurrScreen(UIScreen* c) {
curr = c;
_next_refresh = 100;
Expand Down Expand Up @@ -785,6 +808,7 @@ void UITask::loop() {
}

userLedHandler();
bleLedHandler();

#ifdef PIN_BUZZER
if (buzzer.isPlaying()) buzzer.loop();
Expand Down
11 changes: 11 additions & 0 deletions examples/companion_radio/ui-new/UITask.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
#define LED_STATE_ON 1
#endif

#ifdef LED_CONN
#define BLE_LED_PIN LED_CONN
#elif defined(LED_BLUE)
#define BLE_LED_PIN LED_BLUE
#endif

#ifdef PIN_BUZZER
#include <helpers/ui/buzzer.h>
#endif
Expand Down Expand Up @@ -43,6 +49,10 @@ class UITask : public AbstractUITask {
int next_led_change = 0;
int last_led_increment = 0;
#endif
#ifdef BLE_LED_PIN
int ble_led_state = 0;
unsigned long next_ble_led_change = 0;
#endif

#ifdef PIN_USER_BTN_ANA
unsigned long _analogue_pin_read_millis = millis();
Expand All @@ -54,6 +64,7 @@ class UITask : public AbstractUITask {
UIScreen* curr;

void userLedHandler();
void bleLedHandler();

// Button action handlers
char checkDisplayOn(char c);
Expand Down
26 changes: 26 additions & 0 deletions examples/companion_radio/ui-orig/UITask.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ void UITask::userLedHandler() {
static int next_change = 0;
static int last_increment = 0;

if (_node_prefs->led_status_mode == LED_STATUS_DISABLED) {
if (state != 0) {
state = 0;
digitalWrite(PIN_STATUS_LED, !LED_STATE_ON);
}
return;
}
int cur_time = millis();
if (cur_time > next_change) {
if (state == 0) {
Expand All @@ -285,6 +292,24 @@ void UITask::userLedHandler() {
#endif
}

void UITask::bleLedHandler() {
#ifdef BLE_LED_PIN
if (_node_prefs->led_ble_mode != LED_BLE_DISCONN_ONLY) return;

// Disconnected-only mode: blink when not connected
if (hasConnection()) return;

static int ble_led_state = 0;
static unsigned long next_ble_led_change = 0;
unsigned long now = millis();
if (now >= next_ble_led_change) {
ble_led_state = !ble_led_state;
digitalWrite(BLE_LED_PIN, ble_led_state ? LED_STATE_ON : !LED_STATE_ON);
next_ble_led_change = now + 250;
}
#endif
}

/*
hardware-agnostic pre-shutdown activity should be done here
*/
Expand Down Expand Up @@ -323,6 +348,7 @@ void UITask::loop() {
}
#endif
userLedHandler();
bleLedHandler();

#ifdef PIN_BUZZER
if (buzzer.isPlaying()) buzzer.loop();
Expand Down
Loading