Pack STM32WL Companion help while retaining every command
This commit is contained in:
1 parent
9d973d353a
commit
73789a64e8
9 files changed
+211
-31
No files matched your search
@@ -0,0 +1,35 @@
|
||||
<!-- meshcore-hosted-doc-link:start -->
|
||||
<p class="meshcore-hosted-doc-link"><a href="https://mikecarper.github.io/MeshCore/test-results/stm32_companion_help_2026-10-23/">View this page on MeshCore Docs</a>.</p>
|
||||
<!-- meshcore-hosted-doc-link:end -->
|
||||
|
||||
# STM32WL Companion help packing, 23 October 2026
|
||||
|
||||
Small STM32WL USB Companions now use the existing packed help producer with a
|
||||
256-byte history. Every command, option and terminal suffix is retained. The
|
||||
same feature-gated `CompanionHelp.inc` supplies the packed and ordinary output.
|
||||
nRF52 RAK Full keeps its 2,048-byte history; ESP32 retains its ordinary help
|
||||
printing path. Contact/channel limits and the application/filesystem boundary
|
||||
are unchanged.
|
||||
|
||||
Only the selected `CompanionHelpPacked.cpp` is compiled as C++17. A source-role
|
||||
policy registers that override for STM32WL Companion builds, excludes other
|
||||
roles and reuses the existing per-object helper without installing nRF52
|
||||
trims. Other project sources and framework/library language standards remain
|
||||
unchanged. The final producer also requires the STM32WL platform and text
|
||||
terminal capability.
|
||||
|
||||
Native tests compare the STM32 output with the preceding ordinary-printing
|
||||
bytes, including both USB and network terminal suffixes. They exercise short,
|
||||
zero and invalid writes, overlapping matches, ring wrap, malformed tokens and
|
||||
a match farther back than the selected history. The STM32 profile's USB help
|
||||
remains 1,908 bytes including its terminal suffix. Its SHA-256 is
|
||||
`9e587ac9f287c8f8fe64fe5c899fe635b6de5d6cf436c4c43a27ffd4add42474`.
|
||||
C++11 callers link and execute the actual C++17 producer under address and
|
||||
undefined-behavior sanitizers for both STM32WL and RAK Full.
|
||||
|
||||
The standalone Cortex-M4 object comparison saves 733 bytes including the
|
||||
decoder and its data, before whole-firmware LTO effects. Emitted producer and
|
||||
helper frames total 384 bytes; a 512-byte caller/output reserve keeps that
|
||||
local estimate at 896 bytes within the existing 1,024-byte minimum stack
|
||||
allowance. This is compiler frame accounting, excluding physical interrupt
|
||||
high-water. The complete firmware's linked size is a separate build check.
|
||||
@@ -1,9 +1,11 @@
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL)
|
||||
// This is the only Companion translation unit compiled as C++17. Include the
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL) \
|
||||
|| (defined(STM32_PLATFORM) && defined(STM32WLxx))
|
||||
// This is the only selected Companion source compiled as C++17. Include the
|
||||
// same feature/board context as MyMesh before expanding the single help source.
|
||||
#include "MyMesh.h"
|
||||
#include "CompanionHelpWriter.h"
|
||||
|
||||
#if !defined(STM32_PLATFORM) || COMPANION_FEATURE_TEXT_TERMINAL
|
||||
namespace mesh { namespace companion {
|
||||
size_t writePackedCompanionHelp(Stream& output) {
|
||||
// Constant evaluation retains only the exact-sized packed help image.
|
||||
@@ -30,3 +32,4 @@ size_t writePackedCompanionHelp(Stream& output) {
|
||||
}
|
||||
} } // namespace mesh::companion
|
||||
#endif
|
||||
#endif
|
||||
@@ -7,7 +7,7 @@ class Stream;
|
||||
|
||||
namespace mesh { namespace companion {
|
||||
|
||||
// The implementation is isolated in the RAK Full C++17 packing source.
|
||||
// The implementation is isolated in one selected C++17 packing source.
|
||||
size_t writePackedCompanionHelp(Stream& output);
|
||||
|
||||
// Native USB accepts bounded producer writes. Honor partial progress, and stop
|
||||
@@ -33,7 +33,13 @@ namespace help_packing {
|
||||
// ASCII literals retain bit 7 clear. Two-byte references encode a 3..18 byte
|
||||
// match at distance 1..2048. Only the final packed bytes survive evaluation;
|
||||
// the hash chains and original text occupy no runtime storage.
|
||||
#if defined(STM32_PLATFORM) && defined(STM32WLxx)
|
||||
// Small STM32WL Companions retain their 1 KiB minimum stack allowance. A
|
||||
// smaller history preserves every help byte without adding a 2 KiB frame.
|
||||
constexpr size_t WindowSize = 256;
|
||||
#else
|
||||
constexpr size_t WindowSize = 2048;
|
||||
#endif
|
||||
constexpr size_t MaximumMatch = 18;
|
||||
constexpr uint8_t hash(const char* text) {
|
||||
return static_cast<uint8_t>((static_cast<uint8_t>(text[0]) * 31u +
|
||||
@@ -134,7 +140,7 @@ size_t writePackedHelp(Output& output, const uint8_t* packed, size_t length) {
|
||||
if (position == length) return written;
|
||||
distance = ((static_cast<size_t>(next & 7) << 8) | packed[position++]) + 1;
|
||||
remaining = ((next >> 3) & 15) + 3;
|
||||
if (distance > produced) return written;
|
||||
if (distance > produced || distance > help_packing::WindowSize) return written;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10404,7 +10404,8 @@ void MyMesh::handleTerminalCommand(char* command, bool reader_prompt,
|
||||
FIRMWARE_VERSION, (unsigned)FIRMWARE_VER_CODE, FIRMWARE_BUILD_DATE);
|
||||
} else if (strcmp(command, "help") == 0) {
|
||||
auto& output = terminalOutput();
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL)
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL) \
|
||||
|| (defined(STM32_PLATFORM) && defined(STM32WLxx) && COMPANION_FEATURE_TEXT_TERMINAL)
|
||||
mesh::companion::writePackedCompanionHelp(output);
|
||||
#else
|
||||
#define MESH_COMPANION_HELP_BEGIN output.print(
|
||||
|
||||
@@ -375,6 +375,7 @@ extends = arduino_base
|
||||
platform = ststm32@19.5.0
|
||||
extra_scripts = post:arch/stm32/build_hex.py
|
||||
pre:scripts/client_acl_role.py
|
||||
pre:scripts/stm32_companion_help.py
|
||||
pre:scripts/micronmea_time_fix.py
|
||||
pre:scripts/rtttl_rollover_fix.py
|
||||
post:scripts/check_firmware_ram.py
|
||||
|
||||
@@ -208,7 +208,7 @@ def compile_packed_help(build_env, node):
|
||||
if not str(value).startswith("-std=")]
|
||||
for name in ("CCFLAGS", "CXXFLAGS")}
|
||||
flags["CXXFLAGS"].append("-std=gnu++17")
|
||||
print("nRF52 Companion help: isolated C++17 source")
|
||||
print("Companion help: isolated C++17 source")
|
||||
# Builder overrides stay attached to this object even when PlatformIO
|
||||
# collects it through the parent construction environment afterward.
|
||||
return build_env.Object(node, **flags)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Compile only the selected small STM32WL Companion help producer as C++17.
|
||||
|
||||
All source selection and object overrides are reused without installing any
|
||||
nRF52 trim, changing a global language standard or changing feature defines.
|
||||
The C++ producer itself checks the final platform and terminal capabilities.
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent if "__file__" in globals() else Path("scripts").resolve()
|
||||
SPEC = importlib.util.spec_from_file_location("stm32_help_source_policy", SCRIPT_DIR / "nrf52_flash_trim.py")
|
||||
POLICY = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(POLICY)
|
||||
COMPANION_SOURCES = ("../examples/companion_radio/main.cpp",
|
||||
"../examples/companion_radio/MyMesh.cpp")
|
||||
|
||||
|
||||
def packed_help_policy(platform, mcu, src_filter):
|
||||
if platform != "ststm32" or not str(mcu).lower().startswith("stm32wl"):
|
||||
return False
|
||||
return (all(POLICY.source_selected(src_filter, source) for source in COMPANION_SOURCES)
|
||||
and not any(POLICY.source_selected(src_filter, source)
|
||||
for source in POLICY.ROLE_SOURCES + POLICY.COMPANION_SOURCES[1:]))
|
||||
|
||||
|
||||
def install(build_env):
|
||||
source_filter = build_env.get("SRC_FILTER")
|
||||
if source_filter is None:
|
||||
source_filter = build_env.GetProjectOption("build_src_filter", "")
|
||||
if packed_help_policy(build_env.PioPlatform().name,
|
||||
build_env.BoardConfig().get("build.mcu", ""), source_filter):
|
||||
build_env.AddBuildMiddleware(POLICY.compile_packed_help, "*CompanionHelpPacked.cpp")
|
||||
|
||||
|
||||
if "Import" in globals():
|
||||
Import("env")
|
||||
install(env)
|
||||
@@ -1,10 +1,11 @@
|
||||
"""The C++17 help encoder is one RAK Full source, never a global ABI change."""
|
||||
"""Compact Companions isolate one C++17 help source, never a global ABI change."""
|
||||
from pathlib import Path
|
||||
import copy
|
||||
import importlib.util
|
||||
import unittest
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
spec=importlib.util.spec_from_file_location('help_language_policy',ROOT/'scripts/nrf52_flash_trim.py');m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
|
||||
spec=importlib.util.spec_from_file_location('stm32_help_language_policy',ROOT/'scripts/stm32_companion_help.py');stm=importlib.util.module_from_spec(spec);spec.loader.exec_module(stm)
|
||||
class HelpBuildPolicy(unittest.TestCase):
|
||||
def test_companion_source_registers_before_late_board_and_profile_flags(self):
|
||||
yes=dict(platform='nordicnrf52',mcu='nrf52840',src_filter='+<*> +<../examples/companion_radio>',flags='-DRAK_4631 -DCOMPANION_RADIO_FULL')
|
||||
@@ -38,4 +39,24 @@ class HelpBuildPolicy(unittest.TestCase):
|
||||
branch=mesh[mesh.index(' auto& output = terminalOutput();',mesh.index('} else if (strcmp(command, "help") == 0)')):]
|
||||
self.assertIn('mesh::companion::writePackedCompanionHelp(output);',branch)
|
||||
self.assertNotIn('constexpr',branch[:branch.index('#else')])
|
||||
def test_stm32_registration_is_only_the_small_companion_source_role(self):
|
||||
selected='+<*> +<../examples/companion_radio/*.cpp>'
|
||||
for mcu in ('stm32wle5jc','STM32WL55JC','stm32wle5cc'):
|
||||
self.assertTrue(stm.packed_help_policy('ststm32',mcu,selected))
|
||||
for platform,mcu,source in (('nordicnrf52','nrf52840',selected),
|
||||
('espressif32','esp32s3',selected),('ststm32','stm32f405rg',selected),
|
||||
('ststm32','stm32wle5jc','+<*>'),
|
||||
('ststm32','stm32wle5jc',selected+' +<../examples/simple_repeater>'),
|
||||
('ststm32','stm32wle5jc',selected+' +<../examples/simple_secure_chat>')):
|
||||
self.assertFalse(stm.packed_help_policy(platform,mcu,source))
|
||||
def test_stm32_installer_registers_one_source_without_installing_nrf_trims(self):
|
||||
class Env(dict):
|
||||
def PioPlatform(self):return type('Platform',(),{'name':'ststm32'})()
|
||||
def BoardConfig(self):return {'build.mcu':'stm32wle5jc'}
|
||||
def AddBuildMiddleware(self,callback,pattern):self.calls.append((callback,pattern))
|
||||
env=Env(SRC_FILTER='+<*> +<../examples/companion_radio/*.cpp>');env.calls=[]
|
||||
before=copy.deepcopy(dict(env));stm.install(env)
|
||||
self.assertEqual(dict(env),before)
|
||||
self.assertEqual(env.calls,[(stm.POLICY.compile_packed_help,'*CompanionHelpPacked.cpp')])
|
||||
self.assertIn('pre:scripts/stm32_companion_help.py',(ROOT/'platformio.ini').read_text())
|
||||
if __name__=='__main__':unittest.main()
|
||||
@@ -16,6 +16,7 @@ FEATURES = ['COMPANION_FEATURE_READER=1', 'COMPANION_FEATURE_TEMP_RADIO=1',
|
||||
'COMPANION_FEATURE_OTA_CLI=1', 'COMPANION_FEATURE_NOTIFICATIONS=1',
|
||||
'MESH_ENABLE_LOST_REPLY=1', 'MESH_ENABLE_TX_BEACONS=1',
|
||||
'MESH_ENABLE_MESHTEXT_CHAT=1', 'MESH_ENABLE_ETHERNET_TUNNEL=1']
|
||||
PACKED_PROFILES = {'rak_full', 'stm32_wl_usb'}
|
||||
PROFILES = {
|
||||
'rak_full': ['NRF52_PLATFORM', 'RAK_4631', 'COMPANION_RADIO_FULL', 'EXTRAFS',
|
||||
'DISPLAY_CLASS', 'OLED_AUTO_DETECT=1', 'BLE_PIN_CODE=123456',
|
||||
@@ -25,6 +26,8 @@ PROFILES = {
|
||||
'OLED_AUTO_DETECT=1', 'BLE_PIN_CODE=123456', 'MESH_USB_LOGGING_AVAILABLE=1',
|
||||
'MESH_USB_CONSOLE_COOPERATIVE=1', *FEATURES],
|
||||
'nrf_minimal': ['NRF52_PLATFORM'],
|
||||
'stm32_wl_usb': ['STM32_PLATFORM', 'STM32WLxx', 'ENABLE_USB_INTERFACE',
|
||||
'COMPANION_FEATURE_TEXT_TERMINAL=1', 'MESH_RADIO_SWITCH_TUNING=0'],
|
||||
'esp_full': ['ESP32_PLATFORM', 'ESP32', 'WIFI_SSID', 'COMPANION_RADIO_FULL',
|
||||
'COMPANION_FEATURE_MEMORY_DIAGNOSTICS=1', 'WITH_WEBCONFIG',
|
||||
'MESH_USB_LOGGING_AVAILABLE=1', 'MESH_USB_CONSOLE_COOPERATIVE=1',
|
||||
@@ -35,6 +38,8 @@ PROFILES = {
|
||||
# suffix. Non-RAK call hashes bind print/println order as well as all text bytes.
|
||||
GOLDEN = {'rak_full': {'text': '9a68ce5a10905a201c3bddeb19f4819ce03c6dcaff248cff9c140803b70b14a7', 'calls': '56ed09dee7d53881b2c9fcf3f4ec9b6223e1562ce63e110a065e46706702b53b'}, 'nrf_full': {'text': 'b114ac12ae2ea01b7c47609b72f7eafb616c1dbeb7fb796a95b1377465fd9105', 'calls': '4973a7f21887d07b129cce4ae4cc8145f3f35e664bae09be658957210440d52e'}, 'nrf_minimal': {'text': '15fa8f290cd8c75cfde2fd2ac1cb5b5151a6755c68448fb2793dde3f27669033', 'calls': '6063fd1a699f9c0b588252bd8ce5922e0ff31cec6bbd24522789284ab2a37644'}, 'esp_full': {'text': '1e3b6781c81b5603f7092108e5e309543c60cef8ff4d7cb41a891f67396f716f', 'calls': 'b6335df2bd05b9cab44a7933469b76c5b2611c4b81057436a959feaf3bfbaf60'}}
|
||||
|
||||
GOLDEN['stm32_wl_usb'] = {'text': '9e587ac9f287c8f8fe64fe5c899fe635b6de5d6cf436c4c43a27ffd4add42474'}
|
||||
|
||||
FAST_HELP = (b' get radio.fast / get radio.fast.caps (supported saved controls)\r\n'
|
||||
b' set radio.fast <on|off> / set radio.fast.spi <0|2|4|8|16>\r\n')
|
||||
|
||||
@@ -82,13 +87,21 @@ void actualHelp(bool _terminal_mode) {
|
||||
}
|
||||
int main() {
|
||||
actualHelp(true);
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL)
|
||||
#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL) \
|
||||
|| (defined(STM32_PLATFORM) && defined(STM32WLxx) && COMPANION_FEATURE_TEXT_TERMINAL)
|
||||
assert(!output.writes.empty());
|
||||
#else
|
||||
assert(output.writes.empty());
|
||||
#endif
|
||||
fwrite(output.text.data(),1,output.text.size(),stdout);
|
||||
fwrite(output.trace.data(),1,output.trace.size(),stderr);
|
||||
const std::string usb_text=output.text;
|
||||
const std::string usb_suffix=" +++MESHCORE-TERM-STOP\r\n";
|
||||
assert(usb_text.substr(usb_text.size()-usb_suffix.size())==usb_suffix);
|
||||
output=Output{};
|
||||
actualHelp(false);
|
||||
assert(output.text==usb_text.substr(0,usb_text.size()-usb_suffix.size())
|
||||
+" disconnect (network terminal)\r\n");
|
||||
std::string text(777,'x');
|
||||
Output partial;partial.throttle=7;
|
||||
assert(mesh::companion::writeHelpChunks(partial,text.data(),text.size())==text.size());
|
||||
@@ -151,18 +164,26 @@ int main() {
|
||||
assert(mesh::companion::writePackedHelp(repeated,overlap_packed.bytes,
|
||||
sizeof(overlap_packed))==sizeof(overlap)-1 && repeated.text==overlap);
|
||||
|
||||
// Exercise both the 2048-byte distance limit and the ring wrap, independent
|
||||
// of the encoder's choice of nearer repeated phrases.
|
||||
// Exercise the selected distance limit and ring wrap on both families,
|
||||
// independent of the encoder's choice of nearer repeated phrases.
|
||||
constexpr size_t window=mesh::companion::help_packing::WindowSize;
|
||||
std::vector<uint8_t> boundary;
|
||||
for(size_t i=0;i<2048;++i) boundary.push_back('!'+(i%90));
|
||||
for(size_t i=0;i<window;++i) boundary.push_back('!'+(i%90));
|
||||
std::string expected(boundary.begin(),boundary.end());
|
||||
boundary.push_back(0xff);boundary.push_back(0xff); // length18, distance2048
|
||||
boundary.push_back(0xf8 | ((window-1)>>8));boundary.push_back((window-1)&255);
|
||||
expected+=expected.substr(0,18);
|
||||
boundary.push_back(0xf8);boundary.push_back(0); // length18, distance1
|
||||
expected+=std::string(18,expected.back());
|
||||
Output wrapped;wrapped.throttle=5;
|
||||
assert(mesh::companion::writePackedHelp(wrapped,boundary.data(),boundary.size())==expected.size());
|
||||
assert(wrapped.text==expected);
|
||||
if (window<2048) {
|
||||
std::vector<uint8_t> too_far(window+64,'q');
|
||||
too_far.push_back(0x80 | (window>>8));too_far.push_back(window&255);
|
||||
Output rejected;
|
||||
assert(mesh::companion::writePackedHelp(rejected,too_far.data(),too_far.size())==window+64);
|
||||
assert(rejected.text==std::string(window+64,'q'));
|
||||
}
|
||||
for(const auto& corrupt : std::vector<std::vector<uint8_t>>{
|
||||
{0x80}, {0x80,0}, {0xff,0xff}, {'x',0x80,1}}) {
|
||||
Output rejected;
|
||||
@@ -187,7 +208,7 @@ class CompanionHelpWriterTests(unittest.TestCase):
|
||||
source.index('} else if (strcmp(command, "help") == 0)'))
|
||||
end = source.index(' } else if (handleCommand(command, 0, local_reply))', start)
|
||||
implementation = body((ROOT / 'examples/companion_radio/CompanionHelpPacked.cpp').read_text(), 'size_t writePackedCompanionHelp(Stream& output)')
|
||||
implementation = '#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL)\nnamespace mesh { namespace companion {\n' + implementation + '\n} }\n#endif\n'
|
||||
implementation = '#if defined(RAK_4631) && defined(COMPANION_RADIO_FULL) || (defined(STM32_PLATFORM) && defined(STM32WLxx) && COMPANION_FEATURE_TEXT_TERMINAL)\nnamespace mesh { namespace companion {\n' + implementation + '\n} }\n#endif\n'
|
||||
harness = HARNESS.replace('@ACTUAL_HELP@', source[start:end]).replace('@PACKED_IMPLEMENTATION@', implementation)
|
||||
for name, defines in PROFILES.items():
|
||||
with self.subTest(profile=name), tempfile.TemporaryDirectory(prefix='companion-help-') as folder:
|
||||
@@ -201,10 +222,10 @@ class CompanionHelpWriterTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result = subprocess.run([str(work/'test')],capture_output=True)
|
||||
self.assertEqual(result.returncode,0,result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 1)
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 0 if name == 'stm32_wl_usb' else 1)
|
||||
previous_help = result.stdout.replace(FAST_HELP, b'')
|
||||
self.assertEqual(hashlib.sha256(previous_help).hexdigest(),GOLDEN[name]['text'])
|
||||
if name != 'rak_full':
|
||||
if name not in PACKED_PROFILES:
|
||||
fast_call = b'P' + len(FAST_HELP).to_bytes(4, 'little') + FAST_HELP
|
||||
self.assertEqual(result.stderr.count(fast_call), 1)
|
||||
previous_calls = result.stderr.replace(fast_call, b'')
|
||||
@@ -222,23 +243,76 @@ class CompanionHelpWriterTests(unittest.TestCase):
|
||||
consumer=consumer[:consumer.index(' constexpr char literal[] =')]+"}\n"
|
||||
stream=HARNESS[HARNESS.index('class Stream {'):HARNESS.index('using Output=Stream;')]
|
||||
headers='#include <cstdint>\n#include <algorithm>\n#include <cassert>\n#include <cstdio>\n#include <cstring>\n#include <string>\n#include <vector>\n#include <utility>\n'
|
||||
with tempfile.TemporaryDirectory(prefix='companion-help-language-') as folder:
|
||||
work=Path(folder)
|
||||
(work/'MyMesh.h').write_text(headers+stream)
|
||||
(work/'CompanionHelpPacked.cpp').write_bytes((ROOT/'examples/companion_radio/CompanionHelpPacked.cpp').read_bytes())
|
||||
(work/'consumer.cpp').write_text(consumer)
|
||||
compiler=os.environ.get('CXX','g++')
|
||||
options=['-Os','-Wall','-Wextra','-Werror','-fsanitize=address,undefined','-fno-sanitize-recover=all','-fno-pie','-no-pie',*['-D'+v for v in PROFILES['rak_full']],'-I'+str(work),'-I'+str(ROOT/'examples/companion_radio')]
|
||||
for name,standard in [('CompanionHelpPacked','c++17'),('consumer','c++11')]:
|
||||
result=subprocess.run([compiler,'-std='+standard,*options,'-c',str(work/(name+'.cpp')),'-o',str(work/(name+'.o'))],capture_output=True,text=True)
|
||||
for profile in ('rak_full', 'stm32_wl_usb'):
|
||||
with tempfile.TemporaryDirectory(prefix='companion-help-language-') as folder:
|
||||
work=Path(folder)
|
||||
(work/'MyMesh.h').write_text(headers+stream)
|
||||
(work/'CompanionHelpPacked.cpp').write_bytes((ROOT/'examples/companion_radio/CompanionHelpPacked.cpp').read_bytes())
|
||||
(work/'consumer.cpp').write_text(consumer)
|
||||
compiler=os.environ.get('CXX','g++')
|
||||
options=['-Os','-Wall','-Wextra','-Werror','-fsanitize=address,undefined','-fno-sanitize-recover=all','-fno-pie','-no-pie',*['-D'+v for v in PROFILES[profile]],'-I'+str(work),'-I'+str(ROOT/'examples/companion_radio')]
|
||||
for name,standard in [('CompanionHelpPacked','c++17'),('consumer','c++11')]:
|
||||
result=subprocess.run([compiler,'-std='+standard,*options,'-c',str(work/(name+'.cpp')),'-o',str(work/(name+'.o'))],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result=subprocess.run([compiler,*options,str(work/'consumer.o'),str(work/'CompanionHelpPacked.o'),'-o',str(work/'test')],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result=subprocess.run([compiler,*options,str(work/'consumer.o'),str(work/'CompanionHelpPacked.o'),'-o',str(work/'test')],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result=subprocess.run([str(work/'test')],capture_output=True)
|
||||
self.assertEqual(result.returncode,0,result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 1)
|
||||
self.assertEqual(hashlib.sha256(result.stdout.replace(FAST_HELP, b'')).hexdigest(),GOLDEN['rak_full']['text'])
|
||||
self.assertNotIn(b'Commands:\r\n stats-core / stats-radio / stats-radio-diag',(work/'test').read_bytes())
|
||||
result=subprocess.run([str(work/'test')],capture_output=True)
|
||||
self.assertEqual(result.returncode,0,result.stderr.decode(errors='replace'))
|
||||
self.assertEqual(result.stdout.count(FAST_HELP), 0 if profile == 'stm32_wl_usb' else 1)
|
||||
self.assertEqual(hashlib.sha256(result.stdout.replace(FAST_HELP, b'')).hexdigest(),GOLDEN[profile]['text'])
|
||||
self.assertNotIn(b'Commands:\r\n stats-core / stats-radio / stats-radio-diag',(work/'test').read_bytes())
|
||||
|
||||
def test_small_stm32_arm_help_saves_flash_with_a_bounded_frame(self):
|
||||
tool = shutil.which('arm-none-eabi-g++')
|
||||
if not tool:
|
||||
candidate = Path.home()/'.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-g++'
|
||||
if candidate.is_file(): tool = str(candidate)
|
||||
if not tool:
|
||||
self.skipTest('ARM compiler unavailable; linked image qualification remains required')
|
||||
size_tool = str(Path(tool).with_name('arm-none-eabi-size'))
|
||||
header = ('#include <stdint.h>\n#include <stddef.h>\n'
|
||||
'class Stream { public: virtual size_t write(const uint8_t*,size_t)=0;'
|
||||
'size_t print(const char*);size_t println(const char*);};\n')
|
||||
raw = '''#include "MyMesh.h"
|
||||
extern "C" void rawHelp(Stream& output) {
|
||||
#define MESH_COMPANION_HELP_BEGIN output.print(
|
||||
#define MESH_COMPANION_HELP_END );
|
||||
#define MESH_COMPANION_HELP_LINE_BEGIN output.println(
|
||||
#define MESH_COMPANION_HELP_LINE_END );
|
||||
#include "CompanionHelp.inc"
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix='stm32-help-arm-') as folder:
|
||||
work=Path(folder)
|
||||
(work/'MyMesh.h').write_text(header)
|
||||
(work/'packed.cpp').write_bytes((ROOT/'examples/companion_radio/CompanionHelpPacked.cpp').read_bytes())
|
||||
(work/'raw.cpp').write_text(raw)
|
||||
flags=['-std=gnu++17','-mcpu=cortex-m4','-mthumb','-Oz',
|
||||
'-fno-inline-small-functions','-fno-rtti','-fno-exceptions',
|
||||
'-fmerge-all-constants','-fno-tree-dominator-opts','-fstack-usage',
|
||||
*['-D'+v for v in PROFILES['stm32_wl_usb']],
|
||||
'-I'+str(work),'-I'+str(ROOT/'examples/companion_radio')]
|
||||
sizes={}
|
||||
for name in ('raw','packed'):
|
||||
result=subprocess.run([tool,*flags,'-c',str(work/(name+'.cpp')),
|
||||
'-o',str(work/(name+'.o'))],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
result=subprocess.run([size_tool,str(work/(name+'.o'))],capture_output=True,text=True)
|
||||
self.assertEqual(result.returncode,0,result.stdout+result.stderr)
|
||||
sizes[name]=int(result.stdout.splitlines()[-1].split()[0])
|
||||
self.assertGreaterEqual(sizes['raw']-sizes['packed'],700)
|
||||
frames=[]
|
||||
for line in (work/'packed.su').read_text().splitlines():
|
||||
function,count,kind=line.rsplit('\t',2)
|
||||
self.assertEqual(kind,'static',line)
|
||||
frames.append(int(count))
|
||||
# Overbound all emitted producer/helper frames. The reserve covers
|
||||
# its caller and output sink here, not physical ISR high-water.
|
||||
self.assertTrue(frames)
|
||||
self.assertLessEqual(sum(frames),512)
|
||||
self.assertLessEqual(sum(frames)+512,1024)
|
||||
print('STM32WL help: object saving %d bytes; producer/helper stack %d + 512 reserve / 1024'
|
||||
% (sizes['raw']-sizes['packed'],sum(frames)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user