Fix Indicator transport taps and fresh LoRa defaults
This commit is contained in:
1 parent
7791f3f8c9
commit
bbb9312340
13 files changed
+344
-123
No files matched your search
@@ -1490,7 +1490,9 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.autoadd_config = DEFAULT_AUTOADD_CONFIG;
|
||||
_prefs.path_hash_mode = DEFAULT_PATH_HASH_MODE;
|
||||
_prefs.radio_fem_txgain = 0;
|
||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||
#ifdef DEFAULT_RX_DELAY_BASE
|
||||
_prefs.rx_delay_base = DEFAULT_RX_DELAY_BASE;
|
||||
#endif
|
||||
_prefs.setRepeatEn(false);
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110) \
|
||||
|| defined(USE_LR2021) || defined(SX126X_RX_BOOSTED_GAIN) \
|
||||
|
||||
@@ -86,18 +86,28 @@ static void drawCompanionTransportChoice(DisplayDriver& display,
|
||||
}
|
||||
|
||||
const bool large_transport_text = display.height() >= 96;
|
||||
display.setTextSize(large_transport_text ? 3 : 1);
|
||||
const bool show_status_label = height >= 44;
|
||||
const int label_y = show_status_label
|
||||
? y + (large_transport_text ? 17 : 12)
|
||||
: y + (height - 8) / 2;
|
||||
display.drawTextCentered(x + width / 2, label_y, label);
|
||||
if (large_transport_text) {
|
||||
display.setTextSize(4);
|
||||
if (strcmp(label, "WiFi") == 0) {
|
||||
// A single size-4 "WiFi" row is wider than one half of the screen.
|
||||
// Reflow it into two large rows instead of shrinking both choices.
|
||||
display.drawTextCentered(x + width / 2, y + 2, "Wi");
|
||||
display.drawTextCentered(x + width / 2, y + 36, "Fi");
|
||||
} else {
|
||||
display.drawTextCentered(x + width / 2, y + 25, label);
|
||||
}
|
||||
} else {
|
||||
display.setTextSize(1);
|
||||
const int label_y = show_status_label ? y + 12 : y + (height - 8) / 2;
|
||||
display.drawTextCentered(x + width / 2, label_y, label);
|
||||
}
|
||||
if (show_status_label && (active || selected)) {
|
||||
display.setTextSize(large_transport_text ? 2 : 1);
|
||||
display.setTextSize(large_transport_text ? 3 : 1);
|
||||
display.drawTextCentered(
|
||||
x + width / 2,
|
||||
y + height - (large_transport_text ? 25 : 17),
|
||||
active ? "ACTIVE"
|
||||
active ? (large_transport_text ? "ON" : "ACTIVE")
|
||||
: (large_transport_text ? "NEXT" : "NEXT BOOT"));
|
||||
}
|
||||
}
|
||||
@@ -381,17 +391,19 @@ public:
|
||||
mesh::ui::usesExpandedCompanionHomeTypography(
|
||||
display.width(), display.height(),
|
||||
display.renderWidth(), display.renderHeight());
|
||||
sprintf(tmp, expanded_home ? "INBOX %d" : "INBOX: %d",
|
||||
_task->getPreviewCount());
|
||||
display.setTextSize(expanded_home ? 3 : 2);
|
||||
if (expanded_home && display.getTextWidth(tmp) > display.width() - 4) {
|
||||
if (expanded_home) {
|
||||
display.setTextSize(4);
|
||||
display.drawTextCentered(display.width() / 2, 20, "INBOX");
|
||||
} else {
|
||||
sprintf(tmp, "INBOX: %d", _task->getPreviewCount());
|
||||
display.setTextSize(2);
|
||||
display.drawTextCentered(display.width() / 2, 22, tmp);
|
||||
}
|
||||
#else
|
||||
display.setTextSize(2);
|
||||
sprintf(tmp, "INBOX: %d", _task->getPreviewCount());
|
||||
#endif
|
||||
display.drawTextCentered(display.width() / 2, 22, tmp);
|
||||
#endif
|
||||
#ifdef UI_DEDICATED_PAIRING_BLOCK
|
||||
const mesh::ui::CompanionHomeLayout layout =
|
||||
mesh::ui::makeLargeCompanionHomeLayout(display.width(),
|
||||
@@ -405,27 +417,35 @@ public:
|
||||
mesh::ui::clearDisplayRegion(display, layout.info);
|
||||
mesh::ui::clearDisplayRegion(display, layout.pairing);
|
||||
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.instruction_y,
|
||||
expanded_home ? "tap inbox" : "tap center: inbox");
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
if (isCompanionWiFiEnabled()) {
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
|
||||
ip[0], ip[1], ip[2], ip[3]);
|
||||
} else {
|
||||
strcpy(tmp, "WiFi: OFF");
|
||||
}
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
if (expanded_home) {
|
||||
snprintf(tmp, sizeof(tmp), "%d", _task->getPreviewCount());
|
||||
display.setTextSize(4);
|
||||
if (display.getTextWidth(tmp) > layout.info.width) {
|
||||
display.setTextSize(1);
|
||||
display.setTextSize(3);
|
||||
}
|
||||
display.setColor(UIColor::primary_txt);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.network_y, tmp);
|
||||
#endif
|
||||
display, layout.info, layout.instruction_y, tmp);
|
||||
} else {
|
||||
display.setTextSize(1);
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.instruction_y,
|
||||
"tap center: inbox");
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
if (isCompanionWiFiEnabled()) {
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
|
||||
ip[0], ip[1], ip[2], ip[3]);
|
||||
} else {
|
||||
strcpy(tmp, "WiFi: OFF");
|
||||
}
|
||||
display.setTextSize(1);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.info, layout.network_y, tmp);
|
||||
#endif
|
||||
}
|
||||
|
||||
const bool bluetooth_enabled = _task->isBluetoothEnabled();
|
||||
const bool bluetooth_connected = _task->hasBluetoothConnection();
|
||||
@@ -436,10 +456,11 @@ public:
|
||||
char pairing_pin[16];
|
||||
if (bluetooth_connected) {
|
||||
pairing_label = expanded_home ? "BLE" : "BLUETOOTH";
|
||||
pairing_value = "CONNECTED";
|
||||
pairing_value = expanded_home ? "LINKED" : "CONNECTED";
|
||||
if (expanded_home) pairing_value_size = 3;
|
||||
} else if (mesh::ui::shouldDisplayBluetoothPairingPin(
|
||||
bluetooth_enabled, bluetooth_connected, bluetooth_pin)) {
|
||||
pairing_label = expanded_home ? "BLE PIN" : "BLUETOOTH PIN";
|
||||
pairing_label = expanded_home ? "PIN" : "BLUETOOTH PIN";
|
||||
snprintf(pairing_pin, sizeof(pairing_pin), "%06u",
|
||||
(unsigned int)bluetooth_pin);
|
||||
pairing_value = pairing_pin;
|
||||
@@ -450,12 +471,30 @@ public:
|
||||
display.setColor(UIColor::title_bkg);
|
||||
mesh::ui::clearDisplayRegion(display, layout.pairing);
|
||||
display.setColor(UIColor::title_txt);
|
||||
display.setTextSize(expanded_home ? 2 : 1);
|
||||
display.setTextSize(expanded_home ? 3 : 1);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_label_y, pairing_label);
|
||||
display.setTextSize(pairing_value_size);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_value_y, pairing_value);
|
||||
} else if (expanded_home) {
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.setTextSize(3);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_label_y, "TAP");
|
||||
#ifdef WIFI_SSID
|
||||
if (isCompanionWiFiEnabled()) {
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d",
|
||||
ip[0], ip[1], ip[2], ip[3]);
|
||||
display.setTextSize(1);
|
||||
} else {
|
||||
strcpy(tmp, "OFF");
|
||||
display.setTextSize(3);
|
||||
}
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_value_y, tmp);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
const bool bluetooth_enabled = _task->isBluetoothEnabled();
|
||||
@@ -611,16 +650,15 @@ public:
|
||||
mesh::ui::makeCompanionTransportSelectorLayout(
|
||||
display.width(), display.height());
|
||||
|
||||
// Keep this page independent of the additional native-480 text boost:
|
||||
// the size-3 choice names and size-2 status already consume nearly the
|
||||
// full half-screen width, and should remain equally readable in the
|
||||
// memory-saving 320 profile.
|
||||
// Keep this page independent of the additional native-480 text boost.
|
||||
// Its deliberately reflowed size-4 choices then retain the same large
|
||||
// physical dimensions in the 320 and 480 render profiles.
|
||||
display.setCompactText(true);
|
||||
if (layout.show_title) {
|
||||
display.setColor(UIColor::primary_txt);
|
||||
display.setTextSize(2);
|
||||
display.setTextSize(3);
|
||||
display.drawTextCentered(
|
||||
display.width() / 2, layout.title_y, "TRANSPORT");
|
||||
display.width() / 2, layout.title_y, "MODE");
|
||||
}
|
||||
drawCompanionTransportChoice(
|
||||
display, layout.wifi.x, layout.wifi.y,
|
||||
@@ -632,10 +670,11 @@ public:
|
||||
"BLE", !wifi_active, !wifi_selected);
|
||||
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.setTextSize(1);
|
||||
display.setTextSize(layout.show_title ? 2 : 1);
|
||||
#ifdef HAS_TOUCH
|
||||
display.drawTextCentered(
|
||||
display.width() / 2, layout.prompt_y, "tap a box");
|
||||
display.width() / 2, layout.prompt_y,
|
||||
layout.show_title ? "TAP SIDE" : "tap a box");
|
||||
#else
|
||||
display.drawTextCentered(
|
||||
display.width() / 2, layout.prompt_y, PRESS_LABEL);
|
||||
|
||||
@@ -48,16 +48,26 @@ class UITask : public AbstractUITask {
|
||||
#ifndef TOUCH_CENTER_ZONE_PERCENT
|
||||
#define TOUCH_CENTER_ZONE_PERCENT 34
|
||||
#endif
|
||||
#if defined(TOUCH_REVERSE_SWIPE) \
|
||||
&& defined(TOUCH_SEPARATE_VERTICAL_SWIPES)
|
||||
mesh::ui::TouchInput touch_input{true, true, TOUCH_CENTER_ZONE_PERCENT};
|
||||
#elif defined(TOUCH_REVERSE_SWIPE)
|
||||
mesh::ui::TouchInput touch_input{true, false, TOUCH_CENTER_ZONE_PERCENT};
|
||||
#elif defined(TOUCH_SEPARATE_VERTICAL_SWIPES)
|
||||
mesh::ui::TouchInput touch_input{false, true, TOUCH_CENTER_ZONE_PERCENT};
|
||||
#ifdef TOUCH_REVERSE_SWIPE
|
||||
static constexpr bool TOUCH_REVERSE_SWIPE_ENABLED = true;
|
||||
#else
|
||||
mesh::ui::TouchInput touch_input{false, false, TOUCH_CENTER_ZONE_PERCENT};
|
||||
static constexpr bool TOUCH_REVERSE_SWIPE_ENABLED = false;
|
||||
#endif
|
||||
#ifdef TOUCH_SEPARATE_VERTICAL_SWIPES
|
||||
static constexpr bool TOUCH_SEPARATE_VERTICAL_SWIPES_ENABLED = true;
|
||||
#else
|
||||
static constexpr bool TOUCH_SEPARATE_VERTICAL_SWIPES_ENABLED = false;
|
||||
#endif
|
||||
#ifdef TOUCH_MIRROR_TAP_X
|
||||
static constexpr bool TOUCH_MIRROR_TAP_X_ENABLED = true;
|
||||
#else
|
||||
static constexpr bool TOUCH_MIRROR_TAP_X_ENABLED = false;
|
||||
#endif
|
||||
mesh::ui::TouchInput touch_input{
|
||||
TOUCH_REVERSE_SWIPE_ENABLED,
|
||||
TOUCH_SEPARATE_VERTICAL_SWIPES_ENABLED,
|
||||
TOUCH_CENTER_ZONE_PERCENT,
|
||||
TOUCH_MIRROR_TAP_X_ENABLED};
|
||||
unsigned long next_touch_check = 0;
|
||||
#endif
|
||||
#ifdef PIN_STATUS_LED
|
||||
|
||||
@@ -70,13 +70,17 @@ inline CompanionHomeLayout makeLargeCompanionHomeLayout(int width,
|
||||
int height,
|
||||
bool expanded = false) {
|
||||
if (expanded && width == 160 && height == 160) {
|
||||
// Native 480 uses three large vertical bands: an INBOX title above this
|
||||
// count region, then one lower region that is either the BLE state or a
|
||||
// large tap/network hint. Reusing the lower region conditionally gives
|
||||
// the prominent text room to grow without overlapping another status.
|
||||
return {
|
||||
{2, 51, 156, 46},
|
||||
{8, 100, 144, 58},
|
||||
52,
|
||||
76,
|
||||
{2, 61, 156, 39},
|
||||
{8, 102, 144, 58},
|
||||
61,
|
||||
61,
|
||||
102,
|
||||
126,
|
||||
131,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ inline CompanionTransportSelectorLayout makeCompanionTransportSelectorLayout(
|
||||
const bool tall_display = height >= 96;
|
||||
const int margin = 2;
|
||||
const int gap = 4;
|
||||
const int box_y = tall_display ? 36 : 20;
|
||||
const int prompt_y = height - (tall_display ? 20 : 11);
|
||||
int box_height = prompt_y - box_y - (tall_display ? 6 : 8);
|
||||
if (box_height > 98) box_height = 98;
|
||||
const int box_y = tall_display ? 40 : 20;
|
||||
const int prompt_y = height - (tall_display ? 17 : 11);
|
||||
int box_height = prompt_y - box_y - (tall_display ? 3 : 8);
|
||||
if (box_height > 100) box_height = 100;
|
||||
if (box_height < 20) box_height = 20;
|
||||
const int box_width = (width - margin * 2 - gap) / 2;
|
||||
const int bluetooth_x = margin + box_width + gap;
|
||||
@@ -34,7 +34,7 @@ inline CompanionTransportSelectorLayout makeCompanionTransportSelectorLayout(
|
||||
return {
|
||||
{margin, box_y, box_width, box_height},
|
||||
{bluetooth_x, box_y, box_width, box_height},
|
||||
tall_display ? 17 : 0,
|
||||
tall_display ? 14 : 0,
|
||||
prompt_y,
|
||||
tall_display,
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ class TouchInput {
|
||||
bool _active = false;
|
||||
bool _reverse_swipes;
|
||||
bool _separate_vertical_swipes;
|
||||
bool _mirror_tap_x;
|
||||
uint8_t _center_zone_percent;
|
||||
uint8_t _touch_samples = 0;
|
||||
uint8_t _release_samples = 0;
|
||||
@@ -57,9 +58,11 @@ class TouchInput {
|
||||
public:
|
||||
explicit TouchInput(bool reverse_swipes = false,
|
||||
bool separate_vertical_swipes = false,
|
||||
uint8_t center_zone_percent = 34)
|
||||
uint8_t center_zone_percent = 34,
|
||||
bool mirror_tap_x = false)
|
||||
: _reverse_swipes(reverse_swipes),
|
||||
_separate_vertical_swipes(separate_vertical_swipes),
|
||||
_mirror_tap_x(mirror_tap_x),
|
||||
_center_zone_percent(center_zone_percent > 100
|
||||
? 100
|
||||
: center_zone_percent) {}
|
||||
@@ -113,15 +116,21 @@ public:
|
||||
return verticalSwipeAction(dy < 0);
|
||||
}
|
||||
|
||||
// Some panels report a mirrored X axis. Swipe direction has its own
|
||||
// independent policy because gesture direction and stationary screen
|
||||
// coordinates are different concerns. Apply the panel correction only
|
||||
// after movement has been ruled out so it cannot alter swipe detection.
|
||||
const int tap_x = _mirror_tap_x ? width - 1 - _start_x : _start_x;
|
||||
|
||||
// Message screens may reserve the otherwise empty ends of their bottom
|
||||
// status bar as forgiving arrow buttons. Keep the label in the middle
|
||||
// inert so an imprecise arrow tap cannot accidentally close the screen.
|
||||
if (bottom_selector && _separate_vertical_swipes
|
||||
&& _start_y >= (height * 3) / 4) {
|
||||
if (_start_x < width / 4) {
|
||||
if (tap_x < width / 4) {
|
||||
return TouchAction::VerticalPrevious;
|
||||
}
|
||||
if (_start_x >= (width * 3) / 4) {
|
||||
if (tap_x >= (width * 3) / 4) {
|
||||
return TouchAction::VerticalNext;
|
||||
}
|
||||
return TouchAction::None;
|
||||
@@ -135,13 +144,13 @@ public:
|
||||
|| _start_y >= split_selector->top_y + split_selector->height) {
|
||||
return TouchAction::None;
|
||||
}
|
||||
if (_start_x >= split_selector->left_x
|
||||
&& _start_x < split_selector->left_x
|
||||
if (tap_x >= split_selector->left_x
|
||||
&& tap_x < split_selector->left_x
|
||||
+ split_selector->left_width) {
|
||||
return TouchAction::SelectLeft;
|
||||
}
|
||||
if (_start_x >= split_selector->right_x
|
||||
&& _start_x < split_selector->right_x
|
||||
if (tap_x >= split_selector->right_x
|
||||
&& tap_x < split_selector->right_x
|
||||
+ split_selector->right_width) {
|
||||
return TouchAction::SelectRight;
|
||||
}
|
||||
@@ -156,8 +165,8 @@ public:
|
||||
const int side_percent = (100 - _center_zone_percent) / 2;
|
||||
const int center_left = (width * side_percent) / 100;
|
||||
const int center_right = width - center_left;
|
||||
if (_start_x < center_left) return TouchAction::Previous;
|
||||
if (_start_x >= center_right) return TouchAction::Next;
|
||||
if (tap_x < center_left) return TouchAction::Previous;
|
||||
if (tap_x >= center_right) return TouchAction::Next;
|
||||
return TouchAction::Select;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# SenseCAP Indicator controls
|
||||
|
||||
The primary mesh radio is selected by the firmware image, not by the WiFi/BLE
|
||||
screen. Use `SenseCapIndicator-LoRa_companion_radio_full` for a LoRa-equipped
|
||||
D1L or D1Pro. Build a fresh image explicitly with:
|
||||
|
||||
```sh
|
||||
bash build.sh build-firmware SenseCapIndicator-LoRa_companion_radio_full \
|
||||
--radio-preset usa-cascadia --profile cascade --clean
|
||||
```
|
||||
|
||||
The build uses the live USA/Canada preset when the settings service is
|
||||
available; its offline USA Cascadia fallback is 910.525 MHz, BW 62.5, SF7,
|
||||
CR5. The Cascade profile also supplies its fresh-install defaults, including
|
||||
RX delay 2. `SenseCapIndicator-ESPNow_companion_radio_full` remains the image
|
||||
for an Indicator without the LoRa assembly. An application-only update
|
||||
preserves the previously saved numeric radio tuple; erase/install the LoRa
|
||||
merged image when the compiled fresh defaults must replace it.
|
||||
|
||||
The Indicator preserves the existing 160x160 UI coordinate system. Ordinary
|
||||
images use a 320x320, 4-bit internal canvas scaled 1.5x onto the 480x480 panel.
|
||||
Full Companion images first request a native 480x480 canvas, then normally
|
||||
@@ -16,6 +33,9 @@ wakes it without selecting anything.
|
||||
side performs Previous or Next; horizontal swipes remain available anywhere.
|
||||
- A gesture is committed after a stable release. Brief missing touch samples
|
||||
are ignored so one swipe cannot become a second, opposite endpoint tap.
|
||||
- The SenseCAP controller reports a mirrored physical X coordinate. The board
|
||||
profile corrects stationary visual left/right taps independently from its
|
||||
existing swipe-direction correction.
|
||||
- The transport page is the exception for a very quick stationary contact:
|
||||
either tall, outlined WiFi/BLE box can accept one sampled point after the
|
||||
same stable-release debounce. Taps in the title, center gap, or footer stay
|
||||
@@ -117,10 +137,12 @@ available in both modes for recovery.
|
||||
|
||||
The Indicator transport page presents WiFi and BLE as full-height side-by-side
|
||||
choices. Tapping either choice saves it and reboots only when it differs from
|
||||
the active mode. Its size-3 choice labels render at the same physical size in
|
||||
the active mode. WiFi is split over two size-4 rows, BLE uses size 4, and the
|
||||
short `ON`/`NEXT` state uses size 3; all render at the same physical size in
|
||||
the 320 and 480 profiles. On a retained native 480 canvas, the first home page
|
||||
also reflows its inbox heading and short status labels at larger sizes; long IP
|
||||
addresses automatically fall back to the smaller row size instead of clipping.
|
||||
uses separate full-width size-4 `INBOX` and count rows plus a large lower
|
||||
action or BLE-status block. Long IP addresses retain a smaller bounded row
|
||||
instead of clipping.
|
||||
|
||||
LoRa remains the primary radio in both LoRa modes. On the ESP-NOW layout,
|
||||
selecting BLE leaves the primary ESP-NOW WiFi radio and its fixed channel
|
||||
|
||||
@@ -53,6 +53,9 @@ build_flags =
|
||||
-D PIN_USER_BTN=38
|
||||
-D HAS_TOUCH
|
||||
-D TOUCH_REVERSE_SWIPE
|
||||
; The panel's physical X axis is mirrored. Swipe direction is corrected
|
||||
; separately; stationary taps also need visual left/right hit testing.
|
||||
-D TOUCH_MIRROR_TAP_X
|
||||
-D TOUCH_SEPARATE_VERTICAL_SWIPES
|
||||
; The outer 15% remains available for Previous/Next taps; the middle 70%
|
||||
; is Select. This makes the primary tap target much easier on a 480px panel.
|
||||
|
||||
Reference in new issue
Block a user