Integrate mjgodat's keyboard/display Companion, USB Companion, Repeater and Room Server support for Cardputer-Adv with Cap LoRa-1262. Fix UI retry dispatch and ACK ownership, packet/storage bounds, numeric input validation, sparse channel selection, optional-display shutdown, keyboard layout limits, reserved GPIOs and Windows cross-drive includes. Retain all 350 contacts and queue capacity using the existing contact cache; account for the custom UI's runtime allocations in the RAM admission gate. Carry the MultiMote and DejaVu notices with firmware packages and add CI coverage for all four board roles plus the shared T-Deck Companion. Validation: all four Cardputer roles and the T-Deck BLE Companion build; Cardputer sanitizer, UI, layout, ownership, licensing and storage tests; 65 RAM-policy tests and 14 contact-cache tests. PlatformIO ran serially. PR: https://github.com/mikecarper/MeshCore/pull/26
87 lines
4.0 KiB
Python
87 lines
4.0 KiB
Python
"""Bound custom Cardputer runtime owners and truncated display text."""
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
from check_firmware_ram import requirements
|
|
from test_replay_reset_integration import extract_braced
|
|
|
|
|
|
class CardputerRuntimeMemoryTests(unittest.TestCase):
|
|
def test_custom_ui_owners_are_additive_and_fonts_fit_their_allowance(self):
|
|
base = {"ESP32_PLATFORM": "1", "BLE_PIN_CODE": "123456"}
|
|
target = "M5Stack_Cardputer_Adv_companion_radio_ble"
|
|
ordinary = requirements("ESP32_PLATFORM", base, target)
|
|
custom = requirements("ESP32_PLATFORM", dict(base, CARDPUTER_ADV_UI="1"), target)
|
|
self.assertEqual(ordinary["components"], {
|
|
key: value for key, value in custom["components"].items()
|
|
if not key.startswith("cardputer_")})
|
|
self.assertGreater(custom["required_heap_bytes"], ordinary["required_heap_bytes"])
|
|
fonts = ROOT / "variants/m5stack_cardputer_adv/companion_radio/ui-cardputer"
|
|
for name in ("font.h", "font_16.h", "font_19.h"):
|
|
data = (fonts / name).read_text().split("{", 1)[1]
|
|
prefix = bytes(int(byte, 16) for byte in re.findall(r"0x([0-9a-fA-F]{2})", data)[:4])
|
|
glyphs = int.from_bytes(prefix, "big")
|
|
self.assertGreater(glyphs, 0)
|
|
self.assertLessEqual(glyphs, 716)
|
|
# Five VLW arrays use 4+2+1+1+1 bytes/glyph. Also allow
|
|
# allocation metadata and the runtime font object itself.
|
|
self.assertLessEqual(glyphs * 9 + 1024,
|
|
custom["components"]["cardputer_font_metadata"])
|
|
disabled = requirements("ESP32_PLATFORM", dict(base, CARDPUTER_ADV_UI="0"), target)
|
|
self.assertEqual(disabled, ordinary)
|
|
with self.assertRaisesRegex(ValueError, "ESP32 Companion"):
|
|
requirements("NRF52_PLATFORM", {"CARDPUTER_ADV_UI": "1"}, target)
|
|
|
|
def test_scroll_keeps_truncated_utf8_inside_the_input_buffer(self):
|
|
driver = (ROOT / "variants/m5stack_cardputer_adv/hw/CardputerDisplay.cpp").read_text()
|
|
method = extract_braced(driver, "void CardputerDisplay::drawTextLeftAlignWithScroll(")
|
|
prelude = r'''
|
|
#include <cassert>
|
|
#include <cstring>
|
|
class CardputerDisplay {
|
|
public:
|
|
const char* begin = nullptr;
|
|
const char* end = nullptr;
|
|
int getTextWidth(const char* text) { return strlen(text); }
|
|
void drawTextLeftAlign(int, int, const char* text) {
|
|
assert(text >= begin && text <= end);
|
|
(void)strlen(text);
|
|
}
|
|
void drawTextLeftAlignWithScroll(int, int, int, const char*);
|
|
};
|
|
int main() {
|
|
CardputerDisplay display;
|
|
for (unsigned lead : {0xC2U, 0xE2U, 0xF0U}) {
|
|
char text[2] = {static_cast<char>(lead), 0};
|
|
display.begin = text; display.end = text + 1;
|
|
display.drawTextLeftAlignWithScroll(0, 0, 0, text);
|
|
}
|
|
}
|
|
'''
|
|
prelude = '#include <initializer_list>\n' + prelude
|
|
for legacy in (False, True):
|
|
body = method.replace(" offset = text_len;\n", "") if legacy else method
|
|
with self.subTest(legacy=legacy), tempfile.TemporaryDirectory() as directory:
|
|
work = Path(directory)
|
|
cpp, binary = work / "scroll.cpp", work / "scroll"
|
|
cpp.write_text(prelude + body)
|
|
build = subprocess.run(["g++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
|
"-fsanitize=address,undefined", "-fno-sanitize-recover=all", "-fno-pie", "-no-pie",
|
|
str(cpp), "-o", str(binary)], capture_output=True, text=True)
|
|
self.assertEqual(build.returncode, 0, build.stdout + build.stderr)
|
|
run = subprocess.run([str(binary)], capture_output=True, text=True)
|
|
if legacy:
|
|
self.assertNotEqual(run.returncode, 0, "Unclamped scroll unexpectedly passed")
|
|
else:
|
|
self.assertEqual(run.returncode, 0, run.stdout + run.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|