diff --git a/build.sh b/build.sh index 5d4083457f..80ff4cdf2d 100755 --- a/build.sh +++ b/build.sh @@ -34,6 +34,9 @@ $ sh build.sh build-repeater-firmwares Build all chat room server firmwares $ sh build.sh build-room-server-firmwares +Build all kiss radio firmwares +$ sh build.sh build-kiss-radio-firmwares + Environment Variables: DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.) If not set, debug flags from variant platformio.ini files are used. @@ -242,6 +245,17 @@ build_room_server_firmwares() { } +build_kiss_modem_firmwares() { + +# # build specific kiss radio firmwares +# build_firmware "Heltec_v3_kiss_modem" +# build_firmware "RAK_4631_kiss_modem" + + # build all room server firmwares + build_all_firmwares_by_suffix "_kiss_modem" + +} + build_firmwares() { build_companion_firmwares build_repeater_firmwares @@ -278,6 +292,8 @@ elif [[ $1 == "build-repeater-firmwares" ]]; then build_repeater_firmwares elif [[ $1 == "build-room-server-firmwares" ]]; then build_room_server_firmwares +elif [[ $1 == "build-kiss-radio-firmwares" ]]; then + build_kiss_modem_firmwares elif [[ $1 == "get-companion-firmwares-to-build" ]]; then get_pio_envs_ending_with_string "_companion_radio_usb" get_pio_envs_ending_with_string "_companion_radio_ble" diff --git a/docs/cli_commands.md b/docs/cli_commands.md index b618ae2bfe..8772b929fe 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -291,6 +291,22 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### View or change the LoRa FEM transmit-path gain state on supported boards +**Usage:** +- `get radio.fem.txgain` +- `set radio.fem.txgain ` + +**Parameters:** +- `state`: `on`|`off` + +**Notes:** +- This controls a software-selectable external LoRa FEM transmit gain where the board supports it. +- On Station G3, remove the PA PL1 jumper to allow software control. `on` selects PA PL1 high/short and `off` selects PA PL1 low/open. The PA PL2 hardware jumper determines whether this switches between power levels 1/3 or 2/4. +- Select an operating level and SX1262 transmit power that comply with local RF limits and the Station G3 power-supply requirements. +- The setting is saved immediately, but on Station G3 the level is applied to the hardware at the start of the next transmit, so that the PA supply rail is never re-targeted while the PA is being driven. `get` reports the configured state, which may lead the hardware until the node next transmits. + +--- + ### System #### View or change this node's name diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index b03334c1c9..2c33406632 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -885,6 +885,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default + _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; //_prefs.rx_delay_base = 10.0f; enable once new algo fixed _prefs.setRepeatEn(false); #if defined(USE_SX1262) || defined(USE_SX1268) @@ -974,6 +976,8 @@ void MyMesh::begin(bool has_display) { radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setTxPower(_prefs.tx_power_dbm); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); + board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 4ec1803e1c..21766de82d 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -33,6 +33,8 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint32_t gps_interval = 0; // GPS read interval in seconds uint8_t autoadd_config = 0; // bitmask for auto-add contacts config uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted) + uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA) + uint8_t radio_fem_txgain = 0; // external LoRa FEM TX gain (low by default) uint8_t _client_repeat = 0; // DEPRECATED -> use repeat.disable_fwd uint8_t path_hash_mode = 0; // which path mode to use when sending uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) @@ -51,7 +53,12 @@ class NodePrefs : public ConfigSerializer { // persisted to file //def("cad", _parent->cad_enabled); //def("int_thr", _parent->interference_threshold); def("rxgain", _parent->rx_boosted_gain); - def("fem_rxgain", _parent->rx_boosted_gain); + #if 0 + // NOTE: these cannot be set (yet) so don't load/save until we can. + // also, fem_rxgain WAS mapped to wrong JSON property previously + def("fem_rxgain", _parent->radio_fem_rxgain); + def("fem_txgain", _parent->radio_fem_txgain); + #endif def("tx", _parent->tx_power_dbm); def("af", _parent->airtime_factor); def("rxdelay", _parent->rx_delay_base); @@ -135,4 +142,4 @@ class NodePrefs : public ConfigSerializer { // persisted to file // new accessor methods bool isRepeatEn() const { return repeat.disable_fwd == 0; } void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } -}; \ No newline at end of file +}; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c93ba1a4cd..7d0179f3ab 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -411,24 +411,31 @@ bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) } void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) { - if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // if _request_ packet scope is known, send reply with same scope - TransportKey scope; - if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) > 0) { - sendFloodScoped(scope, packet, delay_millis, path_hash_size); - } else { + TransportKey req_scope; + bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard(); + bool req_scope_known = recv_pkt_region != NULL && !is_wildcard + && region_map.getTransportKeysFor(*recv_pkt_region, &req_scope, 1) > 0; + + switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) { + case mesh::REPLY_SCOPE_REQUEST: + sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request + break; + case mesh::REPLY_SCOPE_DEFAULT: + // requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region. + // un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0 + sendFloodScoped(default_scope, packet, delay_millis, path_hash_size); + break; + case mesh::REPLY_SCOPE_NONE: sendFlood(packet, delay_millis, path_hash_size); // send un-scoped - } - } else { - sendFlood(packet, delay_millis, path_hash_size); // send un-scoped + break; } } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood()) { - if (packet->getPathHashCount() >= _prefs.flood_max) return false; - if (packet->getRouteType() == ROUTE_TYPE_FLOOD && packet->getPathHashCount() >= _prefs.flood_max_unscoped) return false; - if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= _prefs.flood_max_advert) return false; + if (packet->isRouteFlood() + && mesh::isFloodHopLimitExceeded(packet, _prefs.flood_max, _prefs.flood_max_unscoped, _prefs.flood_max_advert)) { + return false; } if (packet->isRouteFlood() && recv_pkt_region == NULL) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); @@ -586,17 +593,29 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m if (reply_len == 0) return; // invalid request - if (packet->isRouteFlood()) { + // a DIRECT login can reply via the stored out_path, as onPeerDataRecv() does for REQ + ClientInfo* client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); + bool have_out_path = client != NULL && client->out_path_len != OUT_PATH_UNKNOWN; + + auto route = mesh::chooseReplyRoute(packet->isRouteFlood(), reply_path_len != 0xFF, have_out_path); + + if (route == mesh::REPLY_ROUTE_PATH_RETURN) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); - } else if (reply_path_len == 0xFF) { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + return; + } + + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply == NULL) return; + + if (route == mesh::REPLY_ROUTE_DIRECT_SUPPLIED) { + sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); + } else if (route == mesh::REPLY_ROUTE_DIRECT_OUT_PATH) { + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); + sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -913,6 +932,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #endif #endif _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; pending_discover_tag = 0; pending_discover_until = 0; @@ -962,6 +982,7 @@ void MyMesh::begin(FILESYSTEM *fs) { MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 2f923c871a..6d9cf459ce 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -34,6 +34,7 @@ #include #include #include +#include #include "RateLimiter.h" #ifdef WITH_BRIDGE diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0aff39cc1a..546d094fc8 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -299,10 +299,9 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood()) { - if (packet->getPathHashCount() >= _prefs.flood_max) return false; - if (packet->getRouteType() == ROUTE_TYPE_FLOOD && packet->getPathHashCount() >= _prefs.flood_max_unscoped) return false; - if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= _prefs.flood_max_advert) return false; + if (packet->isRouteFlood() + && mesh::isFloodHopLimitExceeded(packet, _prefs.flood_max, _prefs.flood_max_unscoped, _prefs.flood_max_advert)) { + return false; } return true; } @@ -683,6 +682,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #endif #endif _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; next_post_idx = 0; next_client_idx = 0; @@ -726,6 +726,7 @@ void MyMesh::begin(FILESYSTEM *fs) { radio_driver.setTxPower(_prefs.tx_power_dbm); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); updateAdvertTimer(); updateFloodAdvertTimer(); @@ -749,15 +750,23 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3 } void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) { - if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // if _request_ packet scope is known, send reply with same scope - TransportKey scope; - if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) > 0) { - sendFloodScoped(scope, packet, delay_millis, path_hash_size); - } else { + TransportKey req_scope; + bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard(); + bool req_scope_known = recv_pkt_region != NULL && !is_wildcard + && region_map.getTransportKeysFor(*recv_pkt_region, &req_scope, 1) > 0; + + switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) { + case mesh::REPLY_SCOPE_REQUEST: + sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request + break; + case mesh::REPLY_SCOPE_DEFAULT: + // requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region. + // un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0 + sendFloodScoped(default_scope, packet, delay_millis, path_hash_size); + break; + case mesh::REPLY_SCOPE_NONE: sendFlood(packet, delay_millis, path_hash_size); // send un-scoped - } - } else { - sendFlood(packet, delay_millis, path_hash_size); // send un-scoped + break; } } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 878c2e9593..ba9f7eecaf 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 17f8e323e5..9bfa5ec6a0 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -735,6 +735,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.radio_fem_rxgain = 1; + _prefs.radio_fem_txgain = 0; memset(default_scope.key, 0, sizeof(default_scope.key)); } @@ -771,6 +772,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setTxPower(_prefs.tx_power_dbm); board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); + board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain); updateAdvertTimer(); updateFloodAdvertTimer(); diff --git a/src/Identity.cpp b/src/Identity.cpp index 25419fd4fe..51a01ae71d 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -27,11 +27,9 @@ bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) c // needs much less, around 600-700bytes. The CC310 workspace is static, faster, // should save power at scale as well. static CRYS_ECEDW_TempBuff_t cc310_tmp; - nRFCrypto.begin(); CRYSError_t rc = CRYS_ECEDW_Verify((uint8_t*)sig, CRYS_ECEDW_SIGNATURE_BYTES, (uint8_t*)pub_key, CRYS_ECEDW_MOD_SIZE_IN_BYTES, (uint8_t*)message, (size_t)msg_len, &cc310_tmp); - nRFCrypto.end(); return rc == CRYS_OK; #elif 0 // NOTE: memory corruption bug was found in this function!! diff --git a/src/MeshCore.h b/src/MeshCore.h index 89e60b1f7e..e67371ef17 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -67,6 +67,10 @@ class MainBoard { virtual bool setLoRaFemLnaEnabled(bool enable) { return false; } virtual bool canControlLoRaFemLna() const { return false; } virtual bool isLoRaFemLnaEnabled() const { return false; } + // Software-selectable external FEM transmit gain. This is not a PA power switch. + virtual bool setLoRaFemPaGainEnabled(bool enable) { return false; } + virtual bool canControlLoRaFemPaGain() const { return false; } + virtual bool isLoRaFemPaGainEnabled() const { return false; } // Power management interface (boards with power management override these) virtual bool isExternalPowered() { return false; } diff --git a/src/Utils.cpp b/src/Utils.cpp index d4bc8c4502..5ae7f0e27e 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -24,9 +24,7 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) { #ifdef USE_CC310_HW_CRYPTO static CRYS_HASH_Result_t result; - nRFCrypto.begin(); CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result); - nRFCrypto.end(); memcpy(hash, result, hash_len); #else SHA256 sha; @@ -39,12 +37,10 @@ void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int fra #ifdef USE_CC310_HW_CRYPTO static CRYS_HASHUserContext_t ctx; static CRYS_HASH_Result_t result; - nRFCrypto.begin(); CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode); CRYS_HASH_Update(&ctx, (uint8_t*)frag1, (size_t)frag1_len); CRYS_HASH_Update(&ctx, (uint8_t*)frag2, (size_t)frag2_len); CRYS_HASH_Finish(&ctx, result); - nRFCrypto.end(); memcpy(hash, result, hash_len); #else SHA256 sha; @@ -62,7 +58,6 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s const uint8_t* sp = src; size_t dummy_out = 0; - nRFCrypto.begin(); SaSi_AesInit(&ctx, SASI_AES_DECRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); while (sp - src < src_len) { @@ -71,7 +66,6 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s } SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); SaSi_AesFree(&ctx); - nRFCrypto.end(); return sp - src; #else AES128 aes; @@ -95,7 +89,6 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s uint8_t* dp = dest; size_t dummy_out = 0; - nRFCrypto.begin(); SaSi_AesInit(&ctx, SASI_AES_ENCRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); while (src_len >= 16) { @@ -110,7 +103,6 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s } SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); SaSi_AesFree(&ctx); - nRFCrypto.end(); return dp - dest; #else AES128 aes; @@ -138,11 +130,9 @@ int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uin #ifdef USE_CC310_HW_CRYPTO static CRYS_HMACUserContext_t hmac_ctx; static CRYS_HASH_Result_t hmac_result; - nRFCrypto.begin(); CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); CRYS_HMAC_Update(&hmac_ctx, dest + CIPHER_MAC_SIZE, enc_len); CRYS_HMAC_Finish(&hmac_ctx, hmac_result); - nRFCrypto.end(); memcpy(dest, hmac_result, CIPHER_MAC_SIZE); #else SHA256 sha; @@ -162,11 +152,9 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin { static CRYS_HMACUserContext_t hmac_ctx; static CRYS_HASH_Result_t hmac_result; - nRFCrypto.begin(); CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); CRYS_HMAC_Update(&hmac_ctx, (uint8_t*)(src + CIPHER_MAC_SIZE), src_len - CIPHER_MAC_SIZE); CRYS_HMAC_Finish(&hmac_ctx, hmac_result); - nRFCrypto.end(); memcpy(hmac, hmac_result, CIPHER_MAC_SIZE); } #else diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 07181e16ad..b318bb58e8 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -133,6 +133,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy // sanitise settings _prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean _prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean + _prefs->radio_fem_txgain = constrain(_prefs->radio_fem_txgain, 0, 1); // boolean _prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean file.close(); @@ -562,6 +563,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } else { strcpy(reply, "Error: state must be on or off"); } + } else if (memcmp(config, "radio.fem.txgain ", 17) == 0) { + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else if (memcmp(&config[17], "on", 2) == 0) { + if (_board->setLoRaFemPaGainEnabled(true)) { + _prefs->radio_fem_txgain = 1; + savePrefs(); + strcpy(reply, "OK - LoRa FEM TX gain on"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM TX gain"); + } + } else if (memcmp(&config[17], "off", 3) == 0) { + if (_board->setLoRaFemPaGainEnabled(false)) { + _prefs->radio_fem_txgain = 0; + savePrefs(); + strcpy(reply, "OK - LoRa FEM TX gain off"); + } else { + strcpy(reply, "Error: failed to apply LoRa FEM TX gain"); + } + } else { + strcpy(reply, "Error: state must be on or off"); + } } else if (memcmp(config, "radio ", 6) == 0) { strcpy(tmp, &config[6]); const char *parts[4]; @@ -827,6 +850,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep } else { sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off"); } + } else if (memcmp(config, "radio.fem.txgain", 16) == 0) { + if (!_board->canControlLoRaFemPaGain()) { + strcpy(reply, "Error: unsupported"); + } else { + sprintf(reply, "> %s", _board->isLoRaFemPaGainEnabled() ? "on" : "off"); + } } else if (memcmp(config, "radio", 5) == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 2a9ec43bcb..237c758e9f 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -65,6 +65,7 @@ class NodePrefs : public ConfigSerializer { char owner_info[120]; uint8_t rx_boosted_gain = 0; // power settings uint8_t radio_fem_rxgain = 0; // LoRa FEM RX gain setting + uint8_t radio_fem_txgain = 0; // LoRa FEM TX gain setting uint8_t path_hash_mode = 0; // which path mode to use when sending uint8_t loop_detect = 0; uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean) @@ -82,7 +83,8 @@ class NodePrefs : public ConfigSerializer { def("cad", _parent->cad_enabled); def("int_thr", _parent->interference_threshold); def("rxgain", _parent->rx_boosted_gain); - def("fem_rxgain", _parent->rx_boosted_gain); + def("fem_rxgain", _parent->radio_fem_rxgain); + def("fem_txgain", _parent->radio_fem_txgain); def("tx", _parent->tx_power_dbm); def("af", _parent->airtime_factor); def("rxdelay", _parent->rx_delay_base); diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index b6c8fec56c..eb88c89744 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -5,6 +5,10 @@ #include #include +#ifdef USE_CC310_HW_CRYPTO +#include +#endif + static BLEDfu bledfu; static void connect_callback(uint16_t conn_handle) { @@ -21,6 +25,11 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; + + #ifdef USE_CC310_HW_CRYPTO + // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. + nRFCrypto.begin(); + #endif } #ifdef NRF52_POWER_MANAGEMENT @@ -352,6 +361,10 @@ void NRF52Board::shutdownPeripherals() { sensors.getLocationProvider()->stop(); } +#ifdef USE_CC310_HW_CRYPTO + nRFCrypto.end(); +#endif + // Flush serial buffers Serial.flush(); delay(100); diff --git a/src/helpers/RoutingPolicy.h b/src/helpers/RoutingPolicy.h new file mode 100644 index 0000000000..5251a590a4 --- /dev/null +++ b/src/helpers/RoutingPolicy.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +namespace mesh { + +/** + * \brief Test a flood packet against the configured hop limits. + * \param packet inbound flood packet (caller has already checked isRouteFlood()) + * \param flood_max max hops for any flood packet + * \param flood_max_unscoped max hops for ROUTE_TYPE_FLOOD (ie. un-scoped) packets + * \param flood_max_advert max hops for ADVERT packets + * \returns true if the packet has exceeded a limit, and must not be forwarded + */ +inline bool isFloodHopLimitExceeded(const Packet* packet, uint8_t flood_max, + uint8_t flood_max_unscoped, uint8_t flood_max_advert) { + uint8_t hops = packet->getPathHashCount(); + if (hops >= flood_max) return true; + if (packet->getRouteType() == ROUTE_TYPE_FLOOD && hops >= flood_max_unscoped) return true; + if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && hops >= flood_max_advert) return true; + return false; +} + +/** + * \brief How a server routes a reply back to the requesting client. + */ +enum ReplyRoute : uint8_t { + REPLY_ROUTE_PATH_RETURN, // request arrived by flood: reply with a PATH return, flooded back + REPLY_ROUTE_DIRECT_SUPPLIED, // reply DIRECT, along the return path supplied in the request + REPLY_ROUTE_DIRECT_OUT_PATH, // reply DIRECT, along the out_path already stored for this client + REPLY_ROUTE_FLOOD, // no return path known: flood the reply +}; + +/** + * \param inbound_is_flood the request arrived as a flood packet + * \param have_supplied_path the request payload carried an explicit reply path + * \param have_out_path this server already has a stored out_path for the client + */ +inline ReplyRoute chooseReplyRoute(bool inbound_is_flood, bool have_supplied_path, bool have_out_path) { + if (inbound_is_flood) return REPLY_ROUTE_PATH_RETURN; + if (have_supplied_path) return REPLY_ROUTE_DIRECT_SUPPLIED; + if (have_out_path) return REPLY_ROUTE_DIRECT_OUT_PATH; + return REPLY_ROUTE_FLOOD; +} + +/** + * \brief Which transport scope a flooded reply should be sent with. + */ +enum ReplyScope : uint8_t { + REPLY_SCOPE_REQUEST, // re-use the scope the request arrived on + REPLY_SCOPE_DEFAULT, // fall back to this node's default region scope + REPLY_SCOPE_NONE, // send un-scoped (ROUTE_TYPE_FLOOD) +}; + +/** + * \param request_scope_known request arrived scoped, and we resolved its Region's key + * \param request_was_unscoped_flood request arrived as an un-scoped flood + * \param default_scope_known this node has a default Region with a usable transport key + */ +inline ReplyScope chooseReplyScope(bool request_scope_known, bool request_was_unscoped_flood, + bool default_scope_known) { + if (request_scope_known) return REPLY_SCOPE_REQUEST; + if (request_was_unscoped_flood) return REPLY_SCOPE_NONE; // requester chose un-scoped, so mirror it + if (default_scope_known) return REPLY_SCOPE_DEFAULT; // scope unknowable: DIRECT, or unresolved Region + return REPLY_SCOPE_NONE; +} + +} diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 44230c61c3..e7aaeb937a 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -19,7 +19,6 @@ class CustomLR1110Wrapper : public RadioLibWrapper { ((CustomLR1110 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); } - void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } @@ -50,4 +49,6 @@ class CustomLR1110Wrapper : public RadioLibWrapper { bool getRxBoostedGainMode() const override { return ((CustomLR1110 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz(), getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomLR2021.h b/src/helpers/radiolib/CustomLR2021.h index 17944ef006..a89ae94330 100644 --- a/src/helpers/radiolib/CustomLR2021.h +++ b/src/helpers/radiolib/CustomLR2021.h @@ -4,6 +4,10 @@ #include "MeshCore.h" class CustomLR2021 : public LR2021 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; bool _rx_boosted = false; public: @@ -66,11 +70,62 @@ class CustomLR2021 : public LR2021 { bool getRxBoostedGainMode() const { return _rx_boosted; } + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return LR2021::startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { uint32_t irq = getIrqStatus(); - bool detected = ((irq & RADIOLIB_LR2021_IRQ_SYNCWORD_VALID) || (irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED)); - return detected; + bool preamble = irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED; // bit 5 + bool header = irq & RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID; // bit 6 + bool hdrErr = irq & RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR; // bit 9 + uint32_t now = millis(); + if (hdrErr) { + clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); + } + uint8_t getSpreadingFactor() const { return spreadingFactor; } }; \ No newline at end of file diff --git a/src/helpers/radiolib/CustomLR2021Wrapper.h b/src/helpers/radiolib/CustomLR2021Wrapper.h index 879898315e..8299e2bf55 100644 --- a/src/helpers/radiolib/CustomLR2021Wrapper.h +++ b/src/helpers/radiolib/CustomLR2021Wrapper.h @@ -22,6 +22,10 @@ class CustomLR2021Wrapper : public RadioLibWrapper { ((CustomLR2021 *)_radio)->setCodingRate(cr); updatePreamble(sf); applySideDetectorConfig(); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomLR2021 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomLR2021 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); + } bool configSideDetectors(const uint8_t* sideDetSFs, uint8_t num, float bw) override { diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index a792a87750..a4ba4971d0 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -35,5 +35,5 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { } uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index bfea50ec9c..be3144716b 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -41,12 +41,12 @@ class CustomSX1262Wrapper : public RadioLibWrapper { ((CustomSX1262 *)_radio)->sleep(false); } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - bool setRxBoostedGainMode(bool en) override { return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomSX1262 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 104ba08b20..70f5dabdc6 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -38,12 +38,12 @@ class CustomSX1268Wrapper : public RadioLibWrapper { } uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; } - void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } - bool setRxBoostedGainMode(bool en) override { return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; } bool getRxBoostedGainMode() const override { return ((CustomSX1268 *)_radio)->getRxBoostedGainMode(); } + + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); } }; diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index d06ffc538e..cdfc1f9fee 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -5,7 +5,7 @@ // Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). // Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, // then re-applies RX settings that calibration may reset. -inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { +inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz, bool rx_boost_gain) { radio->sleep(true, 0); radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); @@ -16,6 +16,6 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); #ifdef RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); + radio->setRxBoostedGainMode(rx_boost_gain); #endif } diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index dc851e5cfe..b9c095ac40 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -15,7 +15,7 @@ static volatile uint8_t state = STATE_IDLE; // this function is called when a complete packet // is transmitted by the module -static +static #if defined(ESP8266) || defined(ESP32) ICACHE_RAM_ATTR #endif @@ -100,7 +100,9 @@ void RadioLibWrapper::loop() { } _floor_sample_sum = 0; + #ifdef MESH_DEBUG_NOISE_FLOOR MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor); + #endif } } @@ -224,10 +226,10 @@ static float snr_threshold[] = { -17.5,// SF11 needs at least -17.5 dB SNR -20 // SF12 needs at least -20 dB SNR }; - + float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) { if (sf < 7) return 0.0f; - + if (snr < snr_threshold[sf - 7]) return 0.0f; // Below threshold, no chance of success auto success_rate_based_on_snr = (snr - snr_threshold[sf - 7]) / 10.0; @@ -243,7 +245,7 @@ PacketMillis RadioLibWrapper::calcMaxPacketMillis(uint8_t sf, float bw, uint8_t // preamble + syncword + sfd + header uint32_t preamble_us = (((preambleSymbols + 8) * 4 + sfCoeff1_x4) * tsym_us) / 4; - + // airtime for max packet at current radio settings uint32_t total_us = _radio->getTimeOnAir(MAX_TRANS_UNIT); // airtime for payload only (no preamble, header or SOF) diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 99f5ebbd8e..5db1e41f0f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -93,9 +93,7 @@ class RadioNoiseListener : public mesh::RNG { void random(uint8_t* dest, size_t sz) override { #ifdef USE_CC310_HW_CRYPTO // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. - nRFCrypto.begin(); nRFCrypto.Random.generate(dest, (uint16_t)sz); - nRFCrypto.end(); #else for (int i = 0; i < sz; i++) { dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h index 39ddb73eed..472eb33bca 100644 --- a/src/helpers/radiolib/SX126xReset.h +++ b/src/helpers/radiolib/SX126xReset.h @@ -5,7 +5,7 @@ // Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx). // Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration, // then re-applies RX settings that calibration may reset. -inline void sx126xResetAGC(SX126x* radio) { +inline void sx126xResetAGC(SX126x* radio, bool rx_boost_gain) { radio->sleep(true); radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); @@ -26,7 +26,7 @@ inline void sx126xResetAGC(SX126x* radio) { radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); #endif #ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); + radio->setRxBoostedGainMode(rx_boost_gain); #endif #ifdef SX126X_REGISTER_PATCH uint8_t r_data = 0; diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index c3840c02af..8b91d857c0 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -24,8 +24,21 @@ bool SH1106Display::begin() { // Wire must already be initialised by board.begin() before this is called. // Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS - // in their variant/platformio configuration. - return i2c_probe(Wire, DISPLAY_ADDRESS) && display.begin(DISPLAY_ADDRESS, true); + // in their variant/platformio configuration. The SA0 strap selects 0x3C or + // 0x3D and differs between revisions of the same board (e.g. T-Beam + // Supreme), so fall back to the other address of the pair. + uint8_t addr = 0; + if (i2c_probe(Wire, DISPLAY_ADDRESS)) { + addr = DISPLAY_ADDRESS; + } else if (i2c_probe(Wire, DISPLAY_ADDRESS ^ 1)) { + addr = DISPLAY_ADDRESS ^ 1; + } + // Run the Adafruit init even when no panel answered: it is what allocates + // the frame buffer and the I2C device. Skipping it leaves i2c_dev and + // spi_dev NULL, and UITask::begin() calls turnOn() regardless of our + // return value, which then dereferences the null spi_dev. + bool ok = display.begin(addr ? addr : DISPLAY_ADDRESS, true); + return addr != 0 && ok; } void SH1106Display::turnOn() diff --git a/test/test_companion_node_prefs/test_companion_node_prefs.cpp b/test/test_companion_node_prefs/test_companion_node_prefs.cpp new file mode 100644 index 0000000000..433c971060 --- /dev/null +++ b/test/test_companion_node_prefs/test_companion_node_prefs.cpp @@ -0,0 +1,83 @@ +#include + +#include +#include +#include + +#include "../../examples/companion_radio/NodePrefs.h" + +class ReplayStream : public Stream { + const char* _text; + int _pos = 0; + int _len; + +public: + explicit ReplayStream(const char* text) : _text(text), _len(strlen(text)) { } + + int available() override { return _len - _pos; } + int read() override { return _pos < _len ? _text[_pos++] : -1; } + int peek() override { return _pos < _len ? _text[_pos] : -1; } +}; + +class CaptureStream : public Stream { + std::string _text; + + size_t emit(long long value) { + char text[24]; + int length = snprintf(text, sizeof(text), "%lld", value); + return write(reinterpret_cast(text), length); + } + +public: + size_t write(uint8_t value) override { + _text.push_back(static_cast(value)); + return 1; + } + + size_t write(const uint8_t* buffer, size_t size) override { + _text.append(reinterpret_cast(buffer), size); + return size; + } + + size_t print(unsigned char value, int = DEC) override { return emit(value); } + size_t print(int value, int = DEC) override { return emit(value); } + size_t print(unsigned int value, int = DEC) override { return emit(value); } + size_t print(long value, int = DEC) override { return emit(value); } + size_t print(unsigned long value, int = DEC) override { return emit(value); } + size_t print(long long value, int = DEC) override { return emit(value); } + size_t print(unsigned long long value, int = DEC) override { return emit(value); } + + const std::string& text() const { return _text; } +}; + +#if 0 +// Re-enable test once we can SET fem_ values in companion +TEST(CompanionNodePrefs, RxGainSettingsRoundTripIndependently) { + NodePrefs saved; + saved.rx_boosted_gain = 0; + saved.radio_fem_rxgain = 1; + saved.radio_fem_txgain = 0; + + CaptureStream output; + ASSERT_TRUE(saved.saveSerial(output)); + EXPECT_NE(std::string::npos, output.text().find("rxgain:0")); + EXPECT_NE(std::string::npos, output.text().find("fem_rxgain:1")); + EXPECT_NE(std::string::npos, output.text().find("fem_txgain:0")); + + ReplayStream input("{radio:{rxgain:1,fem_rxgain:0,fem_txgain:1}}"); + NodePrefs loaded; + loaded.rx_boosted_gain = 0; + loaded.radio_fem_rxgain = 1; + loaded.radio_fem_txgain = 0; + + ASSERT_TRUE(loaded.loadSerial(input)); + EXPECT_EQ(1, loaded.rx_boosted_gain); + EXPECT_EQ(0, loaded.radio_fem_rxgain); + EXPECT_EQ(1, loaded.radio_fem_txgain); +} +#endif + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test_config_serializer/test_config_serializer.cpp b/test/test_config_serializer/test_config_serializer.cpp index 7a13f487e2..27c3c8119e 100644 --- a/test/test_config_serializer/test_config_serializer.cpp +++ b/test/test_config_serializer/test_config_serializer.cpp @@ -1,6 +1,14 @@ #include #include "helpers/ConfigSerializer.h" +class NativeFileSystem { +public: + void mkdir(const char*) { } +}; +#define FILESYSTEM NativeFileSystem +#include "helpers/CommonCLI.h" +#undef FILESYSTEM + #define TEST_INT_S "56" #define TEST_INT 56 #define TEST_FLOAT_S "-6.123" @@ -21,6 +29,19 @@ class MockInputStream : public Stream { class MockPrintStream : public Stream { int len = 0; uint8_t _buf[1024]; + + size_t printSigned(long long value) { + char text[24]; + snprintf(text, sizeof(text), "%lld", value); + return Print::print(text); + } + + size_t printUnsigned(unsigned long long value) { + char text[24]; + snprintf(text, sizeof(text), "%llu", value); + return Print::print(text); + } + public: size_t write(uint8_t b) override { if (len < sizeof(_buf)) { @@ -30,17 +51,17 @@ class MockPrintStream : public Stream { return 0; } - size_t print(unsigned char b, int r) override { if (b == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(unsigned int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(unsigned long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(unsigned long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } - size_t print(double v, int p = 2) override { - if (p == 6) return Print::print(TEST_DOUBLE_S); - if (p == 4) return Print::print(TEST_FLOAT_S); - return 0; + size_t print(unsigned char v, int r) override { return printUnsigned(v); } + size_t print(int v, int r) override { return printSigned(v); } + size_t print(unsigned int v, int r) override { return printUnsigned(v); } + size_t print(long v, int r) override { return printSigned(v); } + size_t print(unsigned long v, int r) override { return printUnsigned(v); } + size_t print(long long v, int r) override { return printSigned(v); } + size_t print(unsigned long long v, int r) override { return printUnsigned(v); } + size_t print(double v, int p = 2) override { + char text[32]; + snprintf(text, sizeof(text), "%.*f", p, v); + return Print::print(text); } int getLength() const { return len; } @@ -171,6 +192,28 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) { EXPECT_TRUE(match); } +TEST(NodePrefs, FemGainSettingsRoundTrip) { + NodePrefs saved; + saved.radio_fem_rxgain = 0; + saved.radio_fem_txgain = 1; + + MockPrintStream output; + ASSERT_TRUE(saved.saveSerial(output)); + + std::string serialised(reinterpret_cast(output.getBytes()), output.getLength()); + EXPECT_NE(std::string::npos, serialised.find("fem_rxgain:0")); + EXPECT_NE(std::string::npos, serialised.find("fem_txgain:1")); + + MockInputStream input(serialised.c_str()); + NodePrefs loaded; + loaded.radio_fem_rxgain = 1; + loaded.radio_fem_txgain = 0; + + ASSERT_TRUE(loaded.loadSerial(input)); + EXPECT_EQ(0, loaded.radio_fem_rxgain); + EXPECT_EQ(1, loaded.radio_fem_txgain); +} + // ── main ─────────────────────────────────────────────────────── diff --git a/test/test_routing_policy/test_routing_policy.cpp b/test/test_routing_policy/test_routing_policy.cpp new file mode 100644 index 0000000000..bba9ec7901 --- /dev/null +++ b/test/test_routing_policy/test_routing_policy.cpp @@ -0,0 +1,110 @@ +#include +#include "helpers/RoutingPolicy.h" + +using namespace mesh; + +static Packet makeFlood(uint8_t route_type, uint8_t payload_type, uint8_t hops) { + Packet p; + p.header = route_type | (payload_type << PH_TYPE_SHIFT); + p.setPathHashSizeAndCount(1, hops); + p.payload_len = 1; + return p; +} + +TEST(FloodHopLimit, UnscopedFloodIsDroppedAtFirstHopWhenMaxUnscopedIsZero) { + auto pkt = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 0); + EXPECT_TRUE(isFloodHopLimitExceeded(&pkt, 64, 0, 8)); +} + +TEST(FloodHopLimit, ScopedFloodIsForwardedWhenMaxUnscopedIsZero) { + for (uint8_t hops = 0; hops < 4; hops++) { + auto pkt = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_RESPONSE, hops); + EXPECT_FALSE(isFloodHopLimitExceeded(&pkt, 64, 0, 8)) << "hops=" << (int)hops; + } +} + +TEST(FloodHopLimit, UnscopedFloodSurvivesUpToMaxUnscopedHops) { + // matches the reported workaround: raising flood.max.unscoped to the expected hop count + auto ok = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 2); + EXPECT_FALSE(isFloodHopLimitExceeded(&ok, 64, 3, 8)); + + auto too_far = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 3); + EXPECT_TRUE(isFloodHopLimitExceeded(&too_far, 64, 3, 8)); +} + +TEST(FloodHopLimit, ScopedFloodStillHonoursFloodMaxAndAdvertMax) { + auto beyond_max = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_RESPONSE, 5); + EXPECT_TRUE(isFloodHopLimitExceeded(&beyond_max, 5, 64, 8)); + + auto advert = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_ADVERT, 8); + EXPECT_TRUE(isFloodHopLimitExceeded(&advert, 64, 64, 8)); +} + +// flood.max.unscoped=0 hits adverts too, well before flood_max_advert applies: a node +// still advertising un-scoped is invisible past its immediate neighbours +TEST(FloodHopLimit, UnscopedAdvertIsAlsoDroppedAtHopZero) { + auto advert = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_ADVERT, 0); + EXPECT_TRUE(isFloodHopLimitExceeded(&advert, 64, 0, 8)); + + auto scoped = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_ADVERT, 0); + EXPECT_FALSE(isFloodHopLimitExceeded(&scoped, 64, 0, 8)); +} + +TEST(ReplyRoute, FloodRequestGetsAPathReturn) { + EXPECT_EQ(REPLY_ROUTE_PATH_RETURN, + chooseReplyRoute(true, false, false)); + EXPECT_EQ(REPLY_ROUTE_PATH_RETURN, + chooseReplyRoute(true, false, true)); +} + +TEST(ReplyRoute, DirectRequestWithSuppliedPathRepliesDirect) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_SUPPLIED, chooseReplyRoute(false, true, false)); +} + +// the reported bug: a DIRECT login (app already has a path) was answered by flooding, even +// with an out_path stored. Under flood.max.unscoped=0 that reply never arrives. +TEST(ReplyRoute, DirectRequestWithKnownOutPathRepliesDirect) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_OUT_PATH, + chooseReplyRoute(false, false, true)); +} + +TEST(ReplyRoute, SuppliedPathWinsOverStoredOutPath) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_SUPPLIED, chooseReplyRoute(false, true, true)); +} + +TEST(ReplyRoute, DirectRequestWithNoReturnPathFallsBackToFlood) { + EXPECT_EQ(REPLY_ROUTE_FLOOD, chooseReplyRoute(false, false, false)); +} + +TEST(ReplyScope, MirrorsTheRequestScopeWhenKnown) { + EXPECT_EQ(REPLY_SCOPE_REQUEST, chooseReplyScope(true, + false, + false)); + EXPECT_EQ(REPLY_SCOPE_REQUEST, chooseReplyScope(true, false, true)); +} + +// un-scoped is itself a known scope, so mirror it. Replying scoped would change a path that +// works today, and repeaters not holding our default Region would drop it anyway. +TEST(ReplyScope, RepliesUnscopedToAnUnscopedFloodEvenWhenADefaultScopeExists) { + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, + true, + true)); +} + +// second half of the bug: a DIRECT request carries no transport codes, so recv_pkt_region is +// always NULL. Un-scoped is dropped under flood.max.unscoped=0, and floods the mesh otherwise. +TEST(ReplyScope, FallsBackToDefaultScopeWhenRequestScopeUnknown) { + EXPECT_EQ(REPLY_SCOPE_DEFAULT, chooseReplyScope(false, + false, + true)); +} + +TEST(ReplyScope, SendsUnscopedOnlyWhenNoScopeIsAvailableAtAll) { + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, false, false)); + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, true, false)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/variants/heltec_t096/variant.h b/variants/heltec_t096/variant.h index c240c1f27b..bfbf2e636c 100644 --- a/variants/heltec_t096/variant.h +++ b/variants/heltec_t096/variant.h @@ -104,7 +104,7 @@ #define PIN_SPI_SCK (32 + 8) #define PIN_SPI_NSS LORA_CS -#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MISO (0) #define PIN_SPI1_MOSI (0+17) #define PIN_SPI1_SCK (0+20) diff --git a/variants/heltec_t1/variant.h b/variants/heltec_t1/variant.h index 7ee2181615..b52aeba0ae 100644 --- a/variants/heltec_t1/variant.h +++ b/variants/heltec_t1/variant.h @@ -25,8 +25,8 @@ #define ST7735_SDA (0 + 24) #define ST7735_SCK (32 + 0) #define ST7735_RESET (0 + 20) -#define ST7735_MISO (-1) -#define ST7735_BUSY (-1) +#define ST7735_MISO (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED +#define ST7735_BUSY (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define ST7735_BL (0 + 15) #define VTFT_CTRL (0 + 13) @@ -66,8 +66,7 @@ // UART // No longer populated on PCB. -#define PIN_SERIAL2_RX (-1) -#define PIN_SERIAL2_TX (-1) +// Removed the pin definitions to avoid potential issues with Uart. //////////////////////////////////////////////////////////////////////////////// // I2C diff --git a/variants/lilygo_techo_card/platformio.ini b/variants/lilygo_techo_card/platformio.ini index 4ebf43da9d..17ba8d1b2b 100644 --- a/variants/lilygo_techo_card/platformio.ini +++ b/variants/lilygo_techo_card/platformio.ini @@ -119,3 +119,8 @@ lib_deps = ${LilyGo_T-Echo_Card.lib_deps} end2endzone/NonBlockingRTTTL@^1.3.0 densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_T-Echo_Card_kiss_modem] +extends = LilyGo_T-Echo_Card +build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_techo_card/variant.h b/variants/lilygo_techo_card/variant.h index 28c5f550c1..699453e0ee 100644 --- a/variants/lilygo_techo_card/variant.h +++ b/variants/lilygo_techo_card/variant.h @@ -107,7 +107,7 @@ #define P_LORA_NSS (11) // P0.11 #define SX126X_RXEN (33) // P1.01 #define SX126X_TXEN (27) // P0.27 -#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) +#define SX126X_DIO3_TCXO_VOLTAGE (3.0f) //////////////////////////////////////////////////////////////////////////////// diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 9ec73d59a1..1a8ad67723 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -12,10 +12,9 @@ build_flags = ${nrf52_base.build_flags} -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_POWER_EN=30 - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D SX126X_USE_REGULATOR_LDO=1 -D P_LORA_TX_LED=LED_GREEN -D DISABLE_DIAGNOSTIC_OUTPUT -D ENV_INCLUDE_GPS=1 @@ -117,7 +116,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_POWER_EN=30 - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D P_LORA_TX_LED=LED_GREEN @@ -162,6 +161,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_POWER_EN=30 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D P_LORA_TX_LED=LED_GREEN diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 702ab33499..83bb6f07ce 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -58,7 +58,7 @@ #define PIN_SPI_MISO _PINNUM(0, 17) // (MISO) #define PIN_SPI_MOSI _PINNUM(0, 15) // (MOSI) #define PIN_SPI_SCK _PINNUM(0, 13) // (SCK) -#define PIN_SPI_NSS (-1) +#define PIN_SPI_NSS (0) //////////////////////////////////////////////////////////////////////////////// // QSPI FLASH @@ -105,12 +105,14 @@ #define LORA_CS _PINNUM(0, 11) #define SX126X_POWER_EN _PINNUM(0, 30) #define SX126X_DIO1 _PINNUM(1, 8) +#define SX126X_DIO2 _PINNUM(0, 5) #define SX126X_BUSY _PINNUM(0, 14) #define SX126X_RESET _PINNUM(0, 7) #define SX126X_RXEN _PINNUM(1, 1) #define SX126X_TXEN _PINNUM(0, 27) #define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_DIO_2 SX126X_DIO2 #define P_LORA_NSS LORA_CS #define P_LORA_RESET SX126X_RESET #define P_LORA_BUSY SX126X_BUSY @@ -121,7 +123,7 @@ //////////////////////////////////////////////////////////////////////////////// // SPI1 -#define PIN_SPI1_MISO (-1) // Not used for Display +#define PIN_SPI1_MISO (0) // Not used for Display, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_SPI1_MOSI _PINNUM(0, 20) #define PIN_SPI1_SCK _PINNUM(0, 19) @@ -133,7 +135,7 @@ extern const int SCK; //////////////////////////////////////////////////////////////////////////////// // Display -// #define DISP_MISO (-1) // Not used for Display +// #define DISP_MISO (0) // Not used for Display, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define DISP_MOSI _PINNUM(0, 20) #define DISP_SCLK _PINNUM(0, 19) #define DISP_CS _PINNUM(0, 22) @@ -141,7 +143,7 @@ extern const int SCK; #define DISP_RST _PINNUM(0, 28) #define DISP_BUSY _PINNUM(0, 3) #define DISP_POWER _PINNUM(1, 12) -// #define DISP_BACKLIGHT (-1) // Display has no backlight +// #define DISP_BACKLIGHT (0) // Display has no backlight, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_DISPLAY_CS DISP_CS #define PIN_DISPLAY_DC DISP_DC diff --git a/variants/lilygo_teth_elite/platformio.ini b/variants/lilygo_teth_elite/platformio.ini index ee1b987953..2debb03962 100644 --- a/variants/lilygo_teth_elite/platformio.ini +++ b/variants/lilygo_teth_elite/platformio.ini @@ -98,3 +98,8 @@ build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter} lib_deps = ${LilyGo_TETH_Elite_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_TETH_Elite_sx1262_kiss_modem] +extends = LilyGo_TETH_Elite_sx1262 +build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/mesh_pocket/variant.h b/variants/mesh_pocket/variant.h index 870d062a75..9774276cf1 100644 --- a/variants/mesh_pocket/variant.h +++ b/variants/mesh_pocket/variant.h @@ -108,7 +108,7 @@ #define PIN_DISPLAY_DC (31) #define PIN_DISPLAY_RST (32 + 4) -#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MISO (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_SPI1_MOSI (20) #define PIN_SPI1_SCK (22) diff --git a/variants/meshtracker_x1/platformio.ini b/variants/meshtracker_x1/platformio.ini index 1ce8bf17ad..2194d48d98 100644 --- a/variants/meshtracker_x1/platformio.ini +++ b/variants/meshtracker_x1/platformio.ini @@ -130,3 +130,8 @@ lib_deps = ${MeshTracker_X1.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 adafruit/Adafruit DRV2605 Library @ ^1.2.4 + +[env:MeshTracker_X1_kiss_modem] +extends = MeshTracker_X1 +build_src_filter = ${MeshTracker_X1.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index d115a1f1a8..1743e71992 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -21,6 +21,7 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_INA219=1 build_src_filter = ${nrf52_base.build_src_filter} + + + +<../variants/minewsemi_me25ls01> + debug_tool = jlink @@ -55,7 +56,6 @@ build_flags = ${me25ls01.build_flags} ;-D PIN_BUZZER=25 ;-D PIN_BUZZER_EN=37 build_src_filter = ${me25ls01.build_src_filter} - + + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> @@ -63,10 +63,6 @@ build_src_filter = ${me25ls01.build_src_filter} [env:Minewsemi_me25ls01_repeater] extends = me25ls01 build_flags = ${me25ls01.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 @@ -85,10 +81,6 @@ build_src_filter = ${me25ls01.build_src_filter} [env:Minewsemi_me25ls01_room_server] extends = me25ls01 build_flags = ${me25ls01.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 -; -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 @@ -103,15 +95,10 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_room_server> - + [env:Minewsemi_me25ls01_terminal_chat] extends = me25ls01 build_flags = ${me25ls01.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 - -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 @@ -126,7 +113,6 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_secure_chat/main.cpp> - + [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 @@ -137,7 +123,6 @@ build_flags = ${me25ls01.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ;-D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 @@ -147,7 +132,6 @@ build_flags = ${me25ls01.build_flags} -D ENABLE_USB_INTERFACE build_src_filter = ${me25ls01.build_src_filter} + - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index 9944a38b93..41fc71a7d9 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -4,7 +4,6 @@ MinewsemiME25LS01Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); - WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; @@ -18,7 +17,8 @@ extern EnvironmentSensorManager sensors; #endif #ifdef DISPLAY_CLASS - NullDisplayDriver display; + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif #ifndef LORA_CR diff --git a/variants/minewsemi_me25ls01/target.h b/variants/minewsemi_me25ls01/target.h index 978e616b96..db44058c81 100644 --- a/variants/minewsemi_me25ls01/target.h +++ b/variants/minewsemi_me25ls01/target.h @@ -11,16 +11,17 @@ #include #ifdef DISPLAY_CLASS #include -#endif - -#ifdef DISPLAY_CLASS - extern NullDisplayDriver display; + #include #endif extern MinewsemiME25LS01Board board; extern WRAPPER_CLASS radio_driver; extern VolatileRTCClock rtc_clock; extern EnvironmentSensorManager sensors; +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif bool radio_init(); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/muziworks_r1_neo/platformio.ini b/variants/muziworks_r1_neo/platformio.ini index 52dc3e38fe..c139e34a07 100644 --- a/variants/muziworks_r1_neo/platformio.ini +++ b/variants/muziworks_r1_neo/platformio.ini @@ -10,6 +10,7 @@ build_flags = ${nrf52_base.build_flags} -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper + -D DISPLAY_CLASS=NullDisplayDriver -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 @@ -100,7 +101,6 @@ build_src_filter = ${R1Neo.build_src_filter} +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${R1Neo.lib_deps} - ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/muziworks_r1_neo/target.cpp b/variants/muziworks_r1_neo/target.cpp index 68655c5d78..f667a2827e 100644 --- a/variants/muziworks_r1_neo/target.cpp +++ b/variants/muziworks_r1_neo/target.cpp @@ -4,12 +4,17 @@ R1NeoBoard board; -DISPLAY_CLASS display; - RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); - WRAPPER_CLASS radio_driver(radio, board); +#ifdef DISPLAY_CLASS + NullDisplayDriver display; +#endif + +#ifdef PIN_USER_BTN +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index b190c47c81..ee16c9654b 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -4,19 +4,6 @@ #include #include -#define P_LORA_NSS 13 //P1.13 45 -#define P_LORA_DIO_1 11 //P0.10 10 -#define P_LORA_RESET 10 //P0.09 9 -#define P_LORA_BUSY 16 //P0.29 29 -#define P_LORA_MISO 15 //P0.02 2 -#define P_LORA_SCLK 12 //P1.11 43 -#define P_LORA_MOSI 14 //P1.15 47 -#define SX126X_POWER_EN 21 //P0.13 13 -#define SX126X_RXEN 2 //P0.17 -#define SX126X_TXEN RADIOLIB_NC -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) - #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking diff --git a/variants/promicro/variant.cpp b/variants/promicro/variant.cpp index 0a4c3aac5b..69b61745bd 100644 --- a/variants/promicro/variant.cpp +++ b/variants/promicro/variant.cpp @@ -3,13 +3,29 @@ #include "wiring_digital.h" const uint32_t g_ADigitalPinMap[] = { - 8, 6, 17, 20, 22, 24, 32, 11, 36, 38, - 9, 10, 43, 45, 47, 2, 29, 31, - 33, 34, 37, - 13, 15 + 8, // P0.08 = 0 + 6, // P0.06 = 1 + 17, // P0.17 = 2 + 20, // P0.20 = 3 + 22, // P0.22 = 4 + 24, // P0.24 = 5 + 32, // P1.00 = 6 + 11, // P0.11 = 7 + 36, // P1.04 = 8 + 38, // P1.06 = 9 + 9, // P0.09 = 10 + 10, // P0.10 = 11 + 43, // P1.11 = 12 + 45, // P1.13 = 13 + 47, // P1.15 = 14 + 2, // P0.02 = 15 + 29, // P0.29 = 16 + 31, // P0.31 = 17 + 33, // P1.01 = 18 + 34, // P1.02 = 19 + 37, // P1.05 = 20 + 13, // P0.13 = 21 + 15 // P0.15 = 22 }; -void initVariant() -{ -} - +void initVariant() {} diff --git a/variants/promicro/variant.h b/variants/promicro/variant.h index 98489da193..e8a7833169 100644 --- a/variants/promicro/variant.h +++ b/variants/promicro/variant.h @@ -7,9 +7,9 @@ #pragma once #include "WVariant.h" - + //////////////////////////////////////////////////////////////////////////////// - // Low frequency clock source + // Low frequency clock source #define VARIANT_MCK (64000000ul) @@ -79,4 +79,18 @@ #define PIN_BUTTON1 (6) #define BUTTON_PIN PIN_BUTTON1 - +////////////////////////////////////////////////////////////////////////////// +// LoRa + +#define P_LORA_NSS (13) +#define P_LORA_DIO_1 (11) +#define P_LORA_RESET (10) +#define P_LORA_BUSY (16) +#define P_LORA_MISO (15) +#define P_LORA_SCLK (12) +#define P_LORA_MOSI (14) +#define SX126X_POWER_EN (21) +#define SX126X_RXEN (2) +#define SX126X_TXEN (-1) +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) diff --git a/variants/rpi_picow/PicoWBoard.cpp b/variants/rpi_picow/PicoWBoard.cpp index f345f96dfb..aa263dd4dd 100644 --- a/variants/rpi_picow/PicoWBoard.cpp +++ b/variants/rpi_picow/PicoWBoard.cpp @@ -27,7 +27,8 @@ void PicoWBoard::begin() { #endif #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) - Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); + Wire.setSDA(PIN_BOARD_SDA); + Wire.setSCL(PIN_BOARD_SCL); #endif Wire.begin(); diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index 0fe8c43696..ecceebabf0 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -22,10 +22,22 @@ build_flags = ${rp2040_base.build_flags} -D USE_SX1262 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper + -D PIN_BOARD_SCL=5 + -D PIN_BOARD_SDA=4 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_BMP280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 build_src_filter = ${rp2040_base.build_src_filter} - + + + +<../variants/rpi_picow> lib_deps = ${rp2040_base.lib_deps} + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit BMP280 Library @ ^2.6.8 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 [env:PicoW_repeater] extends = rpi_picow diff --git a/variants/rpi_picow/target.cpp b/variants/rpi_picow/target.cpp index 9318b7f0f7..12bbe74134 100644 --- a/variants/rpi_picow/target.cpp +++ b/variants/rpi_picow/target.cpp @@ -10,7 +10,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +EnvironmentSensorManager sensors; bool radio_init() { rtc_clock.begin(Wire); diff --git a/variants/rpi_picow/target.h b/variants/rpi_picow/target.h index eb918441ee..4bb0a01aae 100644 --- a/variants/rpi_picow/target.h +++ b/variants/rpi_picow/target.h @@ -6,12 +6,12 @@ #include #include #include -#include +#include extern PicoWBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; bool radio_init(); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index e643d03398..a5952d6e32 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -33,7 +33,7 @@ lib_deps=${esp32_base.lib_deps} lovyan03/LovyanGFX @ ^1.2.7 [env:SenseCapIndicator-ESPNow_comp_radio_usb] -extends =SenseCapIndicator-ESPNow +extends = SenseCapIndicator-ESPNow build_flags = ${SenseCapIndicator-ESPNow.build_flags} -I examples/companion_radio/ui-new @@ -47,4 +47,4 @@ build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCapIndicator-ESPNow.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 6e0eadbcb0..70405218f0 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -101,3 +101,8 @@ build_src_filter = ${SenseCap_Solar.build_src_filter} lib_deps = ${SenseCap_Solar.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:SenseCap_Solar_kiss_modem] +extends = SenseCap_Solar +build_src_filter = ${SenseCap_Solar.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 753aee7ab9..bdb7ee0c35 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -229,7 +229,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' - -D OFFLINE_QUEUE_SIZE=256 + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -242,10 +242,8 @@ lib_deps = [env:Station_G2_kiss_modem] extends = Station_G2 -build_unflags = - -DARDUINO_USB_MODE=0 build_flags = ${Station_G2.build_flags} - -DARDUINO_USB_MODE=1 + -D ARDUINO_USB_MODE=1 build_src_filter = ${Station_G2.build_src_filter} +<../examples/kiss_modem/> diff --git a/variants/station_g3_esp32/LoRaFEMControl.cpp b/variants/station_g3_esp32/LoRaFEMControl.cpp new file mode 100644 index 0000000000..8d4f90f6fa --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.cpp @@ -0,0 +1,79 @@ +#include "LoRaFEMControl.h" + +#include +#include + +void LoRaFEMControl::init() { +#ifdef P_PA1_EN + rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN); + pinMode(P_PA1_EN, OUTPUT); + applyPAGain(); +#endif + +#ifdef P_PRIMARY_LNA_EN + rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN); + pinMode(P_PRIMARY_LNA_EN, OUTPUT); + setRxModeEnable(); +#endif +} + +void LoRaFEMControl::setSleepModeEnable() { +#ifdef P_PA1_EN + // PA PL1 low/open selects the lower of the two hardware-jumper-selected levels. + digitalWrite(P_PA1_EN, !P_PA1_EN_ACTIVE); +#endif +#ifdef P_PRIMARY_LNA_EN + // Preserve the existing Station G3 power-off state. + digitalWrite(P_PRIMARY_LNA_EN, P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setTxModeEnable() { + // Latch the requested PA level here, before the SX1262 starts driving the PA. PA PL1 + // retargets the PA's DC-DC rail, so moving it mid-transmit collapses the supply while + // the PA is still driven at full input power. + applyPAGain(); +#ifdef P_PRIMARY_LNA_EN + digitalWrite(P_PRIMARY_LNA_EN, !P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setRxModeEnable() { +#ifdef P_PRIMARY_LNA_EN + digitalWrite(P_PRIMARY_LNA_EN, lna_enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE); +#endif +} + +void LoRaFEMControl::setLNAEnable(bool enabled) { + lna_enabled = enabled; + setRxModeEnable(); +} + +void LoRaFEMControl::setPAGainEnable(bool enabled) { + // Recorded only -- the pin is driven from setTxModeEnable(). The PA level only matters + // while transmitting, so deferring costs nothing and keeps the rail change out of an + // in-flight transmit (the CLI runs on every main-loop pass, including mid-TX). + pa_gain_enabled = enabled; +} + +void LoRaFEMControl::applyPAGain() { +#ifdef P_PA1_EN + digitalWrite(P_PA1_EN, pa_gain_enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE); +#endif +} + +bool LoRaFEMControl::canControlLNA() const { +#ifdef P_PRIMARY_LNA_EN + return true; +#else + return false; +#endif +} + +bool LoRaFEMControl::canControlPAGain() const { +#ifdef P_PA1_EN + return true; +#else + return false; +#endif +} diff --git a/variants/station_g3_esp32/LoRaFEMControl.h b/variants/station_g3_esp32/LoRaFEMControl.h new file mode 100644 index 0000000000..f622de91e4 --- /dev/null +++ b/variants/station_g3_esp32/LoRaFEMControl.h @@ -0,0 +1,22 @@ +#pragma once + +class LoRaFEMControl { +public: + void init(); + void setSleepModeEnable(); + void setTxModeEnable(); + void setRxModeEnable(); + void setLNAEnable(bool enabled); + void setPAGainEnable(bool enabled); + + bool canControlLNA() const; + bool canControlPAGain() const; + bool isLNAEnabled() const { return lna_enabled; } + bool isPAGainEnabled() const { return pa_gain_enabled; } + +private: + void applyPAGain(); + + bool lna_enabled = true; + bool pa_gain_enabled = false; +}; diff --git a/variants/station_g3_esp32/StationG3Board.cpp b/variants/station_g3_esp32/StationG3Board.cpp index 4a49831107..dd863aca61 100644 --- a/variants/station_g3_esp32/StationG3Board.cpp +++ b/variants/station_g3_esp32/StationG3Board.cpp @@ -1,15 +1,46 @@ #include "StationG3Board.h" void StationG3Board::powerOff() { + loRaFEMControl.setSleepModeEnable(); #ifdef P_PA1_EN - setPAModeHigh(false); rtc_gpio_hold_en((gpio_num_t)P_PA1_EN); #endif #ifdef P_PRIMARY_LNA_EN - setPrimaryLNAControl(true); rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN); #endif ESP32Board::powerOff(); } + +bool StationG3Board::setLoRaFemLnaEnabled(bool enable) { + if (!loRaFEMControl.canControlLNA()) { + return false; + } + loRaFEMControl.setLNAEnable(enable); + return true; +} + +bool StationG3Board::canControlLoRaFemLna() const { + return loRaFEMControl.canControlLNA(); +} + +bool StationG3Board::isLoRaFemLnaEnabled() const { + return loRaFEMControl.isLNAEnabled(); +} + +bool StationG3Board::setLoRaFemPaGainEnabled(bool enable) { + if (!loRaFEMControl.canControlPAGain()) { + return false; + } + loRaFEMControl.setPAGainEnable(enable); + return true; +} + +bool StationG3Board::canControlLoRaFemPaGain() const { + return loRaFEMControl.canControlPAGain(); +} + +bool StationG3Board::isLoRaFemPaGainEnabled() const { + return loRaFEMControl.isPAGainEnabled(); +} diff --git a/variants/station_g3_esp32/StationG3Board.h b/variants/station_g3_esp32/StationG3Board.h index 4b1fb81c10..52628eb6cc 100644 --- a/variants/station_g3_esp32/StationG3Board.h +++ b/variants/station_g3_esp32/StationG3Board.h @@ -3,45 +3,15 @@ #include #include #include - -#ifndef P_PRIMARY_LNA_EN_ACTIVE -#define P_PRIMARY_LNA_EN_ACTIVE LOW -#endif - -#ifndef P_PA1_EN_ACTIVE -#define P_PA1_EN_ACTIVE HIGH -#endif +#include "LoRaFEMControl.h" class StationG3Board : public ESP32Board { - void setPAModeHigh(bool enabled) { -#ifdef P_PA1_EN - // Station G3 PA PL1 mode: LOW/open is PA low, HIGH/short is PA high. - digitalWrite(P_PA1_EN, enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE); -#endif - } - - void setPrimaryLNAControl(bool enabled) { -#ifdef P_PRIMARY_LNA_EN - // Station G3 primary LNA mode is active-low: LOW/open is LNA on, HIGH/short is LNA off. - digitalWrite(P_PRIMARY_LNA_EN, enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE); -#endif - } - public: + LoRaFEMControl loRaFEMControl; + void begin() { ESP32Board::begin(); - -#ifdef P_PA1_EN - rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN); - pinMode(P_PA1_EN, OUTPUT); - setPAModeHigh(false); -#endif - -#ifdef P_PRIMARY_LNA_EN - rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN); - pinMode(P_PRIMARY_LNA_EN, OUTPUT); - setPrimaryLNAControl(true); -#endif + loRaFEMControl.init(); esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { @@ -56,23 +26,30 @@ class StationG3Board : public ESP32Board { } void setPrimaryLNAEnable(bool enabled) { - setPrimaryLNAControl(enabled); + loRaFEMControl.setLNAEnable(enabled); } void setPrimaryPAHighPower(bool enabled) { - setPAModeHigh(enabled); + loRaFEMControl.setPAGainEnable(enabled); } void onBeforeTransmit() override { ESP32Board::onBeforeTransmit(); - setPrimaryLNAControl(false); + loRaFEMControl.setTxModeEnable(); } void onAfterTransmit() override { ESP32Board::onAfterTransmit(); - setPrimaryLNAControl(true); + loRaFEMControl.setRxModeEnable(); } + bool setLoRaFemLnaEnabled(bool enable) override; + bool canControlLoRaFemLna() const override; + bool isLoRaFemLnaEnabled() const override; + bool setLoRaFemPaGainEnabled(bool enable) override; + bool canControlLoRaFemPaGain() const override; + bool isLoRaFemPaGainEnabled() const override; + void powerOff() override; uint16_t getBattMilliVolts() override { diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index e4a66a18ee..074d6a2ed4 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -17,11 +17,11 @@ build_flags = -D P_LORA_SCLK=12 -D P_LORA_MISO=14 -D P_LORA_MOSI=13 - -D P_PA1_EN=9 ; PA PL1 Mode: LOW/open is PA low, HIGH/short is PA high. + -D P_PA1_EN=9 ; PA PL1 Mode: LOW/open selects low level, HIGH/short selects high level. -D P_PA1_EN_ACTIVE=HIGH -D P_PRIMARY_LNA_EN=10 ; Primary Slot LNA Mode: LOW/open is LNA on, HIGH/short is LNA off. -D P_PRIMARY_LNA_EN_ACTIVE=LOW - -D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled. + -D LORA_TX_POWER=7 ; SX1262 input power to the Station G3 PA; final output depends on PA PL1/PL2 level. -D MAX_LORA_TX_POWER=22 ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5