Avoid duplicate pairing PINs across Companion displays
This commit is contained in:
1 parent
587d5b79a6
commit
83e4b45221
4 files changed
+92
-44
No files matched your search
@@ -466,6 +466,7 @@ class HomeScreen : public UIScreen {
|
||||
CompanionNodePrefs* _node_prefs;
|
||||
uint8_t _page;
|
||||
bool _shutdown_init;
|
||||
bool _pairing_pin_visible = false;
|
||||
#if UI_WIFI_SETUP_HOME_PAGE == 1
|
||||
bool _wifi_setup_was_active;
|
||||
bool _wifi_was_connected;
|
||||
@@ -960,7 +961,7 @@ public:
|
||||
#endif
|
||||
_page = HomePage::FIRST;
|
||||
}
|
||||
bool isFirstPage() const { return _page == HomePage::FIRST; }
|
||||
bool hasPairingPin() const { return _pairing_pin_visible; }
|
||||
|
||||
#if COMPANION_FEATURE_READER
|
||||
bool isRadioPage() const {
|
||||
@@ -1027,6 +1028,7 @@ public:
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
_pairing_pin_visible = false;
|
||||
#if UI_POCKET_GUI
|
||||
return renderPocket(display);
|
||||
#endif
|
||||
@@ -1191,6 +1193,9 @@ public:
|
||||
display.setTextSize(pairing_value_size);
|
||||
mesh::ui::drawTextCenteredEllipsized(
|
||||
display, layout.pairing, layout.pairing_value_y, pairing_value);
|
||||
_pairing_pin_visible = pairing_value == pairing_pin
|
||||
&& display.getTextWidth(pairing_value) <= layout.pairing.width
|
||||
&& layout.pairing_value_y + display.textLineHeight() <= display.height();
|
||||
} else if (expanded_home) {
|
||||
display.setColor(UIColor::secondary_txt);
|
||||
display.setTextSize(3);
|
||||
@@ -1237,8 +1242,10 @@ public:
|
||||
pairing_value = pairing_pin;
|
||||
}
|
||||
|
||||
mesh::ui::drawBottomPairingBlock(display, home_content_top,
|
||||
pairing_label, pairing_value);
|
||||
const bool value_drawn =
|
||||
mesh::ui::drawBottomPairingBlock(display, home_content_top,
|
||||
pairing_label, pairing_value);
|
||||
_pairing_pin_visible = value_drawn && !show_client;
|
||||
} else {
|
||||
mesh::ui::BoundedTextRows rows(display,
|
||||
{0, home_content_top, display.width(),
|
||||
@@ -2617,11 +2624,9 @@ void UITask::renderPairingBanner() {
|
||||
if (_display == NULL || !isPairingScreenActive()) return;
|
||||
const uint32_t pairing_pin = the_mesh.getBLEPin();
|
||||
if (pairing_pin == 0) return;
|
||||
#if defined(HELTEC_T096)
|
||||
// The native T096 home page already shows the larger PIN at the bottom.
|
||||
if (curr == home && static_cast<HomeScreen*>(home)->isFirstPage()
|
||||
&& _display->height() > 64) return;
|
||||
#endif
|
||||
// Keep one complete PIN per frame. Other pages and layouts without their
|
||||
// own PIN still need this overlay during pairing.
|
||||
if (curr == home && static_cast<HomeScreen*>(home)->hasPairingPin()) return;
|
||||
|
||||
char prompt[24];
|
||||
snprintf(prompt, sizeof(prompt), "PAIR PIN %06u",
|
||||
|
||||
@@ -195,13 +195,14 @@ public:
|
||||
// PIN/connection status owns the bottom of every non-Indicator home screen,
|
||||
// including native 160x80 T096 and 250x122 MeshPocket panels. The caller must
|
||||
// not draw ordinary instructions/network rows in this same area.
|
||||
inline void drawBottomPairingBlock(DisplayDriver& display, int content_top,
|
||||
// Return true only when the complete value fits and was drawn.
|
||||
inline bool drawBottomPairingBlock(DisplayDriver& display, int content_top,
|
||||
const char* label, const char* value) {
|
||||
const int bottom = display.height() - (display.height() > 64 ? 2 : 0);
|
||||
const int available = bottom - content_top;
|
||||
const int margin = display.width() >= 16 ? 4 : 0;
|
||||
const int width = display.width() - 2 * margin;
|
||||
if (available <= 0 || width <= 0) return;
|
||||
if (available <= 0 || width <= 0) return false;
|
||||
display.setTextSize(1);
|
||||
const int label_height = display.textLineHeight();
|
||||
int value_size = 2;
|
||||
@@ -211,7 +212,7 @@ inline void drawBottomPairingBlock(DisplayDriver& display, int content_top,
|
||||
display.setTextSize(value_size = 1);
|
||||
}
|
||||
const int value_height = display.textLineHeight();
|
||||
if (value_height <= 0 || value_height > available) return;
|
||||
if (value_height <= 0 || value_height > available) return false;
|
||||
// Prefer a readable complete PIN; omit its label on unusually short
|
||||
// viewports instead of colliding with the title or clipping the digits.
|
||||
const bool show_label = label_height > 0
|
||||
@@ -227,6 +228,7 @@ inline void drawBottomPairingBlock(DisplayDriver& display, int content_top,
|
||||
}
|
||||
display.setTextSize(value_size);
|
||||
drawTextCenteredEllipsized(display, region, bottom - value_height, value);
|
||||
return display.getTextWidth(value) <= width;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the production pairing UI methods and OLED selection with USB present."""
|
||||
"""Run production pairing UI methods and keep exactly one visible PIN."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
@@ -52,9 +52,10 @@ struct Interfaces {
|
||||
};
|
||||
struct UIScreen {};
|
||||
struct HomeScreen : UIScreen {
|
||||
bool first = true;
|
||||
bool isFirstPage() const { return first; }
|
||||
void showFirstPage() { first = true; }
|
||||
bool pairing_pin_visible = false;
|
||||
bool hasPairingPin() const { return pairing_pin_visible; }
|
||||
void showFirstPage() {}
|
||||
void flushPocketSettings() {}
|
||||
};
|
||||
struct MsgPreviewScreen : UIScreen { bool hasMessages() const { return false; } };
|
||||
struct Mesh {
|
||||
@@ -96,23 +97,28 @@ struct UITask {
|
||||
};
|
||||
@METHODS@
|
||||
static void homePairing(UITask* _task, TestDisplay& display) {
|
||||
bool& _pairing_pin_visible = _task->home_screen.pairing_pin_visible;
|
||||
_pairing_pin_visible = false;
|
||||
#if !UI_POCKET_GUI
|
||||
const int home_content_top = 20;
|
||||
@HOME_SELECTION@
|
||||
#endif
|
||||
}
|
||||
static void checkPairingPresentation(UITask& ui, TestDisplay& display) {
|
||||
display.text.clear();
|
||||
homePairing(&ui, display);
|
||||
const bool home_pin = std::count(display.text.begin(), display.text.end(), "246810") == 1;
|
||||
assert(ui.home_screen.hasPairingPin() == home_pin);
|
||||
ui.renderPairingBanner();
|
||||
#if defined(HELTEC_T096)
|
||||
if (display.height() > 64) {
|
||||
if (home_pin) {
|
||||
assert(std::count(display.text.begin(), display.text.end(), "246810") == 1);
|
||||
assert(std::count(display.text.begin(), display.text.end(), "PAIR PIN 246810") == 0);
|
||||
// Another home page or asynchronous screen still needs the top PIN.
|
||||
ui.home_screen.first = false;
|
||||
ui.home_screen.pairing_pin_visible = false;
|
||||
display.text.clear();
|
||||
ui.renderPairingBanner();
|
||||
assert(display.text.back() == "PAIR PIN 246810");
|
||||
ui.home_screen.showFirstPage();
|
||||
ui.home_screen.pairing_pin_visible = true;
|
||||
ui.curr = ui.msg_preview;
|
||||
display.text.clear();
|
||||
ui.renderPairingBanner();
|
||||
@@ -120,7 +126,6 @@ static void checkPairingPresentation(UITask& ui, TestDisplay& display) {
|
||||
ui.curr = ui.home;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
assert(display.text.back() == "PAIR PIN 246810");
|
||||
}
|
||||
int main(int argc, char** argv) {
|
||||
@@ -155,7 +160,13 @@ int main(int argc, char** argv) {
|
||||
assert(!ui.isPairingScreenActive() && !ui._pairing_from_button);
|
||||
display.text.clear();
|
||||
homePairing(&ui, display);
|
||||
assert(display.text.back() == "246810"); // USB must not replace the idle PIN.
|
||||
#if UI_POCKET_GUI
|
||||
assert(display.text.empty()); // Pocket's board view has no standalone PIN.
|
||||
#else
|
||||
if (display.height() >= 64)
|
||||
assert(display.text.back() == "246810"); // USB must not replace the idle PIN.
|
||||
else assert(display.text.empty()); // Too short: only the pairing overlay fits.
|
||||
#endif
|
||||
|
||||
interfaces.pending = true;
|
||||
ui.servicePairingState();
|
||||
@@ -164,7 +175,10 @@ int main(int argc, char** argv) {
|
||||
assert(!ui.isPairingScreenActive());
|
||||
display.text.clear();
|
||||
homePairing(&ui, display);
|
||||
assert(display.text.back() == "CONNECTED");
|
||||
#if !UI_POCKET_GUI
|
||||
if (display.height() >= 64) assert(display.text.back() == "CONNECTED");
|
||||
else assert(display.text.empty());
|
||||
#endif
|
||||
|
||||
// Explicit off must remain off; disabling BLE must not reveal a stale PIN.
|
||||
interfaces.ble = false;
|
||||
@@ -183,7 +197,11 @@ int main(int argc, char** argv) {
|
||||
assert(display.isOn() && !ui.isPairingScreenActive());
|
||||
display.text.clear();
|
||||
homePairing(&ui, display);
|
||||
assert(display.text.back() == "CONNECTED"); // Only USB status remains.
|
||||
#if !UI_POCKET_GUI
|
||||
if (display.height() >= 64)
|
||||
assert(display.text.back() == "CONNECTED"); // Only USB status remains.
|
||||
else assert(display.text.empty());
|
||||
#endif
|
||||
}
|
||||
'''
|
||||
|
||||
@@ -213,8 +231,11 @@ class CompanionPairingDisplayTests(unittest.TestCase):
|
||||
cpp, binary = Path(temp) / "test.cpp", Path(temp) / "test"
|
||||
cpp.write_text(program)
|
||||
for profile, flags, sizes in (
|
||||
("oled", [], [(128, 64)]),
|
||||
("shared", [], [(128, 64), (160, 80), (80, 160), (128, 128),
|
||||
(240, 135), (320, 170), (320, 240), (240, 320),
|
||||
(250, 122), (200, 200), (128, 24)]),
|
||||
("t096", ["-DHELTEC_T096=1"], [(160, 80), (80, 160), (128, 64)]),
|
||||
("pocket", ["-DUI_POCKET_GUI=1"], [(250, 122), (200, 200)]),
|
||||
):
|
||||
with self.subTest(profile=profile):
|
||||
result = subprocess.run([
|
||||
|
||||
@@ -21,17 +21,20 @@ ColorVal UIColor::window_bkg=0, UIColor::title_bkg=0, UIColor::title_txt=1;
|
||||
ColorVal UIColor::primary_txt=1, UIColor::secondary_txt=1, UIColor::warning_txt=1;
|
||||
ColorVal UIColor::popup_bkg=0, UIColor::popup_txt=1, UIColor::corp_blue=1;
|
||||
#define PRESS_LABEL "Press"
|
||||
#define SELECT_LABEL "Select"
|
||||
struct Display : DisplayDriver {
|
||||
struct Row { mesh::ui::DisplayRegion box; std::string text; };
|
||||
std::vector<Row> rows;
|
||||
int size=1,x=0,y=0,small,large;
|
||||
Display(int w,int h,int a,int b):DisplayDriver(w,h),small(a),large(b) {}
|
||||
int size=1,x=0,y=0,small,large,render_size;
|
||||
Display(int w,int h,int a,int b,int r=0):DisplayDriver(w,h),small(a),large(b),render_size(r) {}
|
||||
int renderWidth() const override { return render_size?render_size:width(); }
|
||||
int renderHeight() const override { return render_size?render_size:height(); }
|
||||
bool isOn() override { return true; }
|
||||
void turnOn() override {} void turnOff() override {}
|
||||
void clear() override { rows.clear(); }
|
||||
void startFrame(ColorVal=0) override { clear(); } void endFrame() override {}
|
||||
void setTextSize(int s) override { size=s; }
|
||||
int textLineHeight() override { return size==1?small:large; }
|
||||
int textLineHeight() override { return size==1?small:size==2?large:size*small; }
|
||||
void setColor(ColorVal) override {}
|
||||
void setCursor(int a,int b) override { x=a;y=b; }
|
||||
uint16_t getTextWidth(const char* text) override {
|
||||
@@ -86,24 +89,37 @@ struct Board { int getBattMilliVolts(){return 4100;} };
|
||||
|
||||
SCENARIOS = r'''
|
||||
int main() {
|
||||
for(auto profile: {std::vector<int>{128,64,8,16}, {160,80,8,16},
|
||||
{250,122,21,27}, {250,122,22,29}, {200,200,22,29}, {240,135,10,20}}) {
|
||||
Display d(profile[0],profile[1],profile[2],profile[3]); Task task;
|
||||
for(int state=0;state<5;++state) for(int count:{0,99,99999}) {
|
||||
#ifdef UI_DEDICATED_PAIRING_BLOCK
|
||||
const std::vector<std::vector<int>> profiles={{160,160,8,16,320},{160,160,8,16,480}};
|
||||
#else
|
||||
const std::vector<std::vector<int>> profiles={{128,64,8,16}, {160,80,8,16},
|
||||
{250,122,21,27}, {250,122,22,29}, {200,200,22,29}, {240,135,10,20}};
|
||||
#endif
|
||||
for(auto profile:profiles) {
|
||||
Display d(profile[0],profile[1],profile[2],profile[3],profile.size()>4?profile[4]:0); Task task;
|
||||
bool pairing_pin_visible=true;
|
||||
for(int state=0;state<5;++state) for(int count:{0,99,99999}) for(uint32_t pin:{123456U,12345U}) {
|
||||
task.enabled=state!=2; task.connected=state==1; task.prompt=state==4;
|
||||
task.count=count; the_mesh.pin=state==3?0:123456;
|
||||
d.clear(); home(d,&task);
|
||||
task.count=count; the_mesh.pin=state==3?0:pin;
|
||||
d.clear(); home(d,&task,pairing_pin_visible);
|
||||
bool pin_expected=task.enabled && !task.connected && the_mesh.pin
|
||||
&& (!task.prompt || d.height()>64);
|
||||
assert((d.find("123456")!=nullptr)==pin_expected);
|
||||
char pin_text[16]; snprintf(pin_text,sizeof(pin_text),"%06u",(unsigned)the_mesh.pin);
|
||||
assert((d.find(pin_text)!=nullptr)==pin_expected);
|
||||
assert(pairing_pin_visible==(d.find(pin_text)!=nullptr));
|
||||
if(pin_expected) {
|
||||
auto pin=d.find("123456");
|
||||
auto pin=d.find(pin_text);
|
||||
assert(pin->box.y>=d.height()/2);
|
||||
#ifndef UI_DEDICATED_PAIRING_BLOCK
|
||||
assert(pin->box.bottom()==d.height()-(d.height()>64?2:0));
|
||||
#endif
|
||||
assert(!d.find("Press: inbox"));
|
||||
}
|
||||
if(task.connected) assert(d.find("CONNECTED"));
|
||||
if(task.connected) assert(d.find("CONNECTED") || d.find("LINKED"));
|
||||
}
|
||||
// A render on another page must clear a PIN retained from the first page.
|
||||
pairing_pin_visible=true; d.clear(); home(d,&task,pairing_pin_visible,1);
|
||||
assert(!pairing_pin_visible);
|
||||
Prefs prefs; Board board; d.clear(); repeater(&d,&prefs,&board);
|
||||
assert(d.rows.size()==5); // Never solve overlap by losing a status row.
|
||||
d.clear(); d.setTextSize(1);
|
||||
@@ -118,9 +134,10 @@ int main() {
|
||||
}
|
||||
// Sweep font sizes and short viewports: retain complete digits, omit only
|
||||
// a label that cannot fit, and never draw outside the reserved lower band.
|
||||
for(int h=16;h<=160;++h) for(int line=8;line<=32;++line) {
|
||||
Display d(160,h,line,2*line);
|
||||
mesh::ui::drawBottomPairingBlock(d,h/3,"BLUETOOTH PIN","012345");
|
||||
for(int width:{32,80,160}) for(int h=16;h<=160;++h) for(int line=8;line<=32;++line) {
|
||||
Display d(width,h,line,2*line);
|
||||
const bool pin_visible=mesh::ui::drawBottomPairingBlock(d,h/3,"BLUETOOTH PIN","012345");
|
||||
assert(pin_visible==(d.find("012345")!=nullptr));
|
||||
for(const auto& row:d.rows) assert(row.box.y>=h/3);
|
||||
}
|
||||
}
|
||||
@@ -130,23 +147,26 @@ int main() {
|
||||
class HomeTextSpacingTest(unittest.TestCase):
|
||||
def test_actual_home_and_repeater_branches(self):
|
||||
source = (ROOT / 'examples/companion_radio/ui-new/UITask.cpp').read_text()
|
||||
start = source.index(' const int normal_line =')
|
||||
start = source.index(' _pairing_pin_visible = false;')
|
||||
metrics = source[start:source.index(' display.setColor', start)]
|
||||
start = source.index(' if (_page == HomePage::FIRST) {', start)
|
||||
home = source[start:source.index('#if UI_MESSAGES_HOME_PAGE == 1', start)]
|
||||
repeater = (ROOT / 'examples/simple_repeater/UITask.cpp').read_text()
|
||||
start = repeater.index(' // Reserve a full measured font-height')
|
||||
repeater = repeater[start:repeater.index('\n }\n}', start)]
|
||||
implementation = ('void home(DisplayDriver& display, Task* _task) {\n'
|
||||
'display.setTextSize(1); char tmp[80]; int _page=0;\n'
|
||||
implementation = ('void home(DisplayDriver& display, Task* _task, '
|
||||
'bool& _pairing_pin_visible, int _page=0) {\n'
|
||||
'char tmp[80];\n'
|
||||
+ metrics + 'display.drawTextEllipsized(0,2,display.width(),"Node name");\n'
|
||||
+ home + '\n}}\n'
|
||||
'void repeater(DisplayDriver* _display, Prefs* _node_prefs, Board* _board) {'
|
||||
'char tmp[80];\n' + repeater + '\n}\n')
|
||||
for wifi in (False, True):
|
||||
with self.subTest(wifi=wifi), tempfile.TemporaryDirectory() as temp:
|
||||
for wifi, dedicated in ((False, False), (True, False), (False, True), (True, True)):
|
||||
with self.subTest(wifi=wifi, dedicated=dedicated), tempfile.TemporaryDirectory() as temp:
|
||||
binary = str(Path(temp) / 'spacing')
|
||||
flags = ['-DWIFI_SSID="test"', '-DWITH_MQTT_BRIDGE'] if wifi else []
|
||||
if dedicated:
|
||||
flags.append('-DUI_DEDICATED_PAIRING_BLOCK=1')
|
||||
result = subprocess.run(['c++', '-std=c++11', '-fsanitize=address,undefined',
|
||||
'-fno-pie', '-no-pie', '-I'+str(ROOT/'src'),
|
||||
*flags, '-x', 'c++', '-', '-o', binary],
|
||||
|
||||
Reference in new issue
Block a user