Merge branch 'PowerSaving-v17' of https://github.com/IoTThinks/MeshCore into keymindCascade

# Conflicts:
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/NodePrefs.h
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	examples/simple_sensor/SensorMesh.cpp
#	platformio.ini
#	src/MeshCore.h
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/radiolib/CustomSX1262Wrapper.h
#	src/helpers/radiolib/CustomSX1268Wrapper.h
#	src/helpers/radiolib/LR11x0Reset.h
#	src/helpers/radiolib/SX126xReset.h
This commit is contained in:
mikecarper
2026-08-11 13:28:43 -07:00
33 changed files with 679 additions and 204 deletions
+1 -1
View File
@@ -1 +1 @@
github: meshcore-dev
github: IoTThinks
+2
View File
@@ -79,6 +79,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Radio | [`get freq`; `set freq <mhz>`](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes |
| Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature |
| Radio | [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature |
| Radio | [`get/set radio.fem.txgain`](cli_commands.md#view-or-change-the-lora-fem-transmit-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature |
| Radio | [`get/set radio.rxps`; `get rxps.wd`](#radio-rxps) | RX power-saving support | Feature | Feature | Feature |
| System | [`get/set name`](cli_commands.md#view-or-change-this-nodes-name) | All text CLI roles | Yes | Yes | Yes |
| System | [`get/set lat`](cli_commands.md#view-or-change-this-nodes-latitude) | All text CLI roles | Yes | Yes | Yes |
@@ -234,6 +235,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| Radio | [`get freq`; `set freq <mhz>`](cli_commands.md#view-or-change-this-nodes-frequency) | `set` is local serial only | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Radio | [`get/set radio.rxgain`](cli_commands.md#view-or-change-this-nodes-rx-boosted-gain-mode-sx12xx-and-lr1110-v1141) | Supported radio | Feature | Feature | Feature | Feature | Feature | Feature | Feature |
| Radio | [`get/set radio.fem.rxgain`](cli_commands.md#view-or-change-the-lora-fem-receive-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | Feature | Feature | Feature | Feature |
| Radio | [`get/set radio.fem.txgain`](cli_commands.md#view-or-change-the-lora-fem-transmit-path-gain-state-on-supported-boards) | Controllable LoRa FEM | Feature | Feature | Feature | Feature | Feature | Feature | Feature |
| Radio | [`get/set radio.rxps`; `get rxps.wd`](#radio-rxps) | Full parser and RX power-saving support | Feature | Feature | Feature | No | No | Feature | Feature |
| System | [`get/set name`](cli_commands.md#view-or-change-this-nodes-name) | All full-parser text CLI roles | Yes | Yes | Yes | No | No | Yes | Yes |
| System | [`get/set lat`](cli_commands.md#view-or-change-this-nodes-latitude) | All full-parser text CLI roles | Yes | Yes | Yes | No | No | Yes | Yes |
+16
View File
@@ -605,6 +605,22 @@ Station G2/G3 targets default to `off`.
---
#### View or change the LoRa FEM transmit-path gain state on supported boards
**Usage:**
- `get radio.fem.txgain`
- `set radio.fem.txgain <state>`
**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
+5 -1
View File
@@ -57,9 +57,13 @@ boost** switch. From the USB terminal, use:
get radio.fem.rxgain
set radio.fem.rxgain off
set radio.fem.rxgain on
get radio.fem.txgain
set radio.fem.txgain off
set radio.fem.txgain on
```
The selected state is applied immediately and retained after reboot.
The selected states are applied immediately and retained after reboot. FEM TX
gain is reported as unsupported on boards without software-selectable PA gain.
Artifacts are written to `out/` by default.
+7 -5
View File
@@ -87,12 +87,14 @@ Sets the transmit air-time-factor. Deprecated - use `set dutycycle` instead.
```
get radio.fem.rxgain
set radio.fem.rxgain {on|off}
get radio.fem.txgain
set radio.fem.txgain {on|off}
```
Shows or changes the external FEM receive-path LNA on supported Heltec boards.
The change is applied immediately, saved across reboots, and recalibrates the
radio noise floor. This is separate from the SX126x `radio.rxgain` setting.
Boards whose FEM receive path cannot be controlled report the setting as
unsupported.
Shows or changes the external FEM receive-path LNA or transmit-path gain on
supported boards. Changes are applied immediately and saved across reboots;
changing RX gain also recalibrates the radio noise floor. These settings are
separate from the SX126x `radio.rxgain` setting. Boards without the respective
control report it as unsupported.
```
+5
View File
@@ -320,6 +320,9 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
if (file.available() >= (int)sizeof(_prefs.vibe_quiet)) {
file.read((uint8_t *)&_prefs.vibe_quiet, sizeof(_prefs.vibe_quiet)); // 124
}
if (file.available() >= (int)sizeof(_prefs.radio_fem_txgain)) {
file.read((uint8_t *)&_prefs.radio_fem_txgain, sizeof(_prefs.radio_fem_txgain)); // 125
}
file.close();
}
@@ -369,6 +372,8 @@ bool DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
sizeof(_prefs.radio_fem_rxgain_override)) == sizeof(_prefs.radio_fem_rxgain_override); // 123
success = success && file.write((uint8_t *)&_prefs.vibe_quiet,
sizeof(_prefs.vibe_quiet)) == sizeof(_prefs.vibe_quiet); // 124
success = success && file.write((uint8_t *)&_prefs.radio_fem_txgain,
sizeof(_prefs.radio_fem_txgain)) == sizeof(_prefs.radio_fem_txgain); // 125
#if defined(NRF52_PLATFORM)
success = file.commit(success);
+48
View File
@@ -1357,6 +1357,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_prefs.gps_interval = 0; // No automatic GPS updates by default
_prefs.autoadd_config = DEFAULT_AUTOADD_CONFIG;
_prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE;
_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) || defined(USE_LR1110) \
@@ -1436,6 +1437,7 @@ void MyMesh::begin(bool has_display) {
_prefs.radio_fem_rxgain = DEFAULT_FEM_RX_GAIN;
}
_prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1);
_prefs.radio_fem_txgain = constrain(_prefs.radio_fem_txgain, 0, 1);
#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {
@@ -1484,6 +1486,7 @@ void MyMesh::begin(bool has_display) {
if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) {
_radio->recalibrateNoiseFloor();
}
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
@@ -2047,6 +2050,19 @@ void MyMesh::execCommand(char* cmd, char* reply) {
}
return;
}
if (strcmp(key, "radio.fem.txgain") == 0) {
bool enabled;
if (!wcParseBool(value, enabled)) {
strcpy(reply, "Error: must be on or off");
} else if (!board.canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else if (!applyAndSaveFemTxGain(enabled)) {
strcpy(reply, "Error: failed to apply FEM TX gain");
} else {
strcpy(reply, "OK");
}
return;
}
#ifdef WITH_MQTT_BRIDGE
bool changed = false;
@@ -3450,6 +3466,15 @@ bool MyMesh::applyAndSaveFemRxGain(bool enabled) {
return true;
}
bool MyMesh::applyAndSaveFemTxGain(bool enabled) {
if (!board.canControlLoRaFemPaGain()) return false;
if (!board.setLoRaFemPaGainEnabled(enabled)) return false;
_prefs.radio_fem_txgain = enabled ? 1 : 0;
savePrefs();
return true;
}
#ifdef ENABLE_USB_INTERFACE
void MyMesh::enterTerminalMode() {
_terminal_mode = true;
@@ -4236,6 +4261,13 @@ void MyMesh::handleTerminalCommand(char* command) {
Serial.printf(" FEM RX gain: %s\r\n",
board.isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (strcmp(command, "get radio.fem.txgain") == 0) {
if (!board.canControlLoRaFemPaGain()) {
Serial.print(" ERROR: FEM TX gain control is unsupported on this board\r\n");
} else {
Serial.printf(" FEM TX gain: %s\r\n",
board.isLoRaFemPaGainEnabled() ? "on" : "off");
}
} else if (strncmp(command, "set ", 4) == 0) {
const char* config = command + 4;
if (strncmp(config, "af ", 3) == 0) {
@@ -4276,6 +4308,20 @@ void MyMesh::handleTerminalCommand(char* command) {
} else {
Serial.printf(" OK - FEM RX gain %s\r\n", value);
}
} else if (strncmp(config, "radio.fem.txgain", 16) == 0
&& (config[16] == 0 || config[16] == ' '
|| config[16] == '\t')) {
const char* value = config + 16;
while (*value == ' ' || *value == '\t') value++;
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
Serial.print(" ERROR: use set radio.fem.txgain <on|off>\r\n");
} else if (!board.canControlLoRaFemPaGain()) {
Serial.print(" ERROR: FEM TX gain control is unsupported on this board\r\n");
} else if (!applyAndSaveFemTxGain(strcmp(value, "on") == 0)) {
Serial.print(" ERROR: failed to apply FEM TX gain\r\n");
} else {
Serial.printf(" OK - FEM TX gain %s\r\n", value);
}
} else {
Serial.printf(" ERROR: unknown setting: %s\r\n", config);
}
@@ -4287,6 +4333,8 @@ void MyMesh::handleTerminalCommand(char* command) {
Serial.print(" set {name|lat|lon|freq|tx|af} {value}\r\n");
Serial.print(" get radio.fem.rxgain\r\n");
Serial.print(" set radio.fem.rxgain <on|off>\r\n");
Serial.print(" get radio.fem.txgain\r\n");
Serial.print(" set radio.fem.txgain <on|off>\r\n");
Serial.print(" card\r\n");
Serial.print(" import <meshcore://card>\r\n");
Serial.print(" clock\r\n");
+1
View File
@@ -277,6 +277,7 @@ private:
void checkCLIRescueCmd();
void checkSerialInterface();
bool applyAndSaveFemRxGain(bool enabled);
bool applyAndSaveFemTxGain(bool enabled);
#ifdef ENABLE_USB_INTERFACE
ContactInfo* getTerminalRecipient();
void printTerminalPath(const ContactInfo& recipient);
+1
View File
@@ -37,6 +37,7 @@ struct NodePrefs { // persisted to file
uint8_t default_scope_key[16];
uint8_t radio_fem_rxgain_override; // 1 once the user overrides the build default
uint8_t vibe_quiet; // haptic quiet mode; appended for prefs compatibility
uint8_t radio_fem_txgain; // LoRa FEM TX gain; appended for prefs compatibility
// Keep the upstream repeat API while retaining the existing binary prefs
// layout used by this branch.
+2
View File
@@ -3030,6 +3030,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_boosted_gain = mesh::radio::configuredRxBoostedGainDefault();
#endif
_prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards)
_prefs.radio_fem_txgain = 0;
pending_discover_tag = 0;
pending_discover_until = 0;
@@ -3200,6 +3201,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) {
_radio->recalibrateNoiseFloor();
}
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
updateAdvertTimer();
+2
View File
@@ -1164,6 +1164,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.rx_boosted_gain = mesh::radio::configuredRxBoostedGainDefault();
#endif
_prefs.radio_fem_rxgain = 1;
_prefs.radio_fem_txgain = 0;
// Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() - they live
// in /mqtt_prefs now, not NodePrefs.
@@ -1241,6 +1242,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) {
_radio->recalibrateNoiseFloor();
}
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
updateAdvertTimer();
+2
View File
@@ -931,6 +931,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));
}
@@ -974,6 +975,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
if (board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain) && fem_gain_changed) {
_radio->recalibrateNoiseFloor();
}
board.setLoRaFemPaGainEnabled(_prefs.radio_fem_txgain);
setRxPowerSaving(_prefs.rx_powersaving_enabled, _prefs.rx_ps_rx_us, _prefs.rx_ps_sleep_us);
updateAdvertTimer();
+21 -11
View File
@@ -5,8 +5,9 @@
#include <Ed25519.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#include "helpers/NRF52Crypto.h"
#include "nrf_cc310/include/crys_ec_edw_api.h"
#include "nrf_cc310/include/crys_ec_mont_edw_error.h"
#endif
namespace mesh {
@@ -21,20 +22,29 @@ Identity::Identity(const char* pub_hex) {
}
bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const {
if (sig == NULL || message == NULL || msg_len < 0) return false;
#ifdef USE_CC310_HW_CRYPTO
// nRF52840 CryptoCell CC310 hardware Ed25519 verification. The software
// implementations need ~3KB of stack (which can overflow the Adafruit core's
// 4KB loop task stack from the advert receive path); the hardware path
// 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
// needs much less, around 600-700 bytes. Access is serialized because the
// workspace and the CC310 driver are not reentrant.
CC310CryptoSession session;
if (session) {
static CRYS_ECEDW_TempBuff_t cc310_tmp;
const CRYSError_t rc = CRYS_ECEDW_Verify(
const_cast<uint8_t*>(sig), CRYS_ECEDW_SIGNATURE_BYTES,
const_cast<uint8_t*>(pub_key), CRYS_ECEDW_MOD_SIZE_IN_BYTES,
const_cast<uint8_t*>(message), static_cast<size_t>(msg_len), &cc310_tmp);
if (rc == CRYS_OK) return true;
// A normal signature mismatch is a conclusive hardware result. Other
// CC310 errors fall through to the correctness-preserving software path.
if (rc == CRYS_ECEDW_SIGN_VERIFY_FAILED_ERROR) return false;
}
#endif
#if 0
// NOTE: memory corruption bug was found in this function!!
return ed25519_verify(sig, message, msg_len, pub_key);
#else
+4
View File
@@ -93,6 +93,10 @@ public:
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; }
#if defined(ENABLE_OTA)
// 4-byte build-target discriminator for OTA-over-LoRa (docs/ota_protocol.md Section 9). Default is the
// MOTA_TARGET_ID build flag injected by build.sh; 0 when unset (e.g. a bare IDE build).
+199 -120
View File
@@ -3,7 +3,7 @@
#include <SHA256.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#include "helpers/NRF52Crypto.h"
#include "nrf_cc310/include/crys_hash.h"
#include "nrf_cc310/include/crys_hmac.h"
#include "nrf_cc310/include/ssi_aes.h"
@@ -13,6 +13,142 @@
#include <Arduino.h>
#endif
namespace {
void sha256Software(uint8_t* hash, size_t hash_len, const uint8_t* msg, int msg_len) {
SHA256 sha;
sha.update(msg, msg_len);
sha.finalize(hash, hash_len);
}
void sha256Software(uint8_t* hash, size_t hash_len,
const uint8_t* frag1, int frag1_len,
const uint8_t* frag2, int frag2_len) {
SHA256 sha;
sha.update(frag1, frag1_len);
sha.update(frag2, frag2_len);
sha.finalize(hash, hash_len);
}
int decryptSoftware(const uint8_t* shared_secret, uint8_t* dest,
const uint8_t* src, int src_len) {
AES128 aes;
uint8_t* dp = dest;
const uint8_t* sp = src;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (sp - src < src_len) {
aes.decryptBlock(dp, sp);
dp += CIPHER_BLOCK_SIZE;
sp += CIPHER_BLOCK_SIZE;
}
return static_cast<int>(sp - src);
}
int encryptSoftware(const uint8_t* shared_secret, uint8_t* dest,
const uint8_t* src, int src_len) {
AES128 aes;
uint8_t* dp = dest;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (src_len >= CIPHER_BLOCK_SIZE) {
aes.encryptBlock(dp, src);
dp += CIPHER_BLOCK_SIZE;
src += CIPHER_BLOCK_SIZE;
src_len -= CIPHER_BLOCK_SIZE;
}
if (src_len > 0) {
uint8_t tmp[CIPHER_BLOCK_SIZE] = {};
memcpy(tmp, src, src_len);
aes.encryptBlock(dp, tmp);
dp += CIPHER_BLOCK_SIZE;
}
return static_cast<int>(dp - dest);
}
void hmacSoftware(const uint8_t* shared_secret, uint8_t* dest,
const uint8_t* src, int src_len) {
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(src, src_len);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE);
}
#ifdef USE_CC310_HW_CRYPTO
bool rangesOverlap(const uint8_t* first, size_t first_len,
const uint8_t* second, size_t second_len) {
const uintptr_t first_addr = reinterpret_cast<uintptr_t>(first);
const uintptr_t second_addr = reinterpret_cast<uintptr_t>(second);
if (first_addr <= second_addr) return second_addr - first_addr < first_len;
return first_addr - second_addr < second_len;
}
// Returns -1 when the hardware path is unavailable or any CC310 call fails.
// Callers retain the original input and can recompute the complete result in
// software.
int aesHardware(bool encrypting, const uint8_t* shared_secret, uint8_t* dest,
const uint8_t* src, int src_len) {
mesh::CC310CryptoSession session;
if (!session) return -1;
static SaSiAesUserContext_t ctx;
SaSiAesUserKeyData_t key_data = {
const_cast<uint8_t*>(shared_secret), CIPHER_KEY_SIZE
};
SaSiError_t rc = SaSi_AesInit(
&ctx, encrypting ? SASI_AES_ENCRYPT : SASI_AES_DECRYPT,
SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE);
const bool initialized = rc == SASI_OK;
if (rc == SASI_OK) {
rc = SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &key_data, sizeof(key_data));
}
uint8_t* dp = dest;
const uint8_t* sp = src;
int remaining = src_len;
while (rc == SASI_OK && remaining >= CIPHER_BLOCK_SIZE) {
rc = SaSi_AesBlock(&ctx, const_cast<uint8_t*>(sp), CIPHER_BLOCK_SIZE, dp);
if (rc == SASI_OK) {
dp += CIPHER_BLOCK_SIZE;
sp += CIPHER_BLOCK_SIZE;
remaining -= CIPHER_BLOCK_SIZE;
}
}
if (rc == SASI_OK && encrypting && remaining > 0) {
uint8_t padded[CIPHER_BLOCK_SIZE] = {};
memcpy(padded, sp, remaining);
rc = SaSi_AesBlock(&ctx, padded, CIPHER_BLOCK_SIZE, dp);
if (rc == SASI_OK) dp += CIPHER_BLOCK_SIZE;
}
size_t final_size = 0;
if (rc == SASI_OK) {
rc = SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &final_size);
}
const SaSiError_t free_rc = initialized ? SaSi_AesFree(&ctx) : SASI_OK;
if (rc != SASI_OK || free_rc != SASI_OK) return -1;
return static_cast<int>(dp - dest);
}
bool hmacHardware(const uint8_t* shared_secret, uint8_t* dest,
const uint8_t* src, int src_len) {
mesh::CC310CryptoSession session;
if (!session) return false;
static CRYS_HASH_Result_t result;
const CRYSError_t rc = CRYS_HMAC(
CRYS_HASH_SHA256_mode, const_cast<uint8_t*>(shared_secret), PUB_KEY_SIZE,
const_cast<uint8_t*>(src), static_cast<size_t>(src_len), result);
if (rc != CRYS_OK) return false;
memcpy(dest, result, CIPHER_MAC_SIZE);
return true;
}
#endif
} // namespace
namespace mesh {
uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) {
@@ -23,35 +159,53 @@ 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;
sha.update(msg, msg_len);
sha.finalize(hash, hash_len);
if (hash_len <= SHA256::HASH_SIZE && msg_len >= 0) {
CC310CryptoSession session;
if (session) {
static CRYS_HASH_Result_t result;
const CRYSError_t rc = CRYS_HASH(
CRYS_HASH_SHA256_mode, const_cast<uint8_t*>(msg),
static_cast<size_t>(msg_len), result);
if (rc == CRYS_OK) {
memcpy(hash, result, hash_len);
return;
}
}
}
#endif
sha256Software(hash, hash_len, msg, msg_len);
}
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) {
#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;
sha.update(frag1, frag1_len);
sha.update(frag2, frag2_len);
sha.finalize(hash, hash_len);
if (hash_len <= SHA256::HASH_SIZE && frag1_len > 0 && frag2_len > 0) {
CC310CryptoSession session;
if (session) {
static CRYS_HASHUserContext_t ctx;
static CRYS_HASH_Result_t result;
CRYSError_t rc = CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode);
const bool initialized = rc == CRYS_OK;
if (rc == CRYS_OK) {
rc = CRYS_HASH_Update(&ctx, const_cast<uint8_t*>(frag1),
static_cast<size_t>(frag1_len));
}
if (rc == CRYS_OK) {
rc = CRYS_HASH_Update(&ctx, const_cast<uint8_t*>(frag2),
static_cast<size_t>(frag2_len));
}
if (rc == CRYS_OK) rc = CRYS_HASH_Finish(&ctx, result);
if (rc == CRYS_OK) {
memcpy(hash, result, hash_len);
return;
}
if (initialized) {
const bool context_freed = CRYS_HASH_Free(&ctx) == CRYS_OK;
(void) context_freed;
}
}
}
#endif
sha256Software(hash, hash_len, frag1, frag1_len, frag2, frag2_len);
}
int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
@@ -61,100 +215,38 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
}
#ifdef USE_CC310_HW_CRYPTO
static SaSiAesUserContext_t ctx;
SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE };
uint8_t* dp = dest;
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) {
SaSi_AesBlock(&ctx, (uint8_t*)sp, 16, dp);
dp += 16; sp += 16;
if (!rangesOverlap(dest, static_cast<size_t>(src_len),
src, static_cast<size_t>(src_len))) {
const int hardware_len = aesHardware(false, shared_secret, dest, src, src_len);
if (hardware_len >= 0) return hardware_len;
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return sp - src;
#else
AES128 aes;
uint8_t* dp = dest;
const uint8_t* sp = src;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (sp - src < src_len) {
aes.decryptBlock(dp, sp);
dp += 16; sp += 16;
}
return sp - src; // will always be multiple of 16
#endif
return decryptSoftware(shared_secret, dest, src, src_len);
}
int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
if (shared_secret == NULL || dest == NULL || src == NULL || src_len <= 0) return 0;
#ifdef USE_CC310_HW_CRYPTO
static SaSiAesUserContext_t ctx;
SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE };
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) {
SaSi_AesBlock(&ctx, (uint8_t*)src, 16, dp);
dp += 16; src += 16; src_len -= 16;
const size_t output_len = static_cast<size_t>(
(src_len + CIPHER_BLOCK_SIZE - 1) / CIPHER_BLOCK_SIZE * CIPHER_BLOCK_SIZE);
if (!rangesOverlap(dest, output_len, src, static_cast<size_t>(src_len))) {
const int hardware_len = aesHardware(true, shared_secret, dest, src, src_len);
if (hardware_len >= 0) return hardware_len;
}
if (src_len > 0) { // remaining partial block — zero-pad to 16 bytes
uint8_t tmp[16] = {};
memcpy(tmp, src, src_len);
SaSi_AesBlock(&ctx, tmp, 16, dp);
dp += 16;
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return dp - dest;
#else
AES128 aes;
uint8_t* dp = dest;
aes.setKey(shared_secret, CIPHER_KEY_SIZE);
while (src_len >= 16) {
aes.encryptBlock(dp, src);
dp += 16; src += 16; src_len -= 16;
}
if (src_len > 0) { // remaining partial block
uint8_t tmp[16];
memset(tmp, 0, 16);
memcpy(tmp, src, src_len);
aes.encryptBlock(dp, tmp);
dp += 16;
}
return dp - dest; // will always be multiple of 16
#endif
return encryptSoftware(shared_secret, dest, src, src_len);
}
int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len);
#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;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(dest + CIPHER_MAC_SIZE, enc_len);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE);
if (!hmacHardware(shared_secret, dest, dest + CIPHER_MAC_SIZE, enc_len))
#endif
{
hmacSoftware(shared_secret, dest, dest + CIPHER_MAC_SIZE, enc_len);
}
return CIPHER_MAC_SIZE + enc_len;
}
@@ -167,24 +259,11 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin
uint8_t hmac[CIPHER_MAC_SIZE];
#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, (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
{
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(src + CIPHER_MAC_SIZE, enc_len);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE);
}
if (!hmacHardware(shared_secret, hmac, src + CIPHER_MAC_SIZE, enc_len))
#endif
{
hmacSoftware(shared_secret, hmac, src + CIPHER_MAC_SIZE, enc_len);
}
if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) {
return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, enc_len);
}
+61 -1
View File
@@ -909,6 +909,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// Defaults for the trailing fields that older/shorter files may not contain.
// Build-profile defaults - overwritten below when the saved field is present.
_prefs->radio_fem_rxgain = 1;
_prefs->radio_fem_txgain = 0;
_prefs->cad_enabled = DEFAULT_CAD_ENABLED;
memset(_prefs->extra_sf, 0, sizeof(_prefs->extra_sf));
_prefs->rx_powersaving_enabled = 0;
@@ -1193,6 +1194,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
}
if (file.available() >= (int)sizeof(_prefs->extra_sf)) {
file.read((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf));
if (file.available() >= (int)sizeof(_prefs->radio_fem_txgain)) {
file.read((uint8_t *)&_prefs->radio_fem_txgain,
sizeof(_prefs->radio_fem_txgain));
}
} else if (file.available() > 0) {
// Never accept a torn append as a partial detector list.
_com_prefs_needs_upgrade = true;
@@ -1261,6 +1266,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
_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
if (!directRetryPrefsValid(_prefs)) {
setDefaultDirectRetryPrefs(_prefs);
@@ -1467,6 +1473,7 @@ static bool writeCommonPrefsImage(Writer& writer, NodePrefs* prefs) {
WRITE_COMMON_PREFS(&prefs->rx_watchdog_enabled); // 854
WRITE_COMMON_PREFS(&prefs->system_watchdog_enabled); // 855
WRITE_COMMON_PREFS(&prefs->extra_sf); // 856
WRITE_COMMON_PREFS(&prefs->radio_fem_txgain); // 860
#undef WRITE_COMMON_PREFS_BYTES
#undef WRITE_COMMON_PREFS
@@ -1617,7 +1624,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs, PrefsSaveRouting::Scope scope) {
file.write((uint8_t *)&_prefs->system_watchdog_enabled,
sizeof(_prefs->system_watchdog_enabled)); // 855
file.write((uint8_t *)_prefs->extra_sf, sizeof(_prefs->extra_sf)); // 856
// next: 860
file.write((uint8_t *)&_prefs->radio_fem_txgain, sizeof(_prefs->radio_fem_txgain)); // 860
// next: 861
#if defined(NRF52_PLATFORM)
if (!file.commit()) {
@@ -3208,6 +3216,23 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
}
} else if (memcmp(config, "radio.fem.txgain ", 17) == 0) {
const char* value = &config[17];
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
strcpy(reply, "Error: state must be on or off");
} else {
const bool enabled = strcmp(value, "on") == 0;
if (_board->setLoRaFemPaGainEnabled(enabled)) {
_prefs->radio_fem_txgain = enabled ? 1 : 0;
savePrefs();
strcpy(reply, enabled
? "OK - LoRa FEM TX gain on" : "OK - LoRa FEM TX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
}
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char* parts[4];
@@ -3508,6 +3533,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.rxps ", 11) == 0) { // RX PowerSaving
const char* value = &config[11];
uint8_t enable = _prefs->rx_powersaving_enabled;
@@ -4249,6 +4296,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s",
_board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (strcmp(config, "radio.fem.txgain") == 0) {
if (!_board->canControlLoRaFemPaGain()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s",
_board->isLoRaFemPaGainEnabled() ? "on" : "off");
}
} else if (strcmp(config, "radio") == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
@@ -4359,6 +4413,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, "tempradioat", 11) == 0 && (config[11] == 0 || config[11] == ' ')) {
_callbacks->formatScheduledRadioParams(true, skipSpacesConst(&config[11]), reply);
} else if (memcmp(config, "radioat", 7) == 0 && (config[7] == 0 || config[7] == ' ')) {
+1
View File
@@ -160,6 +160,7 @@ struct NodePrefs { // persisted to file
// LR2021 side-detector SFs are appended at /com_prefs offset 856. Their
// in-memory placement here does not shift the established binary layout.
uint8_t extra_sf[4] = {};
uint8_t radio_fem_txgain; // LoRa FEM TX gain; persisted at /com_prefs offset 860
uint8_t retry_preset;
uint8_t direct_retry_attempts;
uint16_t direct_retry_base_ms;
+88
View File
@@ -0,0 +1,88 @@
#include "NRF52Crypto.h"
#if defined(USE_CC310_HW_CRYPTO)
#include <Adafruit_nRFCrypto.h>
#include <Arduino.h>
#include <string.h>
namespace {
enum CC310State : uint8_t {
CC310_UNINITIALIZED,
CC310_AVAILABLE,
CC310_UNAVAILABLE
};
StaticSemaphore_t cc310_mutex_storage;
SemaphoreHandle_t cc310_mutex = NULL;
CC310State cc310_state = CC310_UNINITIALIZED;
SemaphoreHandle_t getCC310Mutex() {
if (cc310_mutex == NULL) {
// nRF52840 is single-core. The critical section makes the lazy static
// mutex construction safe if two tasks make their first crypto call at
// the same time.
taskENTER_CRITICAL();
if (cc310_mutex == NULL) {
cc310_mutex = xSemaphoreCreateMutexStatic(&cc310_mutex_storage);
}
taskEXIT_CRITICAL();
}
return cc310_mutex;
}
} // namespace
namespace mesh {
CC310CryptoSession::CC310CryptoSession()
: _locked(false), _available(false) {
// FreeRTOS mutex APIs and the CC310 driver are not ISR-safe.
if (isInISR()) return;
SemaphoreHandle_t mutex = getCC310Mutex();
if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return;
_locked = true;
if (cc310_state == CC310_UNINITIALIZED) {
// Adafruit_nRFCrypto::begin() also initializes its DRBG. Keep it alive for
// the process lifetime: end()/begin() would repeatedly reset that state.
cc310_state = nRFCrypto.begin() ? CC310_AVAILABLE : CC310_UNAVAILABLE;
}
_available = cc310_state == CC310_AVAILABLE;
}
CC310CryptoSession::~CC310CryptoSession() {
if (_locked) xSemaphoreGive(cc310_mutex);
}
bool initializeCC310Crypto() {
CC310CryptoSession session;
return static_cast<bool>(session);
}
void mixCC310Random(uint8_t* dest, size_t size) {
if (dest == NULL || size == 0) return;
CC310CryptoSession session;
if (!session) return;
uint8_t hardware_random[32];
size_t offset = 0;
while (offset < size) {
const size_t remaining = size - offset;
const uint16_t chunk = static_cast<uint16_t>(
remaining < sizeof(hardware_random) ? remaining : sizeof(hardware_random));
if (!nRFCrypto.Random.generate(hardware_random, chunk)) break;
for (uint16_t i = 0; i < chunk; ++i) {
dest[offset + i] ^= hardware_random[i];
}
offset += chunk;
}
memset(hardware_random, 0, sizeof(hardware_random));
}
} // namespace mesh
#endif
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#if defined(USE_CC310_HW_CRYPTO)
#include <stddef.h>
#include <stdint.h>
namespace mesh {
// Serializes access to the CC310 and performs its process-lifetime
// initialization. A session is unavailable in interrupt context or when the
// one-time hardware initialization failed; callers must then use software.
class CC310CryptoSession {
public:
CC310CryptoSession();
~CC310CryptoSession();
explicit operator bool() const { return _available; }
CC310CryptoSession(const CC310CryptoSession&) = delete;
CC310CryptoSession& operator=(const CC310CryptoSession&) = delete;
private:
bool _locked;
bool _available;
};
// Initialize the CC310 once during normal system startup. Sessions also call
// this lazily so early crypto users remain safe.
bool initializeCC310Crypto();
// XOR CC310 random output into bytes already populated by another entropy
// source. A hardware failure leaves those existing bytes usable.
void mixCC310Random(uint8_t* dest, size_t size);
} // namespace mesh
#endif
+1 -1
View File
@@ -42,7 +42,7 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
protected:
bool radioDeepInit() override {
+2 -1
View File
@@ -40,7 +40,6 @@ public:
return ((CustomLR1110 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
bool isReceivingPacket() override {
return ((CustomLR1110 *)_radio)->isReceiving();
}
@@ -133,4 +132,6 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz(), getRxBoostedGainMode()); }
};
+1 -1
View File
@@ -35,5 +35,5 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+2 -2
View File
@@ -99,8 +99,6 @@ protected:
}
bool supportsRadioDeepInit() const override { return true; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
protected:
bool applyRxBoostedGainMode(bool en) override {
return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
@@ -109,4 +107,6 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+2 -2
View File
@@ -46,8 +46,6 @@ public:
}
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
protected:
bool radioDeepInit() override {
return ((CustomSX1268 *)_radio)->std_init();
@@ -62,4 +60,6 @@ public:
bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio, getRxBoostedGainMode()); }
};
+2 -4
View File
@@ -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,8 +16,6 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) {
radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f);
#ifdef RX_BOOSTED_GAIN
// Fallback for callers without a runtime override. RadioLibWrapper::resetAGC()
// reapplies its cached radio.rxgain selection before receive mode resumes.
radio->setRxBoostedGainMode(RX_BOOSTED_GAIN);
radio->setRxBoostedGainMode(rx_boost_gain);
#endif
}
@@ -32,6 +32,11 @@ void setFlag(void) {
}
void RadioLibWrapper::begin() {
#ifdef USE_CC310_HW_CRYPTO
// Initialize CryptoCell once from normal task context. The session helper
// still initializes lazily if an earlier crypto operation runs first.
(void) mesh::initializeCC310Crypto();
#endif
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt
_preamble_sf = getSpreadingFactor();
_radio->setPreambleLength(preambleLengthForSF(_preamble_sf)); // longer preamble for lower SF improves reliability
+7 -8
View File
@@ -13,7 +13,7 @@
#define RX_PS_FALLBACK_SLEEP_US 60000UL
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#include "../NRF52Crypto.h"
#endif
struct PacketMillis {
uint32_t preambleMillis; // preamble-detect -> header-valid deadline
@@ -217,15 +217,14 @@ public:
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
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++) {
// Preserve the existing radio/PRNG entropy on every platform. On nRF52840,
// independently generated CC310 bytes are mixed in without becoming the
// sole source of randomness.
for (size_t i = 0; i < sz; i++) {
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
}
#ifdef USE_CC310_HW_CRYPTO
mesh::mixCC310Random(dest, sz);
#endif
}
};
+2 -4
View File
@@ -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,9 +26,7 @@ inline void sx126xResetAGC(SX126x* radio) {
radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
#endif
#ifdef SX126X_RX_BOOSTED_GAIN
// Fallback for callers without a runtime override. RadioLibWrapper::resetAGC()
// reapplies its cached radio.rxgain selection before receive mode resumes.
radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
radio->setRxBoostedGainMode(rx_boost_gain);
#endif
#ifdef SX126X_REGISTER_PATCH
uint8_t r_data = 0;
@@ -0,0 +1,79 @@
#include "LoRaFEMControl.h"
#include <Arduino.h>
#include <driver/rtc_io.h>
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
}
@@ -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;
};
+33 -2
View File
@@ -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();
}
+15 -38
View File
@@ -3,45 +3,15 @@
#include <Arduino.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
#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 @@ public:
}
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 {
+2 -2
View File
@@ -18,11 +18,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