77#include < stdio.h>
88
99#include " counter_protocol.h"
10+ #include " device_name.h"
1011#include " measurement_json.h"
1112#include " version.h"
1213
1314namespace ble {
1415namespace {
1516
16- constexpr char BLE_DEVICE_NAME [] = " BeeCounter" ;
17+ // The advertised local name, "HiveTraffic-AB:12", built once in begin() from
18+ // this counter's own BLE address so several in range are distinguishable in a
19+ // scan list (include/device_name.h). File scope rather than a local in begin()
20+ // because the log line below reads it too, and because it is the one place the
21+ // name exists — nothing should reconstruct it.
22+ char deviceName[devicename::CAPACITY ];
23+
1724constexpr char SVC_BEECOUNTER [] = " 8e8b0101-7a1c-4b9e-9a2f-1d6e0b9c1a01" ;
1825constexpr char CHR_MEASUREMENT [] = " 8e8b0102-7a1c-4b9e-9a2f-1d6e0b9c1a01" ;
1926// Night mode: HiveHub writes a suspension DURATION here, and reads back the
@@ -352,6 +359,49 @@ class ServerCallbacks : public NimBLEServerCallbacks {
352359 }
353360};
354361
362+ // Fill deviceName with "HiveTraffic-AB:12", the product name suffixed with the
363+ // last two bytes of this counter's own BLE address.
364+ //
365+ // Must run AFTER NimBLEDevice::init(): the address comes from the controller,
366+ // and init() is what starts it — it blocks until the host and controller have
367+ // synced, so the address is readable the moment it returns, but not one line
368+ // earlier.
369+ //
370+ // The address NimBLE reports here is the one it goes on to advertise with:
371+ // both read the own-address type that init() settled, which is the controller's
372+ // public address whenever it has one — on the ESP32-C6 the factory eFuse MAC.
373+ // The suffix is therefore literally the tail of the address a scanner shows
374+ // beside the entry, needs no provisioning, and survives reboots, reflashes and
375+ // OTA updates.
376+ //
377+ // If the address cannot be read, the bare product name is advertised. That is
378+ // a far better failure than not advertising at all: a counter HiveHub cannot
379+ // see is invisible to the measurement read AND to the OTA relay, and HiveHub
380+ // finds it by the paired MAC regardless of what the name says.
381+ void buildDeviceName () {
382+ const NimBLEAddress address = NimBLEDevice::getAddress ();
383+ const bool haveAddress = !address.isNull ();
384+
385+ if (!devicename::build (deviceName, sizeof (deviceName),
386+ haveAddress ? address.getVal () : nullptr )) {
387+ // Only reachable if CAPACITY and the name it sizes ever disagree, which
388+ // the header's static_assert and test/test_device_name/ both rule out.
389+ // Left as a hard fallback rather than an assert: an unnamed counter
390+ // still counts bees and still relays firmware.
391+ deviceName[0 ] = ' \0 ' ;
392+ }
393+ if (!haveAddress) {
394+ Serial.println (F (" [BLE] no address available; advertising unsuffixed name" ));
395+ }
396+
397+ // Keep the GAP Device Name characteristic in step with the advertised one,
398+ // so a client that connects and reads it — rather than trusting the scan
399+ // response — sees the same identity.
400+ if (!NimBLEDevice::setDeviceName (deviceName)) {
401+ Serial.println (F (" [BLE] GAP device name not updated" ));
402+ }
403+ }
404+
355405// NimBLE stores these pointers for the lifetime of the server and never frees
356406// them: NimBLECharacteristic::setCallbacks() takes no ownership at all, so the
357407// old `new X(), true` form both fails to compile against NimBLE 2.5.x (the
@@ -367,7 +417,13 @@ ServerCallbacks serverCallbacks;
367417} // namespace
368418
369419void begin () {
370- NimBLEDevice::init (BLE_DEVICE_NAME );
420+ // init() takes a name because it must set one before the GATT server
421+ // exists; the address it is built from is only available once init() has
422+ // synced the host and controller, so the suffixed name is applied
423+ // immediately afterwards by buildDeviceName().
424+ NimBLEDevice::init (devicename::BASE );
425+ buildDeviceName ();
426+
371427 NimBLEServer* server = NimBLEDevice::createServer ();
372428 // false: never delete a statically allocated callback object.
373429 server->setCallbacks (&serverCallbacks, false );
@@ -404,18 +460,19 @@ void begin() {
404460 //
405461 // flags 3 (added by NimBLE at start())
406462 // 128-bit service UUID 18 (2 + 16)
407- // "BeeCounter " 12 (2 + 10 ) -> 33 > 31
463+ // "HiveTraffic-AB:12 " 19 (2 + 17 ) -> 40 > 31
408464 //
409465 // NimBLE 2.x leaves scan response DISABLED by default and does not silently
410466 // relocate the name, so setting all three on the advertisement overflows and
411467 // something is dropped — potentially advertising itself. A counter that does
412468 // not advertise is invisible to BOTH the measurement read and the OTA relay,
413469 // which locates it by a scan first (HiveHub ble_sensor.cpp::otaBegin).
414470 // Splitting them keeps the advertisement at 21 bytes and the scan response
415- // at 12, with room to spare on each.
471+ // at 19, with room to spare on each. The name is the element that grows, so
472+ // device_name.h static_asserts its own longest form against that 31.
416473 advertising->addServiceUUID (service->getUUID ());
417474 NimBLEAdvertisementData scanResponse;
418- scanResponse.setName (BLE_DEVICE_NAME );
475+ scanResponse.setName (deviceName );
419476 advertising->setScanResponseData (scanResponse);
420477 advertising->enableScanResponse (true );
421478 advertising->setMinInterval (ADV_INTERVAL_UNITS );
@@ -426,7 +483,9 @@ void begin() {
426483 Serial.println (F (" [BLE] ERROR: advertising failed to start" ));
427484 return ;
428485 }
429- Serial.printf (" [BLE] HiveTraffic %s advertising for HiveHub\n " ,
486+ // The name is logged, not just the version: it is what someone comparing
487+ // the serial console with a scan list on their phone needs to match up.
488+ Serial.printf (" [BLE] %s %s advertising for HiveHub\n " , deviceName,
430489 HIVETRAFFIC_FW_VERSION );
431490}
432491
0 commit comments