@ -22,6 +22,95 @@
// RX drain buffer size for overflow protection
// RX drain buffer size for overflow protection
# define BLE_RX_DRAIN_BUF_SIZE 32
# define BLE_RX_DRAIN_BUF_SIZE 32
// 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 ' ;
}
static SerialBLEInterface * instance = nullptr ;
static SerialBLEInterface * instance = nullptr ;
void SerialBLEInterface : : onConnect ( uint16_t connection_handle ) {
void SerialBLEInterface : : onConnect ( uint16_t connection_handle ) {
@ -133,8 +222,8 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
// Bluefruit.autoConnLed(false);
// Bluefruit.autoConnLed(false);
Bluefruit . configPrphBandwidth ( BANDWIDTH_MAX ) ;
Bluefruit . configPrphBandwidth ( BANDWIDTH_MAX ) ;
Bluefruit . begin ( ) ;
Bluefruit . begin ( ) ;
char dev_name [ 32 + 16 ] ;
// Resolve "@@MAC" now that the SoftDevice is active
if ( strcmp ( name , " @@MAC " ) = = 0 ) {
if ( strcmp ( name , " @@MAC " ) = = 0 ) {
ble_gap_addr_t addr ;
ble_gap_addr_t addr ;
if ( sd_ble_gap_addr_get ( & addr ) = = NRF_SUCCESS ) {
if ( sd_ble_gap_addr_get ( & addr ) = = NRF_SUCCESS ) {
@ -142,7 +231,11 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
addr . addr [ 5 ] , addr . addr [ 4 ] , addr . addr [ 3 ] , addr . addr [ 2 ] , addr . addr [ 1 ] , addr . addr [ 0 ] ) ;
addr . addr [ 5 ] , addr . addr [ 4 ] , addr . addr [ 3 ] , addr . addr [ 2 ] , addr . addr [ 1 ] , addr . addr [ 0 ] ) ;
}
}
}
}
sprintf ( dev_name , " %s%s " , prefix , name ) ;
// Build the BLE name with middle-truncation if needed to fit within the
// SoftDevice's default 31-byte GAP name limit and 29-byte scan response limit
char dev_name [ 32 + 16 ] ;
buildBLEName ( dev_name , sizeof ( dev_name ) , prefix , name ) ;
// Connection interval units: 1.25ms, supervision timeout units: 10ms
// Connection interval units: 1.25ms, supervision timeout units: 10ms
ble_gap_conn_params_t ppcp_params ;
ble_gap_conn_params_t ppcp_params ;