mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 14:32:22 +00:00
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:
commit
285267e3a1
32 files changed
+765
-27
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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 = ®ions[i];
|
||||
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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) == ®ions.wildcard);
|
||||
assert(regions.lookups == 0);
|
||||
packet.transport_codes[1] = 0x9999;
|
||||
assert(regions.findMatch(input, 1) == ®ions.wildcard);
|
||||
packet.transport_codes[0] = 0x1234;
|
||||
assert(regions.findMatch(input, 1) == ®ions.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()
|
||||
@@ -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}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user