mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-22 11:17:49 +10:00
Merge branch 'develop' into pioarduino
This commit is contained in:
@@ -61,6 +61,33 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoEngine::ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user)
|
||||
{
|
||||
if (user.is_licensed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool keygenSuccess = false;
|
||||
if (security.private_key.size == 32) {
|
||||
if (regeneratePublicKey(security.public_key.bytes, security.private_key.bytes)) {
|
||||
keygenSuccess = true;
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Generate new PKI keys");
|
||||
generateKeyPair(security.public_key.bytes, security.private_key.bytes);
|
||||
keygenSuccess = true;
|
||||
}
|
||||
|
||||
if (keygenSuccess) {
|
||||
security.public_key.size = 32;
|
||||
security.private_key.size = 32;
|
||||
user.public_key.size = 32;
|
||||
memcpy(user.public_key.bytes, security.public_key.bytes, 32);
|
||||
}
|
||||
|
||||
return keygenSuccess;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,7 @@ class CryptoEngine
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
|
||||
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
|
||||
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
||||
virtual bool ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user);
|
||||
|
||||
#endif
|
||||
void setDHPrivateKey(uint8_t *_private_key);
|
||||
|
||||
@@ -38,11 +38,13 @@ uint32_t Default::getConfiguredOrDefault(uint32_t configured, uint32_t defaultVa
|
||||
uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes)
|
||||
{
|
||||
// If we are a router, we don't scale the value. It's already significantly higher.
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER)
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
|
||||
return getConfiguredOrDefaultMs(configured, defaultValue);
|
||||
|
||||
// Additionally if we're a tracker or sensor, we want priority to send position and telemetry
|
||||
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER))
|
||||
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_TRACKER,
|
||||
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER))
|
||||
return getConfiguredOrDefaultMs(configured, defaultValue);
|
||||
|
||||
return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes);
|
||||
|
||||
+42
-17
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include <MeshRadio.h>
|
||||
#include <NodeDB.h>
|
||||
#include <RadioInterface.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <meshUtils.h>
|
||||
#define ONE_DAY 24 * 60 * 60
|
||||
@@ -10,12 +13,12 @@
|
||||
#define TEN_SECONDS_MS 10 * 1000
|
||||
#define MAX_INTERVAL INT32_MAX // FIXME: INT32_MAX to avoid overflow issues with Apple clients but should be UINT32_MAX
|
||||
|
||||
#define min_default_telemetry_interval_secs 30 * 60
|
||||
#define min_default_telemetry_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60)
|
||||
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
|
||||
#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
|
||||
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
|
||||
#define default_broadcast_smart_minimum_interval_secs 5 * 60
|
||||
#define min_default_broadcast_interval_secs 60 * 60
|
||||
#define min_default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
|
||||
#define min_default_broadcast_smart_minimum_interval_secs 5 * 60
|
||||
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
|
||||
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
|
||||
@@ -27,6 +30,11 @@
|
||||
#define min_node_info_broadcast_secs 60 * 60 // No regular broadcasts of more than once an hour
|
||||
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
|
||||
#define default_map_publish_interval_secs 60 * 60
|
||||
|
||||
// Traffic management defaults
|
||||
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
|
||||
#define default_traffic_mgmt_position_min_interval_secs ONE_DAY // 1 day between identical positions
|
||||
|
||||
#ifdef USERPREFS_RINGTONE_NAG_SECS
|
||||
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
|
||||
#else
|
||||
@@ -42,7 +50,10 @@
|
||||
#define default_mqtt_tls_enabled false
|
||||
|
||||
#define IF_ROUTER(routerVal, normalVal) \
|
||||
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) ? (routerVal) : (normalVal))
|
||||
((config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || \
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) \
|
||||
? (routerVal) \
|
||||
: (normalVal))
|
||||
|
||||
class Default
|
||||
{
|
||||
@@ -63,25 +74,39 @@ class Default
|
||||
if (numOnlineNodes <= 40) {
|
||||
return 1.0;
|
||||
} else {
|
||||
float throttlingFactor = 0.075;
|
||||
if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW)
|
||||
throttlingFactor = 0.04;
|
||||
else if (config.lora.use_preset && config.lora.modem_preset == meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST)
|
||||
throttlingFactor = 0.02;
|
||||
else if (config.lora.use_preset &&
|
||||
IS_ONE_OF(config.lora.modem_preset, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW))
|
||||
throttlingFactor = 0.01;
|
||||
// Resolve SF and BW from preset or manual config
|
||||
// When use_preset is true, config.lora.spread_factor and bandwidth may be 0
|
||||
// because applyModemConfig() sets them on RadioInterface, not on config.lora
|
||||
float bwKHz;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
if (config.lora.use_preset) {
|
||||
modemPresetToParams(config.lora.modem_preset, false, bwKHz, sf, cr);
|
||||
} else {
|
||||
sf = config.lora.spread_factor;
|
||||
bwKHz = bwCodeToKHz(config.lora.bandwidth);
|
||||
}
|
||||
|
||||
// Guard against invalid values
|
||||
sf = clampSpreadFactor(sf);
|
||||
bwKHz = clampBandwidthKHz(bwKHz);
|
||||
|
||||
// throttlingFactor = 2^SF / (BW_in_kHz * scaling_divisor)
|
||||
// With scaling_divisor=100:
|
||||
// In SF11 and BW=250khz (longfast), this gives 0.08192 rather than the original 0.075
|
||||
// In SF10 and BW=250khz (mediumslow), this gives 0.04096 rather than the original 0.04
|
||||
// In SF9 and BW=250khz (mediumfast), this gives 0.02048 rather than the original 0.02
|
||||
// In SF7 and BW=250khz (shortfast), this gives 0.00512 rather than the original 0.01
|
||||
float throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 100.0f);
|
||||
|
||||
#if USERPREFS_EVENT_MODE
|
||||
// If we are in event mode, scale down the throttling factor
|
||||
throttlingFactor = 0.04;
|
||||
// If we are in event mode, scale down the throttling factor by 4
|
||||
throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 25.0f);
|
||||
#endif
|
||||
|
||||
// Scaling up traffic based on number of nodes over 40
|
||||
int nodesOverForty = (numOnlineNodes - 40);
|
||||
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by 0.075 (default)
|
||||
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -91,10 +91,27 @@ void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (nodeDB)
|
||||
nodeDB->updateFrom(*p);
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
if (traceRouteModule && p->which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
|
||||
// If we got a packet that is not decoded, try to decode it so we can check for traceroute.
|
||||
auto decodedState = perhapsDecode(const_cast<meshtastic_MeshPacket *>(p));
|
||||
if (decodedState == DecodeState::DECODE_SUCCESS) {
|
||||
// parsing was successful, print for debugging
|
||||
printPacket("reprocessPacket(DUP)", p);
|
||||
} else {
|
||||
// Fatal decoding error, we can't do anything with this packet
|
||||
LOG_WARN(
|
||||
"FloodingRouter::reprocessPacket: Fatal decode error (state=%d, id=0x%08x, from=%u), can't check for traceroute",
|
||||
static_cast<int>(decodedState), p->id, getFrom(p));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP)
|
||||
p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) {
|
||||
traceRouteModule->processUpgradedPacket(*p);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,11 @@ template <typename T> bool LR11x0Interface<T>::reconfigure()
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
// Apply RX gain mode — valid in STDBY, matches resetAGC() pattern
|
||||
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_WARN("LR11x0 setRxBoostedGainMode %s%d", radioLibErr, err);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
return RADIOLIB_ERR_NONE;
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#if HAS_LORA_FEM
|
||||
#include "LoRaFEMInterface.h"
|
||||
|
||||
#if defined(ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
|
||||
LoRaFEMInterface loraFEMInterface;
|
||||
void LoRaFEMInterface::init(void)
|
||||
{
|
||||
setLnaCanControl(false); // Default is uncontrollable
|
||||
#ifdef HELTEC_V4
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||
delay(1);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
|
||||
pinMode(LORA_KCT8103L_PA_CSD, INPUT); // detect which FEM is used
|
||||
delay(1);
|
||||
if (digitalRead(LORA_KCT8103L_PA_CSD) == HIGH) {
|
||||
// FEM is KCT8103L
|
||||
fem_type = KCT8103L_PA;
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
|
||||
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW); // LNA enabled by default
|
||||
setLnaCanControl(true);
|
||||
} else if (digitalRead(LORA_KCT8103L_PA_CSD) == LOW) {
|
||||
// FEM is GC1109
|
||||
fem_type = GC1109_PA;
|
||||
// LORA_GC1109_PA_EN and LORA_KCT8103L_PA_CSD are the same pin and do not need to be repeatedly turned off and held.
|
||||
// rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
|
||||
pinMode(LORA_GC1109_PA_EN, OUTPUT);
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH);
|
||||
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
} else {
|
||||
fem_type = OTHER_FEM_TYPES;
|
||||
}
|
||||
#elif defined(USE_GC1109_PA)
|
||||
fem_type = GC1109_PA;
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
#if defined(ARCH_ESP32)
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_EN);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_GC1109_PA_TX_EN);
|
||||
#endif
|
||||
delay(1);
|
||||
pinMode(LORA_GC1109_PA_EN, OUTPUT);
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH);
|
||||
pinMode(LORA_GC1109_PA_TX_EN, OUTPUT);
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
#elif defined(USE_KCT8103L_PA)
|
||||
fem_type = KCT8103L_PA;
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
#if defined(ARCH_ESP32)
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CSD);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_KCT8103L_PA_CTX);
|
||||
#endif
|
||||
delay(1);
|
||||
pinMode(LORA_KCT8103L_PA_CSD, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
pinMode(LORA_KCT8103L_PA_CTX, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW); // LNA enabled by default
|
||||
setLnaCanControl(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoRaFEMInterface::setSleepModeEnable(void)
|
||||
{
|
||||
#ifdef HELTEC_V4
|
||||
if (fem_type == GC1109_PA) {
|
||||
/*
|
||||
* Do not switch the power on and off frequently.
|
||||
* After turning off LORA_GC1109_PA_EN, the power consumption has dropped to the uA level.
|
||||
*/
|
||||
digitalWrite(LORA_GC1109_PA_EN, LOW);
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
} else if (fem_type == KCT8103L_PA) {
|
||||
// shutdown the PA
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
|
||||
}
|
||||
#elif defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_GC1109_PA_EN, LOW);
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
#elif defined(USE_KCT8103L_PA)
|
||||
// shutdown the PA
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, LOW);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoRaFEMInterface::setTxModeEnable(void)
|
||||
{
|
||||
#ifdef HELTEC_V4
|
||||
if (fem_type == GC1109_PA) {
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
|
||||
} else if (fem_type == KCT8103L_PA) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
}
|
||||
#elif defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, HIGH); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
|
||||
#elif defined(USE_KCT8103L_PA)
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoRaFEMInterface::setRxModeEnable(void)
|
||||
{
|
||||
#ifdef HELTEC_V4
|
||||
if (fem_type == GC1109_PA) {
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
} else if (fem_type == KCT8103L_PA) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
if (lna_enabled) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
|
||||
} else {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
}
|
||||
}
|
||||
#elif defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH); // CSD=1: Chip enabled
|
||||
digitalWrite(LORA_GC1109_PA_TX_EN, LOW);
|
||||
#elif defined(USE_KCT8103L_PA)
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
if (lna_enabled) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
|
||||
} else {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoRaFEMInterface::setRxModeEnableWhenMCUSleep(void)
|
||||
{
|
||||
|
||||
#ifdef HELTEC_V4
|
||||
// Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake.
|
||||
// Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown),
|
||||
// then latch with RTC hold so the state survives deep sleep.
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
|
||||
if (fem_type == GC1109_PA) {
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
|
||||
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
|
||||
} else if (fem_type == KCT8103L_PA) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
|
||||
if (lna_enabled) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
|
||||
} else {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
}
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
|
||||
}
|
||||
#elif defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
digitalWrite(LORA_GC1109_PA_EN, HIGH);
|
||||
#if defined(ARCH_ESP32)
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_GC1109_PA_EN);
|
||||
gpio_pulldown_en((gpio_num_t)LORA_GC1109_PA_TX_EN);
|
||||
#endif
|
||||
#elif defined(USE_KCT8103L_PA)
|
||||
digitalWrite(LORA_KCT8103L_PA_CSD, HIGH);
|
||||
if (lna_enabled) {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, LOW);
|
||||
} else {
|
||||
digitalWrite(LORA_KCT8103L_PA_CTX, HIGH);
|
||||
}
|
||||
#if defined(ARCH_ESP32)
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CSD);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_KCT8103L_PA_CTX);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoRaFEMInterface::setLNAEnable(bool enabled)
|
||||
{
|
||||
lna_enabled = enabled;
|
||||
}
|
||||
|
||||
int8_t LoRaFEMInterface::powerConversion(int8_t loraOutputPower)
|
||||
{
|
||||
#ifdef HELTEC_V4
|
||||
const uint16_t gc1109_tx_gain[] = {11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7};
|
||||
const uint16_t kct8103l_tx_gain[] = {13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 12, 12, 11, 11, 10, 9, 8, 7};
|
||||
const uint16_t *tx_gain;
|
||||
uint16_t tx_gain_num;
|
||||
if (fem_type == GC1109_PA) {
|
||||
tx_gain = gc1109_tx_gain;
|
||||
tx_gain_num = sizeof(gc1109_tx_gain) / sizeof(gc1109_tx_gain[0]);
|
||||
} else if (fem_type == KCT8103L_PA) {
|
||||
tx_gain = kct8103l_tx_gain;
|
||||
tx_gain_num = sizeof(kct8103l_tx_gain) / sizeof(kct8103l_tx_gain[0]);
|
||||
} else {
|
||||
return loraOutputPower;
|
||||
}
|
||||
#else
|
||||
#ifdef ARCH_PORTDUINO
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
uint16_t tx_gain_num = portduino_config.num_pa_points;
|
||||
#else
|
||||
const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA};
|
||||
uint16_t tx_gain_num = NUM_PA_POINTS;
|
||||
#endif
|
||||
#endif
|
||||
for (int radio_dbm = 0; radio_dbm < tx_gain_num; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > loraOutputPower) ||
|
||||
((radio_dbm == (tx_gain_num - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= loraOutputPower))) {
|
||||
// we've exceeded the power limit, or hit the max we can do
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", loraOutputPower, tx_gain[radio_dbm]);
|
||||
loraOutputPower -= tx_gain[radio_dbm];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return loraOutputPower;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#if HAS_LORA_FEM
|
||||
#include "configuration.h"
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum { GC1109_PA, KCT8103L_PA, OTHER_FEM_TYPES } LoRaFEMType;
|
||||
|
||||
class LoRaFEMInterface
|
||||
{
|
||||
public:
|
||||
LoRaFEMInterface() : fem_type(OTHER_FEM_TYPES) {}
|
||||
virtual ~LoRaFEMInterface() {}
|
||||
void init(void);
|
||||
void setSleepModeEnable(void);
|
||||
void setTxModeEnable(void);
|
||||
void setRxModeEnable(void);
|
||||
void setRxModeEnableWhenMCUSleep(void);
|
||||
void setLNAEnable(bool enabled);
|
||||
int8_t powerConversion(int8_t loraOutputPower);
|
||||
bool isLnaCanControl(void) { return lna_can_control; }
|
||||
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
|
||||
|
||||
private:
|
||||
LoRaFEMType fem_type;
|
||||
bool lna_enabled = true;
|
||||
bool lna_can_control = false;
|
||||
};
|
||||
extern LoRaFEMInterface loraFEMInterface;
|
||||
|
||||
#endif
|
||||
+76
-3
@@ -5,18 +5,52 @@
|
||||
#include "PointerQueue.h"
|
||||
#include "configuration.h"
|
||||
|
||||
// Sentinel marking the end of a modem preset array
|
||||
static constexpr meshtastic_Config_LoRaConfig_ModemPreset MODEM_PRESET_END =
|
||||
static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(0xFF);
|
||||
|
||||
// Region profile: bundles the preset list with regulatory parameters shared across regions
|
||||
struct RegionProfile {
|
||||
const meshtastic_Config_LoRaConfig_ModemPreset *presets; // sentinel-terminated; first entry is the default
|
||||
float spacing; // gaps between radio channels
|
||||
float padding; // padding at each side of the "operating channel"
|
||||
bool audioPermitted;
|
||||
bool licensedOnly; // a region profile for licensed operators only
|
||||
int8_t textThrottle; // throttle for text - future expansion
|
||||
int8_t positionThrottle; // throttle for location data - future expansion
|
||||
int8_t telemetryThrottle; // throttle for telemetry - future expansion
|
||||
uint8_t overrideSlot; // a per-region override slot for if we need to fix it in place
|
||||
};
|
||||
|
||||
extern const RegionProfile PROFILE_STD;
|
||||
extern const RegionProfile PROFILE_EU868;
|
||||
extern const RegionProfile PROFILE_UNDEF;
|
||||
// extern const RegionProfile PROFILE_LITE;
|
||||
// extern const RegionProfile PROFILE_NARROW;
|
||||
// extern const RegionProfile PROFILE_HAM;
|
||||
|
||||
// Map from old region names to new region enums
|
||||
struct RegionInfo {
|
||||
meshtastic_Config_LoRaConfig_RegionCode code;
|
||||
float freqStart;
|
||||
float freqEnd;
|
||||
float dutyCycle;
|
||||
float spacing;
|
||||
float dutyCycle; // modified by getEffectiveDutyCycle
|
||||
uint8_t powerLimit; // Or zero for not set
|
||||
bool audioPermitted;
|
||||
bool freqSwitching;
|
||||
bool wideLora;
|
||||
const RegionProfile *profile;
|
||||
const char *name; // EU433 etc
|
||||
|
||||
// Preset accessors (delegate through profile)
|
||||
meshtastic_Config_LoRaConfig_ModemPreset getDefaultPreset() const { return profile->presets[0]; }
|
||||
const meshtastic_Config_LoRaConfig_ModemPreset *getAvailablePresets() const { return profile->presets; }
|
||||
size_t getNumPresets() const
|
||||
{
|
||||
size_t n = 0;
|
||||
while (profile->presets[n] != MODEM_PRESET_END)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
extern const RegionInfo regions[];
|
||||
@@ -24,6 +58,45 @@ extern const RegionInfo *myRegion;
|
||||
|
||||
extern void initRegion();
|
||||
|
||||
// Valid LoRa spread factor range and defaults
|
||||
constexpr uint8_t LORA_SF_MIN = 7;
|
||||
constexpr uint8_t LORA_SF_MAX = 12;
|
||||
constexpr uint8_t LORA_SF_DEFAULT = 11; // LONG_FAST default
|
||||
|
||||
// Valid LoRa coding rate range and default
|
||||
constexpr uint8_t LORA_CR_MIN = 4;
|
||||
constexpr uint8_t LORA_CR_MAX = 8;
|
||||
constexpr uint8_t LORA_CR_DEFAULT = 5; // LONG_FAST default
|
||||
|
||||
// Default bandwidth in kHz (LONG_FAST)
|
||||
constexpr float LORA_BW_DEFAULT_KHZ = 250.0f;
|
||||
|
||||
/// Clamp spread factor to the valid LoRa range [7, 12].
|
||||
/// Out-of-range values (including 0 from unset preset mode) return LORA_SF_DEFAULT.
|
||||
static inline uint8_t clampSpreadFactor(uint8_t sf)
|
||||
{
|
||||
if (sf < LORA_SF_MIN || sf > LORA_SF_MAX)
|
||||
return LORA_SF_DEFAULT;
|
||||
return sf;
|
||||
}
|
||||
|
||||
/// Clamp coding rate to the valid LoRa range [4, 8].
|
||||
/// Out-of-range values return LORA_CR_DEFAULT.
|
||||
static inline uint8_t clampCodingRate(uint8_t cr)
|
||||
{
|
||||
if (cr < LORA_CR_MIN || cr > LORA_CR_MAX)
|
||||
return LORA_CR_DEFAULT;
|
||||
return cr;
|
||||
}
|
||||
|
||||
/// Ensure bandwidth is positive. Non-positive values return LORA_BW_DEFAULT_KHZ.
|
||||
static inline float clampBandwidthKHz(float bwKHz)
|
||||
{
|
||||
if (bwKHz <= 0.0f)
|
||||
return LORA_BW_DEFAULT_KHZ;
|
||||
return bwKHz;
|
||||
}
|
||||
|
||||
static inline float bwCodeToKHz(uint16_t bwCode)
|
||||
{
|
||||
if (bwCode == 31)
|
||||
|
||||
@@ -87,7 +87,9 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
|
||||
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
|
||||
|
||||
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
bool isPreferredRebroadcaster =
|
||||
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE,
|
||||
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE);
|
||||
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
||||
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
|
||||
#include "modules/TraceRouteModule.h"
|
||||
#endif
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
#include "modules/TrafficManagementModule.h"
|
||||
#endif
|
||||
#include "NodeDB.h"
|
||||
|
||||
NextHopRouter::NextHopRouter() {}
|
||||
@@ -23,7 +26,7 @@ ErrorCode NextHopRouter::send(meshtastic_MeshPacket *p)
|
||||
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // First set the relayer to us
|
||||
wasSeenRecently(p); // FIXME, move this to a sniffSent method
|
||||
|
||||
p->next_hop = getNextHop(p->to, p->relay_node); // set the next hop
|
||||
p->next_hop = getNextHop(p->to, p->relay_node).value_or(NO_NEXT_HOP_PREFERENCE); // set the next hop
|
||||
LOG_DEBUG("Setting next hop for packet with dest %x to %x", p->to, p->next_hop);
|
||||
|
||||
// If it's from us, ReliableRouter already handles retransmissions if want_ack is set. If a next hop is set and hop limit is
|
||||
@@ -126,15 +129,28 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
/* Check if we should be rebroadcasting this packet if so, do so. */
|
||||
bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) {
|
||||
// Check if traffic management wants to exhaust this packet's hops
|
||||
bool exhaustHops = false;
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
if (trafficManagementModule && trafficManagementModule->shouldExhaustHops(*p)) {
|
||||
exhaustHops = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allow rebroadcast if hop_limit > 0 OR if we're exhausting hops (which sets hop_limit = 0 but still needs one relay)
|
||||
if (!isToUs(p) && !isFromUs(p) && (p->hop_limit > 0 || exhaustHops)) {
|
||||
if (p->id != 0) {
|
||||
if (isRebroadcaster()) {
|
||||
if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) {
|
||||
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
|
||||
LOG_INFO("Rebroadcast received message coming from %x", p->relay_node);
|
||||
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
if (shouldDecrementHopLimit(p)) {
|
||||
// If exhausting hops, force hop_limit = 0 regardless of other logic
|
||||
if (exhaustHops) {
|
||||
tosend->hop_limit = 0;
|
||||
LOG_INFO("Traffic management: exhausting hops for 0x%08x, setting hop_limit=0", getFrom(p));
|
||||
} else if (shouldDecrementHopLimit(p)) {
|
||||
// Use shared logic to determine if hop_limit should be decremented
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
} else {
|
||||
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
|
||||
@@ -170,10 +186,10 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
std::optional<uint8_t> NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
{
|
||||
if (isBroadcast(to))
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
return std::nullopt;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
||||
if (node && node->next_hop) {
|
||||
@@ -184,7 +200,7 @@ uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
} else
|
||||
LOG_WARN("Next hop for 0x%x is 0x%x, same as relayer; set no pref", to, node->next_hop);
|
||||
}
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
PendingPacket *NextHopRouter::findPendingPacket(GlobalPacketId key)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodingRouter.h"
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
@@ -146,7 +147,7 @@ class NextHopRouter : public FloodingRouter
|
||||
* Get the next hop for a destination, given the relay node
|
||||
* @return the node number of the next hop, 0 if no preference (fallback to FloodingRouter)
|
||||
*/
|
||||
uint8_t getNextHop(NodeNum to, uint8_t relay_node);
|
||||
std::optional<uint8_t> getNextHop(NodeNum to, uint8_t relay_node);
|
||||
|
||||
/** Check if we should be rebroadcasting this packet if so, do so.
|
||||
* @return true if we did rebroadcast */
|
||||
|
||||
+50
-5
@@ -322,9 +322,9 @@ NodeDB::NodeDB()
|
||||
// config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST;
|
||||
|
||||
// If we are setup to broadcast on any default channel slot (with default frequency slot semantics),
|
||||
// ensure that the telemetry intervals are coerced to the minimum value of 30 minutes or more.
|
||||
// ensure that the telemetry intervals are coerced to the role-aware minimum value.
|
||||
if (channels.hasDefaultChannel()) {
|
||||
LOG_DEBUG("Coerce telemetry to min of 30 minutes on defaults");
|
||||
LOG_DEBUG("Coerce telemetry to role-aware minimum on defaults");
|
||||
moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue(
|
||||
moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs);
|
||||
moduleConfig.telemetry.environment_update_interval = Default::getConfiguredOrMinimumValue(
|
||||
@@ -347,7 +347,7 @@ NodeDB::NodeDB()
|
||||
}
|
||||
}
|
||||
if (positionUsesDefaultChannel) {
|
||||
LOG_DEBUG("Coerce position broadcasts to min of 1 hour and smart broadcast min of 5 minutes on defaults");
|
||||
LOG_DEBUG("Coerce position broadcasts to role-aware minimum and smart broadcast min of 5 minutes on defaults");
|
||||
config.position.position_broadcast_secs =
|
||||
Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, min_default_broadcast_interval_secs);
|
||||
config.position.broadcast_smart_minimum_interval_secs = Default::getConfiguredOrMinimumValue(
|
||||
@@ -568,6 +568,11 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off)
|
||||
config.lora.override_duty_cycle = false;
|
||||
config.lora.config_ok_to_mqtt = false;
|
||||
#if HAS_LORA_FEM
|
||||
config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED;
|
||||
#else
|
||||
config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT;
|
||||
#endif
|
||||
|
||||
#if HAS_TFT // For the devices that support MUI, default to that
|
||||
config.display.displaymode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
|
||||
@@ -667,7 +672,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
#endif
|
||||
config.position.broadcast_smart_minimum_distance = 100;
|
||||
config.position.broadcast_smart_minimum_interval_secs = default_broadcast_smart_minimum_interval_secs;
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER)
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)
|
||||
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
|
||||
config.security.serial_enabled = true;
|
||||
config.security.admin_channel_enabled = false;
|
||||
@@ -1293,9 +1299,13 @@ void NodeDB::loadFromDisk()
|
||||
// Coerce LoRa config fields derived from presets while bootstrapping.
|
||||
// Some clients/UI components display bandwidth/spread_factor directly from config even in preset mode.
|
||||
if (config.has_lora && config.lora.use_preset) {
|
||||
RadioInterface::bootstrapLoRaConfigFromPreset(config.lora);
|
||||
RadioInterface::clampConfigLora(config.lora);
|
||||
}
|
||||
|
||||
#if defined(USERPREFS_LORA_TX_DISABLED) && USERPREFS_LORA_TX_DISABLED
|
||||
config.lora.tx_enabled = false;
|
||||
#endif
|
||||
|
||||
if (backupSecurity.private_key.size > 0) {
|
||||
LOG_DEBUG("Restoring backup of security config");
|
||||
config.security = backupSecurity;
|
||||
@@ -1640,6 +1650,25 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
|
||||
return delta;
|
||||
}
|
||||
|
||||
HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p)
|
||||
{
|
||||
// Guard against invalid values.
|
||||
if (p.hop_start < p.hop_limit)
|
||||
return HopStartStatus::INVALID;
|
||||
|
||||
if (p.hop_start == 0) {
|
||||
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
|
||||
// bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
|
||||
// version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
|
||||
// the bitfield is encrypted under the channel encryption key.
|
||||
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield)
|
||||
return HopStartStatus::VALID;
|
||||
return HopStartStatus::MISSING_OR_UNKNOWN;
|
||||
}
|
||||
|
||||
return HopStartStatus::VALID;
|
||||
}
|
||||
|
||||
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown)
|
||||
{
|
||||
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
|
||||
@@ -1677,6 +1706,22 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
#include "MeshModule.h"
|
||||
#include "Throttle.h"
|
||||
|
||||
static constexpr uint32_t HOPSTART_DROP_LOG_INTERVAL_MS = 15000;
|
||||
|
||||
void logHopStartDrop(const meshtastic_MeshPacket &p, const char *context)
|
||||
{
|
||||
static uint32_t lastLogMs = 0;
|
||||
if (Throttle::isWithinTimespanMs(lastLogMs, HOPSTART_DROP_LOG_INTERVAL_MS)) {
|
||||
return;
|
||||
}
|
||||
lastLogMs = millis();
|
||||
const bool decoded = (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag);
|
||||
const bool hasBitfield = decoded && p.decoded.has_bitfield;
|
||||
LOG_DEBUG(
|
||||
"Drop packet (%s): hop_start invalid/missing (from=0x%x id=%u hop_start=%u hop_limit=%u decoded=%d has_bitfield=%d)",
|
||||
context ? context : "unknown", p.from, p.id, p.hop_start, p.hop_limit, decoded, hasBitfield);
|
||||
}
|
||||
|
||||
/** Update position info for this node based on received position data
|
||||
*/
|
||||
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
||||
|
||||
@@ -114,6 +114,27 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||
/// Returns defaultIfUnknown if the number of hops couldn't be determined.
|
||||
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
|
||||
|
||||
enum class HopStartStatus : uint8_t { VALID = 0, MISSING_OR_UNKNOWN, INVALID };
|
||||
|
||||
/// Classify hop_start validity for forwarding decisions.
|
||||
HopStartStatus classifyHopStart(const meshtastic_MeshPacket &p);
|
||||
|
||||
inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
|
||||
{
|
||||
#if !MESHTASTIC_PREHOP_DROP
|
||||
(void)p;
|
||||
return false;
|
||||
#else
|
||||
if (isFromUs(&p)) {
|
||||
return false; // local-originated packets should never be dropped by pre-hop drop policy
|
||||
}
|
||||
return classifyHopStart(p) != HopStartStatus::VALID;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Rate-limited debug log when hop_start is invalid/missing and packet is dropped.
|
||||
void logHopStartDrop(const meshtastic_MeshPacket &p, const char *context);
|
||||
|
||||
enum LoadFileResult {
|
||||
// Successfully opened the file
|
||||
LOAD_SUCCESS = 1,
|
||||
|
||||
@@ -122,6 +122,8 @@ void PhoneAPI::close()
|
||||
}
|
||||
packetForPhone = NULL;
|
||||
filesManifest.clear();
|
||||
filesManifest.shrink_to_fit();
|
||||
lastPortNumToRadio.clear();
|
||||
fromRadioNum = 0;
|
||||
config_nonce = 0;
|
||||
config_state = 0;
|
||||
@@ -447,6 +449,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
|
||||
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
|
||||
break;
|
||||
case meshtastic_ModuleConfig_traffic_management_tag:
|
||||
LOG_DEBUG("Send module config: traffic management");
|
||||
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
|
||||
fromRadioScratch.moduleConfig.payload_variant.traffic_management = moduleConfig.traffic_management;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("Unknown module config type %d", config_state);
|
||||
}
|
||||
|
||||
+314
-140
@@ -16,10 +16,12 @@
|
||||
#include "configuration.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
#include "main.h"
|
||||
#include "meshUtils.h" // for pow_of_2
|
||||
#include "sleep.h"
|
||||
#include <assert.h>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
@@ -31,16 +33,32 @@
|
||||
#include "STM32WLE5JCInterface.h"
|
||||
#endif
|
||||
|
||||
// Calculate 2^n without calling pow()
|
||||
uint32_t pow_of_2(uint32_t n)
|
||||
{
|
||||
return 1 << n;
|
||||
}
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = {
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO, MODEM_PRESET_END};
|
||||
|
||||
#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[] = {
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, MODEM_PRESET_END};
|
||||
|
||||
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[] = {meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST,
|
||||
MODEM_PRESET_END};
|
||||
|
||||
// Region profiles: bundle preset list + regulatory parameters shared across regions
|
||||
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle, override slot
|
||||
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 0, 0, 0};
|
||||
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 0, 0, 0};
|
||||
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 0, 0, 0};
|
||||
|
||||
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr) \
|
||||
{ \
|
||||
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \
|
||||
frequency_switching, wide_lora, #name \
|
||||
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, \
|
||||
wide_lora, &profile_ptr, #name \
|
||||
}
|
||||
|
||||
const RegionInfo regions[] = {
|
||||
@@ -48,7 +66,7 @@ const RegionInfo regions[] = {
|
||||
https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf
|
||||
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
|
||||
*/
|
||||
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
|
||||
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
|
||||
@@ -56,8 +74,7 @@ const RegionInfo regions[] = {
|
||||
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
|
||||
FIXME: https://github.com/meshtastic/firmware/issues/3371
|
||||
*/
|
||||
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false),
|
||||
|
||||
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD),
|
||||
/*
|
||||
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
|
||||
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
|
||||
@@ -72,33 +89,33 @@ const RegionInfo regions[] = {
|
||||
AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
|
||||
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
|
||||
*/
|
||||
RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false),
|
||||
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868),
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
*/
|
||||
RDEF(CN, 470.0f, 510.0f, 100, 0, 19, true, false, false),
|
||||
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
|
||||
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
|
||||
*/
|
||||
RDEF(JP, 920.5f, 923.5f, 100, 0, 13, true, false, false),
|
||||
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
|
||||
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
|
||||
Also used in Brazil.
|
||||
*/
|
||||
RDEF(ANZ, 915.0f, 928.0f, 100, 0, 30, true, false, false),
|
||||
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
|
||||
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
|
||||
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100
|
||||
*/
|
||||
RDEF(ANZ_433, 433.05f, 434.79f, 100, 0, 14, true, false, false),
|
||||
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf
|
||||
@@ -106,13 +123,13 @@ const RegionInfo regions[] = {
|
||||
Note:
|
||||
- We do LBT, so 100% is allowed.
|
||||
*/
|
||||
RDEF(RU, 868.7f, 869.2f, 100, 0, 20, true, false, false),
|
||||
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0
|
||||
https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters
|
||||
*/
|
||||
RDEF(KR, 920.0f, 923.0f, 100, 0, 23, true, false, false),
|
||||
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
|
||||
@@ -120,42 +137,38 @@ const RegionInfo regions[] = {
|
||||
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
|
||||
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
|
||||
*/
|
||||
RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false),
|
||||
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
*/
|
||||
RDEF(IN, 865.0f, 867.0f, 100, 0, 30, true, false, false),
|
||||
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752
|
||||
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
|
||||
*/
|
||||
RDEF(NZ_865, 864.0f, 868.0f, 100, 0, 36, true, false, false),
|
||||
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||
*/
|
||||
RDEF(TH, 920.0f, 925.0f, 100, 0, 16, true, false, false),
|
||||
RDEF(TH, 920.0f, 925.0f, 100, 16, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
433,05-434,7 Mhz 10 mW
|
||||
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
|
||||
*/
|
||||
RDEF(UA_433, 433.0f, 434.7f, 10, 0, 10, true, false, false),
|
||||
|
||||
/*
|
||||
868,0-868,6 Mhz 25 mW
|
||||
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
|
||||
*/
|
||||
RDEF(UA_868, 868.0f, 868.6f, 1, 0, 14, true, false, false),
|
||||
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD),
|
||||
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Malaysia
|
||||
433 - 435 MHz at 100mW, no restrictions.
|
||||
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
|
||||
*/
|
||||
RDEF(MY_433, 433.0f, 435.0f, 100, 0, 20, true, false, false),
|
||||
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Malaysia
|
||||
@@ -164,14 +177,14 @@ const RegionInfo regions[] = {
|
||||
Frequency hopping is used for 919 - 923 MHz.
|
||||
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
|
||||
*/
|
||||
RDEF(MY_919, 919.0f, 924.0f, 100, 0, 27, true, true, false),
|
||||
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Singapore
|
||||
SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
|
||||
https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf
|
||||
*/
|
||||
RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false),
|
||||
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Philippines
|
||||
@@ -181,8 +194,9 @@ const RegionInfo regions[] = {
|
||||
https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
|
||||
*/
|
||||
|
||||
RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false),
|
||||
RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false),
|
||||
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD),
|
||||
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD),
|
||||
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Kazakhstan
|
||||
@@ -190,37 +204,38 @@ const RegionInfo regions[] = {
|
||||
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
|
||||
https://github.com/meshtastic/firmware/issues/7204
|
||||
*/
|
||||
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false),
|
||||
RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, false),
|
||||
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD),
|
||||
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Nepal
|
||||
865 MHz to 868 MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
|
||||
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
|
||||
*/
|
||||
RDEF(NP_865, 865.0f, 868.0f, 100, 0, 30, true, false, false),
|
||||
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
Brazil
|
||||
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions
|
||||
https://github.com/meshtastic/firmware/issues/3741
|
||||
*/
|
||||
RDEF(BR_902, 902.0f, 907.5f, 100, 0, 30, true, false, false),
|
||||
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD),
|
||||
|
||||
/*
|
||||
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
|
||||
*/
|
||||
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 0, 10, true, false, true),
|
||||
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD),
|
||||
|
||||
/*
|
||||
This needs to be last. Same as US.
|
||||
*/
|
||||
RDEF(UNSET, 902.0f, 928.0f, 100, 0, 30, true, false, false)
|
||||
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF)
|
||||
|
||||
};
|
||||
|
||||
const RegionInfo *myRegion;
|
||||
bool RadioInterface::uses_default_frequency_slot = true;
|
||||
bool RadioInterface::uses_custom_channel_name = false;
|
||||
|
||||
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
|
||||
|
||||
@@ -506,45 +521,14 @@ void initRegion()
|
||||
myRegion = r;
|
||||
}
|
||||
|
||||
void RadioInterface::bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig)
|
||||
const RegionInfo *getRegion(meshtastic_Config_LoRaConfig_RegionCode code)
|
||||
{
|
||||
if (!loraConfig.use_preset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find region info to determine whether "wide" LoRa is permitted (2.4 GHz uses wider bandwidth codes).
|
||||
const RegionInfo *r = regions;
|
||||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != loraConfig.region; r++)
|
||||
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != code; r++)
|
||||
;
|
||||
|
||||
const bool regionWideLora = r->wideLora;
|
||||
|
||||
float bwKHz = 0;
|
||||
uint8_t sf = 0;
|
||||
uint8_t cr = 0;
|
||||
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
|
||||
|
||||
// If selected preset requests a bandwidth larger than the region span, fall back to LONG_FAST.
|
||||
if (r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && (r->freqEnd - r->freqStart) < (bwKHz / 1000.0f)) {
|
||||
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
|
||||
modemPresetToParams(loraConfig.modem_preset, regionWideLora, bwKHz, sf, cr);
|
||||
}
|
||||
|
||||
loraConfig.bandwidth = bwKHzToCode(bwKHz);
|
||||
loraConfig.spread_factor = sf;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* ## LoRaWAN for North America
|
||||
|
||||
LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
||||
|
||||
The maximum output power for North America is +30 dBM.
|
||||
|
||||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
|
||||
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||||
*/
|
||||
|
||||
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
|
||||
{
|
||||
uint32_t pl = 0;
|
||||
@@ -754,7 +738,7 @@ void RadioInterface::saveFreq(float freq)
|
||||
}
|
||||
|
||||
/**
|
||||
* Save our channel for later reuse.
|
||||
* Save our frequency slot (aka channel) for later reuse.
|
||||
*/
|
||||
void RadioInterface::saveChannelNum(uint32_t channel_num)
|
||||
{
|
||||
@@ -777,113 +761,297 @@ uint32_t RadioInterface::getChannelNum()
|
||||
return savedChannelNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error-level client notification. Safe to call when service is null (e.g. in tests).
|
||||
*/
|
||||
static void sendErrorNotification(const char *msg)
|
||||
{
|
||||
if (!service)
|
||||
return;
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
if (!cn)
|
||||
return;
|
||||
cn->level = meshtastic_LogRecord_Level_ERROR;
|
||||
snprintf(cn->message, sizeof(cn->message), "%s", msg);
|
||||
service->sendClientNotification(cn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a region is valid for the current settings.
|
||||
* Returns false if not compatible.
|
||||
*/
|
||||
bool RadioInterface::validateConfigRegion(const meshtastic_Config_LoRaConfig &loraConfig)
|
||||
{
|
||||
const RegionInfo *newRegion = getRegion(loraConfig.region);
|
||||
|
||||
// If you are not licensed, you can't use ham regions.
|
||||
if (newRegion->profile->licensedOnly && !devicestate.owner.is_licensed) {
|
||||
char err_string[160];
|
||||
snprintf(err_string, sizeof(err_string), "Region %s requires licensed mode", newRegion->name);
|
||||
LOG_ERROR("%s", err_string);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
sendErrorNotification(err_string);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper: validate or clamp a LoRa config against its region.
|
||||
* When clamp==false, returns false on first error (pure validation).
|
||||
* When clamp==true, fixes invalid settings in-place and returns true.
|
||||
*/
|
||||
bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp)
|
||||
{
|
||||
char err_string[160];
|
||||
float check_bw;
|
||||
|
||||
const RegionInfo *newRegion = getRegion(loraConfig.region);
|
||||
|
||||
const char *presetName = DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
|
||||
|
||||
// Check preset validity (only when use_preset is true)
|
||||
if (loraConfig.use_preset) {
|
||||
check_bw = modemPresetToBwKHz(loraConfig.modem_preset, newRegion->wideLora);
|
||||
|
||||
bool preset_valid = false;
|
||||
for (size_t i = 0; i < newRegion->getNumPresets(); i++) {
|
||||
if (loraConfig.modem_preset == newRegion->getAvailablePresets()[i]) {
|
||||
preset_valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!preset_valid) {
|
||||
const char *defaultName = DisplayFormatters::getModemPresetDisplayName(newRegion->getDefaultPreset(), false, true);
|
||||
if (clamp) {
|
||||
snprintf(err_string, sizeof(err_string), "Preset %s invalid for %s, using %s", presetName, newRegion->name,
|
||||
defaultName);
|
||||
} else {
|
||||
snprintf(err_string, sizeof(err_string), "Preset %s invalid for %s", presetName, newRegion->name);
|
||||
}
|
||||
LOG_ERROR("%s", err_string);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
sendErrorNotification(err_string);
|
||||
|
||||
if (clamp) {
|
||||
loraConfig.modem_preset = newRegion->getDefaultPreset();
|
||||
check_bw = modemPresetToBwKHz(loraConfig.modem_preset, newRegion->wideLora);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
check_bw = bwCodeToKHz(loraConfig.bandwidth);
|
||||
}
|
||||
|
||||
// Calculate width of slots (aka channels) based on bandwidth and any spacing or padding required by the region:
|
||||
// spacing = gap between slots (0 for continuous spectrum) and at the beginning of the band
|
||||
// padding = gap at the beginning and end of the slots (0 for no padding)
|
||||
float freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (check_bw / 1000); // in MHz
|
||||
uint32_t numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
|
||||
|
||||
// Check if the region supports the requested bandwidth
|
||||
if ((newRegion->freqEnd - newRegion->freqStart) < freqSlotWidth) {
|
||||
const float regionSpanKHz = (newRegion->freqEnd - newRegion->freqStart) * 1000.0f;
|
||||
snprintf(err_string, sizeof(err_string), "%s span %.0fkHz < requested %.0fkHz", newRegion->name, regionSpanKHz, check_bw);
|
||||
LOG_ERROR("%s", err_string);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
sendErrorNotification(err_string);
|
||||
|
||||
if (clamp) {
|
||||
loraConfig.bandwidth = bwKHzToCode(modemPresetToBwKHz(newRegion->getDefaultPreset(), newRegion->wideLora));
|
||||
check_bw = bwCodeToKHz(loraConfig.bandwidth);
|
||||
|
||||
// Recompute slot width and number of slots based on the new bandwidth
|
||||
freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (check_bw / 1000); // in MHz
|
||||
numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
const char *presetNameDisplay =
|
||||
DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
|
||||
uint32_t channelNameHashSlot = hash(channelName) % numFreqSlots;
|
||||
uint32_t presetNameHashSlot = hash(presetNameDisplay) % numFreqSlots;
|
||||
|
||||
if (loraConfig.override_frequency == 0) {
|
||||
|
||||
// Check if we use the default frequency slot
|
||||
uses_default_frequency_slot =
|
||||
(loraConfig.channel_num == 0) || // user choice unset, no frequency override, so use default
|
||||
(newRegion->profile->overrideSlot != 0 &&
|
||||
loraConfig.channel_num == newRegion->profile->overrideSlot) || // user setting matches override
|
||||
((newRegion->profile->overrideSlot == 0) &&
|
||||
((uint32_t)(loraConfig.channel_num - 1) == presetNameHashSlot)); // user setting matches preset hash, no override
|
||||
|
||||
// check if user setting different to preset name
|
||||
uses_custom_channel_name = (strcmp(channelName, presetNameDisplay) != 0);
|
||||
|
||||
if (loraConfig.channel_num > numFreqSlots) {
|
||||
snprintf(err_string, sizeof(err_string), "Channel number %u invalid for %s, max is %u", loraConfig.channel_num,
|
||||
newRegion->name, numFreqSlots);
|
||||
LOG_ERROR("%s", err_string);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
sendErrorNotification(err_string);
|
||||
|
||||
if (clamp) {
|
||||
if (uses_custom_channel_name) { // clamp to channel name hash
|
||||
loraConfig.channel_num =
|
||||
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
|
||||
} else if ((loraConfig.use_preset) && (newRegion->profile->overrideSlot != 0)) { // clamp to preset override slot
|
||||
loraConfig.channel_num =
|
||||
newRegion->profile->overrideSlot; // use the override slot specified by the region profile
|
||||
uses_default_frequency_slot = true;
|
||||
} else if (loraConfig.use_preset) { // clamp to preset slot
|
||||
loraConfig.channel_num = presetNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
|
||||
uses_default_frequency_slot = true;
|
||||
} else { // if not using preset, and no custom channel name, just clamp to default anyway
|
||||
uses_default_frequency_slot = true;
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} // end of channel number check
|
||||
} else {
|
||||
// if we have a frequency override, we ignore the channel number and just use the override frequency
|
||||
snprintf(err_string, sizeof(err_string), "Frequency override in place, using %.3f", loraConfig.override_frequency);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RadioInterface::validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig)
|
||||
{
|
||||
auto copy = loraConfig;
|
||||
return checkOrClampConfigLora(copy, false);
|
||||
}
|
||||
|
||||
void RadioInterface::clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig)
|
||||
{
|
||||
checkOrClampConfigLora(loraConfig, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull our channel settings etc... from protobufs to the dumb interface settings
|
||||
* Note: this must be given only settings which have been validated or clamped!
|
||||
*/
|
||||
void RadioInterface::applyModemConfig()
|
||||
{
|
||||
// Set up default configuration
|
||||
// No Sync Words in LORA mode
|
||||
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
|
||||
bool validConfig = false; // We need to check for a valid configuration
|
||||
while (!validConfig) {
|
||||
if (loraConfig.use_preset) {
|
||||
modemPresetToParams(loraConfig.modem_preset, myRegion->wideLora, bw, sf, cr);
|
||||
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) {
|
||||
cr = loraConfig.coding_rate;
|
||||
LOG_INFO("Using custom Coding Rate %u", cr);
|
||||
}
|
||||
} else {
|
||||
sf = loraConfig.spread_factor;
|
||||
const RegionInfo *newRegion = getRegion(loraConfig.region);
|
||||
myRegion = newRegion;
|
||||
|
||||
if (loraConfig.use_preset) {
|
||||
if (!validateConfigLora(loraConfig)) {
|
||||
loraConfig.modem_preset = newRegion->getDefaultPreset();
|
||||
}
|
||||
uint8_t newcr;
|
||||
modemPresetToParams(loraConfig.modem_preset, newRegion->wideLora, bw, sf, newcr);
|
||||
// If custom CR is being used already, check if the new preset is higher
|
||||
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate < newcr) {
|
||||
cr = newcr;
|
||||
LOG_INFO("Default Coding Rate is higher than custom setting, using %u", cr);
|
||||
}
|
||||
// If the custom CR is higher than the preset, use it
|
||||
else if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate > newcr) {
|
||||
cr = loraConfig.coding_rate;
|
||||
bw = bwCodeToKHz(loraConfig.bandwidth);
|
||||
}
|
||||
|
||||
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
|
||||
const float regionSpanKHz = (myRegion->freqEnd - myRegion->freqStart) * 1000.0f;
|
||||
const float requestedBwKHz = bw;
|
||||
const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset
|
||||
const char *presetName =
|
||||
DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
|
||||
|
||||
char err_string[160];
|
||||
if (isWideRequest) {
|
||||
snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to LongFast.",
|
||||
myRegion->name, presetName);
|
||||
} else {
|
||||
snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to LongFast.",
|
||||
myRegion->name, regionSpanKHz, requestedBwKHz);
|
||||
}
|
||||
LOG_ERROR("%s", err_string);
|
||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->level = meshtastic_LogRecord_Level_ERROR;
|
||||
snprintf(cn->message, sizeof(cn->message), "%s", err_string);
|
||||
service->sendClientNotification(cn);
|
||||
|
||||
// Set to default modem preset
|
||||
loraConfig.use_preset = true;
|
||||
loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
|
||||
LOG_INFO("Using custom Coding Rate %u", cr);
|
||||
} else {
|
||||
validConfig = true;
|
||||
cr = loraConfig.coding_rate;
|
||||
}
|
||||
|
||||
} else { // if not using preset, then just use the custom settings
|
||||
if (validateConfigLora(loraConfig)) {
|
||||
} else {
|
||||
LOG_WARN("Invalid LoRa config settings, cannot apply requested modem config - falling back to %s defaults",
|
||||
newRegion->name);
|
||||
clampConfigLora(loraConfig);
|
||||
}
|
||||
bw = bwCodeToKHz(loraConfig.bandwidth);
|
||||
sf = loraConfig.spread_factor;
|
||||
cr = loraConfig.coding_rate;
|
||||
}
|
||||
|
||||
power = loraConfig.tx_power;
|
||||
|
||||
if ((power == 0) || ((power > myRegion->powerLimit) && !devicestate.owner.is_licensed))
|
||||
power = myRegion->powerLimit;
|
||||
if ((power == 0) || ((power > newRegion->powerLimit) && !devicestate.owner.is_licensed))
|
||||
power = newRegion->powerLimit;
|
||||
|
||||
if (power == 0)
|
||||
power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of myRegion defaults
|
||||
power = 17; // Default to this power level if we don't have a valid regional power limit (powerLimit of newRegion defaults
|
||||
// to 0, currently no region has an actual power limit of 0 [dBm] so we can assume regions which have this
|
||||
// variable set to 0 don't have a valid power limit)
|
||||
|
||||
// Set final tx_power back onto config
|
||||
loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger
|
||||
|
||||
// Calculate the number of channels
|
||||
uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000)));
|
||||
uint32_t channel_num;
|
||||
float freq;
|
||||
|
||||
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
|
||||
uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
|
||||
// Calculate number of frequency slots (aka Channels):
|
||||
// spacing = gap between channels (0 for continuous spectrum) and at the beginning of the band
|
||||
// padding = gap at the beginning and end of the channel (0 for no padding)
|
||||
float freqSlotWidth = newRegion->profile->spacing + (newRegion->profile->padding * 2) + (bw / 1000); // in MHz
|
||||
uint32_t numFreqSlots = round((newRegion->freqEnd - newRegion->freqStart + newRegion->profile->spacing) / freqSlotWidth);
|
||||
|
||||
// Check if we use the default frequency slot
|
||||
RadioInterface::uses_default_frequency_slot =
|
||||
channel_num ==
|
||||
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
|
||||
|
||||
// Old frequency selection formula
|
||||
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
|
||||
|
||||
// New frequency selection formula
|
||||
float freq = myRegion->freqStart + (bw / 2000) + (channel_num * (bw / 1000));
|
||||
// Calculate hash of channel name and preset name to pick a default frequency slot if user has not specified one.
|
||||
// Note that channel_num is actually (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to
|
||||
// (numFreqSlots - 1).
|
||||
uint32_t presetNameHashSlot =
|
||||
hash(DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset)) % numFreqSlots;
|
||||
|
||||
// override if we have a verbatim frequency
|
||||
if (loraConfig.override_frequency) {
|
||||
freq = loraConfig.override_frequency;
|
||||
channel_num = -1;
|
||||
uses_default_frequency_slot = false;
|
||||
} else {
|
||||
|
||||
// If user has not manually specified a frequency slot, or has not specified one that is different than the default or the
|
||||
// override for the new region, then use the default or override. If the user has not specified one, but has specified a
|
||||
// custom channel name, then use the hash of that channel name to pick a frequency slot. Note that channel_num is actually
|
||||
// (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to (numFreqSlots - 1).
|
||||
// NB: channel_num is also know as frequency slot but it's too late to fix now.
|
||||
if (uses_default_frequency_slot) {
|
||||
// if there's an override slot, use that
|
||||
if (newRegion->profile->overrideSlot != 0) {
|
||||
channel_num = newRegion->profile->overrideSlot - 1;
|
||||
} else {
|
||||
channel_num = presetNameHashSlot;
|
||||
}
|
||||
} else { // use the manually defined one
|
||||
channel_num = loraConfig.channel_num - 1;
|
||||
}
|
||||
|
||||
// Calculate frequency: freqStart is band edge, add half bandwidth (plus optional padding) to get middle of first channel
|
||||
// subsequent channels are spaced by freqSlotWidth
|
||||
freq = newRegion->freqStart + (bw / 2000) + newRegion->profile->padding + (channel_num * freqSlotWidth); // in MHz
|
||||
}
|
||||
|
||||
saveChannelNum(channel_num);
|
||||
saveFreq(freq + loraConfig.frequency_offset);
|
||||
const char *channelName = channels.getName(channels.getPrimaryIndex());
|
||||
|
||||
slotTimeMsec = computeSlotTimeMsec();
|
||||
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
|
||||
|
||||
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
|
||||
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
|
||||
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", newRegion->name, channelName, loraConfig.modem_preset,
|
||||
channel_num, power);
|
||||
LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)", myRegion->freqStart, myRegion->freqEnd,
|
||||
myRegion->freqEnd - myRegion->freqStart);
|
||||
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
|
||||
LOG_INFO("newRegion->freqStart -> newRegion->freqEnd: %f -> %f (%f MHz)", newRegion->freqStart, newRegion->freqEnd,
|
||||
newRegion->freqEnd - newRegion->freqStart);
|
||||
LOG_INFO("numFreqSlots: %d x %.3fkHz", numFreqSlots, bw);
|
||||
if (newRegion->profile->overrideSlot != 0) {
|
||||
LOG_INFO("Using region override slot: %d", newRegion->profile->overrideSlot);
|
||||
}
|
||||
LOG_INFO("channel_num: %d", channel_num + 1);
|
||||
LOG_INFO("frequency: %f", getFreq());
|
||||
LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
|
||||
}
|
||||
} // end of applyModemConfig
|
||||
|
||||
/** Slottime is the time to detect a transmission has started, consisting of:
|
||||
- CAD duration;
|
||||
@@ -920,6 +1088,12 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
power = maxPower;
|
||||
}
|
||||
|
||||
#if HAS_LORA_FEM
|
||||
if (!devicestate.owner.is_licensed) {
|
||||
power = loraFEMInterface.powerConversion(power);
|
||||
}
|
||||
#else
|
||||
// todo:All entries containing "lora fem" are grouped together above.
|
||||
#ifdef ARCH_PORTDUINO
|
||||
size_t num_pa_points = portduino_config.num_pa_points;
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
@@ -945,7 +1119,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
power = loraMaxPower;
|
||||
|
||||
@@ -991,4 +1165,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
|
||||
|
||||
sendingPacket = p;
|
||||
return p->encrypted.size + sizeof(PacketHeader);
|
||||
}
|
||||
}
|
||||
+31
-13
@@ -8,6 +8,10 @@
|
||||
#include "error.h"
|
||||
#include <memory>
|
||||
|
||||
#if HAS_LORA_FEM
|
||||
#include "LoRaFEMInterface.h"
|
||||
#endif
|
||||
|
||||
// Forward decl to avoid a direct include of generated config headers / full LoRaConfig definition in this widely-included file.
|
||||
typedef struct _meshtastic_Config_LoRaConfig meshtastic_Config_LoRaConfig;
|
||||
|
||||
@@ -123,7 +127,7 @@ class RadioInterface
|
||||
* Coerce LoRa config fields (bandwidth/spread_factor) derived from presets.
|
||||
* This is used during early bootstrapping so UIs that display these fields directly remain consistent.
|
||||
*/
|
||||
static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig);
|
||||
// static void bootstrapLoRaConfigFromPreset(meshtastic_Config_LoRaConfig &loraConfig); // maybe superseded?
|
||||
|
||||
/**
|
||||
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
|
||||
@@ -152,7 +156,7 @@ class RadioInterface
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) = 0;
|
||||
|
||||
/** Return TX queue status */
|
||||
virtual meshtastic_QueueStatus getQueueStatus()
|
||||
[[nodiscard]] virtual meshtastic_QueueStatus getQueueStatus()
|
||||
{
|
||||
meshtastic_QueueStatus qs;
|
||||
qs.res = qs.mesh_packet_id = qs.free = qs.maxlen = 0;
|
||||
@@ -178,22 +182,22 @@ class RadioInterface
|
||||
virtual bool reconfigure();
|
||||
|
||||
/** The delay to use for retransmitting dropped packets */
|
||||
uint32_t getRetransmissionMsec(const meshtastic_MeshPacket *p);
|
||||
[[nodiscard]] uint32_t getRetransmissionMsec(const meshtastic_MeshPacket *p);
|
||||
|
||||
/** The delay to use when we want to send something */
|
||||
uint32_t getTxDelayMsec();
|
||||
[[nodiscard]] uint32_t getTxDelayMsec();
|
||||
|
||||
/** The CW to use when calculating SNR_based delays */
|
||||
uint8_t getCWsize(float snr);
|
||||
[[nodiscard]] uint8_t getCWsize(float snr);
|
||||
|
||||
/** The worst-case SNR_based packet delay */
|
||||
uint32_t getTxDelayMsecWeightedWorst(float snr);
|
||||
[[nodiscard]] uint32_t getTxDelayMsecWeightedWorst(float snr);
|
||||
|
||||
/** Returns true if we should rebroadcast early like a ROUTER */
|
||||
bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
|
||||
[[nodiscard]] bool shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p);
|
||||
|
||||
/** The delay to use when we want to flood a message. Use a weighted scale based on SNR */
|
||||
uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
|
||||
[[nodiscard]] uint32_t getTxDelayMsecWeighted(meshtastic_MeshPacket *p);
|
||||
|
||||
/** If the packet is not already in the late rebroadcast window, move it there */
|
||||
virtual void clampToLateRebroadcastWindow(NodeNum from, PacketId id) { return; }
|
||||
@@ -211,18 +215,18 @@ class RadioInterface
|
||||
*
|
||||
* @return num msecs for the packet
|
||||
*/
|
||||
uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
|
||||
virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
|
||||
[[nodiscard]] uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
|
||||
[[nodiscard]] virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
|
||||
|
||||
/**
|
||||
* Get the channel we saved.
|
||||
*/
|
||||
uint32_t getChannelNum();
|
||||
[[nodiscard]] uint32_t getChannelNum();
|
||||
|
||||
/**
|
||||
* Get the frequency we saved.
|
||||
*/
|
||||
virtual float getFreq();
|
||||
[[nodiscard]] virtual float getFreq();
|
||||
|
||||
/// Some boards (1st gen Pinetab Lora module) have broken IRQ wires, so we need to poll via i2c registers
|
||||
virtual bool isIRQPending() { return false; }
|
||||
@@ -230,6 +234,20 @@ class RadioInterface
|
||||
// Whether we use the default frequency slot given our LoRa config (region and modem preset)
|
||||
static bool uses_default_frequency_slot;
|
||||
|
||||
// Whether we have a custom channel name
|
||||
static bool uses_custom_channel_name;
|
||||
|
||||
static bool checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraConfig, bool clamp);
|
||||
|
||||
// Check if a candidate region is compatible and valid.
|
||||
static bool validateConfigRegion(const meshtastic_Config_LoRaConfig &loraConfig);
|
||||
|
||||
// Check if a candidate radio configuration is valid.
|
||||
static bool validateConfigLora(const meshtastic_Config_LoRaConfig &loraConfig);
|
||||
|
||||
// Make a candidate radio configuration valid, even if it isn't.
|
||||
static void clampConfigLora(meshtastic_Config_LoRaConfig &loraConfig);
|
||||
|
||||
protected:
|
||||
int8_t power = 17; // Set by applyModemConfig()
|
||||
|
||||
@@ -242,7 +260,7 @@ class RadioInterface
|
||||
*
|
||||
* Used as the first step of
|
||||
*/
|
||||
size_t beginSending(meshtastic_MeshPacket *p);
|
||||
[[nodiscard]] size_t beginSending(meshtastic_MeshPacket *p);
|
||||
|
||||
/**
|
||||
* Some regulatory regions limit xmit power.
|
||||
|
||||
+68
-1
@@ -11,11 +11,15 @@
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/RoutingModule.h"
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
#include "modules/TrafficManagementModule.h"
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
#include "mqtt/MQTT.h"
|
||||
#endif
|
||||
#include "Default.h"
|
||||
#if ARCH_PORTDUINO
|
||||
#include "Throttle.h"
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
#endif
|
||||
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
||||
@@ -94,6 +98,20 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
|
||||
return true;
|
||||
}
|
||||
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
// When router_preserve_hops is enabled, preserve hops for decoded packets that are not
|
||||
// position or telemetry (those have their own exhaust_hop controls).
|
||||
if (moduleConfig.has_traffic_management && moduleConfig.traffic_management.enabled &&
|
||||
moduleConfig.traffic_management.router_preserve_hops && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
p->decoded.portnum != meshtastic_PortNum_POSITION_APP && p->decoded.portnum != meshtastic_PortNum_TELEMETRY_APP) {
|
||||
LOG_DEBUG("Router hop preserved: port=%d from=0x%08x (traffic_management)", p->decoded.portnum, getFrom(p));
|
||||
if (trafficManagementModule) {
|
||||
trafficManagementModule->recordRouterHopPreserved();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// For subsequent hops, check if previous relay is a favorite router
|
||||
// Optimized search for favorite routers with matching last byte
|
||||
// Check ordering optimized for IoT devices (cheapest checks first)
|
||||
@@ -532,6 +550,25 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
|
||||
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
|
||||
} else if (portduino_config.JSONFilename != "") {
|
||||
if (portduino_config.JSONFileRotate != 0) {
|
||||
static uint32_t fileage = 0;
|
||||
|
||||
if (portduino_config.JSONFileRotate != 0 &&
|
||||
(fileage == 0 || !Throttle::isWithinTimespanMs(fileage, portduino_config.JSONFileRotate * 60 * 1000))) {
|
||||
time_t timestamp = time(NULL);
|
||||
struct tm *timeinfo;
|
||||
char buffer[80];
|
||||
timeinfo = localtime(×tamp);
|
||||
strftime(buffer, 80, "%Y%m%d-%H%M%S", timeinfo);
|
||||
|
||||
std::string datetime(buffer);
|
||||
if (JSONFile.is_open()) {
|
||||
JSONFile.close();
|
||||
}
|
||||
JSONFile.open(portduino_config.JSONFilename + "_" + datetime, std::ios::out | std::ios::app);
|
||||
fileage = millis();
|
||||
}
|
||||
}
|
||||
if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) {
|
||||
JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl;
|
||||
}
|
||||
@@ -765,8 +802,32 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
p_encrypted->pki_encrypted = true;
|
||||
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
|
||||
if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
|
||||
!isFromUs(p) && mqtt)
|
||||
!isFromUs(p) && mqtt) {
|
||||
if (decodedState == DecodeState::DECODE_SUCCESS && p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP &&
|
||||
moduleConfig.mqtt.encryption_enabled) {
|
||||
// For TRACEROUTE_APP packets release the original encrypted packet and encrypt a new from the changed packet
|
||||
// Only release the original after successful allocation to avoid losing an incomplete but valid packet
|
||||
auto *p_encrypted_new = packetPool.allocCopy(*p);
|
||||
if (p_encrypted_new) {
|
||||
auto encodeResult = perhapsEncode(p_encrypted_new);
|
||||
if (encodeResult != meshtastic_Routing_Error_NONE) {
|
||||
// Encryption failed, release the new packet and fall back to sending the original encrypted packet to
|
||||
// MQTT
|
||||
LOG_WARN("Encryption of new TR packet failed, sending original TR to MQTT");
|
||||
packetPool.release(p_encrypted_new);
|
||||
p_encrypted_new = nullptr;
|
||||
} else {
|
||||
// Successfully re-encrypted, release the original encrypted packet and use the new one for MQTT
|
||||
packetPool.release(p_encrypted);
|
||||
p_encrypted = p_encrypted_new;
|
||||
}
|
||||
} else {
|
||||
// Allocation failed, log a warning and fall back to sending the original encrypted packet to MQTT
|
||||
LOG_WARN("Failed to allocate new encrypted packet for TR, sending original TR to MQTT");
|
||||
}
|
||||
}
|
||||
mqtt->onSend(*p_encrypted, *p, p->channel);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -814,6 +875,12 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldDropPacketForPreHop(*p)) {
|
||||
logHopStartDrop(*p, "pre-hop drop");
|
||||
packetPool.release(p);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldFilterReceived(p)) {
|
||||
LOG_DEBUG("Incoming msg was filtered from 0x%x", p->from);
|
||||
packetPool.release(p);
|
||||
|
||||
+3
-3
@@ -58,14 +58,14 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
||||
* The returned packet is guaranteed to have a unique packet ID already assigned
|
||||
*/
|
||||
meshtastic_MeshPacket *allocForSending();
|
||||
[[nodiscard]] meshtastic_MeshPacket *allocForSending();
|
||||
|
||||
/** Return Underlying interface's TX queue status */
|
||||
meshtastic_QueueStatus getQueueStatus();
|
||||
[[nodiscard]] meshtastic_QueueStatus getQueueStatus();
|
||||
|
||||
/**
|
||||
* @return our local nodenum */
|
||||
NodeNum getNodeNum();
|
||||
[[nodiscard]] NodeNum getNodeNum();
|
||||
|
||||
/** Wake up the router thread ASAP, because we just queued a message for it.
|
||||
* FIXME, this is kinda a hack because we don't have a nice way yet to say 'wake us because we are 'blocked on this queue'
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "PortduinoGlue.h"
|
||||
#endif
|
||||
#if defined(USE_GC1109_PA) && defined(ARCH_ESP32)
|
||||
#if defined(ARCH_ESP32)
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
@@ -56,41 +56,12 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GC1109_PA)
|
||||
// GC1109 FEM chip initialization
|
||||
// See variant.h for full pin mapping and control logic documentation
|
||||
//
|
||||
// On deep sleep wake, PA_POWER and PA_EN are held HIGH by RTC latch (set in
|
||||
// enableLoraInterrupt). We configure GPIO registers before releasing the hold
|
||||
// so the pad transitions atomically from held-HIGH to register-HIGH with no
|
||||
// power glitch. On cold boot the hold_dis is a harmless no-op.
|
||||
|
||||
// VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on)
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER);
|
||||
|
||||
// TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait
|
||||
// for VBAT to stabilise before driving CSD/CPS, per GC1109 requirement:
|
||||
// "VBAT must be prior to CSD/CPS/CTX for the power on sequence"
|
||||
// On deep sleep wake the LDO was held on via RTC latch, so no delay needed.
|
||||
#if defined(ARCH_ESP32)
|
||||
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
delayMicroseconds(1000);
|
||||
#if HAS_LORA_FEM
|
||||
loraFEMInterface.init();
|
||||
// Apply saved FEM LNA mode from config
|
||||
if (loraFEMInterface.isLnaCanControl()) {
|
||||
loraFEMInterface.setLNAEnable(config.lora.fem_lna_mode != meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED);
|
||||
}
|
||||
#else
|
||||
delayMicroseconds(1000);
|
||||
#endif
|
||||
|
||||
// CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX
|
||||
pinMode(LORA_PA_EN, OUTPUT);
|
||||
digitalWrite(LORA_PA_EN, HIGH);
|
||||
rtc_gpio_hold_dis((gpio_num_t)LORA_PA_EN);
|
||||
|
||||
// CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care)
|
||||
// Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
|
||||
pinMode(LORA_PA_TX_EN, OUTPUT);
|
||||
digitalWrite(LORA_PA_TX_EN, LOW); // Start in RX-ready state
|
||||
#endif
|
||||
|
||||
#ifdef RF95_FAN_EN
|
||||
@@ -197,9 +168,9 @@ template <typename T> bool SX126xInterface<T>::init()
|
||||
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity.
|
||||
// Sets bit 0 of register 0x8B5.
|
||||
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) {
|
||||
LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement");
|
||||
LOG_INFO("Applied SX1262 register 0x8B5 patch for RX improvement");
|
||||
} else {
|
||||
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109");
|
||||
LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for RX improvement");
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -282,6 +253,11 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
|
||||
LOG_ERROR("SX126X setOutputPower %s%d", radioLibErr, err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
// Apply RX gain mode — valid in STDBY (datasheet §9.6), matches resetAGC() pattern
|
||||
err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
LOG_WARN("SX126X setRxBoostedGainMode %s%d", radioLibErr, err);
|
||||
|
||||
startReceive(); // restart receiving
|
||||
|
||||
return RADIOLIB_ERR_NONE;
|
||||
@@ -419,15 +395,10 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
||||
digitalWrite(SX126X_POWER_EN, LOW);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GC1109_PA)
|
||||
/*
|
||||
* Do not switch the power on and off frequently.
|
||||
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
|
||||
* // digitalWrite(LORA_PA_POWER, LOW);
|
||||
*/
|
||||
digitalWrite(LORA_PA_EN, LOW);
|
||||
digitalWrite(LORA_PA_TX_EN, LOW);
|
||||
#if HAS_LORA_FEM
|
||||
loraFEMInterface.setSleepModeEnable();
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -489,10 +460,12 @@ template <typename T> void SX126xInterface<T>::resetAGC()
|
||||
/** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */
|
||||
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
|
||||
{
|
||||
#if defined(USE_GC1109_PA)
|
||||
digitalWrite(LORA_PA_POWER, HIGH); // Ensure LDO is on
|
||||
digitalWrite(LORA_PA_EN, HIGH); // CSD=1: Chip enabled
|
||||
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
|
||||
#if HAS_LORA_FEM
|
||||
if (txon) {
|
||||
loraFEMInterface.setTxModeEnable();
|
||||
} else {
|
||||
loraFEMInterface.setRxModeEnable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ class StreamAPI : public PhoneAPI
|
||||
virtual int32_t runOncePart();
|
||||
virtual int32_t runOncePart(char *buf, uint16_t bufLen);
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override = 0;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Read any rx chars from the link and call handleToRadio
|
||||
@@ -73,9 +76,6 @@ class StreamAPI : public PhoneAPI
|
||||
|
||||
virtual void onConnectionChanged(bool connected) override;
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override = 0;
|
||||
|
||||
/**
|
||||
* Send the current txBuffer over our stream
|
||||
*/
|
||||
|
||||
@@ -31,5 +31,6 @@ bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, vo
|
||||
/// @param timeSpanMs The interval in milliseconds of the timespan
|
||||
bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs)
|
||||
{
|
||||
return (millis() - lastExecutionMs) < timeSpanMs;
|
||||
uint32_t now = millis();
|
||||
return (now - lastExecutionMs) < timeSpanMs;
|
||||
}
|
||||
+128
-37
@@ -16,6 +16,20 @@ TransmitHistory *TransmitHistory::getInstance()
|
||||
return transmitHistory;
|
||||
}
|
||||
|
||||
TransmitHistory::StoredTimestamp TransmitHistory::makeStoredTimestamp(uint32_t seconds, uint8_t flags)
|
||||
{
|
||||
StoredTimestamp stored;
|
||||
stored.seconds = seconds;
|
||||
stored.flags = flags;
|
||||
return stored;
|
||||
}
|
||||
|
||||
TransmitHistory::StoredTimestamp TransmitHistory::decodeLegacyTimestamp(uint32_t seconds)
|
||||
{
|
||||
const bool isProbablyBootRelative = seconds > 0 && seconds <= LEGACY_BOOT_RELATIVE_MAX_SEC;
|
||||
return makeStoredTimestamp(seconds, isProbablyBootRelative ? ENTRY_FLAG_BOOT_RELATIVE : ENTRY_FLAG_NONE);
|
||||
}
|
||||
|
||||
void TransmitHistory::loadFromDisk()
|
||||
{
|
||||
spiLock->lock();
|
||||
@@ -23,16 +37,33 @@ void TransmitHistory::loadFromDisk()
|
||||
if (file) {
|
||||
FileHeader header{};
|
||||
if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC &&
|
||||
header.version == VERSION && header.count <= MAX_ENTRIES) {
|
||||
(header.version == 1 || header.version == VERSION) && header.count <= MAX_ENTRIES) {
|
||||
for (uint8_t i = 0; i < header.count; i++) {
|
||||
Entry entry{};
|
||||
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) {
|
||||
if (entry.epochSeconds > 0) {
|
||||
history[entry.key] = entry.epochSeconds;
|
||||
// Seed in-memory millis so throttle works even without RTC/GPS.
|
||||
// Treating stored entries as "just sent" is safe — worst case the
|
||||
// node waits one full interval before its first broadcast.
|
||||
lastMillis[entry.key] = millis();
|
||||
if (header.version == 1) {
|
||||
LegacyEntry entry{};
|
||||
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry) && entry.epochSeconds > 0) {
|
||||
history[entry.key] = decodeLegacyTimestamp(entry.epochSeconds);
|
||||
}
|
||||
} else {
|
||||
Entry entry{};
|
||||
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry) && entry.epochSeconds > 0) {
|
||||
history[entry.key] = makeStoredTimestamp(entry.epochSeconds, entry.flags);
|
||||
// Do NOT seed lastMillis here.
|
||||
//
|
||||
// getLastSentToMeshMillis() reconstructs a millis()-relative value
|
||||
// from the stored epoch, and Throttle::isWithinTimespanMs() uses
|
||||
// the same unsigned subtraction pattern. Once getTime() has a valid
|
||||
// wall-clock epoch comparable to stored values, recent reboots still
|
||||
// throttle correctly while long power-off periods no longer look like
|
||||
// "just sent" and incorrectly suppress the first send.
|
||||
//
|
||||
// Before RTC/NTP/GPS time is valid, persisted absolute epochs do not
|
||||
// contribute, but boot-relative entries still suppress near-term reboot
|
||||
// chatter via a narrow recovery window.
|
||||
//
|
||||
// If we seeded lastMillis to millis() here, every loaded entry would
|
||||
// appear to have been sent at boot time, regardless of the true age
|
||||
// of the last transmission. That was the regression behind #9901.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +84,8 @@ void TransmitHistory::setLastSentToMesh(uint16_t key)
|
||||
lastMillis[key] = millis();
|
||||
uint32_t now = getTime();
|
||||
if (now >= 2) {
|
||||
history[key] = now;
|
||||
const uint8_t flags = (getRTCQuality() == RTCQualityNone) ? ENTRY_FLAG_BOOT_RELATIVE : ENTRY_FLAG_NONE;
|
||||
history[key] = makeStoredTimestamp(now, flags);
|
||||
dirty = true;
|
||||
// Don't flush to disk on every transmit — flash has limited write endurance.
|
||||
// The in-memory lastMillis map handles throttle during normal operation.
|
||||
@@ -68,15 +100,84 @@ void TransmitHistory::setLastSentToMesh(uint16_t key)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
void TransmitHistory::setLastSentAtEpoch(uint16_t key, uint32_t epochSeconds)
|
||||
{
|
||||
if (epochSeconds > 0) {
|
||||
history[key] = makeStoredTimestamp(epochSeconds, ENTRY_FLAG_NONE);
|
||||
dirty = true;
|
||||
} else {
|
||||
history.erase(key);
|
||||
lastMillis.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void TransmitHistory::setLastSentAtBootRelative(uint16_t key, uint32_t secondsSinceBoot)
|
||||
{
|
||||
if (secondsSinceBoot > 0) {
|
||||
history[key] = makeStoredTimestamp(secondsSinceBoot, ENTRY_FLAG_BOOT_RELATIVE);
|
||||
dirty = true;
|
||||
} else {
|
||||
history.erase(key);
|
||||
lastMillis.erase(key);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
|
||||
{
|
||||
auto it = history.find(key);
|
||||
if (it != history.end()) {
|
||||
return it->second;
|
||||
return it->second.seconds;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentAbsoluteMillis(uint32_t storedEpoch) const
|
||||
{
|
||||
uint32_t now = getTime();
|
||||
if (now < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (storedEpoch > now) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t secondsAgo = now - storedEpoch;
|
||||
uint32_t msAgo = secondsAgo * 1000;
|
||||
|
||||
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return millis() - msAgo;
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentBootRelativeMillis(uint32_t storedSeconds) const
|
||||
{
|
||||
if (getRTCQuality() != RTCQualityNone) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t now = getTime();
|
||||
|
||||
if (storedSeconds <= now) {
|
||||
uint32_t secondsAgo = now - storedSeconds;
|
||||
if (secondsAgo > BOOT_RELATIVE_RECOVERY_WINDOW_SEC) {
|
||||
return 0;
|
||||
}
|
||||
return millis() - (secondsAgo * 1000);
|
||||
}
|
||||
|
||||
uint32_t secondsAhead = storedSeconds - now;
|
||||
if (secondsAhead > BOOT_RELATIVE_RECOVERY_WINDOW_SEC) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return millis();
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
|
||||
{
|
||||
// Prefer runtime millis value (accurate within this boot)
|
||||
@@ -86,34 +187,23 @@ uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
|
||||
}
|
||||
|
||||
// Fall back to epoch conversion (loaded from disk after reboot)
|
||||
uint32_t storedEpoch = getLastSentToMeshEpoch(key);
|
||||
if (storedEpoch == 0) {
|
||||
auto it = history.find(key);
|
||||
if (it == history.end() || it->second.seconds == 0) {
|
||||
return 0; // No stored time — module has never sent
|
||||
}
|
||||
|
||||
uint32_t now = getTime();
|
||||
if (now < 2) {
|
||||
// No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block.
|
||||
return 0;
|
||||
// Convert to a millis()-relative timestamp: millis() - msAgo.
|
||||
//
|
||||
// The result may wrap if msAgo is larger than the current uptime, and that is
|
||||
// intentional. Throttle::isWithinTimespanMs() also uses unsigned subtraction,
|
||||
// so the reconstructed age is preserved across wraparound:
|
||||
// - recent reboot, 5 min ago -> (millis() - lastMs) == 300000, still throttled
|
||||
// - long reboot, 30 min ago -> (millis() - lastMs) == 1800000, allowed
|
||||
if ((it->second.flags & ENTRY_FLAG_BOOT_RELATIVE) != 0) {
|
||||
return getLastSentBootRelativeMillis(it->second.seconds);
|
||||
}
|
||||
|
||||
if (storedEpoch > now) {
|
||||
// Stored time is in the future (clock went backwards?) — treat as stale
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t secondsAgo = now - storedEpoch;
|
||||
uint32_t msAgo = secondsAgo * 1000;
|
||||
|
||||
// Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle)
|
||||
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert to a millis()-relative timestamp: millis() - msAgo
|
||||
// This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval),
|
||||
// correctly reports whether the transmit was within interval ms.
|
||||
return millis() - msAgo;
|
||||
return getLastSentAbsoluteMillis(it->second.seconds);
|
||||
}
|
||||
|
||||
bool TransmitHistory::saveToDisk()
|
||||
@@ -141,12 +231,13 @@ bool TransmitHistory::saveToDisk()
|
||||
file.write((uint8_t *)&header, sizeof(header));
|
||||
|
||||
uint8_t written = 0;
|
||||
for (auto &pair : history) {
|
||||
for (const auto &[key, stored] : history) {
|
||||
if (written >= MAX_ENTRIES)
|
||||
break;
|
||||
Entry entry{};
|
||||
entry.key = pair.first;
|
||||
entry.epochSeconds = pair.second;
|
||||
entry.key = key;
|
||||
entry.epochSeconds = stored.seconds;
|
||||
entry.flags = stored.flags;
|
||||
file.write((uint8_t *)&entry, sizeof(entry));
|
||||
written++;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,25 @@ class TransmitHistory
|
||||
*/
|
||||
void setLastSentToMesh(uint16_t key);
|
||||
|
||||
#ifdef PIO_UNIT_TESTING
|
||||
/**
|
||||
* Get the last transmit epoch seconds for a given key, or 0 if unknown.
|
||||
* Directly set the stored epoch for a key without touching the runtime lastMillis map.
|
||||
* Intended for testing purposes: lets tests simulate "the last broadcast happened N
|
||||
* seconds ago" without needing to fake the system clock.
|
||||
*/
|
||||
void setLastSentAtEpoch(uint16_t key, uint32_t epochSeconds);
|
||||
|
||||
/**
|
||||
* Directly set a boot-relative timestamp (seconds since boot) for testing.
|
||||
*/
|
||||
void setLastSentAtBootRelative(uint16_t key, uint32_t secondsSinceBoot);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the raw persisted timestamp seconds for a given key, or 0 if unknown.
|
||||
*
|
||||
* The returned value is an absolute epoch when persisted with valid RTC/NTP/GPS time,
|
||||
* or boot-relative seconds when ENTRY_FLAG_BOOT_RELATIVE is set.
|
||||
*/
|
||||
uint32_t getLastSentToMeshEpoch(uint16_t key) const;
|
||||
|
||||
@@ -64,13 +81,31 @@ class TransmitHistory
|
||||
|
||||
static constexpr const char *FILENAME = "/prefs/transmit_history.dat";
|
||||
static constexpr uint32_t MAGIC = 0x54485354; // "THST"
|
||||
static constexpr uint8_t VERSION = 1;
|
||||
static constexpr uint8_t VERSION = 2;
|
||||
static constexpr uint8_t MAX_ENTRIES = 16;
|
||||
static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
static constexpr uint32_t BOOT_RELATIVE_RECOVERY_WINDOW_SEC = 2 * 60;
|
||||
static constexpr uint32_t LEGACY_BOOT_RELATIVE_MAX_SEC = 365UL * 24 * 60 * 60;
|
||||
|
||||
enum EntryFlags : uint8_t {
|
||||
ENTRY_FLAG_NONE = 0,
|
||||
ENTRY_FLAG_BOOT_RELATIVE = 0x01,
|
||||
};
|
||||
|
||||
struct StoredTimestamp {
|
||||
uint32_t seconds = 0;
|
||||
uint8_t flags = ENTRY_FLAG_NONE;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) Entry {
|
||||
uint16_t key;
|
||||
uint32_t epochSeconds;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) LegacyEntry {
|
||||
uint16_t key;
|
||||
uint32_t epochSeconds;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) FileHeader {
|
||||
@@ -79,8 +114,13 @@ class TransmitHistory
|
||||
uint8_t count;
|
||||
};
|
||||
|
||||
std::map<uint16_t, uint32_t> history; // key -> epoch seconds (for disk persistence)
|
||||
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
|
||||
uint32_t getLastSentAbsoluteMillis(uint32_t storedEpoch) const;
|
||||
uint32_t getLastSentBootRelativeMillis(uint32_t storedSeconds) const;
|
||||
static StoredTimestamp makeStoredTimestamp(uint32_t seconds, uint8_t flags = ENTRY_FLAG_NONE);
|
||||
static StoredTimestamp decodeLegacyTimestamp(uint32_t seconds);
|
||||
|
||||
std::map<uint16_t, StoredTimestamp> history; // key -> persisted transmit time
|
||||
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
|
||||
bool dirty = false;
|
||||
uint32_t lastDiskSave = 0; // millis() of last disk flush
|
||||
};
|
||||
|
||||
@@ -15,11 +15,11 @@ class PacketAPI : public PhoneAPI, public concurrency::OSThread
|
||||
static PacketAPI *create(PacketServer *_server);
|
||||
virtual ~PacketAPI(){};
|
||||
virtual int32_t runOnce();
|
||||
// Check the current underlying physical queue to see if the client is fetching packets
|
||||
bool checkIsConnected() override;
|
||||
|
||||
protected:
|
||||
explicit PacketAPI(PacketServer *_server);
|
||||
// Check the current underlying physical queue to see if the client is fetching packets
|
||||
bool checkIsConnected() override;
|
||||
|
||||
void onNowHasData(uint32_t fromRadioNum) override {}
|
||||
void onConnectionChanged(bool connected) override {}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "ServerAPI.h"
|
||||
#include "Throttle.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
static constexpr uint32_t TCP_IDLE_TIMEOUT_MS = 15 * 60 * 1000UL;
|
||||
|
||||
template <typename T>
|
||||
ServerAPI<T>::ServerAPI(T &_client) : StreamAPI(&client), concurrency::OSThread("ServerAPI"), client(_client)
|
||||
{
|
||||
@@ -28,9 +31,16 @@ template <typename T> bool ServerAPI<T>::checkIsConnected()
|
||||
template <class T> int32_t ServerAPI<T>::runOnce()
|
||||
{
|
||||
if (client.connected()) {
|
||||
if (lastContactMsec > 0 && !Throttle::isWithinTimespanMs(lastContactMsec, TCP_IDLE_TIMEOUT_MS)) {
|
||||
LOG_WARN("TCP connection timeout, no data for %lu ms", (unsigned long)(millis() - lastContactMsec));
|
||||
close();
|
||||
enabled = false;
|
||||
return 0;
|
||||
}
|
||||
return StreamAPI::runOncePart();
|
||||
} else {
|
||||
LOG_INFO("Client dropped connection, suspend API service");
|
||||
close();
|
||||
enabled = false; // we no longer need to run
|
||||
return 0;
|
||||
}
|
||||
@@ -45,13 +55,18 @@ template <class T, class U> void APIServerPort<T, U>::init()
|
||||
|
||||
template <class T, class U> int32_t APIServerPort<T, U>::runOnce()
|
||||
{
|
||||
// Clean up previous connection if its client already disconnected
|
||||
if (openAPI && !openAPI->checkIsConnected()) {
|
||||
openAPI.reset();
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
|
||||
auto client = U::accept();
|
||||
#else
|
||||
auto client = U::available();
|
||||
#endif
|
||||
#elif defined(ARCH_RP2040)
|
||||
#elif defined(ARCH_RP2040) || defined(ARCH_NRF52)
|
||||
auto client = U::accept();
|
||||
#else
|
||||
auto client = U::available();
|
||||
@@ -70,10 +85,10 @@ template <class T, class U> int32_t APIServerPort<T, U>::runOnce()
|
||||
}
|
||||
#endif
|
||||
LOG_INFO("Force close previous TCP connection");
|
||||
delete openAPI;
|
||||
openAPI.reset();
|
||||
}
|
||||
|
||||
openAPI = new T(client);
|
||||
openAPI.reset(new T(client));
|
||||
}
|
||||
|
||||
#if RAK_4631
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "StreamAPI.h"
|
||||
#include <memory>
|
||||
|
||||
#define SERVER_API_DEFAULT_PORT 4403
|
||||
|
||||
@@ -21,15 +22,15 @@ template <class T> class ServerAPI : public StreamAPI, private concurrency::OSTh
|
||||
/// override close to also shutdown the TCP link
|
||||
virtual void close();
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override;
|
||||
|
||||
protected:
|
||||
/// We override this method to prevent publishing EVENT_SERIAL_CONNECTED/DISCONNECTED for wifi links (we want the board to
|
||||
/// stay in the POWERED state to prevent disabling wifi)
|
||||
virtual void onConnectionChanged(bool connected) override {}
|
||||
|
||||
virtual int32_t runOnce() override; // Check for dropped client connections
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -42,7 +43,7 @@ template <class T, class U> class APIServerPort : public U, private concurrency:
|
||||
* FIXME: We currently only allow one open TCP connection at a time, because we depend on the loop() call in this class to
|
||||
* delegate to the worker. Once coroutines are implemented we can relax this restriction.
|
||||
*/
|
||||
T *openAPI = NULL;
|
||||
std::unique_ptr<T> openAPI;
|
||||
#if defined(RAK_4631) || defined(RAK11310)
|
||||
// Track wait time for RAK13800 Ethernet requests
|
||||
int32_t waitTime = 100;
|
||||
|
||||
@@ -17,6 +17,15 @@ void initApiServer(int port)
|
||||
}
|
||||
}
|
||||
|
||||
void deInitApiServer()
|
||||
{
|
||||
if (apiPort) {
|
||||
LOG_INFO("Deinit API server");
|
||||
delete apiPort;
|
||||
apiPort = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
|
||||
{
|
||||
LOG_INFO("Incoming ethernet connection");
|
||||
|
||||
@@ -24,4 +24,5 @@ class ethServerPort : public APIServerPort<ethServerAPI, EthernetServer>
|
||||
};
|
||||
|
||||
void initApiServer(int port = SERVER_API_DEFAULT_PORT);
|
||||
void deInitApiServer();
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,69 @@ static Periodic *ethEvent;
|
||||
static int32_t reconnectETH()
|
||||
{
|
||||
if (config.network.eth_enabled) {
|
||||
|
||||
// Detect W5100S chip reset by verifying the MAC address register.
|
||||
// PoE power instability can brownout the W5100S while the MCU keeps running,
|
||||
// causing all chip registers (MAC, IP, sockets) to revert to defaults.
|
||||
uint8_t currentMac[6];
|
||||
Ethernet.MACAddress(currentMac);
|
||||
|
||||
uint8_t expectedMac[6];
|
||||
getMacAddr(expectedMac);
|
||||
expectedMac[0] &= 0xfe;
|
||||
|
||||
if (memcmp(currentMac, expectedMac, 6) != 0) {
|
||||
LOG_WARN("W5100S MAC mismatch (chip reset detected), reinitializing Ethernet");
|
||||
|
||||
syslog.disable();
|
||||
#if !MESHTASTIC_EXCLUDE_SOCKETAPI
|
||||
deInitApiServer();
|
||||
#endif
|
||||
#if HAS_UDP_MULTICAST
|
||||
if (udpHandler) {
|
||||
udpHandler->stop();
|
||||
}
|
||||
#endif
|
||||
|
||||
ethStartupComplete = false;
|
||||
#ifndef DISABLE_NTP
|
||||
ntp_renew = 0;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_ETHERNET_RESET
|
||||
pinMode(PIN_ETHERNET_RESET, OUTPUT);
|
||||
digitalWrite(PIN_ETHERNET_RESET, LOW);
|
||||
delay(100);
|
||||
digitalWrite(PIN_ETHERNET_RESET, HIGH);
|
||||
delay(100);
|
||||
#endif
|
||||
|
||||
#ifdef RAK11310
|
||||
ETH_SPI_PORT.setSCK(PIN_SPI0_SCK);
|
||||
ETH_SPI_PORT.setTX(PIN_SPI0_MOSI);
|
||||
ETH_SPI_PORT.setRX(PIN_SPI0_MISO);
|
||||
ETH_SPI_PORT.begin();
|
||||
#endif
|
||||
Ethernet.init(ETH_SPI_PORT, PIN_ETHERNET_SS);
|
||||
|
||||
int status = 0;
|
||||
if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) {
|
||||
status = Ethernet.begin(expectedMac);
|
||||
} else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) {
|
||||
Ethernet.begin(expectedMac, config.network.ipv4_config.ip, config.network.ipv4_config.dns,
|
||||
config.network.ipv4_config.gateway, config.network.ipv4_config.subnet);
|
||||
status = 1;
|
||||
}
|
||||
|
||||
if (status == 0) {
|
||||
LOG_ERROR("Ethernet re-initialization failed, will retry");
|
||||
return 5000;
|
||||
}
|
||||
|
||||
LOG_INFO("Ethernet reinitialized - IP %u.%u.%u.%u", Ethernet.localIP()[0], Ethernet.localIP()[1],
|
||||
Ethernet.localIP()[2], Ethernet.localIP()[3]);
|
||||
}
|
||||
|
||||
Ethernet.maintain();
|
||||
if (!ethStartupComplete) {
|
||||
// Start web server
|
||||
@@ -39,7 +102,6 @@ static int32_t reconnectETH()
|
||||
|
||||
#ifndef DISABLE_NTP
|
||||
LOG_INFO("Start NTP time client");
|
||||
timeClient.begin();
|
||||
timeClient.setUpdateInterval(60 * 60); // Update once an hour
|
||||
#endif
|
||||
|
||||
@@ -96,6 +158,7 @@ static int32_t reconnectETH()
|
||||
LOG_ERROR("NTP Update failed");
|
||||
ntp_renew = millis() + 300 * 1000; // failure, retry every 5 minutes
|
||||
}
|
||||
timeClient.end(); // W5100S: release UDP socket for other services
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ PB_BIND(meshtastic_SEN5X_config, meshtastic_SEN5X_config, AUTO)
|
||||
PB_BIND(meshtastic_SCD30_config, meshtastic_SCD30_config, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_SHTXX_config, meshtastic_SHTXX_config, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -229,6 +229,12 @@ typedef struct _meshtastic_SCD30_config {
|
||||
bool soft_reset;
|
||||
} meshtastic_SCD30_config;
|
||||
|
||||
typedef struct _meshtastic_SHTXX_config {
|
||||
/* Accuracy mode (0 = low, 1 = medium, 2 = high) */
|
||||
bool has_set_accuracy;
|
||||
uint32_t set_accuracy;
|
||||
} meshtastic_SHTXX_config;
|
||||
|
||||
typedef struct _meshtastic_SensorConfig {
|
||||
/* SCD4X CO2 Sensor configuration */
|
||||
bool has_scd4x_config;
|
||||
@@ -239,6 +245,9 @@ typedef struct _meshtastic_SensorConfig {
|
||||
/* SCD30 CO2 Sensor configuration */
|
||||
bool has_scd30_config;
|
||||
meshtastic_SCD30_config scd30_config;
|
||||
/* SHTXX temperature and relative humidity sensor configuration */
|
||||
bool has_shtxx_config;
|
||||
meshtastic_SHTXX_config shtxx_config;
|
||||
} meshtastic_SensorConfig;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(8) meshtastic_AdminMessage_session_passkey_t;
|
||||
@@ -427,6 +436,7 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
|
||||
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
|
||||
@@ -435,10 +445,11 @@ extern "C" {
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
|
||||
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
|
||||
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
|
||||
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default, false, meshtastic_SCD30_config_init_default}
|
||||
#define meshtastic_SensorConfig_init_default {false, meshtastic_SCD4X_config_init_default, false, meshtastic_SEN5X_config_init_default, false, meshtastic_SCD30_config_init_default, false, meshtastic_SHTXX_config_init_default}
|
||||
#define meshtastic_SCD4X_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SEN5X_config_init_default {false, 0, false, 0}
|
||||
#define meshtastic_SCD30_config_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SHTXX_config_init_default {false, 0}
|
||||
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
|
||||
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
|
||||
@@ -446,10 +457,11 @@ extern "C" {
|
||||
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
|
||||
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
|
||||
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
|
||||
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero, false, meshtastic_SCD30_config_init_zero}
|
||||
#define meshtastic_SensorConfig_init_zero {false, meshtastic_SCD4X_config_init_zero, false, meshtastic_SEN5X_config_init_zero, false, meshtastic_SCD30_config_init_zero, false, meshtastic_SHTXX_config_init_zero}
|
||||
#define meshtastic_SCD4X_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SEN5X_config_init_zero {false, 0, false, 0}
|
||||
#define meshtastic_SCD30_config_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_SHTXX_config_init_zero {false, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_AdminMessage_InputEvent_event_code_tag 1
|
||||
@@ -486,9 +498,11 @@ extern "C" {
|
||||
#define meshtastic_SCD30_config_set_altitude_tag 4
|
||||
#define meshtastic_SCD30_config_set_measurement_interval_tag 5
|
||||
#define meshtastic_SCD30_config_soft_reset_tag 6
|
||||
#define meshtastic_SHTXX_config_set_accuracy_tag 1
|
||||
#define meshtastic_SensorConfig_scd4x_config_tag 1
|
||||
#define meshtastic_SensorConfig_sen5x_config_tag 2
|
||||
#define meshtastic_SensorConfig_scd30_config_tag 3
|
||||
#define meshtastic_SensorConfig_shtxx_config_tag 4
|
||||
#define meshtastic_AdminMessage_get_channel_request_tag 1
|
||||
#define meshtastic_AdminMessage_get_channel_response_tag 2
|
||||
#define meshtastic_AdminMessage_get_owner_request_tag 3
|
||||
@@ -679,12 +693,14 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
|
||||
#define meshtastic_SensorConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, scd4x_config, 1) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, sen5x_config, 2) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, scd30_config, 3)
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, scd30_config, 3) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, shtxx_config, 4)
|
||||
#define meshtastic_SensorConfig_CALLBACK NULL
|
||||
#define meshtastic_SensorConfig_DEFAULT NULL
|
||||
#define meshtastic_SensorConfig_scd4x_config_MSGTYPE meshtastic_SCD4X_config
|
||||
#define meshtastic_SensorConfig_sen5x_config_MSGTYPE meshtastic_SEN5X_config
|
||||
#define meshtastic_SensorConfig_scd30_config_MSGTYPE meshtastic_SCD30_config
|
||||
#define meshtastic_SensorConfig_shtxx_config_MSGTYPE meshtastic_SHTXX_config
|
||||
|
||||
#define meshtastic_SCD4X_config_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, BOOL, set_asc, 1) \
|
||||
@@ -713,6 +729,11 @@ X(a, STATIC, OPTIONAL, BOOL, soft_reset, 6)
|
||||
#define meshtastic_SCD30_config_CALLBACK NULL
|
||||
#define meshtastic_SCD30_config_DEFAULT NULL
|
||||
|
||||
#define meshtastic_SHTXX_config_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, set_accuracy, 1)
|
||||
#define meshtastic_SHTXX_config_CALLBACK NULL
|
||||
#define meshtastic_SHTXX_config_DEFAULT NULL
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
|
||||
@@ -724,6 +745,7 @@ extern const pb_msgdesc_t meshtastic_SensorConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SCD4X_config_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SEN5X_config_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
|
||||
extern const pb_msgdesc_t meshtastic_SHTXX_config_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
|
||||
@@ -737,6 +759,7 @@ extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
|
||||
#define meshtastic_SCD4X_config_fields &meshtastic_SCD4X_config_msg
|
||||
#define meshtastic_SEN5X_config_fields &meshtastic_SEN5X_config_msg
|
||||
#define meshtastic_SCD30_config_fields &meshtastic_SCD30_config_msg
|
||||
#define meshtastic_SHTXX_config_fields &meshtastic_SHTXX_config_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
|
||||
@@ -749,7 +772,8 @@ extern const pb_msgdesc_t meshtastic_SCD30_config_msg;
|
||||
#define meshtastic_SCD30_config_size 27
|
||||
#define meshtastic_SCD4X_config_size 29
|
||||
#define meshtastic_SEN5X_config_size 7
|
||||
#define meshtastic_SensorConfig_size 69
|
||||
#define meshtastic_SHTXX_config_size 6
|
||||
#define meshtastic_SensorConfig_size 77
|
||||
#define meshtastic_SharedContact_size 127
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
|
||||
#define meshtastic_ChannelSet_size 679
|
||||
#define meshtastic_ChannelSet_size 682
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -67,6 +67,8 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -318,6 +318,15 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9
|
||||
} meshtastic_Config_LoRaConfig_ModemPreset;
|
||||
|
||||
typedef enum _meshtastic_Config_LoRaConfig_FEM_LNA_Mode {
|
||||
/* FEM_LNA is present but disabled */
|
||||
meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED = 0,
|
||||
/* FEM_LNA is present and enabled */
|
||||
meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED = 1,
|
||||
/* FEM_LNA is not present on the device */
|
||||
meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT = 2
|
||||
} meshtastic_Config_LoRaConfig_FEM_LNA_Mode;
|
||||
|
||||
typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
|
||||
/* Device generates a random PIN that will be shown on the screen of the device for pairing */
|
||||
meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN = 0,
|
||||
@@ -579,6 +588,8 @@ typedef struct _meshtastic_Config_LoRaConfig {
|
||||
bool ignore_mqtt;
|
||||
/* Sets the ok_to_mqtt bit on outgoing packets */
|
||||
bool config_ok_to_mqtt;
|
||||
/* Set where LORA FEM is enabled, disabled, or not present */
|
||||
meshtastic_Config_LoRaConfig_FEM_LNA_Mode fem_lna_mode;
|
||||
} meshtastic_Config_LoRaConfig;
|
||||
|
||||
typedef struct _meshtastic_Config_BluetoothConfig {
|
||||
@@ -698,6 +709,10 @@ extern "C" {
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN meshtastic_Config_LoRaConfig_FEM_LNA_Mode_DISABLED
|
||||
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MAX meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT
|
||||
#define _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_FEM_LNA_Mode)(meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT+1))
|
||||
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
|
||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_ARRAYSIZE ((meshtastic_Config_BluetoothConfig_PairingMode)(meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN+1))
|
||||
@@ -721,6 +736,7 @@ extern "C" {
|
||||
|
||||
#define meshtastic_Config_LoRaConfig_modem_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset
|
||||
#define meshtastic_Config_LoRaConfig_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode
|
||||
#define meshtastic_Config_LoRaConfig_fem_lna_mode_ENUMTYPE meshtastic_Config_LoRaConfig_FEM_LNA_Mode
|
||||
|
||||
#define meshtastic_Config_BluetoothConfig_mode_ENUMTYPE meshtastic_Config_BluetoothConfig_PairingMode
|
||||
|
||||
@@ -735,7 +751,7 @@ extern "C" {
|
||||
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN}
|
||||
#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_default {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_SessionkeyConfig_init_default {0}
|
||||
@@ -746,7 +762,7 @@ extern "C" {
|
||||
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_DeprecatedGpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN, 0, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, _meshtastic_Config_LoRaConfig_FEM_LNA_Mode_MIN}
|
||||
#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define meshtastic_Config_SecurityConfig_init_zero {{0, {0}}, {0, {0}}, 0, {{0, {0}}, {0, {0}}, {0, {0}}}, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_SessionkeyConfig_init_zero {0}
|
||||
@@ -832,6 +848,7 @@ extern "C" {
|
||||
#define meshtastic_Config_LoRaConfig_ignore_incoming_tag 103
|
||||
#define meshtastic_Config_LoRaConfig_ignore_mqtt_tag 104
|
||||
#define meshtastic_Config_LoRaConfig_config_ok_to_mqtt_tag 105
|
||||
#define meshtastic_Config_LoRaConfig_fem_lna_mode_tag 106
|
||||
#define meshtastic_Config_BluetoothConfig_enabled_tag 1
|
||||
#define meshtastic_Config_BluetoothConfig_mode_tag 2
|
||||
#define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3
|
||||
@@ -983,7 +1000,8 @@ X(a, STATIC, SINGULAR, FLOAT, override_frequency, 14) \
|
||||
X(a, STATIC, SINGULAR, BOOL, pa_fan_disabled, 15) \
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
|
||||
X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104) \
|
||||
X(a, STATIC, SINGULAR, BOOL, config_ok_to_mqtt, 105)
|
||||
X(a, STATIC, SINGULAR, BOOL, config_ok_to_mqtt, 105) \
|
||||
X(a, STATIC, SINGULAR, UENUM, fem_lna_mode, 106)
|
||||
#define meshtastic_Config_LoRaConfig_CALLBACK NULL
|
||||
#define meshtastic_Config_LoRaConfig_DEFAULT NULL
|
||||
|
||||
@@ -1040,7 +1058,7 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
|
||||
#define meshtastic_Config_BluetoothConfig_size 10
|
||||
#define meshtastic_Config_DeviceConfig_size 100
|
||||
#define meshtastic_Config_DisplayConfig_size 36
|
||||
#define meshtastic_Config_LoRaConfig_size 85
|
||||
#define meshtastic_Config_LoRaConfig_size 88
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
|
||||
#define meshtastic_Config_NetworkConfig_size 204
|
||||
#define meshtastic_Config_PositionConfig_size 62
|
||||
|
||||
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||
#define meshtastic_BackupPreferences_size 2426
|
||||
#define meshtastic_BackupPreferences_size 2429
|
||||
#define meshtastic_ChannelFile_size 718
|
||||
#define meshtastic_DeviceState_size 1737
|
||||
#define meshtastic_NodeInfoLite_size 196
|
||||
|
||||
@@ -205,7 +205,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
|
||||
#define meshtastic_LocalConfig_size 751
|
||||
#define meshtastic_LocalConfig_size 754
|
||||
#define meshtastic_LocalModuleConfig_size 820
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -304,6 +304,10 @@ typedef enum _meshtastic_HardwareModel {
|
||||
meshtastic_HardwareModel_TBEAM_BPF = 124,
|
||||
/* LilyGo T-Mini E-paper S3 Kit */
|
||||
meshtastic_HardwareModel_MINI_EPAPER_S3 = 125,
|
||||
/* LilyGo T-Display S3 Pro LR1121 */
|
||||
meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126,
|
||||
/* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */
|
||||
meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -140,6 +140,9 @@ typedef enum _meshtastic_PortNum {
|
||||
meshtastic_PortNum_MAP_REPORT_APP = 73,
|
||||
/* PowerStress based monitoring support (for automated power consumption testing) */
|
||||
meshtastic_PortNum_POWERSTRESS_APP = 74,
|
||||
/* LoraWAN Payload Transport
|
||||
ENCODING: compact binary LoRaWAN uplink (10-byte RF metadata + PHY payload) - see LoRaWANBridgeModule */
|
||||
meshtastic_PortNum_LORAWAN_BRIDGE = 75,
|
||||
/* Reticulum Network Stack Tunnel App
|
||||
ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface */
|
||||
meshtastic_PortNum_RETICULUM_TUNNEL_APP = 76,
|
||||
@@ -147,6 +150,10 @@ typedef enum _meshtastic_PortNum {
|
||||
arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
ENCODING: CayenneLLP */
|
||||
meshtastic_PortNum_CAYENNE_APP = 77,
|
||||
/* GroupAlarm integration
|
||||
Used for transporting GroupAlarm-related messages between Meshtastic nodes
|
||||
and companion applications/services. */
|
||||
meshtastic_PortNum_GROUPALARM_APP = 112,
|
||||
/* Private applications should use portnums >= 256.
|
||||
To simplify initial development and testing you can use "PRIVATE_APP"
|
||||
in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */
|
||||
|
||||
@@ -26,7 +26,7 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
meshtastic_TelemetrySensorType_INA219 = 5,
|
||||
/* High accuracy temperature and pressure */
|
||||
meshtastic_TelemetrySensorType_BMP280 = 6,
|
||||
/* High accuracy temperature and humidity */
|
||||
/* TODO - REMOVE High accuracy temperature and humidity */
|
||||
meshtastic_TelemetrySensorType_SHTC3 = 7,
|
||||
/* High accuracy pressure */
|
||||
meshtastic_TelemetrySensorType_LPS22 = 8,
|
||||
@@ -36,7 +36,7 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
meshtastic_TelemetrySensorType_QMI8658 = 10,
|
||||
/* 3-Axis magnetic sensor */
|
||||
meshtastic_TelemetrySensorType_QMC5883L = 11,
|
||||
/* High accuracy temperature and humidity */
|
||||
/* TODO - REMOVE High accuracy temperature and humidity */
|
||||
meshtastic_TelemetrySensorType_SHT31 = 12,
|
||||
/* PM2.5 air quality sensor */
|
||||
meshtastic_TelemetrySensorType_PMSA003I = 13,
|
||||
@@ -46,7 +46,7 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
meshtastic_TelemetrySensorType_BMP085 = 15,
|
||||
/* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */
|
||||
meshtastic_TelemetrySensorType_RCWL9620 = 16,
|
||||
/* Sensirion High accuracy temperature and humidity */
|
||||
/* TODO - REMOVE Sensirion High accuracy temperature and humidity */
|
||||
meshtastic_TelemetrySensorType_SHT4X = 17,
|
||||
/* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */
|
||||
meshtastic_TelemetrySensorType_VEML7700 = 18,
|
||||
@@ -106,12 +106,14 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
meshtastic_TelemetrySensorType_BH1750 = 45,
|
||||
/* HDC1080 Temperature and Humidity Sensor */
|
||||
meshtastic_TelemetrySensorType_HDC1080 = 46,
|
||||
/* STH21 Temperature and R. Humidity sensor */
|
||||
/* TODO - REMOVE STH21 Temperature and R. Humidity sensor */
|
||||
meshtastic_TelemetrySensorType_SHT21 = 47,
|
||||
/* Sensirion STC31 CO2 sensor */
|
||||
meshtastic_TelemetrySensorType_STC31 = 48,
|
||||
/* SCD30 CO2, humidity, temperature sensor */
|
||||
meshtastic_TelemetrySensorType_SCD30 = 49
|
||||
meshtastic_TelemetrySensorType_SCD30 = 49,
|
||||
/* SHT family of sensors for temperature and humidity */
|
||||
meshtastic_TelemetrySensorType_SHTXX = 50
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -489,8 +491,8 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SCD30
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SCD30+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHTXX
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHTXX+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ class HttpAPI : public PhoneAPI
|
||||
public:
|
||||
HttpAPI() { api_type = TYPE_HTTP; }
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||
};
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
|
||||
void initWebServer();
|
||||
void createSSLCert();
|
||||
|
||||
@@ -24,3 +26,20 @@ class WebServerThread : private concurrency::OSThread
|
||||
};
|
||||
|
||||
extern WebServerThread *webServerThread;
|
||||
|
||||
#else
|
||||
// Stub implementations when web server is excluded
|
||||
inline void initWebServer() {}
|
||||
inline void createSSLCert() {}
|
||||
|
||||
class WebServerThread
|
||||
{
|
||||
public:
|
||||
WebServerThread() {}
|
||||
uint32_t requestRestart = 0;
|
||||
void markActivity() {}
|
||||
};
|
||||
|
||||
inline WebServerThread *webServerThread = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -69,6 +69,22 @@ static inline int get_max_num_nodes()
|
||||
/// Max number of channels allowed
|
||||
#define MAX_NUM_CHANNELS (member_size(meshtastic_ChannelFile, channels) / member_size(meshtastic_ChannelFile, channels[0]))
|
||||
|
||||
// Traffic Management module configuration
|
||||
// Enable per-variant by defining HAS_TRAFFIC_MANAGEMENT=1 in variant.h
|
||||
#ifndef HAS_TRAFFIC_MANAGEMENT
|
||||
#define HAS_TRAFFIC_MANAGEMENT 0
|
||||
#endif
|
||||
|
||||
// Cache size for traffic management (number of nodes to track)
|
||||
// Can be overridden per-variant based on available memory
|
||||
#ifndef TRAFFIC_MANAGEMENT_CACHE_SIZE
|
||||
#if HAS_TRAFFIC_MANAGEMENT
|
||||
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 1000
|
||||
#else
|
||||
#define TRAFFIC_MANAGEMENT_CACHE_SIZE 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
||||
/// returns the encoded packet size
|
||||
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);
|
||||
@@ -90,4 +106,4 @@ bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count);
|
||||
*/
|
||||
bool is_in_helper(uint32_t n, const uint32_t *array, pb_size_t count);
|
||||
|
||||
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
|
||||
#define is_in_repeated(name, n) is_in_helper(n, name, name##_count)
|
||||
|
||||
@@ -29,12 +29,13 @@ class HttpAPI : public PhoneAPI
|
||||
public:
|
||||
HttpAPI() { api_type = TYPE_HTTP; }
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return true; } // FIXME, be smarter about this
|
||||
};
|
||||
|
||||
class PiWebServerThread
|
||||
|
||||
@@ -68,14 +68,16 @@ class UdpMulticastHandler final
|
||||
// FIXME(PORTDUINO): arduino lacks IPAddress::toString()
|
||||
LOG_DEBUG("UDP broadcast from: %s, len=%u", packet.remoteIP().toString().c_str(), packetLength);
|
||||
#endif
|
||||
meshtastic_MeshPacket mp;
|
||||
meshtastic_MeshPacket mp = meshtastic_MeshPacket_init_zero;
|
||||
LOG_DEBUG("Decoding MeshPacket from UDP len=%u", packetLength);
|
||||
bool isPacketDecoded = pb_decode_from_bytes(packet.data(), packetLength, &meshtastic_MeshPacket_msg, &mp);
|
||||
if (isPacketDecoded && router && mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
|
||||
// Drop packets with spoofed local origin — no legitimate LAN node should send from=0 or our own nodeNum
|
||||
if (isFromUs(&mp)) {
|
||||
LOG_WARN("UDP packet with spoofed local from=0x%x, dropping", mp.from);
|
||||
return;
|
||||
}
|
||||
mp.transport_mechanism = meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MULTICAST_UDP;
|
||||
mp.pki_encrypted = false;
|
||||
mp.public_key.size = 0;
|
||||
memset(mp.public_key.bytes, 0, sizeof(mp.public_key.bytes));
|
||||
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(mp);
|
||||
// Unset received SNR/RSSI
|
||||
p->rx_snr = 0;
|
||||
@@ -104,8 +106,7 @@ class UdpMulticastHandler final
|
||||
LOG_DEBUG("Broadcasting packet over UDP (id=%u)", mp->id);
|
||||
uint8_t buffer[meshtastic_MeshPacket_size];
|
||||
size_t encodedLength = pb_encode_to_bytes(buffer, sizeof(buffer), &meshtastic_MeshPacket_msg, mp);
|
||||
udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
|
||||
return true;
|
||||
return udp.writeTo(buffer, encodedLength, udpIpAddress, UDP_MULTICAST_DEFAUL_PORT);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -113,4 +114,4 @@ class UdpMulticastHandler final
|
||||
AsyncUDP udp;
|
||||
bool isRunning;
|
||||
};
|
||||
#endif // HAS_UDP_MULTICAST
|
||||
#endif // HAS_UDP_MULTICAST
|
||||
|
||||
Reference in New Issue
Block a user