Fix Indicator transport taps and fresh LoRa defaults

This commit is contained in:
mikecarper committed 2026-08-31 15:56:54 -07:00
1 parent 2e026caf4a
commit 164a6e8227
13 files changed
+344 -123

No files matched your search

+41 -29
View File
@@ -20,8 +20,8 @@ int main() {
if (!layout.show_title) return 1;
if (layout.wifi.x != 2 || layout.wifi.width != 76) return 2;
if (layout.bluetooth.x != 82 || layout.bluetooth.width != 76) return 3;
if (layout.wifi.y != 36 || layout.wifi.height != 98) return 4;
if (layout.title_y != 17 || layout.prompt_y != 140) return 5;
if (layout.wifi.y != 40 || layout.wifi.height != 100) return 4;
if (layout.title_y != 14 || layout.prompt_y != 143) return 5;
const auto small = mesh::ui::makeCompanionTransportSelectorLayout(128, 64);
if (small.show_title) return 6;
@@ -56,21 +56,25 @@ int main() {
)
self.assertEqual(run_result.returncode, 0, run_result.stderr)
choice_glyph_width = 6 * 3
choice_glyph_height = 8 * 3
for text in ("WiFi", "BLE"):
choice_glyph_width = 6 * 4
choice_glyph_height = 8 * 4
for text in ("Wi", "Fi", "BLE"):
self.assertLessEqual(len(text) * choice_glyph_width, 76)
status_glyph_width = 6 * 2
status_glyph_height = 8 * 2
for text in ("ACTIVE", "NEXT"):
status_glyph_width = 6 * 3
status_glyph_height = 8 * 3
for text in ("ON", "NEXT"):
self.assertLessEqual(len(text) * status_glyph_width, 76)
title_glyph_width = 6 * 2
for text in ("TRANSPORT", "tap a box"):
self.assertLessEqual(len(text) * title_glyph_width, 160)
self.assertLessEqual(17 + 8 * 2, 36)
self.assertLessEqual(53 + choice_glyph_height, 134)
self.assertLessEqual(109 + status_glyph_height, 134)
self.assertLessEqual(140 + 8, 160)
self.assertLessEqual(len("MODE") * 6 * 3, 160)
self.assertLessEqual(len("TAP SIDE") * 6 * 2, 160)
self.assertLessEqual(14 + 8 * 3, 40)
wifi_first_row_y = 42
wifi_second_row_y = 76
self.assertLessEqual(wifi_first_row_y + choice_glyph_height,
wifi_second_row_y)
self.assertLessEqual(wifi_first_row_y + choice_glyph_height, 140)
self.assertLessEqual(76 + choice_glyph_height, 140)
self.assertLessEqual(115 + status_glyph_height, 140)
self.assertLessEqual(143 + 8 * 2, 160)
def test_touch_split_selector_distinguishes_taps_from_swipes(self):
source = r'''
@@ -88,19 +92,19 @@ static TouchAction release(TouchInput& input,
}
int main() {
TouchInput input(true, true, 70);
TouchInput input(true, true, 70, true);
const auto layout = mesh::ui::makeCompanionTransportSelectorLayout(160, 160);
const TouchSplitSelector selector{
layout.wifi.x, layout.wifi.width,
layout.bluetooth.x, layout.bluetooth.width,
layout.wifi.y, layout.wifi.height};
input.update(true, 30, 70, 160, 160, false, &selector);
input.update(true, 31, 70, 160, 160, false, &selector);
input.update(true, 129, 70, 160, 160, false, &selector);
input.update(true, 128, 70, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::SelectLeft) return 1;
input.update(true, 130, 70, 160, 160, false, &selector);
input.update(true, 129, 70, 160, 160, false, &selector);
input.update(true, 30, 70, 160, 160, false, &selector);
input.update(true, 31, 70, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::SelectRight) return 2;
input.update(true, 80, 70, 160, 160, false, &selector);
@@ -121,10 +125,10 @@ int main() {
// A quick selector tap may occupy only one 25 ms poll. Its bounded target
// remains unambiguous after the normal two-sample release debounce.
input.update(true, 30, 120, 160, 160, false, &selector);
input.update(true, 129, 120, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::SelectLeft) return 7;
input.update(true, 130, 120, 160, 160, false, &selector);
input.update(true, 30, 120, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::SelectRight) return 8;
// The gap, title, and exclusive bottom edge remain inert even for a quick
@@ -132,14 +136,14 @@ int main() {
input.update(true, 80, 120, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::None) return 9;
input.update(true, 30, layout.wifi.y - 1, 160, 160, false, &selector);
input.update(true, 129, layout.wifi.y - 1, 160, 160, false, &selector);
if (release(input, &selector) != TouchAction::None) return 10;
input.update(true, 30, layout.wifi.y + layout.wifi.height,
input.update(true, 129, layout.wifi.y + layout.wifi.height,
160, 160, false, &selector);
if (release(input, &selector) != TouchAction::None) return 11;
input.update(true, 30, layout.wifi.y + layout.wifi.height - 1,
input.update(true, 129, layout.wifi.y + layout.wifi.height - 1,
160, 160, false, &selector);
if (release(input, &selector) != TouchAction::SelectLeft) return 12;
@@ -182,12 +186,16 @@ int main() {
self.assertIn('"WiFi", wifi_active, wifi_selected', source)
self.assertIn('"BLE", !wifi_active, !wifi_selected', source)
self.assertIn("display.fillRect(x, y, width, height);", source)
self.assertIn('"ACTIVE"', source)
self.assertIn('large_transport_text ? 3 : 1', source)
self.assertIn('large_transport_text ? "ON" : "ACTIVE"', source)
self.assertIn('display.setTextSize(4);', source)
self.assertIn('display.drawTextCentered(x + width / 2, y + 2, "Wi");', source)
self.assertIn('display.drawTextCentered(x + width / 2, y + 36, "Fi");', source)
self.assertIn("const bool show_status_label = height >= 44;", source)
self.assertIn('active ? "ACTIVE"', source)
self.assertIn('active ? (large_transport_text ? "ON" : "ACTIVE")', source)
self.assertIn('large_transport_text ? "NEXT" : "NEXT BOOT"', source)
self.assertIn('display.setTextSize(2);', source)
self.assertIn('display.setTextSize(3);', source)
self.assertIn('display.width() / 2, layout.title_y, "MODE"', source)
self.assertIn('layout.show_title ? "TAP SIDE" : "tap a box"', source)
self.assertIn("makeCompanionTransportSelectorLayout", source)
handler_start = source.index(
@@ -221,6 +229,10 @@ int main() {
def test_only_split_page_maps_box_taps_to_transport_keys(self):
source = UI.read_text(encoding="utf-8")
profile = (
ROOT / "variants" / "sensecap_indicator-espnow" / "platformio.ini"
).read_text(encoding="utf-8")
self.assertIn("-D TOUCH_MIRROR_TAP_X", profile)
self.assertIn(
"curr == home\n"
" && static_cast<HomeScreen*>(home)"
@@ -192,23 +192,21 @@ TEST(DisplayDriver, NativeIndicatorExpandsHomeTypographyRegions) {
const mesh::ui::CompanionHomeLayout layout =
mesh::ui::makeLargeCompanionHomeLayout(160, 160, true);
EXPECT_EQ(2, layout.info.x);
EXPECT_EQ(51, layout.info.y);
EXPECT_EQ(61, layout.info.y);
EXPECT_EQ(156, layout.info.width);
EXPECT_EQ(46, layout.info.height);
EXPECT_EQ(39, layout.info.height);
EXPECT_EQ(8, layout.pairing.x);
EXPECT_EQ(100, layout.pairing.y);
EXPECT_EQ(102, layout.pairing.y);
EXPECT_EQ(144, layout.pairing.width);
EXPECT_EQ(58, layout.pairing.height);
EXPECT_FALSE(mesh::ui::displayRegionsOverlap(layout.info,
layout.pairing));
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
layout.info, layout.instruction_y, 20));
layout.info, layout.instruction_y, 39));
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
layout.info, layout.network_y, 20));
layout.pairing, layout.pairing_label_y, 29));
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
layout.pairing, layout.pairing_label_y, 20));
EXPECT_TRUE(mesh::ui::displayRegionContainsLine(
layout.pairing, layout.pairing_value_y, 30));
layout.pairing, layout.pairing_value_y, 29));
}
TEST(DisplayDriver, IndicatorPairingValuesFitAtRenderedTextSizes) {
+24 -14
View File
@@ -135,24 +135,34 @@ class IndicatorDisplayProfileTest(unittest.TestCase):
self.assertLessEqual(height, 30)
# Native 480 reflows the spacious home page instead of globally
# enlarging every dense UI row. Its prominent strings fit at their
# requested sizes, while a two-digit inbox count takes the deliberate
# size-2 fallback.
native_title = dimensions("INBOX 0", 3, 3, 1.2)
native_two_digit_title = dimensions("INBOX 10", 3, 3, 1.2)
native_two_digit_fallback = dimensions("INBOX 10", 2, 3, 1.2)
# enlarging every dense UI row. The title, count, action, and pairing
# state each occupy their own large row.
native_title = dimensions("INBOX", 4, 3, 1.2)
native_count = dimensions("9999", 4, 3, 1.2)
native_count_fallback = dimensions("99999", 3, 3, 1.2)
self.assertLessEqual(native_title[0], 156)
self.assertGreater(native_two_digit_title[0], 156)
self.assertLessEqual(native_two_digit_fallback[0], 156)
self.assertLessEqual(native_title[1], 39)
self.assertLessEqual(native_count[0], 156)
self.assertLessEqual(native_count_fallback[0], 156)
for text, region_width in (
("tap inbox", 156),
("WiFi: OFF", 156),
("BLE PIN", 144),
for text, requested_size, region_width in (
("TAP", 3, 144),
("OFF", 3, 144),
("PIN", 3, 144),
("LINKED", 3, 144),
("123456", 3, 144),
("IP: 192.168.100.200", 1, 144),
):
width, height = dimensions(text, 2, 3, 1.2)
width, height = dimensions(text, requested_size, 3, 1.2)
self.assertLessEqual(width, region_width)
self.assertLessEqual(height, 20)
self.assertLessEqual(height, 29 if requested_size == 3 else 10)
# The two lower size-3 rows must not depend on blank pixels inside the
# checked-in VLW: the built-in fallback font can fill its whole cell.
pairing_label_y = 102
pairing_value_y = 131
self.assertLessEqual(pairing_label_y + 29, pairing_value_y)
self.assertLessEqual(pairing_value_y + 29, 160)
if __name__ == "__main__":
+71
View File
@@ -9,6 +9,9 @@ import unittest
ROOT = Path(__file__).resolve().parents[1]
BUILD = ROOT / "build.sh"
MAIN = ROOT / "examples" / "companion_radio" / "main.cpp"
MESH = ROOT / "examples" / "companion_radio" / "MyMesh.cpp"
DATA_STORE = ROOT / "examples" / "companion_radio" / "DataStore.cpp"
PROFILE = ROOT / "variants" / "sensecap_indicator-espnow" / "platformio.ini"
class IndicatorRenderProfileTest(unittest.TestCase):
@@ -117,6 +120,74 @@ int main() {
self.assertIn("static_cast<uint16_t>(renderWidth()), _compactText", display)
self.assertIn("* profile_scale;", display)
def test_lora_fresh_install_uses_compiled_cascade_defaults(self):
profile = PROFILE.read_text(encoding="utf-8")
lora = profile[profile.index("[SenseCapIndicator-LoRa]") :]
lora = lora[: lora.index("[env:")]
self.assertIn("-UMESH_PRIMARY_ESPNOW", lora)
self.assertIn("-D SENSECAP_INDICATOR_LORA", lora)
self.assertIn("-D USE_SX1262", lora)
self.assertIn("-<helpers/esp32/ESPNOWRadio.cpp>", lora)
mesh = MESH.read_text(encoding="utf-8")
self.assertIn("_prefs.freq = LORA_FREQ;", mesh)
self.assertIn("_prefs.sf = LORA_SF;", mesh)
self.assertIn("_prefs.bw = LORA_BW;", mesh)
self.assertIn("_prefs.cr = LORA_CR;", mesh)
guarded_rx_delay = (
"#ifdef DEFAULT_RX_DELAY_BASE\n"
" _prefs.rx_delay_base = DEFAULT_RX_DELAY_BASE;\n"
"#endif"
)
self.assertIn(guarded_rx_delay, mesh)
# Saved preferences load after constructor defaults, so an app-only
# update remains non-destructive while an erased install uses them.
begin = mesh[mesh.index("void MyMesh::begin(") :]
self.assertIn("_store->loadPrefs(_prefs", begin)
store = DATA_STORE.read_text(encoding="utf-8")
self.assertIn('if (_fs->exists("/new_prefs"))', store)
self.assertIn('loadPrefsInt("/new_prefs", prefs', store)
def test_explicit_usa_cascadia_and_cascade_build_flags(self):
command = r'''
set -e
source "$1"
PLATFORMIO_BUILD_FLAGS=""
RADIO_FREQ_OVERRIDE=910.525
RADIO_BW_OVERRIDE=62.5
RADIO_SF_OVERRIDE=7
RADIO_CR_OVERRIDE=5
FIRMWARE_PROFILE_OVERRIDE=cascade
apply_radio_overrides
apply_firmware_profile_overrides
printf '%s\n' "$PLATFORMIO_BUILD_FLAGS"
'''
result = subprocess.run(
["bash", "-c", command, "test", str(BUILD)],
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
flags = result.stdout.split()
for expected in (
"-DLORA_FREQ=910.525",
"-DLORA_BW=62.5",
"-DLORA_SF=7",
"-DLORA_CR=5",
"-DCASCADE_PROFILE=1",
"-DDEFAULT_RX_DELAY_BASE=2.0f",
):
self.assertIn(expected, flags)
build = BUILD.read_text(encoding="utf-8")
self.assertIn(
"'SenseCapIndicator-LoRa_comp_radio_usb_wifi|"
"SenseCapIndicator-LoRa_companion_radio_full'",
build,
)
if __name__ == "__main__":
unittest.main()
@@ -218,6 +218,47 @@ TEST(TouchInput, CanReverseSwipesWithoutReversingTapZones) {
EXPECT_EQ(release(input), TouchAction::Next);
}
TEST(TouchInput, CanMirrorStationaryTapZonesIndependentlyOfSwipes) {
TouchInput input(true, true, 70, true);
const TouchSplitSelector selector{2, 76, 82, 76, 40, 100};
// The controller's raw-right coordinate is the visual-left WiFi box.
input.update(true, 129, 80, 160, 160, false, &selector);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::SelectLeft);
// The controller's raw-left coordinate is the visual-right BLE box.
input.update(true, 30, 80, 160, 160, false, &selector);
input.update(true, 31, 80, 160, 160, false, &selector);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::SelectRight);
// Generic side taps and the message footer arrows use the same visual-X
// correction, while their center zones stay centered.
input.update(true, 150, 60, 160, 160);
input.update(true, 150, 60, 160, 160);
EXPECT_EQ(release(input, 160, 160), TouchAction::Previous);
input.update(true, 10, 140, 160, 160, true);
input.update(true, 10, 140, 160, 160, true);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, true),
TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, true),
TouchAction::VerticalNext);
// Swipe direction remains governed only by reverse_swipes.
input.update(true, 130, 70, 160, 160, false, &selector);
input.update(true, 30, 72, 160, 160, false, &selector);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::None);
EXPECT_EQ(input.update(false, -1, -1, 160, 160, false, &selector),
TouchAction::Previous);
}
TEST(TouchInput, KeepsGestureAcrossOneMissingTouchSample) {
TouchInput input;