Fix Indicator transport taps and fresh LoRa defaults
This commit is contained in:
1 parent
2e026caf4a
commit
164a6e8227
13 files changed
+344
-123
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in new issue
Block a user