Fix BLE name fallback to T1000-E-BOOT for long node names#1801
Fix BLE name fallback to T1000-E-BOOT for long node names#1801andrewdefilippis wants to merge 1 commit intomeshcore-dev:devfrom
Conversation
The Nordic SoftDevice S140 defaults to a 31-byte maximum GAP device
name. When "MeshCore-" (9 bytes) + node_name exceeds 31 bytes (i.e.
node names >= 23 chars), sd_ble_gap_device_name_set() silently fails
and the name falls back to USB_PRODUCT ("T1000-E-BOOT").
Add UTF-8 safe middle-truncation that preserves the beginning and end
of the node name (where users place emoji and device-type identifiers),
fitting the result within the 29-byte scan response limit so it
advertises as COMPLETE_LOCAL_NAME.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
For reference Plan: T1000-E BLE Device Name FixProblemNode names >= 23 characters cause the BLE device name to show as "T1000-E-BOOT" instead of "MeshCore-<nodename>". See research-ble-name-fix.md for root cause analysis. GoalTruncate the BLE device name to fit the SoftDevice's 31-byte GAP name limit and the 29-byte scan response limit, while preserving the beginning and end of the node name (where users place emoji and device-type identifiers). Use middle-truncation with a DesignTarget lengthThe BLE name is composed of
We target 29 bytes total so the scan response can advertise the name as With Middle-truncation algorithmWhen
UTF-8 safetyA UTF-8 character's byte length is determined by its leading byte:
The algorithm only advances by complete characters, so it will never split a multi-byte sequence. The existing codebase already uses this pattern ( ExamplesPrefix:
Code locationA static helper function // Maximum BLE device name length that fits in a scan response AD element.
// 31 (BLE_GAP_ADV_SET_DATA_SIZE_MAX) - 2 (AD length + type bytes) = 29
#define BLE_NAME_MAX_LEN 29
static size_t utf8CharLen(uint8_t lead_byte) {
if (lead_byte < 0x80) return 1;
if ((lead_byte & 0xE0) == 0xC0) return 2;
if ((lead_byte & 0xF0) == 0xE0) return 3;
if ((lead_byte & 0xF8) == 0xF0) return 4;
return 1; // invalid lead byte — treat as single byte to avoid infinite loops
}
// Build a BLE device name from prefix + node name, middle-truncating with
// ".." if the result would exceed BLE_NAME_MAX_LEN bytes.
// Truncation is UTF-8 safe and preserves the beginning and end of the name.
static void buildBLEName(char* dest, size_t dest_size,
const char* prefix, const char* name)
{
size_t prefix_len = strlen(prefix);
size_t name_len = strlen(name);
// Fast path: fits without truncation
if (prefix_len + name_len <= BLE_NAME_MAX_LEN) {
snprintf(dest, dest_size, "%s%s", prefix, name);
return;
}
size_t name_budget = BLE_NAME_MAX_LEN - prefix_len;
const char sep[] = "..";
const size_t sep_len = 2;
// If budget is too small for meaningful middle-truncation (need at least
// 1 char + sep + 1 char), just take the head
if (name_budget <= sep_len + 2) {
memcpy(dest, prefix, prefix_len);
size_t i = 0;
while (i < name_budget && i < name_len) {
size_t cl = utf8CharLen((uint8_t)name[i]);
if (i + cl > name_budget) break;
i += cl;
}
memcpy(dest + prefix_len, name, i);
dest[prefix_len + i] = '\0';
return;
}
size_t content_budget = name_budget - sep_len;
size_t head_target = content_budget / 2;
size_t tail_target = content_budget - head_target;
// Walk forward: collect head (complete UTF-8 characters up to head_target bytes)
size_t head_len = 0;
{
size_t i = 0;
while (i < name_len) {
size_t cl = utf8CharLen((uint8_t)name[i]);
if (i + cl > head_target) break;
i += cl;
}
head_len = i;
}
// Walk backward: collect tail (complete UTF-8 characters up to tail_target bytes)
size_t tail_start = name_len;
size_t tail_len = 0;
{
size_t i = name_len;
while (i > 0 && tail_len < tail_target) {
// Find start of previous UTF-8 character
size_t prev = i - 1;
while (prev > 0 && ((uint8_t)name[prev] & 0xC0) == 0x80)
prev--;
size_t cl = i - prev;
if (tail_len + cl > tail_target) break;
tail_len += cl;
i = prev;
}
tail_start = name_len - tail_len;
}
// Assemble: prefix + head + ".." + tail
size_t pos = 0;
memcpy(dest + pos, prefix, prefix_len); pos += prefix_len;
memcpy(dest + pos, name, head_len); pos += head_len;
memcpy(dest + pos, sep, sep_len); pos += sep_len;
memcpy(dest + pos, name + tail_start, tail_len); pos += tail_len;
dest[pos] = '\0';
}Changes to
|

Fixes #1769
Problem
When a user sets a node name of 23+ characters, the BLE advertised device name shows as
T1000-E-BOOTinstead ofMeshCore-<nodename>. Names of 21-22 characters get silently truncated to 20 characters of node name. Only names <= 20 characters display correctly.Root cause: The Nordic SoftDevice S140 v7.3.0 defaults to a maximum GAP device name length of 31 bytes (
BLE_GAP_DEVNAME_DEFAULT_LEN). The Bluefruit library does not configureBLE_GAP_CFG_DEVICE_NAME, so this default applies.Bluefruit::setName()callssd_ble_gap_device_name_set()without checking the return value — when"MeshCore-"(9 bytes) + node_name exceeds 31 bytes, the call silently fails and the GAP name remains asUSB_PRODUCT("T1000-E-BOOT"), which was set byBluefruit.begin().What is changing
Added a
buildBLEName()helper inSerialBLEInterface.cppthat middle-truncates the BLE device name to fit within the 29-byte scan response limit (31-byte packet minus 2-byte AD overhead). The truncation:📱 Andrew KI7PXC T1000-E,HowlBot🤖,Vya📡)..as the separator (2 bytes)Examples
📱 Andrew KI7PXC T1000-E(26 bytes)MeshCore-📱 Andr..C T1000-E(29 bytes)ABCDEFGHIJKLMNOPQRSTUVW(23 bytes)MeshCore-ABCDEFGHI..OPQRSTUVW(29 bytes)📱📡🧱🤖💀🔥(24 bytes)MeshCore-📱📡..💀🔥(27 bytes)HowlBot🤖(11 bytes)MeshCore-HowlBot🤖(20 bytes, no truncation)Why it is changing
Without this fix, any NRF52 user with a node name >= 23 characters sees
T1000-E-BOOT(or the board'sUSB_PRODUCT) as their BLE device name, making it impossible to identify the device when pairing.Expected outcome
.., preserving both endsCOMPLETE_LOCAL_NAME(no further truncation by the Bluefruit library)sd_ble_gap_device_name_set()always succeeds (name <= 29 < 31 byte limit)Testing
t1000e_companion_radio_bletarget successfullyReferences
ble_gap.h—BLE_GAP_DEVNAME_DEFAULT_LEN(line 541),BLE_GAP_ADV_SET_DATA_SIZE_MAX(line 246)setName()does not check return valueBLE_GAP_CFG_DEVICE_NAMEcommented out🤖 Generated with Claude Code