Merge meshcore-dev dev with routing, WiFi and power-policy compatibility

Integrate upstream 1.18.0, delayed direct ACK forwarding, zero-scope routing, Waveshare LR1121 and dynamic WiFi variants. Preserve fork chemistry-aware power management and bounded multi-interface UI; retain retry/dedup safety while adding upstream ACK delay. Qualify LR1121 band power and HP-only Morse handling with native regressions.
This commit is contained in:
mikecarper committed 2026-10-15 13:30:41 -07:00
commit 285267e3a1
32 files changed
+765 -27

No files matched your search

+3
View File
@@ -197,6 +197,9 @@ jobs:
python3 -B test/test_room_web_service.py -v
python3 -B test/test_contact_clock_bootstrap.py -v
python3 -B test/test_rtc_discovery.py -v
python3 -B test/test_upstream_routing_merge.py -v
python3 -B test/test_upstream_power_policy_merge.py -v
python3 -B test/test_lr1121_power_merge.py -v
python3 -B test/test_radio_interrupt_recovery.py -v
python3 -B test/test_bridge_recovery.py -v
+5 -3
View File
@@ -19,9 +19,11 @@ Once you have a working app/project, you need to be able to demonstrate it exist
| Data-Type range | App name | Contact |
|-----------------|-----------------------------|-------------------------------------------------------------------|
| 0000 - 00FF | -reserved for internal use- | |
| 0100 | MeshCore Open | zsylvester@monitormx.com - https://github.com/zjs81/meshcore-open |
| 0110 - 011F | Ripple | ripple_biz@protonmail.com - https://buymeacoffee.com/ripplebiz |
| 0120 | MCO Advanced | most.original.address@gmail.com - https://github.com/HDDen/meshcore-open/tree/rename-mco-advanced |
| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open |
| 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz |
| 0120 | MCO Advanced | most.original.address@gmail.com — https://hdden.ru/MCOa/ |
| 0130 - 013F | StreamSensor | william@housedillon.com - https://housedillon.com/blog/lora-e5-with-seeed-fusion |
| 0140 | Nelos | support@nelos.app — https://nelos.app |
| FF00 - FFFF | -reserved for testing/dev- | |
(add rows, inside the range 0100 - FEFF for custom apps)
+2 -2
View File
@@ -10,11 +10,11 @@
#define FIRMWARE_VER_CODE 14
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.17.1"
#define FIRMWARE_VERSION "v1.18.0"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+2 -2
View File
@@ -151,14 +151,14 @@ struct NeighbourInfo {
};
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
#endif
#ifndef FIRMWARE_BUILD_EPOCH
#define FIRMWARE_BUILD_EPOCH 0UL
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.17.1"
#define FIRMWARE_VERSION "v1.18.0"
#endif
#define FIRMWARE_ROLE "repeater"
+2
View File
@@ -3836,6 +3836,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
} else {
snprintf(reply, MAX_POST_TEXT_LEN, addSystemPost(msg) ? "OK" : "Err - post not retained");
}
} else if (strcmp(command, "set repeat on") == 0) {
strcpy(reply, "ERR - repeat now deprecated");
}
#if MESH_ENABLE_ROOM_FLOOD_RULE_ENGINE
else if (flood_rules.handleCommand(command, reply)) {
+2 -2
View File
@@ -90,11 +90,11 @@ namespace mesh { class RoomJsonWriter; }
/* ------------------------------ Config -------------------------------- */
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.17.1"
#define FIRMWARE_VERSION "v1.18.0"
#endif
#ifndef LORA_FREQ
+2 -2
View File
@@ -50,11 +50,11 @@
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "14 Aug 2026"
#define FIRMWARE_BUILD_DATE "15 Oct 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.17.1"
#define FIRMWARE_VERSION "v1.18.0"
#endif
#define FIRMWARE_ROLE "sensor"
+6 -6
View File
@@ -1145,13 +1145,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
removePathPrefix(pkt, pkt->getPathHashCount());
} else if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) || maybeShortCircuitDirect(pkt)) {
if (allowPacketForward(pkt)) {
const uint32_t d = getDirectRetransmitDelay(pkt);
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
return forwardMultipartDirect(pkt);
return forwardMultipartDirect(pkt, d);
} else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
if (!_tables->wasSeen(pkt)) { // don't retransmit!
_tables->markSeen(pkt);
removePathPrefix(pkt, 1);
routeDirectRecvAcks(pkt, 0);
routeDirectRecvAcks(pkt, d);
}
return ACTION_RELEASE;
}
@@ -1163,7 +1164,6 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
&& hasValidEncryptedPayloadLength(pkt->payload_len, 2);
removePathPrefix(pkt, 1);
uint32_t d = getDirectRetransmitDelay(pkt);
maybeScheduleDirectRetry(pkt, 0, final_hop_retry);
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
}
@@ -1563,7 +1563,7 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
return ACTION_RELEASE;
}
DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt, uint32_t delay_millis) {
if (pkt == NULL || pkt->payload_len < 1) {
return ACTION_RELEASE;
}
@@ -1580,7 +1580,7 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
removePathPrefix(&tmp, 1);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
routeDirectRecvAcks(&tmp, delay_millis + ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
}
}
return ACTION_RELEASE;
@@ -1590,7 +1590,6 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
if (!packet->isMarkedDoNotRetransmit()) {
uint8_t extra = getExtraAckTransmitCount();
while (extra > 0) {
delay_millis += getDirectRetransmitDelay(packet) + 300;
auto a1 = createMultiAck(packet->payload, packet->payload_len, extra);
if (a1) {
a1->radio_reply = false; // relayed ACK, not a local response
@@ -1599,6 +1598,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
a1->header |= ROUTE_TYPE_DIRECT;
maybeScheduleDirectRetry(a1, 0);
sendPacket(a1, 0, delay_millis);
delay_millis += 300;
}
extra--;
}
+1 -1
View File
@@ -216,7 +216,7 @@ class Mesh : public Dispatcher {
void decayOtaRelayBackoff();
void serviceLoopMaintenance();
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
DispatcherAction forwardMultipartDirect(Packet* pkt);
DispatcherAction forwardMultipartDirect(Packet* pkt, uint32_t delay_millis);
protected:
// Roles with lazily persisted state can veto an OTA reset until that state
+1
View File
@@ -288,6 +288,7 @@ int RegionMap::getTransportKeysFor(const RegionEntry& src, TransportKey dest[],
}
RegionEntry* RegionMap::findMatch(const mesh::Packet* packet, uint8_t mask) {
if (packet->transport_codes[0] == 0) return &wildcard;
for (int i = 0; i < num_regions; i++) {
auto region = &regions[i];
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
+29
View File
@@ -12,6 +12,8 @@ class CustomLR1121 : public LR1121 {
bool _headerSeen = false;
bool _rx_boosted = false;
LR11x0MorseRfSwitch _morse_rf_switch;
int8_t _txPower = 0;
bool _txPowerSet = false;
public:
CustomLR1121(Module *mod) : LR1121(mod) { }
@@ -70,6 +72,33 @@ class CustomLR1121 : public LR1121 {
return len;
}
using LR1121::setOutputPower;
using LR1121::setFrequency;
int16_t setOutputPower(int8_t power) override {
#ifdef LR1121_FORCE_HP_PA
const bool force_hp = true; // boards with only RFO_HP_LF wired: never select the LP PA
#else
const bool force_hp = false;
#endif
// clip to the active PA's range (HF PA tops out at 13 dBm) rather than fail and keep the other band's PA
int8_t clipped = power;
checkOutputPower(power, &clipped, force_hp);
int16_t state = LR1121::setOutputPower(clipped, force_hp);
if (state == RADIOLIB_ERR_NONE) {
_txPower = power;
_txPowerSet = true;
}
return state;
}
int16_t setFrequency(float freq) override {
int16_t state = LR1121::setFrequency(freq);
// PA selection depends on the band, and set radio / tempradio don't call setTxPower
if (state == RADIOLIB_ERR_NONE && _txPowerSet) state = setOutputPower(_txPower);
return state;
}
float getFreqMHz() const { return freqMHz; }
int16_t setRxBoostedGainMode(bool en) {
+11 -2
View File
@@ -54,8 +54,17 @@ protected:
if (!_board->prepareRadioFrequency(frequency_mhz)) return RADIOLIB_ERR_INVALID_FREQUENCY;
auto* radio = static_cast<CustomLR1121*>(_radio);
const int16_t status = radio->setFrequency(frequency_mhz);
return status == RADIOLIB_ERR_NONE
? radio->setOutputPower(_dbm_valid ? _cur_dbm : LORA_TX_POWER, false, 304) : status;
if (status != RADIOLIB_ERR_NONE) return status;
#ifdef LR1121_FORCE_HP_PA
const bool force_hp = true;
#else
const bool force_hp = false;
#endif
// Preserve the Morse ramp while honoring HP-only wiring and the HF PA's
// 13 dBm ceiling, just like the normal packet-power path.
int8_t clipped = _dbm_valid ? _cur_dbm : LORA_TX_POWER;
radio->checkOutputPower(clipped, &clipped, force_hp);
return radio->setOutputPower(clipped, force_hp, 304);
}
bool restoreCarrierWaveModem() override {
if (_morse_rx_fsk_mode) {
+6
View File
@@ -87,6 +87,12 @@ struct Chip {
standby(); if (power_result) return power_result;
power=value; rfo=force_rfo; ramp_time=4; return 0;
}
int checkOutputPower(int8_t value, int8_t* clipped, bool force_hp) {
const int minimum=frequency>1000 ? -18 : force_hp || value>14 ? -9 : -17;
const int maximum=frequency>1000 ? 13 : force_hp || value>14 ? 22 : 14;
if (clipped) *clipped=value<minimum ? minimum : value>maximum ? maximum : value;
return value<minimum || value>maximum ? -1 : 0;
}
int setOutputPower(int8_t value, bool force_rfo, uint16_t ramp) {
++ramp_power_calls;
const int result=setOutputPower(value,force_rfo);
@@ -76,6 +76,7 @@ public:
bool forward_packets = false, reject_primary_groups = false;
bool bind_secondary_groups = false, packet_group_search = false;
uint32_t direct_forward_delay = 0;
uint8_t extra_acks = 0;
unsigned group_admissions = 0, group_searches = 0, received_groups = 0;
uint8_t last_search_origin = 0xFF;
mesh::GroupChannel primary_group, secondary_group, received_group;
@@ -88,6 +89,7 @@ public:
bool canMutateContacts() const override { return writable; }
bool allowPacketForward(const mesh::Packet*) override { return forward_packets; }
uint32_t getDirectRetransmitDelay(const mesh::Packet*) override { return direct_forward_delay; }
uint8_t getExtraAckTransmitCount() const override { return extra_acks; }
bool allowGroupPacketOnRadio(mesh::Packet* packet) override {
++group_admissions;
// Stub only the application's policy, not the core admission/order path.
@@ -500,6 +502,72 @@ TEST_F(ContactImport, GroupAdmissionBindsDirectRelayBeforeCrossProfileFanout) {
EXPECT_EQ(relayed->path[0], 0x93);
EXPECT_EQ(pool.getFreeCount(), 7);
}
TEST_F(ContactImport, RelayedAckHonorsInitialDirectForwardDelay) {
chat.forward_packets = true;
chat.direct_forward_delay = 175;
mesh::Packet packet;
packet.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
packet.setPathHashSizeAndCount(1, 2);
packet.path[0] = chat.self_id.pub_key[0];
packet.path[1] = 0x93;
packet.payload_len = 4;
memset(packet.payload, 0x37, packet.payload_len);
chat.receive(packet);
ASSERT_EQ(pool.getOutboundTotal(), 1);
EXPECT_EQ(pool.getOutboundCount(174), 0);
EXPECT_EQ(pool.getOutboundCount(175), 1);
EXPECT_EQ(pool.getOutboundByIdx(0)->getPathHashCount(), 1);
EXPECT_EQ(pool.getOutboundByIdx(0)->path[0], 0x93);
EXPECT_FALSE(pool.getOutboundByIdx(0)->radio_reply);
}
TEST_F(ContactImport, ExtraForwardedAcksKeepInitialDelayAndThreeHundredMsSpacing) {
chat.forward_packets = true;
chat.direct_forward_delay = 175;
chat.extra_acks = 3;
mesh::Packet packet;
packet.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
packet.setPathHashSizeAndCount(1, 1);
packet.path[0] = chat.self_id.pub_key[0];
packet.payload_len = 4;
memset(packet.payload, 0x47, packet.payload_len);
chat.receive(packet);
ASSERT_EQ(pool.getOutboundTotal(), 4);
for (uint32_t index = 0; index < 4; ++index) {
const uint32_t due = 175 + 300 * index;
EXPECT_EQ(pool.getNextOutbound(due - 1), nullptr);
auto* ack = pool.getNextOutbound(due);
ASSERT_NE(ack, nullptr);
EXPECT_EQ(ack->getPayloadType(), index < 3 ? PAYLOAD_TYPE_MULTIPART : PAYLOAD_TYPE_ACK);
EXPECT_EQ(ack->getPathHashCount(), 0);
EXPECT_FALSE(ack->radio_reply);
pool.free(ack);
}
EXPECT_EQ(pool.getFreeCount(), 8);
}
TEST_F(ContactImport, MultipartAckAddsInitialDelayWithoutLosingDuplicateProtection) {
chat.forward_packets = true;
chat.direct_forward_delay = 175;
tables.deduplicate = true;
mesh::Packet packet;
packet.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT);
packet.setPathHashSizeAndCount(1, 2);
packet.path[0] = chat.self_id.pub_key[0];
packet.path[1] = 0x93;
packet.payload_len = 5;
packet.payload[0] = (2 << 4) | PAYLOAD_TYPE_ACK;
memset(packet.payload + 1, 0x57, packet.payload_len - 1);
const auto duplicate = packet;
chat.receive(packet);
ASSERT_EQ(pool.getOutboundTotal(), 1);
EXPECT_EQ(pool.getOutboundCount(1074), 0);
EXPECT_EQ(pool.getOutboundCount(1075), 1);
auto second = duplicate;
chat.receive(second);
EXPECT_EQ(pool.getOutboundTotal(), 1);
EXPECT_EQ(tables.seen_marks, 1u);
}
} // namespace
int main(int argc, char** argv) {
+182
View File
@@ -0,0 +1,182 @@
"""Execute merged LR1121 band/power and Morse PA selection without hardware."""
from pathlib import Path
import os
import shutil
import subprocess
import tempfile
import unittest
from test_radio_receive_contract import method
ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include <cassert>
#include <cstdint>
#include <algorithm>
constexpr int16_t RADIOLIB_ERR_NONE=0;
constexpr int16_t RADIOLIB_ERR_INVALID_FREQUENCY=-705;
constexpr int8_t LORA_TX_POWER=22;
class LR1121 {
public:
float frequency=909.5f;
int power_calls=0,frequency_calls=0,checks=0,overload_calls=0;
int16_t power_result=0,frequency_result=0;
int8_t applied_power=0;
bool forced_hp=false;
uint32_t applied_ramp=0;
virtual ~LR1121()=default;
virtual int16_t setOutputPower(int8_t power) { return setOutputPower(power,false); }
int16_t setOutputPower(int8_t power,bool hp,uint32_t ramp=48) {
++power_calls;applied_power=power;forced_hp=hp;applied_ramp=ramp;return power_result;
}
int16_t checkOutputPower(int8_t power,int8_t* clipped,bool hp) {
++checks;
const int minimum=frequency>1000 ? -18 : hp || power>14 ? -9 : -17;
const int maximum=frequency>1000 ? 13 : hp || power>14 ? 22 : 14;
if(clipped) *clipped=std::max(minimum,std::min(maximum,int(power)));
return power<minimum || power>maximum ? -1 : 0;
}
virtual int16_t setFrequency(float value) {
++frequency_calls;if(frequency_result==0)frequency=value;return frequency_result;
}
int16_t setFrequency(float value,bool,float=4) {
++overload_calls;return LR1121::setFrequency(value);
}
};
class CustomLR1121:public LR1121 {
int8_t _txPower=0;
bool _txPowerSet=false;
public:
using LR1121::setOutputPower;
using LR1121::setFrequency;
@POWER@
@FREQUENCY@
};
struct Board {
bool frequency_ok=true;
bool prepareRadioFrequency(float) { return frequency_ok; }
};
struct CarrierBase {
virtual ~CarrierBase()=default;
virtual int16_t prepareMorseCarrier(float)=0;
virtual bool restoreCarrierWaveModem()=0;
};
class MorseWrapper:public CarrierBase {
public:
LR1121* _radio;
Board* _board;
bool _dbm_valid=true,_morse_rx_fsk_mode=false;
int8_t _cur_dbm=22;
float _cur_freq=909.5f;
MorseWrapper(LR1121* radio,Board* board):_radio(radio),_board(board) {}
bool restoreAfterDeepInit() { return true; }
@CARRIER@
@RESTORE@
};
int main() {
#ifdef LR1121_FORCE_HP_PA
const bool expected_hp=true;
#else
const bool expected_hp=false;
#endif
CustomLR1121 radio;
assert(radio.setFrequency(909.5f)==0 && radio.power_calls==0);
assert(radio.setOutputPower(22)==0 && radio.applied_power==22);
assert(radio.forced_hp==expected_hp);
assert(radio.setFrequency(2400.0f)==0 && radio.applied_power==13);
assert(radio.setFrequency(909.5f)==0 && radio.applied_power==22);
// A failed explicit power write must not replace the last successful intent.
radio.power_result=-77;
assert(radio.setOutputPower(7)==-77);
radio.power_result=0;
assert(radio.setFrequency(2400.0f)==0 && radio.applied_power==13);
assert(radio.setFrequency(909.5f)==0 && radio.applied_power==22);
// A failed tune must not touch the PA. A failed PA reapply must propagate.
const int calls=radio.power_calls;
radio.frequency_result=-78;
assert(radio.setFrequency(2400.0f)==-78 && radio.power_calls==calls);
radio.frequency_result=0;radio.power_result=-79;
assert(radio.setFrequency(2400.0f)==-79);
radio.power_result=0;
assert(radio.setFrequency(909.5f)==0 && radio.applied_power==22);
assert(radio.setOutputPower(2)==0 && radio.forced_hp==expected_hp);
// The existing explicit calibration/ramp overloads used by Morse stay visible.
assert(radio.setFrequency(902.1f,true)==0 && radio.overload_calls==1);
assert(radio.setOutputPower(2,false,304)==0);
Board board;
MorseWrapper morse(&radio,&board);
morse._cur_dbm=2;
assert(morse.prepareMorseCarrier(909.5f)==0);
assert(radio.applied_power==2 && radio.forced_hp==expected_hp && radio.applied_ramp==304);
morse._cur_dbm=22;
assert(morse.prepareMorseCarrier(2400.0f)==0);
assert(radio.applied_power==13 && radio.applied_ramp==304);
assert(morse.restoreCarrierWaveModem());
assert(radio.frequency==909.5f && radio.applied_power==22 && radio.forced_hp==expected_hp);
morse._dbm_valid=false;
assert(morse.prepareMorseCarrier(2400.0f)==0 && radio.applied_power==13);
assert(morse.restoreCarrierWaveModem() && radio.applied_power==22);
board.frequency_ok=false;
const int tunes=radio.frequency_calls;
assert(morse.prepareMorseCarrier(2400.0f)==RADIOLIB_ERR_INVALID_FREQUENCY);
assert(radio.frequency_calls==tunes);
board.frequency_ok=true;radio.frequency_result=-80;
assert(morse.prepareMorseCarrier(2400.0f)==-80);
assert(!morse.restoreCarrierWaveModem());
radio.frequency_result=0;radio.power_result=-81;
assert(morse.prepareMorseCarrier(2400.0f)==-81);
}
'''
class UpstreamLR1121Test(unittest.TestCase):
def compile(self, *flags):
wrapper = (ROOT / "src/helpers/radiolib/CustomLR1121.h").read_text()
source = HARNESS.replace("@POWER@", method(wrapper, "int16_t setOutputPower(int8_t power)"))
source = source.replace("@FREQUENCY@", method(wrapper, "int16_t setFrequency(float freq)"))
carrier = (ROOT / "src/helpers/radiolib/CustomLR1121Wrapper.h").read_text()
source = source.replace("@CARRIER@", method(carrier, "int16_t prepareMorseCarrier("))
source = source.replace("@RESTORE@", method(carrier, "bool restoreCarrierWaveModem("))
compiler = os.environ.get("CXX") or shutil.which("g++")
self.assertIsNotNone(compiler)
with tempfile.TemporaryDirectory(prefix="upstream-lr1121-") as directory:
folder = Path(directory)
source_file = folder / "test.cpp"
source_file.write_text(source)
binary = folder / ("test.exe" if os.name == "nt" else "test")
sanitizers = [] if os.name == "nt" else [
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie"]
built = subprocess.run([
compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", *sanitizers,
*flags, str(source_file), "-o", str(binary)
], capture_output=True, text=True, timeout=60)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=15)
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
def test_band_changes_restore_original_power_and_propagate_failures(self):
self.compile()
def test_hp_only_boards_keep_force_high_power(self):
self.compile("-DLR1121_FORCE_HP_PA=1")
def test_merge_preserves_morse_switch_and_bounded_companion_ui(self):
radio = (ROOT / "src/helpers/radiolib/CustomLR1121.h").read_text()
self.assertIn("LR11x0MorseRfSwitch _morse_rf_switch;", radio)
self.assertIn("_morse_rf_switch.capture", method(radio, "void setRfSwitchTable("))
self.assertIn("_morse_rf_switch.restore", method(radio, "int16_t beginMorseReceive("))
self.assertIn("using LR1121::setOutputPower;", radio)
self.assertIn("using LR1121::setFrequency;", radio)
ui = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text()
self.assertIn("makeLargeCompanionHomeLayout", ui)
self.assertIn("drawTextCenteredEllipsized", ui)
self.assertIn("isCompanionWiFiConnected()", ui)
self.assertIn("shouldDisplayBluetoothPairingPin", ui)
self.assertNotIn('display.drawTextCentered(display.width() / 2, 32, tmp)', ui)
if __name__ == "__main__":
unittest.main()
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env python3
"""Keep chemistry-aware nRF52 safety through upstream bootlock-stopgap merges.
This parses the actual recipes without PlatformIO and executes the production
voltage rules on the host. It does not claim physical ADC/low-voltage testing.
"""
import configparser
from pathlib import Path
import os
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
BOARDS = {
"gat562_30s_mesh_kit": "GAT562_30S_Mesh_Kit",
"gat562_mesh_evb_pro": "GAT562_Mesh_EVB_Pro",
"gat562_mesh_tracker_pro": "GAT562_Mesh_Tracker_Pro",
"gat562_mesh_watch13": "GAT562_Mesh_Watch13",
"heltec_t096": "Heltec_t096",
"heltec_t114": "Heltec_t114",
"muziworks_r1_neo": "R1Neo",
"rak3401": "rak3401",
"rak4631": "rak4631",
"sensecap_solar": "SenseCap_Solar",
"xiao_nrf52": "Xiao_nrf52",
}
class Recipes:
def __init__(self):
self.config = configparser.ConfigParser(interpolation=None,
inline_comment_prefixes=(";",))
self.config.read([ROOT / "platformio.ini",
*sorted(ROOT.glob("variants/*/platformio.ini"))])
def value(self, section, option, seen=()):
pair = (section, option)
if pair in seen:
raise AssertionError(f"recipe interpolation cycle: {pair}")
seen = (*seen, pair)
if not self.config.has_option(section, option):
for parent in self.config.get(section, "extends", fallback="").split(","):
if parent.strip():
value = self.value(parent.strip(), option, seen)
if value:
return value
return ""
raw = self.config.get(section, option)
return re.sub(r"\$\{([^}.]+)\.([^}]+)\}",
lambda match: self.value(match[1], match[2], seen), raw)
class UpstreamPowerPolicyMergeTests(unittest.TestCase):
def test_all_eleven_board_bases_keep_chemistry_power_policy(self):
recipes = Recipes()
for directory, section in BOARDS.items():
with self.subTest(board=directory):
self.assertTrue(recipes.config.has_section(section))
flags = recipes.value(section, "build_flags")
directives = re.findall(
r"(?:^|\s)-([DU])\s*NRF52_POWER_MANAGEMENT(?=$|\s|=)", flags)
self.assertTrue(directives, f"{directory}: voltage policy compiled out")
self.assertEqual(directives[-1], "D", f"{directory}: policy later undefined")
self.assertNotIn("NRF52_POWER_MANAGEMENT",
recipes.value(section, "build_unflags"))
def test_affected_board_roles_inherit_chemistry_policy(self):
recipes = Recipes()
roles = 0
for directory in BOARDS:
local = configparser.ConfigParser(interpolation=None)
local.read(ROOT / "variants" / directory / "platformio.ini")
for section in local.sections():
if not section.startswith("env:"):
continue
with self.subTest(role=section):
flags = recipes.value(section, "build_flags")
directives = re.findall(
r"(?:^|\s)-([DU])\s*NRF52_POWER_MANAGEMENT(?=$|\s|=)", flags)
self.assertTrue(directives)
self.assertEqual(directives[-1], "D")
self.assertNotIn("NRF52_POWER_MANAGEMENT",
recipes.value(section, "build_unflags"))
roles += 1
self.assertGreater(roles, 30)
def test_voltage_policy_still_loads_saved_chemistry_and_uses_safe_adc(self):
board = (ROOT / "src/helpers/NRF52Board.cpp").read_text()
policy = (ROOT / "src/helpers/NRF52VoltagePolicy.cpp").read_text()
header = (ROOT / "src/helpers/NRF52VoltagePolicy.h").read_text()
self.assertIn("defined(NRF52_POWER_MANAGEMENT)", header)
self.assertIn("handleVoltagePolicyCommand", header)
self.assertIn("loadVoltagePolicyAtBoot(config->voltage_bootlock)", board)
self.assertIn("medianVoltage(samples[0], samples[1], samples[2])", board)
self.assertIn("if (threshold == 0 || external) return true;", board)
self.assertIn("lifepo4", policy)
self.assertIn("configuredBootlock()", policy)
self.assertIn("configuredCutoff()", policy)
self.assertIn("AtomicFileWriter", policy)
def test_native_voltage_rules_preserve_lifepo4_cutoff_and_boot_validation(self):
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
if not compiler:
self.skipTest("a host C++17 compiler is required")
source = r'''
#include <helpers/NRF52VoltagePolicy.h>
#include <helpers/PowerManagementUtils.h>
#include <cassert>
#include <type_traits>
int main() {
using namespace mesh::power;
static_assert(std::is_same<decltype(&configuredBootlock), uint16_t(*)()>::value, "boot API");
static_assert(std::is_same<decltype(&configuredCutoff), uint16_t(*)()>::value, "cutoff API");
assert(validThresholdPair(3300, 0, false));
assert(validThresholdPair(3000, 2700, false));
assert(!validThresholdPair(0, 0, false));
assert(validThresholdPair(0, 0, true));
assert(!validThresholdPair(3000, 3100, false));
assert(batteryPercent(3550, 2700, 3550) == 100);
assert(batteryPercent(2700, 2700, 3550) == 0);
assert(medianVoltage(999, 3500, 3510) == 3500);
assert(!shouldBootLock(999, 3300, false));
assert(!shouldBootLock(3000, 3300, true));
assert(shouldBootLock(3000, 3300, false));
assert(nextLowVoltageCount(2, 2600, 2700, false) == 3);
assert(nextLowVoltageCount(2, 2600, 2700, true) == 0);
}
'''
with tempfile.TemporaryDirectory(prefix="meshcore-power-merge-") as directory:
cpp = Path(directory) / "test.cpp"
binary = Path(directory) / "test.exe"
cpp.write_text(source)
sanitizers = (["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"] if sys.platform.startswith("linux") else [])
built = subprocess.run(
[compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror", *sanitizers,
"-DNRF52_PLATFORM=1", "-DNRF52_POWER_MANAGEMENT=1",
f"-I{ROOT / 'src'}", str(cpp), "-o", str(binary)],
capture_output=True, text=True)
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
run = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(run.returncode, 0, run.stdout + run.stderr)
if __name__ == "__main__":
unittest.main()
+77
View File
@@ -0,0 +1,77 @@
"""Check upstream zero-scope semantics without dropping fork safety rules."""
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
class UpstreamRoutingMergeTests(unittest.TestCase):
def test_zero_transport_code_matches_wildcard_without_key_lookup(self):
method = extract_braced((ROOT / "src/helpers/RegionMap.cpp").read_text(),
"RegionEntry* RegionMap::findMatch(")
compiler = shutil.which("g++") or shutil.which("c++")
if not compiler:
self.skipTest("native C++ compiler required")
source = r'''
#include <cassert>
#include <cstdint>
#include <cstddef>
namespace mesh { struct Packet { uint16_t transport_codes[2] = {}; }; }
struct RegionEntry { uint8_t flags; };
struct TransportKey {
uint16_t code = 0x1234;
uint16_t calcTransportCode(const mesh::Packet*) const { return code; }
};
class RegionMap {
public:
RegionEntry wildcard{0}, regions[1]{{0}};
int num_regions = 1, lookups = 0;
int getTransportKeysFor(const RegionEntry&, TransportKey keys[], int) {
++lookups; keys[0].code = 0x1234; return 1;
}
RegionEntry* findMatch(const mesh::Packet*, uint8_t);
};
@METHOD@
int main() {
RegionMap regions;
mesh::Packet packet;
const mesh::Packet* input = &packet;
assert(regions.findMatch(input, 1) == &regions.wildcard);
assert(regions.lookups == 0);
packet.transport_codes[1] = 0x9999;
assert(regions.findMatch(input, 1) == &regions.wildcard);
packet.transport_codes[0] = 0x1234;
assert(regions.findMatch(input, 1) == &regions.regions[0]);
regions.regions[0].flags = 1;
assert(regions.findMatch(input, 1) == nullptr);
regions.regions[0].flags = 0;
packet.transport_codes[0] = 0x9999;
assert(regions.findMatch(input, 1) == nullptr);
}
'''.replace("@METHOD@", method)
with tempfile.TemporaryDirectory(prefix="mesh-upstream-routing-") as directory:
cpp = Path(directory) / "routing.cpp"
exe = Path(directory) / "routing"
cpp.write_text(source)
subprocess.run([compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror",
str(cpp), "-o", str(exe)], check=True, timeout=30,
capture_output=True)
subprocess.run([str(exe)], check=True, timeout=10, capture_output=True)
def test_room_repeat_deprecation_keeps_full_rules_and_clock_sync(self):
source = (ROOT / "examples/simple_room_server/MyMesh.cpp").read_text()
handler = extract_braced(source, "void MyMesh::handleCommand(")
self.assertIn('strcmp(command, "set repeat on") == 0', handler)
self.assertIn('"ERR - repeat now deprecated"', handler)
self.assertIn("flood_rules.handleCommand(command, reply)", handler)
self.assertIn("_clock_sync.handleCommand(command, reply)", handler)
self.assertIn("_local_cli_output.owns", handler)
if __name__ == "__main__":
unittest.main()
+5 -1
View File
@@ -313,12 +313,14 @@ build_flags =
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
@@ -517,12 +519,14 @@ build_flags =
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
+2
View File
@@ -487,12 +487,14 @@ build_flags =
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=512
-D ENABLE_WIFI_INTERFACE
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${heltec_v4_oled.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
+2
View File
@@ -243,10 +243,12 @@ build_flags =
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=512
-D ENABLE_WIFI_INTERFACE
build_src_filter = ${heltec_v4_r8_oled.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
@@ -52,11 +52,13 @@ build_flags =
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=E213Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
+<helpers/ui/E213Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
@@ -9,7 +9,7 @@ protected:
uint8_t btn_prev_state;
public:
TImpulsePlusBoard() : NRF52Board("T-Impulse-Plus OTA") {}
TImpulsePlusBoard() : NRF52Board("T-Impulse-Plus_OTA") {}
void begin();
#if defined(P_LORA_TX_LED)
+1 -1
View File
@@ -9,7 +9,7 @@ protected:
uint8_t btn_prev_state;
public:
MeshtinyBoard() : NRF52Board("Meshtiny OTA") {}
MeshtinyBoard() : NRF52Board("Meshtiny_OTA") {}
void begin();
#if defined(P_LORA_TX_LED)
+3 -1
View File
@@ -266,12 +266,14 @@ build_flags =
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${rak3112.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-orig/*.cpp>
lib_deps =
@@ -0,0 +1,11 @@
#pragma once
#include <Arduino.h>
#include <helpers/ESP32Board.h>
class WaveshareS3LR1121Board : public ESP32Board {
public:
const char* getManufacturerName() const override {
return "Waveshare ESP32-S3-LR1121";
}
};
@@ -0,0 +1,102 @@
; Waveshare ESP32-S3-LR1121 (HF: 850-930 MHz + 2.4 GHz)
; ESP32-S3FH4R2 (4MB flash, 2MB PSRAM), native USB-CDC via the Tiny-Adapter FPC.
; No user LED, battery sense or user button (BOOT is GPIO0).
[Waveshare_S3_LR1121]
extends = esp32_base
board = esp32-s3-zero
; min_spiffs: 1.9MB OTA app slots; one layout for every role so switching roles keeps the filesystem
board_build.partitions = min_spiffs.csv
build_flags = ${esp32_base.build_flags}
-I variants/waveshare_esp32s3_lr1121
-D BOARD_HAS_PSRAM
-D P_LORA_DIO_1=38
-D P_LORA_NSS=42
-D P_LORA_RESET=39
-D P_LORA_BUSY=41
-D P_LORA_SCLK=40
-D P_LORA_MISO=46
-D P_LORA_MOSI=45
-D RADIO_CLASS=CustomLR1121
-D WRAPPER_CLASS=CustomLR1121Wrapper
-D LR11X0_DIO3_TCXO_VOLTAGE=1.8
-D LR1121_FORCE_HP_PA ; RFO_LP_LF is not fitted (R4 NC)
-D RX_BOOSTED_GAIN=true
-D LORA_TX_POWER=22
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/waveshare_esp32s3_lr1121>
lib_deps =
${esp32_base.lib_deps}
[env:Waveshare_S3_LR1121_repeater]
extends = Waveshare_S3_LR1121
build_flags =
${Waveshare_S3_LR1121.build_flags}
-D ADVERT_NAME='"Waveshare LR1121 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Waveshare_S3_LR1121.build_src_filter}
+<../examples/simple_repeater/*.cpp>
lib_deps =
${Waveshare_S3_LR1121.lib_deps}
${esp32_ota.lib_deps}
[env:Waveshare_S3_LR1121_room_server]
extends = Waveshare_S3_LR1121
build_flags =
${Waveshare_S3_LR1121.build_flags}
-D ADVERT_NAME='"Waveshare LR1121 Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Waveshare_S3_LR1121.build_src_filter}
+<../examples/simple_room_server>
lib_deps =
${Waveshare_S3_LR1121.lib_deps}
${esp32_ota.lib_deps}
[env:Waveshare_S3_LR1121_companion_radio_usb]
extends = Waveshare_S3_LR1121
build_flags =
${Waveshare_S3_LR1121.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_USB_INTERFACE
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Waveshare_S3_LR1121.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${Waveshare_S3_LR1121.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Waveshare_S3_LR1121_companion_radio_ble]
extends = Waveshare_S3_LR1121
build_flags =
${Waveshare_S3_LR1121.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
-D BLE_PIN_CODE=123456
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Waveshare_S3_LR1121.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${Waveshare_S3_LR1121.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Waveshare_S3_LR1121_kiss_modem]
extends = Waveshare_S3_LR1121
build_src_filter = ${Waveshare_S3_LR1121.build_src_filter}
+<../examples/kiss_modem/>
@@ -0,0 +1,63 @@
#include <Arduino.h>
#include "target.h"
WaveshareS3LR1121Board board;
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5
#endif
// DIO5/DIO6 drive the RTC6603SP sub-GHz T/R switch; the 2.4 GHz port (RFIO_HF) is unswitched
static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
RADIOLIB_LR11X0_DIO5,
RADIOLIB_LR11X0_DIO6,
RADIOLIB_NC
};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6
{ LR11x0::MODE_STBY, {LOW, LOW}},
{ LR11x0::MODE_RX, {HIGH, LOW}},
{ LR11x0::MODE_TX, {LOW, HIGH}},
{ LR11x0::MODE_TX_HP, {LOW, HIGH}},
{ LR11x0::MODE_TX_HF, {LOW, LOW}},
{ LR11x0::MODE_GNSS, {LOW, LOW}},
{ LR11x0::MODE_WIFI, {LOW, LOW}},
END_OF_MODE_TABLE,
};
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE,
LORA_TX_POWER, 16, LR11X0_DIO3_TCXO_VOLTAGE);
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
return false;
}
radio.setCRC(2);
radio.explicitHeader();
radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
#ifdef RX_BOOSTED_GAIN
radio.setRxBoostedGainMode(RX_BOOSTED_GAIN);
#endif
return true;
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng);
}
@@ -0,0 +1,17 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomLR1121Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#include "WaveshareS3LR1121Board.h"
extern WaveshareS3LR1121Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
mesh::LocalIdentity radio_new_identity();
@@ -11,7 +11,7 @@ protected:
KeyValueStore* _prefs = NULL;
public:
WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {}
WioTrackerL1Board() : NRF52Board("WioTrackerL1_OTA") {}
void begin();
void attachDynamicPrefs(KeyValueStore* prefs);
bool setLoRaFemPaGainEnabled(bool enable) override;
+1 -1
View File
@@ -9,7 +9,7 @@ protected:
uint8_t btn_prev_state;
public:
WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {}
WioTrackerL1Board() : NRF52Board("WioTrackerL1_OTA") {}
void begin();
#if defined(P_LORA_TX_LED)
+2
View File
@@ -100,12 +100,14 @@ build_flags =
-D BLE_PIN_CODE=123456
-D DISPLAY_CLASS=SSD1306Display
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
+2
View File
@@ -333,12 +333,14 @@ build_flags =
-D BLE_PIN_CODE=123456
-D DISPLAY_CLASS=SSD1306Display
-D OFFLINE_QUEUE_SIZE=256
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_S3_WIO.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>