Merge meshcore-dev dev with companion and board integrations

This commit is contained in:
mikecarper committed 2026-09-15 20:51:43 -07:00
commit 4139e7e24d
78 files changed
+1904 -190

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
#include <helpers/esp32/SerialWifiInterface.h>
#include <helpers/wifi/SerialWifiInterface.h>
#include <cassert>
#include <cstdio>
#include <cstdlib>
+3 -3
View File
@@ -470,11 +470,11 @@ for full_env in "${SUPPORTED_PIO_ENVS[@]}"; do
&& [[ "$PLATFORMIO_BUILD_FLAGS" != *"WIFI_SSID"* ]]; then
fail "$full_env Full Companion omitted ordinary WiFi credentials"
fi
if ! pio_env_option_contains "$pio_env" build_src_filter "helpers/esp32/*.cpp" \
if ! pio_env_option_contains "$pio_env" build_src_filter "helpers/wifi/*.cpp" \
&& ! pio_env_option_contains "$pio_env" build_src_filter \
"helpers/esp32/SerialWifiInterface.cpp" \
"helpers/wifi/SerialWifiInterface.cpp" \
&& [[ "$PLATFORMIO_BUILD_SRC_FILTER" \
!= *"helpers/esp32/SerialWifiInterface.cpp"* ]]; then
!= *"helpers/wifi/SerialWifiInterface.cpp"* ]]; then
fail "$full_env Full Companion omitted SerialWifiInterface"
fi
[[ "$PLATFORMIO_BUILD_FLAGS" == *"WIFI_OTA_SEEDER=1"* ]] \
+1 -1
View File
@@ -190,7 +190,7 @@ class CLISettingsContractTest(unittest.TestCase):
'password', 'prv.key', # Local secret/identity reads
'pwrmgt.bootreason', 'role', # Boot/build facts
'radio.rxps.config', # Detail view of radio.rxps
'wifi.pwd', # Saved transport credentials
'wifi.status', 'wifi.ip', # Live WiFi link state
})
def test_literal_key_comparison_lengths(self):
+74
View File
@@ -0,0 +1,74 @@
"""Execute the merged discovery parser with truncated and unrelated radio data."""
from pathlib import Path
import subprocess
import tempfile
import unittest
from test_radio_receive_contract import method
ROOT = Path(__file__).resolve().parents[1]
class CompanionDiscoveryTests(unittest.TestCase):
def test_bounded_responses_and_request_lifetime(self):
source = (ROOT / 'examples/companion_radio/MyMesh.cpp').read_text()
parser = method(source, 'void MyMesh::checkControlDataForPendingDiscovery(')
query = method(source, 'int MyMesh::getDiscoveredNodes(')
code = r'''
#include <cassert>
#include <cstdint>
#include <cstring>
#include <cstddef>
#define DISCOVERED_NODES_TABLE_SIZE 4
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
static uint32_t now=100;
uint32_t millis(){return now;}
struct StrHelper{static void strncpy(char* o,const char* i,size_t n){::strncpy(o,i,n-1);o[n-1]=0;}};
struct DiscoveredNode{uint8_t pubkey_prefix[9];float snr_in,snr_out;char name[32];uint8_t type;};
struct ContactInfo{char name[32];};
struct Radio{float getLastSNR(){return 5.0f;}};
struct MyMesh {
DiscoveredNode discovered_nodes[4]={};
uint32_t disc_node_req_tag=123,disc_nodes_count=0,disc_node_req_at=100;
bool disc_node_req_active=true;
Radio radio;Radio* _radio=&radio;
ContactInfo contact;
MyMesh(){memset(contact.name,'A',32);}
ContactInfo* lookupContactByPubKey(const uint8_t*,size_t){return &contact;}
void checkControlDataForPendingDiscovery(uint8_t[],size_t);
int getDiscoveredNodes(DiscoveredNode[],int);
};
@METHODS@
int main(){
MyMesh mesh;
uint8_t response[14]={0x92,20};memcpy(response+2,&mesh.disc_node_req_tag,4);
for(size_t len=0;len<14;len++){
uint8_t* short_frame=new uint8_t[len];memcpy(short_frame,response,len);
mesh.checkControlDataForPendingDiscovery(short_frame,len);delete[] short_frame;
assert(mesh.disc_nodes_count==0);
}
response[0]=0x82;mesh.checkControlDataForPendingDiscovery(response,14);
assert(mesh.disc_nodes_count==0);response[0]=0x92;
response[2]^=1;mesh.checkControlDataForPendingDiscovery(response,14);
assert(mesh.disc_nodes_count==0);response[2]^=1;
mesh.disc_node_req_active=false;mesh.checkControlDataForPendingDiscovery(response,14);
assert(mesh.disc_nodes_count==0);mesh.disc_node_req_active=true;
now=30100;mesh.checkControlDataForPendingDiscovery(response,14);assert(mesh.disc_nodes_count==0);
mesh.disc_node_req_at=0xfffffff0;now=20;
mesh.checkControlDataForPendingDiscovery(response,14);assert(mesh.disc_nodes_count==1);
assert(mesh.discovered_nodes[0].name[31]==0&&mesh.discovered_nodes[0].snr_out==5.0f);
for(int i=0;i<10;i++)mesh.checkControlDataForPendingDiscovery(response,14);
assert(mesh.disc_nodes_count==4);
DiscoveredNode result[4];assert(mesh.getDiscoveredNodes(result,100)==4);
assert(mesh.getDiscoveredNodes(nullptr,4)==0&&mesh.getDiscoveredNodes(result,-1)==0);
}
'''.replace('@METHODS@', parser + '\n' + query)
with tempfile.TemporaryDirectory() as directory:
path = Path(directory)
(path / 'test.cpp').write_text(code)
subprocess.run(['g++', '-std=c++17', '-fsanitize=address,undefined',
'-fno-pie', '-no-pie', str(path / 'test.cpp'), '-o', str(path / 'test')], check=True)
subprocess.run([str(path / 'test')], check=True)
if __name__ == '__main__':
unittest.main()
+29 -3
View File
@@ -49,6 +49,14 @@ int main(int argc,char** argv){
strcpy(original.node_name,"durable node");original.freq=910.525f;
original.ble_pin=876543;original.bluetooth_stealth_mode=2;
original.gps_interval=123;original.sf=7;original.cr=7;original.bw=62.5f;
original.tx_delay_factor=1.25f;original.direct_tx_delay_factor=0.75f;
original.interference_threshold=8;original.agc_reset_interval=15;original.tz_offset=-7;
#ifdef TBEAM_1W
strcpy(original.fan_mode,"on");original.fan_lo=33;original.fan_hi=42;
#endif
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
strcpy(original.wifi_ssid,"test-network");strcpy(original.wifi_pwd,"test-password");
#endif
assert(store.savePrefs(original,47.1,-122.2));
const auto disk=store.fs.files["/new_prefs"];
CompanionNodePrefs changed=original;changed.freq=910.1f;
@@ -74,6 +82,14 @@ int main(int argc,char** argv){
CompanionNodePrefs loaded;double lat=0,lon=0;
assert(store.loadPrefs(loaded,lat,lon));
assert(loaded.freq==changed.freq&&loaded.ble_pin==original.ble_pin);
assert(loaded.tx_delay_factor==1.25f&&loaded.direct_tx_delay_factor==0.75f);
assert(loaded.interference_threshold==8&&loaded.agc_reset_interval==15&&loaded.tz_offset==-7);
#ifdef TBEAM_1W
assert(!strcmp(loaded.fan_mode,"on")&&loaded.fan_lo==33&&loaded.fan_hi==42);
#endif
#if defined(RP2040_PLATFORM) && defined(ENABLE_WIFI_INTERFACE)
assert(!strcmp(loaded.wifi_ssid,"test-network")&&!strcmp(loaded.wifi_pwd,"test-password"));
#endif
assert(lat==42.3&&lon==-121.2);
} else if(scenario==1){
for(int fault : {0,1,2,3,4}){
@@ -143,6 +159,14 @@ int main(int argc,char** argv){
CompanionNodePrefs snapshot=*prefs;
assert(!snapshot.isDirty());
prefs->~CompanionNodePrefs();
} else if(scenario==5){
DataStore legacy;legacy.fs.files["/new_prefs"]=disk;
legacy.fs.files["/new_prefs"].resize(215);
CompanionNodePrefs loaded;double lat=0,lon=0;
assert(legacy.loadPrefs(loaded,lat,lon));
assert(loaded.tx_delay_factor==0.5f&&loaded.direct_tx_delay_factor==0.2f);
assert(loaded.interference_threshold==0&&loaded.agc_reset_interval==0&&loaded.tz_offset==0);
assert(loaded.freq==original.freq&&loaded.ble_pin==original.ble_pin);
} else if(scenario==4){
DataStore legacy;legacy.fs.files["/node_prefs"]=disk;
legacy.fs.fail_write_after=17;
@@ -211,11 +235,13 @@ inline char* utoa(unsigned int value,char* output,int base){
(work / 'ContactFileTransaction.h').write_text(transaction.replace(
'#include "IdentityStore.h"', '#include <helpers/IdentityStore.h>'))
(work / 'test.cpp').write_text(HARNESS.replace('@METHODS@', methods))
for platform in ('ESP32_PLATFORM', 'RP2040_PLATFORM', 'STM32_PLATFORM', 'NRF52_PLATFORM'):
for platform in ('ESP32_PLATFORM', 'RP2040_PLATFORM', 'STM32_PLATFORM', 'NRF52_PLATFORM',
'ESP32_PLATFORM,TBEAM_1W', 'RP2040_PLATFORM,ENABLE_WIFI_INTERFACE'):
with self.subTest(platform=platform):
binary = work / 'test'
build = subprocess.run(['g++', '-std=c++17',
'-D'+platform+'=1', '-fsanitize=address,undefined', '-fno-sanitize-recover=all',
*['-D'+flag+'=1' for flag in platform.split(',')],
'-fsanitize=address,undefined', '-fno-sanitize-recover=all',
'-fno-pie', '-no-pie', '-include', str(work / 'platform_shim.h'),
'-I', str(work),
'-I', str(ROOT / 'test/fixtures/radio_profiles/mocks'),
@@ -228,7 +254,7 @@ inline char* utoa(unsigned int value,char* output,int base){
str(ROOT / 'src/helpers/TxtDataHelpers.cpp'),
'-o', str(binary)], capture_output=True, text=True)
self.assertEqual(build.returncode, 0, build.stdout + build.stderr)
for scenario in range(5):
for scenario in range(6):
with self.subTest(scenario=scenario):
run = subprocess.run([str(binary), str(scenario)], capture_output=True, text=True)
self.assertEqual(run.returncode, 0, run.stdout + run.stderr)
@@ -343,6 +343,47 @@ TEST(NodePrefs, ExplicitWrappedBridgeFormatReplacesRawInMemoryValue) {
EXPECT_EQ(mesh::bridge::ESPNOW_FORMAT_WRAPPED, loaded.bridge_format);
}
class TestNested : public ConfigSerializer {
class Inner : public ConfigSerializer {
protected:
void structure() override { } // no properties, so it writes as '{}'
};
Inner inner;
protected:
void structure() override {
def("age", age);
def("inner", inner);
def("name", name, sizeof(name)); // comes *after* the empty sub-object
}
public:
int32_t age;
char name[16];
};
TEST(ConfigSerializer, LoadSerial_EmptyObject) {
MockInputStream s("{age:" TEST_INT_S ",inner:{},name:\"Scott\"}");
TestNested data;
data.name[0] = 0;
bool success = data.loadSerial(s);
EXPECT_TRUE(success);
EXPECT_EQ(TEST_INT, data.age);
bool match = strcmp("Scott", data.name) == 0;
EXPECT_TRUE(match); // properties after an empty object must still load
}
TEST(ConfigSerializer, LoadSerial_EmptyObjectWithWhitespace) {
MockInputStream s("{age:" TEST_INT_S ",inner:{ },name:\"Scott\"}");
TestNested data;
data.name[0] = 0;
bool success = data.loadSerial(s);
EXPECT_TRUE(success);
bool match = strcmp("Scott", data.name) == 0;
EXPECT_TRUE(match);
}
TEST(DynamicConfigSerializer, GetSet_Basic) {
DynamicConfigSerializer data;
+1 -1
View File
@@ -88,7 +88,7 @@ assert re.search(
setup,
)
assert re.search(
r"#ifdef WIFI_SSID\s+"
r"#if defined\(ESP32\) && defined\(WIFI_SSID\)\s+"
# Registering a callback does not start the WiFi stack. It must remain
# available for session cleanup, while startup stays behind the boot gate.
r"wifi_interface\.setSessionChangedCallback\(cancelCompanionWiFiSession, nullptr\);\s+"
+2 -1
View File
@@ -63,7 +63,8 @@ static_assert(TRANSPORT == 4, "transport");
static_assert(ADVERT == 5, "advert");
static_assert(SENSORS == 6, "sensors");
static_assert(WIFI_SETUP == 7, "wifi setup");
static_assert(Count == 8, "count");
static_assert(DISCOVERY == 8, "discovery appended without reordering");
static_assert(Count == 9, "count");
int main() {{ return 0; }}
"""
with tempfile.TemporaryDirectory() as temp_dir:
+3
View File
@@ -63,6 +63,9 @@ struct Store {
bool formatFileSystem(){++erased;return success;}
};
struct Prefs {
float tx_delay_factor=0.5f, direct_tx_delay_factor=0.2f;
uint8_t interference_threshold=0, agc_reset_interval=0;
int8_t tz_offset=0;
float freq=910, bw=125, airtime_factor=1, rx_delay_base=1;
uint8_t sf=7, cr=5, path_hash_mode=0, multi_acks=0, rx_ps_level=0, rx_ps_preamble=16;
uint32_t rx_ps_rx_us=1, rx_ps_sleep_us=1;
+1 -1
View File
@@ -368,7 +368,7 @@ class MessageNavigationTest(unittest.TestCase):
home_navigation = home_navigation.split("#ifdef COMPANION_EXCLUSIVE_WIFI_BLE", 1)[0]
home_navigation = home_navigation.replace("{\n", "{\n Screen::handleInput(c);\n", 1)
implementation += ("class HomeScreen : public Screen { public: UITask* _task; "
"enum HomePage {FIRST,MESSAGES,RECENT,RADIO,Count}; int _page=0; "
"enum HomePage {FIRST,MESSAGES,RECENT,RADIO,DISCOVERY,Count}; int _page=0; "
"HomeScreen(UITask* task):_task(task){}\n" + home_navigation +
"return false; } };\n")
target = (ROOT / "variants/sensecap_indicator-espnow/target.cpp").read_text()
+1 -1
View File
@@ -71,7 +71,7 @@ class SerialWifiSessionTests(unittest.TestCase):
*SANITIZER_FLAGS,
f"-I{FIXTURE / 'mocks'}", f"-I{ROOT / 'src'}",
str(FIXTURE / "test.cpp"),
str(ROOT / "src/helpers/esp32/SerialWifiInterface.cpp"),
str(ROOT / "src/helpers/wifi/SerialWifiInterface.cpp"),
"-o", str(binary)],
capture_output=True, text=True, timeout=60,
)