diff --git a/README.md b/README.md index f7ba7fce..4f58e901 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This fork was created specifically to focus on enabling BLE companion firmware f - [Sending a Direct Message](#sending-a-direct-message) - [Repeater Admin Screen](#repeater-admin-screen) - [Settings Screen](#settings-screen) + - [Serial Settings (USB)](Serial_Settings_Guide.md) - [Compose Mode](#compose-mode) - [Symbol Entry (Sym Key)](#symbol-entry-sym-key) - [Emoji Picker](#emoji-picker) @@ -51,6 +52,8 @@ The T-Deck Pro BLE companion firmware includes full keyboard support for standal | B | Open web browser (BLE and 4G variants only) | | T | Open SMS & Phone app (4G variant only) | | P | Open audiobook player (audio variant only) | +| F | Open node discovery (search for nearby repeaters/nodes) | +| G | Open map screen (shows contacts with GPS positions) | | Q | Back to home screen | ### Bluetooth (BLE) @@ -165,6 +168,8 @@ When adding a hashtag channel, type the channel name and press Enter. The channe If you've changed radio parameters, pressing Q will prompt you to apply changes before exiting. +> **Tip:** All device settings (plus mesh tuning parameters not available on-screen) can also be configured via USB serial. See the [Serial Settings Guide](Serial_Settings_Guide.md) for complete documentation. + ### Compose Mode | Key | Action | @@ -349,4 +354,4 @@ However, this firmware links against libraries with different license terms. Bec | [base64](https://github.com/Densaugeo/base64_arduino) | MIT | densaugeo | | [Arduino Crypto](https://github.com/rweather/arduinolibs) | MIT | Rhys Weatherley | -Full license texts for each dependency are available in their respective repositories linked above. +Full license texts for each dependency are available in their respective repositories linked above. \ No newline at end of file diff --git a/Serial Settings Guide.md b/Serial Settings Guide.md index c65ee6f0..b1250ae7 100644 --- a/Serial Settings Guide.md +++ b/Serial Settings Guide.md @@ -59,6 +59,12 @@ All commands follow a simple pattern: `get` to read, `set` to write. | `get notify` | Keyboard flash notification (on/off) | | `get gps` | GPS status and interval | | `get pin` | BLE pairing PIN | +| `get path.hash.mode` | Path hash size (0=1-byte, 1=2-byte, 2=3-byte) | +| `get rxdelay` | Rx delay base (0=disabled) | +| `get af` | Airtime factor | +| `get multi.acks` | Redundant ACKs (0 or 1) | +| `get int.thresh` | Interference threshold (0=disabled) | +| `get gps.baud` | GPS baud rate (0=compile-time default) | | `get channels` | List all channels with index numbers | | `get presets` | List all radio presets with parameters | | `get pubkey` | Device public key (hex) | @@ -163,6 +169,78 @@ set notify off set pin 123456 ``` +#### Path Hash Mode + +Controls the byte size of each repeater's identity stamp in forwarded flood packets. Larger hashes reduce collisions at the cost of fewer maximum hops. + +``` +set path.hash.mode 1 +``` + +| Mode | Bytes/hop | Max hops | Notes | +|------|-----------|----------|-------| +| 0 | 1 | 64 | Legacy — prone to hash collisions in larger networks | +| 1 | 2 | 32 | Recommended — effectively eliminates collisions | +| 2 | 3 | 21 | Maximum precision, rarely needed | + +Nodes with different modes can coexist — the mode only affects packets your node originates. The hash size is encoded in each packet's header, so receiving nodes adapt automatically. + +### Mesh Tuning + +These settings control how the device participates in the mesh network. They take effect immediately — no reboot required (except `gps.baud`). + +#### Rx Delay (rxdelay) + +Delays processing of flood packets based on signal quality. Stronger signals are processed first; weaker copies wait longer and are typically discarded as duplicates. Direct messages are always processed immediately. + +``` +set rxdelay 3 +``` + +Range: 0–20 (0 = disabled, default). Higher values create larger timing differences between strong and weak signals. Values below 1.0 have no practical effect. See the [MeshSydney wiki](https://meshsydney.com/wiki) for detailed tuning profiles. + +#### Airtime Factor (af) + +Adjusts how long certain internal timing windows remain open. Does not change the LoRa radio parameters (SF, BW, CR) — those remain as configured. + +``` +set af 1.0 +``` + +Range: 0–9 (default: 1.0). Keep this value consistent across nodes in your mesh for best coherence. + +#### Multiple Acknowledgments (multi.acks) + +Sends redundant ACK packets for direct messages. When enabled, two ACKs are sent (a multi-ack first, then the standard ACK), improving delivery confirmation reliability. + +``` +set multi.acks 1 +``` + +Values: 0 (single ACK) or 1 (redundant ACKs, default). + +#### Interference Threshold (int.thresh) + +Enables channel activity scanning before transmitting. Not recommended unless your device is in a high RF interference environment — specifically where the noise floor is low but shows significant fluctuations indicating interference. Enabling this adds approximately 4 seconds of receive delay per packet. + +``` +set int.thresh 14 +set int.thresh 0 +``` + +Values: 0 (disabled, default) or 14+ (14 is the typical setting). Values between 1–13 are not functional and will be rejected. + +#### GPS Baud Rate (gps.baud) + +Override the GPS serial baud rate. The default (0) uses the compile-time value of 38400. **Requires a reboot to take effect** — the GPS serial port is only configured at startup. + +``` +set gps.baud 9600 +set gps.baud 0 +``` + +Valid rates: 0 (default), 4800, 9600, 19200, 38400, 57600, 115200. + ### Channel Management #### List Channels diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 16d2d2c9..ab74c543 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -257,7 +257,7 @@ float MyMesh::getAirtimeBudgetFactor() const { } int MyMesh::getInterferenceThreshold() const { - return 0; // disabled for now, until currentRSSI() problem is resolved + return _prefs.interference_threshold; } int MyMesh::calcRxDelay(float score, uint32_t air_time) const { @@ -1133,6 +1133,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe // defaults memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half + _prefs.multi_acks = 1; // redundant ACKs on by default strcpy(_prefs.node_name, "NONAME"); _prefs.freq = LORA_FREQ; _prefs.sf = LORA_SF; @@ -1183,6 +1184,17 @@ void MyMesh::begin(bool has_display) { _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.utc_offset_hours = constrain(_prefs.utc_offset_hours, -12, 14); // Valid timezone range + // gps_baudrate: 0 means use compile-time default; validate known rates + if (_prefs.gps_baudrate != 0 && _prefs.gps_baudrate != 4800 && + _prefs.gps_baudrate != 9600 && _prefs.gps_baudrate != 19200 && + _prefs.gps_baudrate != 38400 && _prefs.gps_baudrate != 57600 && + _prefs.gps_baudrate != 115200) { + _prefs.gps_baudrate = 0; // reset to default if invalid + } + // interference_threshold: 0 = disabled, minimum functional value is 14 + if (_prefs.interference_threshold > 0 && _prefs.interference_threshold < 14) { + _prefs.interference_threshold = 0; + } #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -2130,20 +2142,26 @@ void MyMesh::enterCLIRescue() { void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); + bool line_complete = false; while (Serial.available() && len < sizeof(cli_command)-1) { char c = Serial.read(); - if (c != '\n') { - cli_command[len++] = c; - cli_command[len] = 0; + if (c == '\r' || c == '\n') { + if (len > 0) { + line_complete = true; + Serial.println(); // echo newline + } + break; // stop reading — remaining LF (from CR+LF) is consumed next loop } + cli_command[len++] = c; + cli_command[len] = 0; Serial.print(c); // echo } - if (len == sizeof(cli_command)-1) { // command buffer full - cli_command[sizeof(cli_command)-1] = '\r'; + if (len == sizeof(cli_command)-1) { // buffer full — force processing + line_complete = true; } - if (len > 0 && cli_command[len - 1] == '\r') { // received complete line - cli_command[len - 1] = 0; // replace newline with C string null terminator + if (line_complete && len > 0) { + cli_command[len] = 0; // ensure null terminated // ===================================================================== // GET commands — read settings @@ -2174,6 +2192,21 @@ void MyMesh::checkCLIRescueCmd() { _prefs.gps_enabled ? "on" : "off", _prefs.gps_interval); } else if (strcmp(key, "pin") == 0) { Serial.printf(" > %06d\n", _prefs.ble_pin); + + // --- Mesh tuning parameters --- + } else if (strcmp(key, "rxdelay") == 0) { + Serial.printf(" > %.1f\n", _prefs.rx_delay_base); + } else if (strcmp(key, "af") == 0) { + Serial.printf(" > %.1f\n", _prefs.airtime_factor); + } else if (strcmp(key, "multi.acks") == 0) { + Serial.printf(" > %d\n", _prefs.multi_acks); + } else if (strcmp(key, "int.thresh") == 0) { + Serial.printf(" > %d\n", _prefs.interference_threshold); + } else if (strcmp(key, "gps.baud") == 0) { + uint32_t effective = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE; + Serial.printf(" > %lu (effective: %lu)\n", + (unsigned long)_prefs.gps_baudrate, (unsigned long)effective); + } else if (strcmp(key, "radio") == 0) { Serial.printf(" > freq=%.3f bw=%.1f sf=%d cr=%d tx=%d\n", _prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr, _prefs.tx_power_dbm); @@ -2225,6 +2258,14 @@ void MyMesh::checkCLIRescueCmd() { Serial.printf(" gps: %s (interval: %ds)\n", _prefs.gps_enabled ? "on" : "off", _prefs.gps_interval); Serial.printf(" pin: %06d\n", _prefs.ble_pin); + Serial.printf(" rxdelay: %.1f\n", _prefs.rx_delay_base); + Serial.printf(" af: %.1f\n", _prefs.airtime_factor); + Serial.printf(" multi.acks: %d\n", _prefs.multi_acks); + Serial.printf(" int.thresh: %d\n", _prefs.interference_threshold); + { + uint32_t eff_baud = _prefs.gps_baudrate ? _prefs.gps_baudrate : GPS_BAUDRATE; + Serial.printf(" gps.baud: %lu\n", (unsigned long)eff_baud); + } #ifdef HAS_4G_MODEM Serial.printf(" modem: %s\n", ModemManager::loadEnabledConfig() ? "on" : "off"); Serial.printf(" apn: %s\n", modemManager.getAPN()); @@ -2558,6 +2599,69 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" > modem disabled"); #endif + // --- Mesh tuning parameters --- + } else if (memcmp(config, "rxdelay ", 8) == 0) { + float val = atof(&config[8]); + if (val >= 0.0f && val <= 20.0f) { + _prefs.rx_delay_base = val; + savePrefs(); + Serial.printf(" > rxdelay = %.1f\n", _prefs.rx_delay_base); + } else { + Serial.println(" Error: rxdelay out of range (0-20)"); + } + + } else if (memcmp(config, "af ", 3) == 0) { + float val = atof(&config[3]); + if (val >= 0.0f && val <= 9.0f) { + _prefs.airtime_factor = val; + savePrefs(); + Serial.printf(" > af = %.1f\n", _prefs.airtime_factor); + } else { + Serial.println(" Error: af out of range (0-9)"); + } + + } else if (memcmp(config, "multi.acks ", 11) == 0) { + int val = atoi(&config[11]); + if (val == 0 || val == 1) { + _prefs.multi_acks = (uint8_t)val; + savePrefs(); + Serial.printf(" > multi.acks = %d\n", _prefs.multi_acks); + } else { + Serial.println(" Error: use 0 or 1"); + } + + // Interference threshold — not recommended unless the device is in a high + // RF interference environment (low noise floor with significant fluctuations). + // Enabling adds ~4s receive delay per packet for channel activity scanning. + } else if (memcmp(config, "int.thresh ", 11) == 0) { + int val = atoi(&config[11]); + if (val == 0) { + _prefs.interference_threshold = 0; + savePrefs(); + Serial.println(" > int.thresh = 0 (disabled)"); + } else if (val >= 14 && val <= 255) { + _prefs.interference_threshold = (uint8_t)val; + savePrefs(); + Serial.printf(" > int.thresh = %d (enabled — adds ~4s rx delay)\n", + _prefs.interference_threshold); + Serial.println(" Note: only recommended for high RF interference environments"); + } else { + Serial.println(" Error: use 0 (disabled) or 14+ (typical: 14)"); + } + + } else if (memcmp(config, "gps.baud ", 9) == 0) { + uint32_t val = (uint32_t)atol(&config[9]); + if (val == 0 || val == 4800 || val == 9600 || val == 19200 || + val == 38400 || val == 57600 || val == 115200) { + _prefs.gps_baudrate = val; + savePrefs(); + uint32_t effective = val ? val : GPS_BAUDRATE; + Serial.printf(" > gps.baud = %lu (effective: %lu, reboot to apply)\n", + (unsigned long)val, (unsigned long)effective); + } else { + Serial.println(" Error: use 0 (default), 4800, 9600, 19200, 38400, 57600, or 115200"); + } + } else { Serial.printf(" Error: unknown setting '%s' (try 'help')\n", config); } @@ -2574,6 +2678,13 @@ void MyMesh::checkCLIRescueCmd() { Serial.println(" name, freq, bw, sf, cr, tx, utc, notify, pin"); Serial.println(" path.hash.mode Path hash size (0=1B, 1=2B, 2=3B)"); Serial.println(""); + Serial.println(" Mesh tuning:"); + Serial.println(" rxdelay <0-20> Rx delay base (0=disabled)"); + Serial.println(" af <0-9> Airtime factor"); + Serial.println(" multi.acks <0|1> Redundant ACKs (default: 1)"); + Serial.println(" int.thresh <0|14+> Interference threshold dB (0=off, 14=typical)"); + Serial.println(" gps.baud GPS baud (0=default, reboot to apply)"); + Serial.println(""); Serial.println(" Compound commands:"); Serial.println(" get all Dump all settings"); Serial.println(" get radio Show all radio params"); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 469ee599..aca50425 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -33,4 +33,6 @@ struct NodePrefs { // persisted to file uint8_t ringtone_enabled; // Ringtone on incoming call (0=off, 1=on) — 4G only uint8_t path_hash_mode; // 0=1-byte (legacy), 1=2-byte, 2=3-byte path hashes uint8_t autoadd_max_hops; // 0=no limit, N=up to N-1 hops (max 64) + uint32_t gps_baudrate; // GPS baud rate (0 = use compile-time GPS_BAUDRATE default) + uint8_t interference_threshold; // Interference threshold in dB (0=disabled, 14+=enabled) }; \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 663fce6b..96eaa619 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -730,8 +730,12 @@ void setup() { // We need to reinitialize Serial2 to reclaim them #if HAS_GPS Serial2.end(); // Close any existing Serial2 - Serial2.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); - MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS after sensors.begin()"); + { + uint32_t gps_baud = the_mesh.getNodePrefs()->gps_baudrate; + if (gps_baud == 0) gps_baud = GPS_BAUDRATE; + Serial2.begin(gps_baud, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); + MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS at %lu baud", (unsigned long)gps_baud); + } #endif #ifdef DISPLAY_CLASS diff --git a/variants/lilygo_tdeck_pro/target.cpp b/variants/lilygo_tdeck_pro/target.cpp index 324c1485..b0092c70 100644 --- a/variants/lilygo_tdeck_pro/target.cpp +++ b/variants/lilygo_tdeck_pro/target.cpp @@ -49,19 +49,11 @@ bool radio_init() { loraSpi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI, P_LORA_NSS); MESH_DEBUG_PRINTLN("radio_init() - SPI initialized, calling radio.std_init()..."); bool result = radio.std_init(&loraSpi); - if (result) { - radio.setPreambleLength(32); - MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols"); - } MESH_DEBUG_PRINTLN("radio_init() - radio.std_init() returned: %s", result ? "SUCCESS" : "FAILED"); return result; #else MESH_DEBUG_PRINTLN("radio_init() - calling radio.std_init() without custom SPI..."); bool result = radio.std_init(); - if (result) { - radio.setPreambleLength(32); - MESH_DEBUG_PRINTLN("radio_init() - preamble set to 32 symbols"); - } return result; #endif } @@ -75,6 +67,14 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + + // Longer preamble for low SF improves reliability — each symbol is shorter + // at low SF, so more symbols are needed for reliable detection. + // SF <= 8 gets 32 symbols (~65ms at SF7/62.5kHz); SF >= 9 keeps 16 (already ~131ms+). + // See: https://github.com/meshcore-dev/MeshCore/pull/1954 + uint16_t preamble = (sf <= 8) ? 32 : 16; + radio.setPreambleLength(preamble); + MESH_DEBUG_PRINTLN("radio_set_params() - bw=%.1f sf=%u preamble=%u", bw, sf, preamble); } void radio_set_tx_power(uint8_t dbm) {