feat: add customizable companion Bluetooth names
This commit is contained in:
1 parent
42b6a0df60
commit
65e40ff05f
20 files changed
+2060
-1737
No files matched your search
@@ -221,6 +221,7 @@ catalog; bytes `0x2C`-`0x31` are parked and `0x35` is unused.
|
||||
| `0x42` / `0x43` | `CMD_GET_RADIO_FEM_RXGAIN` / `CMD_SET_RADIO_FEM_RXGAIN` | Read or set FEM receive gain. |
|
||||
| `0x44` / `0x45` | `CMD_GET_RADIO_RXGAIN` / `CMD_SET_RADIO_RXGAIN` | Read or set the radio chip's boosted receive-gain mode. |
|
||||
| `0x46` / `0x47` | `CMD_GET_WIFI_POWER_SAVE` / `CMD_SET_WIFI_POWER_SAVE` | Read or set ESP32 Companion WiFi modem sleep. |
|
||||
| `0x48` / `0x49` | `CMD_GET_BLUETOOTH_NAME` / `CMD_SET_BLUETOOTH_NAME` | Read or set the independent Bluetooth device name. |
|
||||
|
||||
The sections below detail the most common frames. Refer to the source named
|
||||
above for command bodies that are not expanded here.
|
||||
@@ -260,6 +261,18 @@ coexistence requires modem sleep. A storage failure also returns
|
||||
WiFi driver, SET still returns `RESP_CODE_OK` and the mode is applied on the
|
||||
next connection. Device `powersaving` does not overwrite this setting.
|
||||
|
||||
The Bluetooth-name pair is available on Companion builds regardless of the
|
||||
currently selected transport, so a host can configure a BLE-capable image over
|
||||
USB or TCP before rebooting into it. `CMD_GET_BLUETOOTH_NAME` has no body and
|
||||
replies with `RESP_CODE_OK`, a one-byte mode (`0` for the default name, `1` for
|
||||
a custom name), then the effective UTF-8 device name without a terminator.
|
||||
`CMD_SET_BLUETOOTH_NAME` is followed by zero to 31 UTF-8 bytes. A non-empty
|
||||
body stores that exact complete Bluetooth name; a zero-length body clears the
|
||||
override and restores `MeshCore-<advert name>`. A successful SET replies with
|
||||
`RESP_CODE_OK`, and the new label takes effect after reboot. Embedded NULs,
|
||||
control characters, malformed UTF-8, or oversized values return
|
||||
`ERR_CODE_ILLEGAL_ARG`; a storage failure returns `ERR_CODE_BAD_STATE`.
|
||||
|
||||
### 1. App Start
|
||||
|
||||
**Purpose**: Initialize communication with the device. Must be sent first after connection.
|
||||
|
||||
@@ -226,6 +226,14 @@ saved BLE PIN, display builds generate a new PIN at boot; builds without a
|
||||
physical display use `123456`. A PIN saved through the Companion protocol takes
|
||||
effect after reboot.
|
||||
|
||||
The Bluetooth device name is independently configurable. In the text terminal,
|
||||
use `get bluetooth.name` and `set bluetooth.name <name>`; use
|
||||
`set bluetooth.name default` to restore `MeshCore-<node name>`. The WebConfig
|
||||
Node card exposes the same optional field on Bluetooth-capable ESP32 builds.
|
||||
Custom names replace the complete Bluetooth label rather than inheriting the
|
||||
prefix, accept up to 31 valid UTF-8 bytes, and take effect after reboot. This
|
||||
does not change the node's mesh advertisement name.
|
||||
|
||||
ESP32 ports 5000, 5001, 5002, and WebConfig have no independent login layer.
|
||||
Expose them only on a trusted LAN or temporary setup network. See
|
||||
[WiFi setup](./WiFi.md) for credential setup and reconnect behavior.
|
||||
|
||||
@@ -66,6 +66,15 @@ set name {name}
|
||||
```
|
||||
Sets your advertisement name.
|
||||
|
||||
```
|
||||
get bluetooth.name
|
||||
set bluetooth.name {name|default}
|
||||
```
|
||||
Shows or changes the complete Bluetooth device name without changing the mesh
|
||||
advertisement identity. A custom name may contain spaces and is limited to 31
|
||||
valid UTF-8 bytes. `default` restores `MeshCore-<node name>`. The saved change
|
||||
takes effect after reboot. `get ble.name` and `set ble.name` are short aliases.
|
||||
|
||||
```
|
||||
set lat {latitude}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <helpers/UTF8Helpers.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace companion {
|
||||
|
||||
// Bluetooth names are stored as UTF-8 bytes. Keeping the override the same
|
||||
// size as the node name makes its limit predictable on every Companion target.
|
||||
static constexpr size_t BLUETOOTH_NAME_SIZE = 32;
|
||||
static constexpr size_t BLUETOOTH_NAME_MAX_BYTES = BLUETOOTH_NAME_SIZE - 1;
|
||||
|
||||
inline bool isValidBluetoothName(const char* name) {
|
||||
if (name == nullptr) return false;
|
||||
|
||||
const size_t length = strnlen(name, BLUETOOTH_NAME_SIZE);
|
||||
if (length == 0 || length >= BLUETOOTH_NAME_SIZE
|
||||
|| validUtf8PrefixLength(name, length) != length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_visible_character = false;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
const uint8_t byte = static_cast<uint8_t>(name[i]);
|
||||
if (byte < 0x20 || byte == 0x7F) return false;
|
||||
if (byte != 0x20) has_visible_character = true;
|
||||
}
|
||||
return has_visible_character;
|
||||
}
|
||||
|
||||
inline bool hasCustomBluetoothName(const char* name) {
|
||||
return name != nullptr && name[0] != '\0' && isValidBluetoothName(name);
|
||||
}
|
||||
|
||||
inline void formatBluetoothName(char* output, size_t output_size,
|
||||
const char* custom_name,
|
||||
const char* default_prefix,
|
||||
const char* node_name) {
|
||||
if (output == nullptr || output_size == 0) return;
|
||||
|
||||
if (hasCustomBluetoothName(custom_name)) {
|
||||
snprintf(output, output_size, "%s", custom_name);
|
||||
} else {
|
||||
snprintf(output, output_size, "%s%s",
|
||||
default_prefix == nullptr ? "" : default_prefix,
|
||||
node_name == nullptr ? "" : node_name);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace companion
|
||||
} // namespace mesh
|
||||
@@ -352,6 +352,10 @@ void DataStore::loadPrefsInt(const char *filename, CompanionNodePrefs& _prefs, d
|
||||
if (file.available() >= (int)sizeof(_prefs.usb_logging_enabled)) {
|
||||
file.read((uint8_t *)&_prefs.usb_logging_enabled,
|
||||
sizeof(_prefs.usb_logging_enabled)); // 140
|
||||
if (file.available() >= (int)sizeof(_prefs.bluetooth_name)) {
|
||||
file.read((uint8_t *)_prefs.bluetooth_name,
|
||||
sizeof(_prefs.bluetooth_name)); // 141
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,6 +432,8 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
|
||||
success = success && file.write((uint8_t *)&_prefs.usb_logging_enabled,
|
||||
sizeof(_prefs.usb_logging_enabled))
|
||||
== sizeof(_prefs.usb_logging_enabled); // 140
|
||||
success = success && file.write((uint8_t *)_prefs.bluetooth_name,
|
||||
sizeof(_prefs.bluetooth_name)) == sizeof(_prefs.bluetooth_name); // 141
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
success = file.commit(success);
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#include <helpers/TracePathHelpers.h>
|
||||
#endif
|
||||
|
||||
@@ -122,6 +122,8 @@ static const uint32_t COMMAND_RADIO_APPLY_TIMEOUT_MS = 5000UL;
|
||||
#define CMD_SET_RADIO_RXGAIN 69
|
||||
#define CMD_GET_WIFI_POWER_SAVE 70
|
||||
#define CMD_SET_WIFI_POWER_SAVE 71
|
||||
#define CMD_GET_BLUETOOTH_NAME 72
|
||||
#define CMD_SET_BLUETOOTH_NAME 73
|
||||
|
||||
#if defined(RADIO_FEM_RXGAIN) && (RADIO_FEM_RXGAIN == 0)
|
||||
static constexpr uint8_t DEFAULT_FEM_RX_GAIN = 0;
|
||||
@@ -1474,6 +1476,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.powersaving_enabled = 1;
|
||||
_prefs.powersaving_policy_version = 0;
|
||||
_prefs.wifi_enabled = 1;
|
||||
memset(_prefs.bluetooth_name, 0, sizeof(_prefs.bluetooth_name));
|
||||
#if defined(MESH_DUAL_CDC_LOGGING)
|
||||
// Keep the primary CDC stream exclusively framed unless the owner opts in
|
||||
// to the separate plaintext logging interface and reboots.
|
||||
@@ -1552,6 +1555,18 @@ void MyMesh::begin(bool has_display, bool radio_available) {
|
||||
const bool power_saving_default_migrated =
|
||||
migrateCompanionPowerSavingDefault(_prefs);
|
||||
|
||||
_prefs.node_name[sizeof(_prefs.node_name) - 1] = 0;
|
||||
const bool bluetooth_name_had_terminator =
|
||||
memchr(_prefs.bluetooth_name, 0, sizeof(_prefs.bluetooth_name)) != NULL;
|
||||
_prefs.bluetooth_name[sizeof(_prefs.bluetooth_name) - 1] = 0;
|
||||
const bool bluetooth_name_repaired =
|
||||
!bluetooth_name_had_terminator
|
||||
|| (_prefs.bluetooth_name[0] != 0
|
||||
&& !mesh::companion::isValidBluetoothName(_prefs.bluetooth_name));
|
||||
if (bluetooth_name_repaired) {
|
||||
memset(_prefs.bluetooth_name, 0, sizeof(_prefs.bluetooth_name));
|
||||
}
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
|
||||
_prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f);
|
||||
@@ -1585,7 +1600,7 @@ void MyMesh::begin(bool has_display, bool radio_available) {
|
||||
recalcRxPowerSavingFromLevel(_prefs.rx_ps_level, _prefs.sf, _prefs.bw,
|
||||
_prefs.rx_ps_preamble, &_prefs.rx_ps_rx_us,
|
||||
&_prefs.rx_ps_sleep_us);
|
||||
if (power_saving_default_migrated) {
|
||||
if (power_saving_default_migrated || bluetooth_name_repaired) {
|
||||
_store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon);
|
||||
}
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
@@ -1848,6 +1863,38 @@ bool MyMesh::handleLocalControlCommand(const char* command, char* reply,
|
||||
if (!command || !reply || reply_size == 0) return false;
|
||||
while (*command == ' ') command++;
|
||||
|
||||
if (strcmp(command, "get bluetooth.name") == 0
|
||||
|| strcmp(command, "get ble.name") == 0) {
|
||||
formatBluetoothNameStatus(reply, reply_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* bluetooth_name_value = NULL;
|
||||
if (strncmp(command, "set bluetooth.name", 18) == 0
|
||||
&& (command[18] == 0 || command[18] == ' '
|
||||
|| command[18] == '\t')) {
|
||||
bluetooth_name_value = command + 18;
|
||||
} else if (strncmp(command, "set ble.name", 12) == 0
|
||||
&& (command[12] == 0 || command[12] == ' '
|
||||
|| command[12] == '\t')) {
|
||||
bluetooth_name_value = command + 12;
|
||||
}
|
||||
if (bluetooth_name_value != NULL) {
|
||||
while (*bluetooth_name_value == ' ' || *bluetooth_name_value == '\t') {
|
||||
bluetooth_name_value++;
|
||||
}
|
||||
if (bluetooth_name_value[0] == 0) {
|
||||
snprintf(reply, reply_size,
|
||||
"Error: use set bluetooth.name <name|default>");
|
||||
} else {
|
||||
const bool clear_override = strcmp(bluetooth_name_value, "default") == 0
|
||||
|| strcmp(bluetooth_name_value, "clear") == 0;
|
||||
applyAndSaveBluetoothName(clear_override ? "" : bluetooth_name_value,
|
||||
reply, reply_size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
#ifdef WITH_WEBCONFIG
|
||||
if (strncmp(command, "get ", 4) == 0) {
|
||||
@@ -2221,6 +2268,8 @@ static bool wcCopyValue(char* dest, size_t dest_size, const char* value) {
|
||||
void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
|
||||
memset(&s, 0, sizeof(s));
|
||||
wcCopyValue(s.name, sizeof(s.name), _prefs.node_name);
|
||||
wcCopyValue(s.bluetooth_name, sizeof(s.bluetooth_name),
|
||||
_prefs.bluetooth_name);
|
||||
s.lat = sensors.node_lat;
|
||||
s.lon = sensors.node_lon;
|
||||
s.freq = _prefs.freq;
|
||||
@@ -2241,6 +2290,9 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
|
||||
s.repeat = _prefs.client_repeat != 0;
|
||||
s.capabilities = WebConfigServer::CAP_LOCATION | WebConfigServer::CAP_AIRTIME
|
||||
| WebConfigServer::CAP_RX_DELAY | WebConfigServer::CAP_POWER_SAVING;
|
||||
#ifdef BLE_PIN_CODE
|
||||
s.capabilities |= WebConfigServer::CAP_BLUETOOTH_NAME;
|
||||
#endif
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
s.capabilities |= WebConfigServer::CAP_WIFI_POWER_SAVE;
|
||||
#endif
|
||||
@@ -2342,6 +2394,11 @@ void MyMesh::onConfigBatchEnd() {
|
||||
|
||||
void MyMesh::execCommand(char* cmd, char* reply) {
|
||||
reply[0] = 0;
|
||||
if (cmd && (strcmp(cmd, "get bluetooth.name") == 0
|
||||
|| strcmp(cmd, "get ble.name") == 0)) {
|
||||
formatBluetoothNameStatus(reply, 160);
|
||||
return;
|
||||
}
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
if (cmd && strcmp(cmd, "get wifi.powersave") == 0) {
|
||||
formatWiFiPowerSaving(reply, 160);
|
||||
@@ -2370,6 +2427,10 @@ void MyMesh::execCommand(char* cmd, char* reply) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(key, "bluetooth.name") == 0) {
|
||||
applyAndSaveBluetoothName(value, reply, 160);
|
||||
return;
|
||||
}
|
||||
if (strcmp(key, "lat") == 0 || strcmp(key, "lon") == 0) {
|
||||
double parsed;
|
||||
const bool latitude = key[1] == 'a';
|
||||
@@ -3738,6 +3799,31 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
#else
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
#endif
|
||||
} else if (cmd_frame[0] == CMD_GET_BLUETOOTH_NAME) {
|
||||
out_frame[0] = RESP_CODE_OK;
|
||||
out_frame[1] = mesh::companion::hasCustomBluetoothName(
|
||||
_prefs.bluetooth_name) ? 1 : 0;
|
||||
char* effective_name = reinterpret_cast<char*>(&out_frame[2]);
|
||||
mesh::companion::formatBluetoothName(
|
||||
effective_name, MAX_FRAME_SIZE - 1, _prefs.bluetooth_name,
|
||||
BLE_NAME_PREFIX, _prefs.node_name);
|
||||
_serial->writeFrame(out_frame, 2 + strlen(effective_name));
|
||||
} else if (cmd_frame[0] == CMD_SET_BLUETOOTH_NAME) {
|
||||
const size_t name_len = len - 1;
|
||||
if (name_len > mesh::companion::BLUETOOTH_NAME_MAX_BYTES
|
||||
|| memchr(&cmd_frame[1], 0, name_len) != NULL) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else {
|
||||
char name[mesh::companion::BLUETOOTH_NAME_SIZE] = {0};
|
||||
if (name_len != 0) memcpy(name, &cmd_frame[1], name_len);
|
||||
if (name_len != 0 && !mesh::companion::isValidBluetoothName(name)) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (!saveBluetoothNameOverride(name)) {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
} else {
|
||||
writeOKFrame();
|
||||
}
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_GET_ADVERT_PATH && len >= PUB_KEY_SIZE+2) {
|
||||
// FUTURE use: uint8_t reserved = cmd_frame[1];
|
||||
uint8_t *pub_key = &cmd_frame[2];
|
||||
@@ -3971,6 +4057,62 @@ bool MyMesh::applyAndSaveRxBoostedGain(bool enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyMesh::saveBluetoothNameOverride(const char* name) {
|
||||
if (name == NULL) return false;
|
||||
|
||||
char previous[sizeof(_prefs.bluetooth_name)];
|
||||
memcpy(previous, _prefs.bluetooth_name, sizeof(previous));
|
||||
memset(_prefs.bluetooth_name, 0, sizeof(_prefs.bluetooth_name));
|
||||
if (name[0] != 0) {
|
||||
StrHelper::strncpy(_prefs.bluetooth_name, name,
|
||||
sizeof(_prefs.bluetooth_name));
|
||||
}
|
||||
|
||||
if (savePrefs()) return true;
|
||||
memcpy(_prefs.bluetooth_name, previous, sizeof(_prefs.bluetooth_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyMesh::applyAndSaveBluetoothName(const char* value, char* reply,
|
||||
size_t reply_size) {
|
||||
if (reply == NULL || reply_size == 0) return false;
|
||||
|
||||
const bool use_default = value == NULL || value[0] == 0;
|
||||
if (!use_default && !mesh::companion::isValidBluetoothName(value)) {
|
||||
snprintf(reply, reply_size,
|
||||
"Error: Bluetooth name must be 1-%u valid UTF-8 bytes without control characters",
|
||||
(unsigned)mesh::companion::BLUETOOTH_NAME_MAX_BYTES);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!saveBluetoothNameOverride(use_default ? "" : value)) {
|
||||
snprintf(reply, reply_size, "Error: Bluetooth name save failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (use_default) {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - Bluetooth name follows %s<node name>; reboot required",
|
||||
BLE_NAME_PREFIX);
|
||||
} else {
|
||||
snprintf(reply, reply_size,
|
||||
"OK - Bluetooth name saved as '%s'; reboot required", value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MyMesh::formatBluetoothNameStatus(char* reply, size_t reply_size) const {
|
||||
if (reply == NULL || reply_size == 0) return;
|
||||
|
||||
char effective_name[64];
|
||||
mesh::companion::formatBluetoothName(
|
||||
effective_name, sizeof(effective_name), _prefs.bluetooth_name,
|
||||
BLE_NAME_PREFIX, _prefs.node_name);
|
||||
snprintf(reply, reply_size, "> %s (%s)", effective_name,
|
||||
mesh::companion::hasCustomBluetoothName(_prefs.bluetooth_name)
|
||||
? "custom" : "default from node name");
|
||||
}
|
||||
|
||||
bool MyMesh::applyAndSavePowerSaving(const char* value, char* reply) {
|
||||
bool enabled;
|
||||
if (strcmp(value, "on") == 0) {
|
||||
@@ -5276,6 +5418,8 @@ void MyMesh::handleTerminalCommand(char* command) {
|
||||
} else if (strcmp(command, "help") == 0) {
|
||||
terminalOutput().print("Commands:\r\n");
|
||||
terminalOutput().print(" set {name|lat|lon|freq|tx|af} {value}\r\n");
|
||||
terminalOutput().print(" get bluetooth.name\r\n");
|
||||
terminalOutput().print(" set bluetooth.name <name|default>\r\n");
|
||||
terminalOutput().print(" powersaving [on|off]\r\n");
|
||||
#if MESH_USB_LOGGING_AVAILABLE
|
||||
terminalOutput().print(" get usb.logging\r\n");
|
||||
|
||||
@@ -311,6 +311,10 @@ private:
|
||||
bool applyAndSaveFemRxGain(bool enabled);
|
||||
bool applyAndSaveFemTxGain(bool enabled);
|
||||
bool applyAndSaveRxBoostedGain(bool enabled);
|
||||
bool saveBluetoothNameOverride(const char* name);
|
||||
bool applyAndSaveBluetoothName(const char* value, char* reply,
|
||||
size_t reply_size);
|
||||
void formatBluetoothNameStatus(char* reply, size_t reply_size) const;
|
||||
bool applyAndSavePowerSaving(const char* value, char* reply);
|
||||
bool applyAndSaveRxPowerSaving(const char* value, char* reply);
|
||||
void appendRxPowerSavingAdjustmentNote(char* reply, size_t reply_size,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <cstdint> // For uint8_t, uint32_t
|
||||
#include "BluetoothName.h"
|
||||
|
||||
#define TELEM_MODE_DENY 0
|
||||
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
|
||||
@@ -54,6 +55,8 @@ struct CompanionNodePrefs { // persisted to file
|
||||
uint8_t wifi_enabled; // Companion WiFi radio and services
|
||||
uint8_t powersaving_policy_version; // one-time default migration marker
|
||||
uint8_t usb_logging_enabled; // live USB packet/debug output
|
||||
char bluetooth_name[mesh::companion::BLUETOOTH_NAME_SIZE];
|
||||
// exact BLE name; empty uses BLE_NAME_PREFIX + node_name
|
||||
|
||||
// Keep the upstream repeat API while retaining the existing binary prefs
|
||||
// layout used by this branch.
|
||||
|
||||
@@ -982,8 +982,13 @@ void halt() {
|
||||
scheduleCompanionBluetoothRetry();
|
||||
return;
|
||||
}
|
||||
if (!bluetooth_interface.begin(BLE_NAME_PREFIX,
|
||||
the_mesh.getNodePrefs()->node_name,
|
||||
CompanionNodePrefs* prefs = the_mesh.getNodePrefs();
|
||||
const bool custom_bluetooth_name =
|
||||
mesh::companion::hasCustomBluetoothName(prefs->bluetooth_name);
|
||||
if (!bluetooth_interface.begin(custom_bluetooth_name ? "" : BLE_NAME_PREFIX,
|
||||
custom_bluetooth_name
|
||||
? prefs->bluetooth_name
|
||||
: prefs->node_name,
|
||||
the_mesh.getBLEPin())) {
|
||||
interface_manager.removeInterface(&bluetooth_interface);
|
||||
mesh::usbLoggingPort().println(
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// separately, see wcIsAdminPasswordKey below.
|
||||
static const char* const WC_ALLOWED_SET_KEYS[] = {
|
||||
// NodePrefs (radio / node)
|
||||
"name", "lat", "lon", "radio", "tx", "af", "rxdelay", "txdelay",
|
||||
"name", "bluetooth.name", "lat", "lon", "radio", "tx", "af", "rxdelay", "txdelay",
|
||||
"cad", "radio.rxgain", "radio.fem.rxgain", "radio.rxps", "powersaving",
|
||||
"repeat",
|
||||
"advert.interval", "flood.advert.interval",
|
||||
|
||||
@@ -26,18 +26,27 @@ extern "C" bool bleInUse(void) {
|
||||
|
||||
#define ADVERT_RESTART_DELAY 1000 // millis
|
||||
|
||||
bool SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
|
||||
bool SerialBLEInterface::begin(const char* prefix, const char* name, uint32_t pin_code) {
|
||||
_pin_code = pin_code;
|
||||
|
||||
char resolved_name[32];
|
||||
const char* suffix = name;
|
||||
if (strcmp(name, "@@MAC") == 0) {
|
||||
uint8_t addr[8];
|
||||
memset(addr, 0, sizeof(addr));
|
||||
esp_efuse_mac_get_default(addr);
|
||||
sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param)
|
||||
addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||
snprintf(resolved_name, sizeof(resolved_name),
|
||||
"%02X%02X%02X%02X%02X%02X",
|
||||
addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||
suffix = resolved_name;
|
||||
}
|
||||
char dev_name[32+16];
|
||||
sprintf(dev_name, "%s%s", prefix, name);
|
||||
const int dev_name_len = snprintf(dev_name, sizeof(dev_name), "%s%s",
|
||||
prefix, suffix);
|
||||
if (dev_name_len < 0 || dev_name_len >= (int)sizeof(dev_name)) {
|
||||
BLE_DEBUG_PRINTLN("Bluetooth device name is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the BLE Device
|
||||
#if defined(CONFIG_NIMBLE_ENABLED)
|
||||
|
||||
@@ -106,10 +106,10 @@ public:
|
||||
/**
|
||||
* init the BLE interface.
|
||||
* @param prefix a prefix for the device name
|
||||
* @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned
|
||||
* @param name a name for the device (combined with prefix); "@@MAC" uses the hardware address
|
||||
* @param pin_code the BLE security pin
|
||||
*/
|
||||
bool begin(const char* prefix, char* name, uint32_t pin_code);
|
||||
bool begin(const char* prefix, const char* name, uint32_t pin_code);
|
||||
|
||||
// BaseSerialInterface methods
|
||||
void enable() override;
|
||||
|
||||
+1724
-1718
File diff suppressed because it is too large.
Load diff
@@ -1512,6 +1512,7 @@ void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) {
|
||||
static const char* const LOOP_MODES[] = { "off", "minimal", "moderate", "strict" };
|
||||
radio["loop_detect"] = LOOP_MODES[node.loop_detect <= 3 ? node.loop_detect : 0];
|
||||
radio["name"] = (const char*)node.name;
|
||||
radio["bluetooth_name"] = (const char*)node.bluetooth_name;
|
||||
radio["lat"] = node.lat;
|
||||
radio["lon"] = node.lon;
|
||||
radio["advert_interval"] = node.advert_interval;
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
CAP_FEM_RX_GAIN = 1UL << 11,
|
||||
CAP_RX_POWER_SAVING = 1UL << 12,
|
||||
CAP_POWER_SAVING = 1UL << 13,
|
||||
CAP_BLUETOOTH_NAME = 1UL << 14,
|
||||
CAP_DELAYS = CAP_RX_DELAY | CAP_TX_DELAY,
|
||||
};
|
||||
|
||||
@@ -80,6 +81,7 @@ public:
|
||||
// assumes their member offsets match.
|
||||
struct NodeSnapshot {
|
||||
char name[32];
|
||||
char bluetooth_name[32]; // empty = transport's default device name
|
||||
char admin_password[32]; // empty = trusted LAN/no login prompt
|
||||
double lat;
|
||||
double lon;
|
||||
|
||||
@@ -199,7 +199,7 @@ bool SerialBLEInterface::removeStoredBondForPeer(const char* cause) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
|
||||
bool SerialBLEInterface::begin(const char* prefix, const char* name, uint32_t pin_code) {
|
||||
instance = this;
|
||||
|
||||
char charpin[20];
|
||||
@@ -214,15 +214,26 @@ bool SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code
|
||||
return false;
|
||||
}
|
||||
|
||||
char dev_name[32+16];
|
||||
char resolved_name[32];
|
||||
const char* suffix = name;
|
||||
if (strcmp(name, "@@MAC") == 0) {
|
||||
ble_gap_addr_t addr;
|
||||
if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) {
|
||||
sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param)
|
||||
addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]);
|
||||
snprintf(resolved_name, sizeof(resolved_name),
|
||||
"%02X%02X%02X%02X%02X%02X",
|
||||
addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2],
|
||||
addr.addr[1], addr.addr[0]);
|
||||
suffix = resolved_name;
|
||||
}
|
||||
}
|
||||
sprintf(dev_name, "%s%s", prefix, name);
|
||||
char dev_name[32+16];
|
||||
const int dev_name_len = snprintf(dev_name, sizeof(dev_name), "%s%s",
|
||||
prefix, suffix);
|
||||
if (dev_name_len < 0 || dev_name_len >= (int)sizeof(dev_name)) {
|
||||
instance = nullptr;
|
||||
BLE_DEBUG_PRINTLN("Bluetooth device name is too long");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connection interval units: 1.25ms, supervision timeout units: 10ms
|
||||
ble_gap_conn_params_t ppcp_params;
|
||||
|
||||
@@ -73,10 +73,10 @@ public:
|
||||
/**
|
||||
* init the BLE interface.
|
||||
* @param prefix a prefix for the device name
|
||||
* @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned
|
||||
* @param name a name for the device (combined with prefix); "@@MAC" uses the hardware address
|
||||
* @param pin_code the BLE security pin
|
||||
*/
|
||||
bool begin(const char* prefix, char* name, uint32_t pin_code);
|
||||
bool begin(const char* prefix, const char* name, uint32_t pin_code);
|
||||
|
||||
void disconnect();
|
||||
void enable() override;
|
||||
|
||||
@@ -96,6 +96,47 @@ TEST(CompanionNodePrefs, UsbLoggingStateIsIndependentFromTransports) {
|
||||
EXPECT_EQ(1, prefs.powersaving_enabled);
|
||||
}
|
||||
|
||||
TEST(CompanionNodePrefs, BluetoothNameOverrideIsIndependentFromNodeName) {
|
||||
CompanionNodePrefs prefs = {};
|
||||
strcpy(prefs.node_name, "RidgeNode");
|
||||
strcpy(prefs.bluetooth_name, "Workshop Companion");
|
||||
|
||||
char effective[64];
|
||||
mesh::companion::formatBluetoothName(
|
||||
effective, sizeof(effective), prefs.bluetooth_name, "MeshCore-",
|
||||
prefs.node_name);
|
||||
EXPECT_STREQ("Workshop Companion", effective);
|
||||
|
||||
strcpy(prefs.bluetooth_name, "default");
|
||||
mesh::companion::formatBluetoothName(
|
||||
effective, sizeof(effective), prefs.bluetooth_name, "MeshCore-",
|
||||
prefs.node_name);
|
||||
EXPECT_STREQ("default", effective);
|
||||
|
||||
prefs.bluetooth_name[0] = 0;
|
||||
mesh::companion::formatBluetoothName(
|
||||
effective, sizeof(effective), prefs.bluetooth_name, "MeshCore-",
|
||||
prefs.node_name);
|
||||
EXPECT_STREQ("MeshCore-RidgeNode", effective);
|
||||
}
|
||||
|
||||
TEST(CompanionNodePrefs, BluetoothNameRequiresBoundedValidUtf8) {
|
||||
EXPECT_TRUE(mesh::companion::isValidBluetoothName("Field Unit 4"));
|
||||
EXPECT_TRUE(mesh::companion::isValidBluetoothName(
|
||||
"Field Unit \xF0\x9F\x93\xA1"));
|
||||
EXPECT_FALSE(mesh::companion::isValidBluetoothName(""));
|
||||
EXPECT_FALSE(mesh::companion::isValidBluetoothName(" "));
|
||||
EXPECT_FALSE(mesh::companion::isValidBluetoothName("bad\nname"));
|
||||
|
||||
char too_long[mesh::companion::BLUETOOTH_NAME_SIZE + 1];
|
||||
memset(too_long, 'A', sizeof(too_long));
|
||||
too_long[sizeof(too_long) - 1] = 0;
|
||||
EXPECT_FALSE(mesh::companion::isValidBluetoothName(too_long));
|
||||
|
||||
const char malformed[] = {'A', static_cast<char>(0x80), 0};
|
||||
EXPECT_FALSE(mesh::companion::isValidBluetoothName(malformed));
|
||||
}
|
||||
|
||||
TEST(CompanionNodePrefs, MigratesRegressedPowerSavingDefaultOnce) {
|
||||
CompanionNodePrefs prefs = {};
|
||||
prefs.powersaving_enabled = 0;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
TEST(WebConfigKeys, AllowsKnownScalarKeys) {
|
||||
EXPECT_TRUE(wcIsAllowedSetKey("name"));
|
||||
EXPECT_TRUE(wcIsAllowedSetKey("bluetooth.name"));
|
||||
EXPECT_TRUE(wcIsAllowedSetKey("radio"));
|
||||
EXPECT_TRUE(wcIsAllowedSetKey("radio.rxps"));
|
||||
EXPECT_TRUE(wcIsAllowedSetKey("powersaving"));
|
||||
|
||||
+5
-1
@@ -345,6 +345,9 @@ body.tab-cli{padding-bottom:0}
|
||||
<div class="card">
|
||||
<h2>Node</h2>
|
||||
<div class="f"><label>Name</label><input type="text" data-k="name" maxlength="31"></div>
|
||||
<div class="f" data-cap="16384"><label>Bluetooth name</label>
|
||||
<input type="text" data-k="bluetooth.name" maxlength="31" placeholder="Use MeshCore-<node name>">
|
||||
<div class="hint">Optional complete Bluetooth device name. Leave blank to follow the node name.</div></div>
|
||||
<div class="row" data-cap="1">
|
||||
<div class="f"><label>Latitude</label><input type="number" data-k="lat" step="any"></div>
|
||||
<div class="f"><label>Longitude</label><input type="number" data-k="lon" step="any"></div>
|
||||
@@ -734,7 +737,8 @@ function cfgVal(k){ // map a `set` key to its current value string, from st.cfg
|
||||
var c=st.cfg,m,r=c.radio,q=c.mqtt||{slots:[]},w=c.wifi;
|
||||
if((m=k.match(/^mqtt([1-9])\.(\w+)$/))){var s=(q.slots||[])[+m[1]-1]||{};var v=s[m[2]];return v==null?"":String(v)}
|
||||
switch(k){
|
||||
case"name":return r.name;case"lat":return String(r.lat);case"lon":return String(r.lon);
|
||||
case"name":return r.name;case"bluetooth.name":return r.bluetooth_name||"";
|
||||
case"lat":return String(r.lat);case"lon":return String(r.lon);
|
||||
case"tx":return String(r.tx);case"af":return String(r.af);
|
||||
case"rxdelay":return String(r.rxdelay);case"txdelay":return String(r.txdelay);
|
||||
case"cad":return r.cad?"on":"off";case"radio.rxgain":return r.rxgain?"on":"off";
|
||||
|
||||
Reference in new issue
Block a user