mirror of
https://github.com/meshtastic/firmware.git
synced 2026-03-28 12:03:22 -04:00
Merge remote-tracking branch 'origin/develop'
This commit is contained in:
10
.github/workflows/package_pio_deps.yml
vendored
10
.github/workflows/package_pio_deps.yml
vendored
@@ -54,6 +54,16 @@ jobs:
|
||||
PLATFORMIO_LIBDEPS_DIR: pio/libdeps
|
||||
PLATFORMIO_PACKAGES_DIR: pio/packages
|
||||
PLATFORMIO_CORE_DIR: pio/core
|
||||
PLATFORMIO_SETTING_ENABLE_TELEMETRY: 0
|
||||
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL: 3650
|
||||
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD: 10240
|
||||
|
||||
- name: Mangle platformio cache
|
||||
# Add "1" to epoch-timestamps of all downloads in the cache.
|
||||
# This is a hack to prevent internet access at build-time.
|
||||
run: |
|
||||
cp pio/core/.cache/downloads/usage.db pio/core/.cache/downloads/usage.db.bak
|
||||
jq -c 'with_entries(.value |= (. | tostring + "1" | tonumber))' pio/core/.cache/downloads/usage.db.bak > pio/core/.cache/downloads/usage.db
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
3
debian/ci_pack_sdeb.sh
vendored
3
debian/ci_pack_sdeb.sh
vendored
@@ -3,6 +3,9 @@ export DEBEMAIL="jbennett@incomsystems.biz"
|
||||
export PLATFORMIO_LIBDEPS_DIR=pio/libdeps
|
||||
export PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
export PLATFORMIO_CORE_DIR=pio/core
|
||||
export PLATFORMIO_SETTING_ENABLE_TELEMETRY=0
|
||||
export PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650
|
||||
export PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
|
||||
|
||||
# Download libraries to `pio`
|
||||
platformio pkg install -e native-tft
|
||||
|
||||
5
debian/rules
vendored
5
debian/rules
vendored
@@ -9,7 +9,10 @@
|
||||
PIO_ENV:=\
|
||||
PLATFORMIO_CORE_DIR=pio/core \
|
||||
PLATFORMIO_LIBDEPS_DIR=pio/libdeps \
|
||||
PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
PLATFORMIO_PACKAGES_DIR=pio/packages \
|
||||
PLATFORMIO_SETTING_ENABLE_TELEMETRY=0 \
|
||||
PLATFORMIO_SETTING_CHECK_PLATFORMIO_INTERVAL=3650 \
|
||||
PLATFORMIO_SETTING_CHECK_PRUNE_SYSTEM_THRESHOLD=10240
|
||||
|
||||
# Raspbian armhf builds should be compatible with armv6-hardfloat
|
||||
# https://www.valvers.com/open-software/raspberry-pi/bare-metal-programming-in-c-part-1/#rpi1-compiler-flags
|
||||
|
||||
@@ -163,6 +163,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define TX_GAIN_LORA 0
|
||||
#endif
|
||||
|
||||
#ifndef HAS_LORA_FEM
|
||||
#define HAS_LORA_FEM 0
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Feature toggles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@@ -92,7 +92,8 @@ class ScanI2C
|
||||
SEN5X,
|
||||
SFA30,
|
||||
CW2015,
|
||||
SCD30
|
||||
SCD30,
|
||||
ADS1115
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -718,11 +718,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
if (len == 5 && memcmp(expectedInfo, info, len) == 0) {
|
||||
LOG_INFO("NXP SE050 crypto chip found");
|
||||
type = NXP_SE050;
|
||||
|
||||
} else {
|
||||
LOG_INFO("FT6336U touchscreen found");
|
||||
type = FT6336U;
|
||||
break;
|
||||
}
|
||||
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 2);
|
||||
if (registerValue == 0x8583 || registerValue == 0x8580) {
|
||||
type = ADS1115;
|
||||
logFoundDevice("ADS1115 ADC", (uint8_t)addr.address);
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_INFO("FT6336U touchscreen found");
|
||||
type = FT6336U;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ static const char *getGPSPowerStateString(GPSPowerState state)
|
||||
|
||||
#ifdef PIN_GPS_SWITCH
|
||||
// If we have a hardware switch, define a periodic watcher outside of the GPS runOnce thread, since this can be sleeping
|
||||
// idefinitely
|
||||
// indefinitely
|
||||
|
||||
int lastState = LOW;
|
||||
bool firstrun = true;
|
||||
@@ -586,14 +586,14 @@ bool GPS::setup()
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
|
||||
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
|
||||
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
|
||||
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Config");
|
||||
}
|
||||
|
||||
// Set the update frequence to 1Hz
|
||||
// Set the update frequency to 1Hz
|
||||
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
|
||||
@@ -700,7 +700,7 @@ bool GPS::setup()
|
||||
} else { // 8,9
|
||||
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
|
||||
}
|
||||
// Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
|
||||
delay(1000);
|
||||
}
|
||||
@@ -733,7 +733,7 @@ bool GPS::setup()
|
||||
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
|
||||
// For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
|
||||
// For M8 we want to enable NMEA version 4.10 so we can see the additional sats.
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
|
||||
@@ -1211,7 +1211,7 @@ int32_t GPS::runOnce()
|
||||
return disable(); // This should trigger when we have a fixed position, and get that first position
|
||||
|
||||
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
|
||||
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
|
||||
// if not awake we can run super infrequently (once every 5 secs?) to see if we need to wake.
|
||||
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ GeoCoord::GeoCoord(int32_t lat, int32_t lon, int32_t alt) : _latitude(lat), _lon
|
||||
|
||||
GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
|
||||
{
|
||||
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
_latitude = int32_t(lat * 1e+7);
|
||||
_longitude = int32_t(lon * 1e+7);
|
||||
GeoCoord::setCoords();
|
||||
@@ -20,7 +20,7 @@ GeoCoord::GeoCoord(float lat, float lon, int32_t alt) : _altitude(alt)
|
||||
|
||||
GeoCoord::GeoCoord(double lat, double lon, int32_t alt) : _altitude(alt)
|
||||
{
|
||||
// Change decimial representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
// Change decimal representation to int32_t. I.e., 12.345 becomes 123450000
|
||||
_latitude = int32_t(lat * 1e+7);
|
||||
_longitude = int32_t(lon * 1e+7);
|
||||
GeoCoord::setCoords();
|
||||
@@ -467,10 +467,10 @@ int32_t GeoCoord::bearingTo(const GeoCoord &pointB)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new point bassed on the passed in poin
|
||||
* Create a new point based on the passed-in point
|
||||
* Ported from http://www.edwilliams.org/avform147.htm#LL
|
||||
* @param bearing
|
||||
* The bearing in raidans
|
||||
* The bearing in radians
|
||||
* @param range_meters
|
||||
* range in meters
|
||||
* @return GeoCoord object of point at bearing and range from initial point
|
||||
@@ -593,4 +593,4 @@ double GeoCoord::toRadians(double deg)
|
||||
double GeoCoord::toDegrees(double r)
|
||||
{
|
||||
return r * 180 / PI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
||||
// This delta value works on all platforms
|
||||
timeStartMsec = now;
|
||||
zeroOffsetSecs = tv->tv_sec;
|
||||
// If this platform has a setable RTC, set it
|
||||
// If this platform has a settable RTC, set it
|
||||
#ifdef RV3028_RTC
|
||||
if (rtc_found.address == RV3028_RTC) {
|
||||
Melopero_RV3028 rtc;
|
||||
@@ -402,7 +402,7 @@ time_t gm_mktime(const struct tm *tm)
|
||||
#if !MESHTASTIC_EXCLUDE_TZ
|
||||
time_t result = 0;
|
||||
|
||||
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
|
||||
// First, get us to the start of tm->year, by calculating the number of days since the Unix epoch.
|
||||
int year = 1900 + tm->tm_year; // tm_year is years since 1900
|
||||
int year_minus_one = year - 1;
|
||||
int days_before_this_year = 0;
|
||||
|
||||
@@ -37,7 +37,7 @@ static const uint8_t _message_CAS_CFG_RATE_1HZ[] = {
|
||||
|
||||
// CFG-NAVX (0x06, 0x07)
|
||||
// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
|
||||
// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
|
||||
// Quirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
|
||||
// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
|
||||
static const uint8_t _message_CAS_CFG_NAVX_CONF[] = {
|
||||
0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
|
||||
|
||||
@@ -57,7 +57,7 @@ static const uint8_t _message_CFG_PM2[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11
|
||||
};
|
||||
|
||||
// Constallation setup, none required for Neo-6
|
||||
// Constellation setup, none required for Neo-6
|
||||
|
||||
// For Neo-7 GPS & SBAS
|
||||
static const uint8_t _message_GNSS_7[] = {
|
||||
@@ -157,7 +157,7 @@ static const uint8_t _message_NAVX5[] = {
|
||||
0x00, 0x00, 0x00, 0x00, // Reserved 9
|
||||
0x00, // Reserved 10
|
||||
0x00, // Reserved 11
|
||||
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
|
||||
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
|
||||
0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled)
|
||||
0x00, // Reserved 12
|
||||
0x00, // Reserved 13
|
||||
@@ -185,7 +185,7 @@ static const uint8_t _message_NAVX5_8[] = {
|
||||
0x00, // Reserved 4
|
||||
0x00, 0x00, // Reserved 5
|
||||
0x00, 0x00, // Reserved 6
|
||||
0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
|
||||
0x00, // usePPP (Precise Point Positioning) (0 = false, 1 = true)
|
||||
0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
|
||||
0x00, 0x00, // Reserved 7
|
||||
0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
|
||||
@@ -314,7 +314,7 @@ static const uint8_t _message_DISABLE_TXT_INFO[] = {
|
||||
// This command applies to M8 products
|
||||
static const uint8_t _message_PMS[] = {
|
||||
0x00, // Version (0)
|
||||
0x03, // Power setup value 3 = Agresssive 1Hz
|
||||
0x03, // Power setup value 3 = Agressive 1Hz
|
||||
0x00, 0x00, // period: not applicable, set to 0
|
||||
0x00, 0x00, // onTime: not applicable, set to 0
|
||||
0x00, 0x00 // reserved, generated by u-center
|
||||
@@ -337,7 +337,7 @@ static const uint8_t _message_SAVE_10[] = {
|
||||
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
|
||||
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast acquisition after
|
||||
// sleep
|
||||
|
||||
// VALSET Commands for M10
|
||||
@@ -462,7 +462,7 @@ Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled
|
||||
The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need
|
||||
the PM config. Lets try without it.
|
||||
PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats.
|
||||
The defination of "Got Fix" doesn't seem to include SBAS. Much more too this...
|
||||
The definition of "Got Fix" doesn't seem to include SBAS. Much more too this...
|
||||
Even if it was, it can take minutes (up to 12.5),
|
||||
even under good sat visibility conditions to re-acquire the SBAS data.
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
||||
return true;
|
||||
}
|
||||
|
||||
// End the update process - virtual method, overriden in derived class
|
||||
// End the update process - virtual method, overridden in derived class
|
||||
void EInkDisplay::endUpdate()
|
||||
{
|
||||
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
|
||||
|
||||
@@ -95,7 +95,7 @@ void EInkDynamicDisplay::adjustRefreshCounters()
|
||||
// Trigger the display update by calling base class
|
||||
bool EInkDynamicDisplay::update()
|
||||
{
|
||||
// Detemine the refresh mode to use, and start the update
|
||||
// Determine the refresh mode to use, and start the update
|
||||
bool refreshApproved = determineMode();
|
||||
if (refreshApproved) {
|
||||
EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
|
||||
@@ -317,7 +317,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
|
||||
LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
|
||||
}
|
||||
|
||||
// Have too many fast-refreshes occured consecutively, since last full refresh?
|
||||
// Have too many fast-refreshes occurred consecutively, since last full refresh?
|
||||
void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
|
||||
{
|
||||
// If a decision was already reached, don't run the check
|
||||
@@ -561,4 +561,4 @@ void EInkDynamicDisplay::awaitRefresh()
|
||||
}
|
||||
#endif // HAS_EINK_ASYNCFULL
|
||||
|
||||
#endif // USE_EINK_DYNAMICDISPLAY
|
||||
#endif // USE_EINK_DYNAMICDISPLAY
|
||||
|
||||
@@ -43,7 +43,7 @@ InputEvent NotificationRenderer::inEvent;
|
||||
int8_t NotificationRenderer::curSelected = 0;
|
||||
char NotificationRenderer::alertBannerMessage[256] = {0};
|
||||
uint32_t NotificationRenderer::alertBannerUntil = 0; // 0 is a special case meaning forever
|
||||
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are seelctable options
|
||||
uint8_t NotificationRenderer::alertBannerOptions = 0; // last x lines are selectable options
|
||||
const char **NotificationRenderer::optionsArrayPtr = nullptr;
|
||||
const int *NotificationRenderer::optionsEnumPtr = nullptr;
|
||||
std::function<void(int)> NotificationRenderer::alertBannerCallback = NULL;
|
||||
@@ -95,7 +95,7 @@ void NotificationRenderer::resetBanner()
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
inEvent.kbchar = 0;
|
||||
curSelected = 0;
|
||||
alertBannerOptions = 0; // last x lines are seelctable options
|
||||
alertBannerOptions = 0; // last x lines are selectable options
|
||||
optionsArrayPtr = nullptr;
|
||||
optionsEnumPtr = nullptr;
|
||||
alertBannerCallback = NULL;
|
||||
@@ -781,4 +781,4 @@ void NotificationRenderer::showKeyboardMessagePopupWithTitle(const char *title,
|
||||
}
|
||||
|
||||
} // namespace graphics
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,7 @@ class NotificationRenderer
|
||||
static uint32_t alertBannerUntil; // 0 is a special case meaning forever
|
||||
static const char **optionsArrayPtr;
|
||||
static const int *optionsEnumPtr;
|
||||
static uint8_t alertBannerOptions; // last x lines are seelctable options
|
||||
static uint8_t alertBannerOptions; // last x lines are selectable options
|
||||
static std::function<void(int)> alertBannerCallback;
|
||||
static uint32_t numDigits;
|
||||
static uint32_t currentNumber;
|
||||
|
||||
@@ -37,8 +37,8 @@ class DEPG0213BNS800 : public SSD16XX
|
||||
void configWaveform() override;
|
||||
void configUpdateSequence() override;
|
||||
void detachFromUpdate() override;
|
||||
void finalizeUpdate() override; // Only overriden for a slight optimization
|
||||
void finalizeUpdate() override; // Only overridden for a slight optimization
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::Drivers
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
@@ -35,8 +35,8 @@ class DEPG0290BNS800 : public SSD16XX
|
||||
void configWaveform() override;
|
||||
void configUpdateSequence() override;
|
||||
void detachFromUpdate() override;
|
||||
void finalizeUpdate() override; // Only overriden for a slight optimization
|
||||
void finalizeUpdate() override; // Only overridden for a slight optimization
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::Drivers
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/*
|
||||
|
||||
Base class for InkHUD applets
|
||||
Must be overriden
|
||||
Must be overridden
|
||||
|
||||
An applet is one "program" which may show info on the display.
|
||||
|
||||
@@ -208,4 +208,4 @@ class Applet : public GFX
|
||||
|
||||
}; // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -525,7 +525,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
||||
}
|
||||
|
||||
// Determine the conversion factor between metres, and pixels on screen
|
||||
// May be overriden by derived applet, if custom scale required (fixed map size?)
|
||||
// May be overridden by derived applet, if custom scale required (fixed map size?)
|
||||
void InkHUD::MapApplet::calculateMapScale()
|
||||
{
|
||||
// Aspect ratio of map and screen
|
||||
@@ -555,4 +555,4 @@ void InkHUD::MapApplet::drawCross(int16_t x, int16_t y, uint8_t size)
|
||||
drawLine(x0, y1, x1, y0, BLACK);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
|
||||
|
||||
InkHUD::BatteryIconApplet::BatteryIconApplet()
|
||||
{
|
||||
alwaysRender = true; // render everytime the screen is updated
|
||||
alwaysRender = true; // render every time the screen is updated
|
||||
|
||||
// Show at boot, if user has previously enabled the feature
|
||||
if (settings->optionalFeatures.batteryIcon)
|
||||
@@ -91,4 +91,4 @@ void InkHUD::BatteryIconApplet::onRender(bool full)
|
||||
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
|
||||
System Applet to render an on-screeen keyboard
|
||||
System Applet to render an on-screen keyboard
|
||||
|
||||
*/
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ void InkHUD::LogoApplet::onRender(bool full)
|
||||
int16_t logoCY = Y(0.5 - 0.05);
|
||||
|
||||
// Invert colors if black-on-white
|
||||
// Used during shutdown, to resport display health
|
||||
// Used during shutdown, to report display health
|
||||
// Todo: handle this in InkHUD::Renderer instead
|
||||
if (inverted) {
|
||||
fillScreen(BLACK);
|
||||
@@ -186,4 +186,4 @@ int32_t InkHUD::LogoApplet::runOnce()
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -2028,7 +2028,7 @@ void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true); // Send to mesh, cc to phone
|
||||
}
|
||||
|
||||
// Free up any heap mmemory we'd used while selecting / sending canned messages
|
||||
// Free up any heap memory we'd used while selecting / sending canned messages
|
||||
void InkHUD::MenuApplet::freeCannedMessageResources()
|
||||
{
|
||||
cm.selectedMessageItem = nullptr;
|
||||
|
||||
@@ -7,7 +7,7 @@ Displays a thread-view of incoming and outgoing message for a specific channel
|
||||
The channel for this applet is set in the constructor,
|
||||
when the applet is added to WindowManager in the setupNicheGraphics method.
|
||||
|
||||
Several messages are saved to flash at shutdown, to preseve applet between reboots.
|
||||
Several messages are saved to flash at shutdown, to preserve applet between reboots.
|
||||
This class has its own internal method for saving and loading to fs, which interacts directly with the FSCommon layer.
|
||||
If the amount of flash usage is unacceptable, we could keep these in RAM only.
|
||||
|
||||
@@ -55,4 +55,4 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -53,7 +53,7 @@ class Renderer : protected concurrency::OSThread
|
||||
uint16_t height();
|
||||
|
||||
private:
|
||||
// Make attemps to render / update, once triggered by requestUpdate or forceUpdate
|
||||
// Make attempts to render / update, once triggered by requestUpdate or forceUpdate
|
||||
int32_t runOnce() override;
|
||||
|
||||
// Apply the display rotation to handled pixels
|
||||
@@ -95,4 +95,4 @@ class Renderer : protected concurrency::OSThread
|
||||
|
||||
} // namespace NicheGraphics::InkHUD
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -649,7 +649,7 @@ void InkHUD::WindowManager::refocusTile()
|
||||
}
|
||||
}
|
||||
|
||||
// Seach for any applets which believe they are foreground, but no longer have a valid tile
|
||||
// Search for any applets which believe they are foreground, but no longer have a valid tile
|
||||
// Tidies up after layout changes at runtime
|
||||
void InkHUD::WindowManager::findOrphanApplets()
|
||||
{
|
||||
@@ -679,4 +679,4 @@ void InkHUD::WindowManager::findOrphanApplets()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -59,7 +59,7 @@ void TwoButton::stop()
|
||||
}
|
||||
|
||||
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
|
||||
// This helper method isn't used by the TweButton class itself, it could be moved elsewhere.
|
||||
// This helper method isn't used by the TwoButton class itself, it could be moved elsewhere.
|
||||
// Intention is to pass this value to TwoButton::setWiring in the setupNicheGraphics method.
|
||||
uint8_t TwoButton::getUserButtonPin()
|
||||
{
|
||||
@@ -308,4 +308,4 @@ int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -271,8 +271,8 @@ int32_t ButtonThread::runOnce()
|
||||
break;
|
||||
} // end multipress event
|
||||
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediatedly.
|
||||
// Do actual shutdown when button released, otherwise the button release
|
||||
// may wake the board immediately.
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
|
||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||
@@ -347,4 +347,4 @@ int ButtonThread::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
void ButtonThread::storeClickCount()
|
||||
{
|
||||
multipressClickCount = userButton.getNumberClicks();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ void MPR121Keyboard::reset()
|
||||
delay(20);
|
||||
writeRegister(_MPR121_REG_CONFIG2, 0x21);
|
||||
delay(20);
|
||||
// Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
|
||||
// Enter run mode by setting partial filter calibration tracking, disable proximity detection, enable 12 channels
|
||||
writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
|
||||
ECR_CALIBRATION_TRACK_FROM_FULL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
|
||||
delay(100);
|
||||
@@ -430,4 +430,4 @@ void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
|
||||
if (writeCallback) {
|
||||
writeCallback(m_addr, data[0], &(data[1]), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ int32_t SeesawRotary::runOnce()
|
||||
wasPressed = currentlyPressed;
|
||||
|
||||
int32_t new_position = ss.getEncoderPosition();
|
||||
// did we move arounde?
|
||||
// did we move around?
|
||||
if (encoder_position != new_position) {
|
||||
if (encoder_position == 0 && new_position != 1) {
|
||||
e.inputEvent = INPUT_BROKER_ALT_PRESS;
|
||||
@@ -80,4 +80,4 @@ int32_t SeesawRotary::runOnce()
|
||||
|
||||
return 50;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -88,7 +88,7 @@ void TCA8418Keyboard::pressed(uint8_t key)
|
||||
// Check if the key is the same as the last one or if the time interval has passed
|
||||
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
|
||||
char_idx = 0; // Reset char index if new key or long press
|
||||
should_backspace = false; // dont backspace on new key
|
||||
should_backspace = false; // don't backspace on new key
|
||||
} else {
|
||||
char_idx += 1; // Cycle through characters if same key pressed
|
||||
should_backspace = true; // allow backspace on same key
|
||||
|
||||
@@ -43,7 +43,7 @@ int32_t TouchScreenBase::runOnce()
|
||||
// process touch events
|
||||
int16_t x, y;
|
||||
bool touched = getTouch(x, y);
|
||||
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turing off the screen
|
||||
if (x < 0 || y < 0) // T-deck can emit phantom touch events with a negative value when turning off the screen
|
||||
touched = false;
|
||||
if (touched) {
|
||||
this->setInterval(20);
|
||||
@@ -123,7 +123,7 @@ int32_t TouchScreenBase::runOnce()
|
||||
}
|
||||
}
|
||||
#else
|
||||
// fire TAP event when no 2nd tap occured within time
|
||||
// fire TAP event when no 2nd tap occurred within time
|
||||
if (_tapped) {
|
||||
_tapped = false;
|
||||
e.touchEvent = static_cast<char>(TOUCH_ACTION_TAP);
|
||||
|
||||
@@ -487,7 +487,7 @@ int32_t KbI2cBase::runOnce()
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
|
||||
// toggle moddifiers button.
|
||||
// toggle modifiers button.
|
||||
is_sym = !is_sym;
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
|
||||
|
||||
@@ -22,6 +22,8 @@ const char *Channels::serialChannel = "serial";
|
||||
const char *Channels::mqttChannel = "mqtt";
|
||||
#endif
|
||||
|
||||
meshtastic_Channel dummyChannel = {.index = -1};
|
||||
|
||||
uint8_t xorHash(const uint8_t *p, size_t len)
|
||||
{
|
||||
uint8_t code = 0;
|
||||
@@ -309,13 +311,7 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
|
||||
return *ch;
|
||||
} else {
|
||||
LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count);
|
||||
|
||||
static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel));
|
||||
memset(ch, 0, sizeof(meshtastic_Channel));
|
||||
// ch.index -1 means we don't know the channel locally and need to look it up by settings.name
|
||||
// not sure this is handled right everywhere
|
||||
ch->index = -1;
|
||||
return *ch;
|
||||
return dummyChannel;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -63,25 +66,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
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
193
src/mesh/LoRaFEMInterface.cpp
Normal file
193
src/mesh/LoRaFEMInterface.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#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, HIGH);
|
||||
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;
|
||||
#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
|
||||
pinMode(LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(LORA_PA_POWER, HIGH);
|
||||
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);
|
||||
#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);
|
||||
#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)
|
||||
#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);
|
||||
#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
|
||||
#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
|
||||
size_t num_pa_points = portduino_config.num_pa_points;
|
||||
const uint16_t *tx_gain = portduino_config.tx_gain_lora;
|
||||
uint16_t tx_gain_num = num_pa_points;
|
||||
#else
|
||||
size_t num_pa_points = NUM_PA_POINTS;
|
||||
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
|
||||
30
src/mesh/LoRaFEMInterface.h
Normal file
30
src/mesh/LoRaFEMInterface.h
Normal file
@@ -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() {}
|
||||
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 = false;
|
||||
bool lna_can_control = false;
|
||||
};
|
||||
extern LoRaFEMInterface loraFEMInterface;
|
||||
|
||||
#endif
|
||||
@@ -184,6 +184,29 @@ bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
|
||||
}
|
||||
}
|
||||
|
||||
if (backPacket->tx_after) {
|
||||
// Check if there's a late packet at the queue end
|
||||
auto now = millis();
|
||||
if (backPacket->tx_after < now && (!p->tx_after || backPacket->tx_after > p->tx_after)) {
|
||||
int32_t dt = (int32_t)(backPacket->tx_after - now);
|
||||
if (p->tx_after) {
|
||||
LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x with "
|
||||
"TX delay %ums",
|
||||
backPacket->id, dt, p->id, p->tx_after - now);
|
||||
|
||||
} else {
|
||||
LOG_WARN("Dropping late packet 0x%08x with TX delay %dms to make room in the TX queue for packet 0x%08x "
|
||||
"with no TX delay",
|
||||
backPacket->id, dt, p->id);
|
||||
}
|
||||
queue.pop_back();
|
||||
packetPool.release(backPacket);
|
||||
// Insert the new packet in the correct order
|
||||
enqueue(p);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the back packet's priority is not lower, no replacement occurs
|
||||
return false;
|
||||
}
|
||||
@@ -24,6 +24,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)
|
||||
|
||||
@@ -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_DISABLED;
|
||||
#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;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#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>
|
||||
@@ -31,12 +32,6 @@
|
||||
#include "STM32WLE5JCInterface.h"
|
||||
#endif
|
||||
|
||||
// Calculate 2^n without calling pow()
|
||||
uint32_t pow_of_2(uint32_t n)
|
||||
{
|
||||
return 1 << n;
|
||||
}
|
||||
|
||||
#define RDEF(name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, frequency_switching, wide_lora) \
|
||||
{ \
|
||||
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, spacing, power_limit, audio_permitted, \
|
||||
@@ -920,6 +915,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 +946,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
if (power > loraMaxPower) // Clamp power to maximum defined level
|
||||
power = loraMaxPower;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_ENABLED);
|
||||
}
|
||||
#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
|
||||
@@ -419,15 +390,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 +455,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
|
||||
}
|
||||
|
||||
|
||||
@@ -38,4 +38,10 @@ const std::string vformat(const char *const zcFormat, ...);
|
||||
// Get actual string length for nanopb char array fields.
|
||||
size_t pb_string_length(const char *str, size_t max_len);
|
||||
|
||||
/// Calculate 2^n without calling pow() - used for spreading factor and other calculations
|
||||
inline uint32_t pow_of_2(uint32_t n)
|
||||
{
|
||||
return 1 << n;
|
||||
}
|
||||
|
||||
#define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#endif
|
||||
|
||||
#include "Default.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "TypeConversions.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_MQTT
|
||||
@@ -756,20 +757,14 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
LOG_INFO("Set config: LoRa");
|
||||
config.has_lora = true;
|
||||
|
||||
if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
|
||||
LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
|
||||
validatedLora.coding_rate = 5;
|
||||
if (validatedLora.coding_rate != clampCodingRate(validatedLora.coding_rate)) {
|
||||
LOG_WARN("Invalid coding_rate %d, setting to %d", validatedLora.coding_rate, LORA_CR_DEFAULT);
|
||||
validatedLora.coding_rate = LORA_CR_DEFAULT;
|
||||
}
|
||||
|
||||
if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
|
||||
LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
|
||||
validatedLora.spread_factor = 11;
|
||||
}
|
||||
|
||||
if (validatedLora.bandwidth == 0) {
|
||||
int originalBandwidth = validatedLora.bandwidth;
|
||||
validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
|
||||
LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
|
||||
if (validatedLora.spread_factor != clampSpreadFactor(validatedLora.spread_factor)) {
|
||||
LOG_WARN("Invalid spread_factor %d, setting to %d", validatedLora.spread_factor, LORA_SF_DEFAULT);
|
||||
validatedLora.spread_factor = LORA_SF_DEFAULT;
|
||||
}
|
||||
|
||||
// If no lora radio parameters change, don't need to reboot
|
||||
@@ -800,6 +795,17 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
|
||||
}
|
||||
#endif
|
||||
config.lora = validatedLora;
|
||||
|
||||
#if HAS_LORA_FEM
|
||||
// Apply FEM LNA mode from config (only meaningful on hardware that supports it)
|
||||
if (loraFEMInterface.isLnaCanControl()) {
|
||||
loraFEMInterface.setLNAEnable(config.lora.fem_lna_mode == meshtastic_Config_LoRaConfig_FEM_LNA_Mode_ENABLED);
|
||||
} else if (config.lora.fem_lna_mode != meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT) {
|
||||
// Hardware FEM does not support LNA control; normalize stored config to match actual capability
|
||||
LOG_WARN("FEM LNA mode configured but current FEM does not support LNA control; normalizing to NOT_PRESENT");
|
||||
config.lora.fem_lna_mode = meshtastic_Config_LoRaConfig_FEM_LNA_Mode_NOT_PRESENT;
|
||||
}
|
||||
#endif
|
||||
// If we're setting region for the first time, init the region and regenerate the keys
|
||||
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
||||
|
||||
0
src/motion/AccelerometerThread.h
Executable file → Normal file
0
src/motion/AccelerometerThread.h
Executable file → Normal file
0
src/motion/BMA423Sensor.cpp
Executable file → Normal file
0
src/motion/BMA423Sensor.cpp
Executable file → Normal file
0
src/motion/BMA423Sensor.h
Executable file → Normal file
0
src/motion/BMA423Sensor.h
Executable file → Normal file
0
src/motion/BMX160Sensor.cpp
Executable file → Normal file
0
src/motion/BMX160Sensor.cpp
Executable file → Normal file
0
src/motion/BMX160Sensor.h
Executable file → Normal file
0
src/motion/BMX160Sensor.h
Executable file → Normal file
0
src/motion/ICM20948Sensor.cpp
Executable file → Normal file
0
src/motion/ICM20948Sensor.cpp
Executable file → Normal file
0
src/motion/ICM20948Sensor.h
Executable file → Normal file
0
src/motion/ICM20948Sensor.h
Executable file → Normal file
0
src/motion/LIS3DHSensor.cpp
Executable file → Normal file
0
src/motion/LIS3DHSensor.cpp
Executable file → Normal file
0
src/motion/LIS3DHSensor.h
Executable file → Normal file
0
src/motion/LIS3DHSensor.h
Executable file → Normal file
0
src/motion/LSM6DS3Sensor.cpp
Executable file → Normal file
0
src/motion/LSM6DS3Sensor.cpp
Executable file → Normal file
0
src/motion/LSM6DS3Sensor.h
Executable file → Normal file
0
src/motion/LSM6DS3Sensor.h
Executable file → Normal file
0
src/motion/MPU6050Sensor.cpp
Executable file → Normal file
0
src/motion/MPU6050Sensor.cpp
Executable file → Normal file
0
src/motion/MPU6050Sensor.h
Executable file → Normal file
0
src/motion/MPU6050Sensor.h
Executable file → Normal file
0
src/motion/MotionSensor.cpp
Executable file → Normal file
0
src/motion/MotionSensor.cpp
Executable file → Normal file
0
src/motion/MotionSensor.h
Executable file → Normal file
0
src/motion/MotionSensor.h
Executable file → Normal file
0
src/motion/STK8XXXSensor.cpp
Executable file → Normal file
0
src/motion/STK8XXXSensor.cpp
Executable file → Normal file
0
src/motion/STK8XXXSensor.h
Executable file → Normal file
0
src/motion/STK8XXXSensor.h
Executable file → Normal file
@@ -163,13 +163,6 @@ void initDeepSleep()
|
||||
if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
LOG_DEBUG("Disable any holds on RTC IO pads");
|
||||
for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) {
|
||||
#if defined(USE_GC1109_PA)
|
||||
// Skip GC1109 FEM power pins - they are held HIGH during deep sleep to keep
|
||||
// the LNA active for RX wake. Released later in SX126xInterface::init() after
|
||||
// GPIO registers are set HIGH first, avoiding a power glitch.
|
||||
if (i == LORA_PA_POWER || i == LORA_PA_EN)
|
||||
continue;
|
||||
#endif
|
||||
if (rtc_gpio_is_valid_gpio((gpio_num_t)i))
|
||||
rtc_gpio_hold_dis((gpio_num_t)i);
|
||||
|
||||
@@ -567,15 +560,8 @@ void enableLoraInterrupt()
|
||||
gpio_pullup_en((gpio_num_t)LORA_CS);
|
||||
#endif
|
||||
|
||||
#if defined(USE_GC1109_PA)
|
||||
// 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);
|
||||
digitalWrite(LORA_PA_EN, HIGH);
|
||||
rtc_gpio_hold_en((gpio_num_t)LORA_PA_EN);
|
||||
gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
|
||||
#if HAS_LORA_FEM
|
||||
loraFEMInterface.setRxModeEnableWhenMCUSleep();
|
||||
#endif
|
||||
|
||||
LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
|
||||
|
||||
109
test/test_default/test_main.cpp
Normal file
109
test/test_default/test_main.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
// Unit tests for Default::getConfiguredOrDefaultMsScaled
|
||||
#include "Default.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "TestUtil.h"
|
||||
#include "meshUtils.h"
|
||||
#include <unity.h>
|
||||
|
||||
// Helper to compute expected ms using same logic as Default::congestionScalingCoefficient
|
||||
static uint32_t computeExpectedMs(uint32_t defaultSeconds, uint32_t numOnlineNodes)
|
||||
{
|
||||
uint32_t baseMs = Default::getConfiguredOrDefaultMs(0, defaultSeconds);
|
||||
|
||||
// Routers don't scale
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
|
||||
return baseMs;
|
||||
}
|
||||
|
||||
// Sensors and trackers don't scale
|
||||
if ((config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR) ||
|
||||
(config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER)) {
|
||||
return baseMs;
|
||||
}
|
||||
|
||||
if (numOnlineNodes <= 40) {
|
||||
return baseMs;
|
||||
}
|
||||
|
||||
float bwKHz =
|
||||
config.lora.use_preset ? modemPresetToBwKHz(config.lora.modem_preset, false) : bwCodeToKHz(config.lora.bandwidth);
|
||||
|
||||
uint8_t sf = config.lora.spread_factor;
|
||||
if (sf < 7)
|
||||
sf = 7;
|
||||
else if (sf > 12)
|
||||
sf = 12;
|
||||
|
||||
float throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 100.0f);
|
||||
#if USERPREFS_EVENT_MODE
|
||||
throttlingFactor = static_cast<float>(pow_of_2(sf)) / (bwKHz * 25.0f);
|
||||
#endif
|
||||
|
||||
int nodesOverForty = (numOnlineNodes - 40);
|
||||
float coeff = 1.0f + (nodesOverForty * throttlingFactor);
|
||||
return static_cast<uint32_t>(baseMs * coeff + 0.5f);
|
||||
}
|
||||
|
||||
void test_router_no_scaling()
|
||||
{
|
||||
config.device.role = meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
// set some sane lora config so bootstrap paths are deterministic
|
||||
config.lora.use_preset = false;
|
||||
config.lora.spread_factor = 9;
|
||||
config.lora.bandwidth = 250;
|
||||
|
||||
uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 100);
|
||||
uint32_t expected = computeExpectedMs(60, 100);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected, res);
|
||||
}
|
||||
|
||||
void test_client_below_threshold()
|
||||
{
|
||||
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
config.lora.use_preset = false;
|
||||
config.lora.spread_factor = 9;
|
||||
config.lora.bandwidth = 250;
|
||||
|
||||
uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 40);
|
||||
uint32_t expected = computeExpectedMs(60, 40);
|
||||
TEST_ASSERT_EQUAL_UINT32(expected, res);
|
||||
}
|
||||
|
||||
void test_client_default_preset_scaling()
|
||||
{
|
||||
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
config.lora.use_preset = false;
|
||||
config.lora.spread_factor = 9; // SF9
|
||||
config.lora.bandwidth = 250; // 250 kHz
|
||||
|
||||
uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 50);
|
||||
uint32_t expected = computeExpectedMs(60, 50); // nodesOverForty = 10
|
||||
TEST_ASSERT_EQUAL_UINT32(expected, res);
|
||||
}
|
||||
|
||||
void test_client_medium_fast_preset_scaling()
|
||||
{
|
||||
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||
config.lora.use_preset = true;
|
||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
|
||||
// nodesOverForty = 30 -> test with nodes=70
|
||||
uint32_t res = Default::getConfiguredOrDefaultMsScaled(0, 60, 70);
|
||||
uint32_t expected = computeExpectedMs(60, 70);
|
||||
// Allow ±1 ms tolerance for floating-point rounding
|
||||
TEST_ASSERT_INT_WITHIN(1, expected, res);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
// Small delay to match other test mains
|
||||
delay(10);
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_router_no_scaling);
|
||||
RUN_TEST(test_client_below_threshold);
|
||||
RUN_TEST(test_client_default_preset_scaling);
|
||||
RUN_TEST(test_client_medium_fast_preset_scaling);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -289,18 +289,23 @@ class MQTTUnitTest : public MQTT
|
||||
mqtt = unitTest = new MQTTUnitTest();
|
||||
mqtt->start();
|
||||
|
||||
auto clearStartupOutput = []() {
|
||||
pubsub->published_.clear();
|
||||
if (mockMeshService != nullptr) {
|
||||
mockMeshService->messages_.clear();
|
||||
mockMeshService->notifications_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
if (!moduleConfig.mqtt.enabled || moduleConfig.mqtt.proxy_to_client_enabled || *moduleConfig.mqtt.root) {
|
||||
loopUntil([] { return true; }); // Loop once
|
||||
} else {
|
||||
// Wait for MQTT to subscribe to all topics.
|
||||
TEST_ASSERT_TRUE(loopUntil(
|
||||
[] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); }));
|
||||
clearStartupOutput();
|
||||
return;
|
||||
}
|
||||
// Clear any side effects from startup (e.g. map report triggered by runOnce)
|
||||
mockMeshService->messages_.clear();
|
||||
mockMeshService->notifications_.clear();
|
||||
mockRouter->packets_.clear();
|
||||
mockRoutingModule->ackNacks_.clear();
|
||||
// Wait for MQTT to subscribe to all topics.
|
||||
TEST_ASSERT_TRUE(loopUntil(
|
||||
[] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); }));
|
||||
clearStartupOutput();
|
||||
}
|
||||
PubSubClient &getPubSub() { return pubSub; }
|
||||
};
|
||||
@@ -935,4 +940,4 @@ void setup()
|
||||
UNITY_END();
|
||||
}
|
||||
#endif
|
||||
void loop() {}
|
||||
void loop() {}
|
||||
|
||||
@@ -6,6 +6,7 @@ board_build.partitions = default_16MB.csv
|
||||
build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-D HELTEC_V4
|
||||
-D HAS_LORA_FEM=1
|
||||
-I variants/esp32s3/heltec_v4
|
||||
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
// ---- GC1109 RF FRONT END CONFIGURATION ----
|
||||
// The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA
|
||||
// RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna
|
||||
// The Heltec V4.2 uses a GC1109 FEM chip with integrated PA and LNA
|
||||
// RF path: SX1262 -> Pi attenuator -> GC1109 PA -> Antenna
|
||||
// Measured net TX gain (non-linear due to PA compression):
|
||||
// +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out)
|
||||
// +10dB at 16-17dBm input
|
||||
@@ -47,15 +47,31 @@
|
||||
// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown)
|
||||
// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
|
||||
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
|
||||
#define USE_GC1109_PA
|
||||
#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
|
||||
#define LORA_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on)
|
||||
#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
|
||||
|
||||
// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
|
||||
// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
|
||||
// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
|
||||
|
||||
#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 and KCT8103L LDO power enable
|
||||
#define LORA_GC1109_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on)
|
||||
#define LORA_GC1109_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
|
||||
|
||||
// ---- KCT8103L RF FRONT END CONFIGURATION ----
|
||||
// The Heltec V4.3 uses a KCT8103L FEM chip with integrated PA and LNA
|
||||
// RF path: SX1262 -> Pi attenuator -> KCT8103L PA -> Antenna
|
||||
// Control logic (from KCT8103L datasheet):
|
||||
// Transmit PA: CSD=1, CTX=1, CPS=1
|
||||
// Receive LNA: CSD=1, CTX=0, CPS=X (21dB gain, 1.9dB NF)
|
||||
// Receive bypass: CSD=1, CTX=1, CPS=0
|
||||
// Shutdown: CSD=0, CTX=X, CPS=X
|
||||
// Pin mapping:
|
||||
// CPS (pin 5) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH)
|
||||
// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown)
|
||||
// CTX (pin 6) -> GPIO5: Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=bypass PA, LOW=LNA)
|
||||
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
|
||||
// KCT8103L FEM: TX/RX path switching is handled by DIO2 -> CPS pin (via SX126X_DIO2_AS_RF_SWITCH)
|
||||
|
||||
#define LORA_KCT8103L_PA_CSD 2 // CSD - KCT8103L chip enable (HIGH=on)
|
||||
#define LORA_KCT8103L_PA_CTX 5 // CTX - Switch between Receive LNA Mode and Receive Bypass Mode. (HIGH=bypass PA, LOW=LNA)
|
||||
|
||||
#if HAS_TFT
|
||||
#define USE_TFTDISPLAY 1
|
||||
#endif
|
||||
|
||||
@@ -17,6 +17,7 @@ build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/esp32s3/heltec_wireless_tracker_v2
|
||||
-D HELTEC_WIRELESS_TRACKER_V2
|
||||
-D HAS_LORA_FEM=1
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
|
||||
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
|
||||
#define USE_GC1109_PA
|
||||
#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
|
||||
#define LORA_PA_EN 4 // CSD - GC1109 chip enable (HIGH=on)
|
||||
#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
|
||||
#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
|
||||
#define LORA_GC1109_PA_EN 4 // CSD - GC1109 chip enable (HIGH=on)
|
||||
#define LORA_GC1109_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
|
||||
|
||||
// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
|
||||
// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
|
||||
|
||||
0
variants/esp32s3/station-g2/pins_arduino.h
Executable file → Normal file
0
variants/esp32s3/station-g2/pins_arduino.h
Executable file → Normal file
0
variants/esp32s3/station-g2/platformio.ini
Executable file → Normal file
0
variants/esp32s3/station-g2/platformio.ini
Executable file → Normal file
0
variants/esp32s3/station-g2/variant.h
Executable file → Normal file
0
variants/esp32s3/station-g2/variant.h
Executable file → Normal file
Reference in New Issue
Block a user