diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 63fa9c87d6..57a39e5fdd 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -701,11 +701,16 @@ void drawFrameBluetoothPair(OLEDDisplay *display, OLEDDisplayUiState *state, int return; } - // 2-module quiet zone; largest whole-pixel scale that fits below the header - const int span = qr.size + 4; + // Largest whole-pixel scale that fits below the header, with the spec's 4-module quiet zone, + // or 2 modules (which phones still read) when 4 won't fit at one pixel per module const int availH = display->getHeight() - (top - y); const int availW = display->getWidth(); - int scale = (availH < availW ? availH : availW) / span; + const int avail = availH < availW ? availH : availW; + int quiet = 4; + if (avail < qr.size + 2 * quiet) + quiet = 2; + const int span = qr.size + 2 * quiet; + const int scale = avail / span; if (scale < 1) { display->drawString(x + BASEUI_BODY_LR_MARGIN, top, "Screen too small"); return; @@ -720,7 +725,7 @@ void drawFrameBluetoothPair(OLEDDisplay *display, OLEDDisplayUiState *state, int for (uint8_t my = 0; my < qr.size; my++) for (uint8_t mx = 0; mx < qr.size; mx++) if (qrGetModule(&qr, mx, my)) - display->fillRect(qrX + (mx + 2) * scale, qrY + (my + 2) * scale, scale, scale); + display->fillRect(qrX + (mx + quiet) * scale, qrY + (my + quiet) * scale, scale, scale); display->setColor(WHITE); #if GRAPHICS_TFT_COLORING_ENABLED // Keep dark-on-light whatever the theme, so phone scanners read it diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index eca8e9e500..b32fbb20cd 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -69,6 +69,7 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) if (!config.bluetooth.enabled) { disableBluetooth(); IF_SCREEN(screen->showSimpleBanner("Bluetooth OFF", 3000)); + IF_SCREEN(screen->setFrames(graphics::Screen::FOCUS_PRESERVE)); // Drop the Pair frame } else { IF_SCREEN(screen->showSimpleBanner("Bluetooth ON\nRebooting", 3000)); rebootAtMsec = Time::timerEndsAtMillis(DEFAULT_REBOOT_SECONDS * 1000); diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index af3e14a3d3..d65aefed0b 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -415,9 +415,11 @@ void NRF52Bluetooth::setup() configuredPasskey = hwrand % 900000u + 100000u; } passkeyConfigured = true; - auto pinString = std::to_string(configuredPasskey); - LOG_INFO("Bluetooth pin set to '%i'", configuredPasskey); - Bluefruit.Security.setPIN(pinString.c_str()); + // setPIN wants exactly six digits, so keep a fixed PIN's leading zeros + char pinString[12]; + snprintf(pinString, sizeof(pinString), "%06lu", (unsigned long)configuredPasskey); + LOG_INFO("Bluetooth pin set to '%s'", pinString); + Bluefruit.Security.setPIN(pinString); Bluefruit.Security.setIOCaps(true, false, false); Bluefruit.Security.setPairPasskeyCallback(NRF52Bluetooth::onPairingPasskey); Bluefruit.Security.setPairCompleteCallback(NRF52Bluetooth::onPairingCompleted); @@ -542,7 +544,9 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke #if HAS_SCREEN && !defined(MESHTASTIC_EXCLUDE_SCREEN) if (screen) { - std::string configuredPasskeyText = std::to_string(configuredPasskey); + char pinText[12]; + snprintf(pinText, sizeof(pinText), "%06lu", (unsigned long)configuredPasskey); + std::string configuredPasskeyText = pinText; std::string ble_message = "Bluetooth\nPIN\n[M]" + configuredPasskeyText.substr(0, 3) + " " + configuredPasskeyText.substr(3, 6); // Use the pairing_pin notification type so the lockdown UI short- diff --git a/test/test_qr_encode/test_main.cpp b/test/test_qr_encode/test_main.cpp index da9e39425b..ff08fc990f 100644 --- a/test/test_qr_encode/test_main.cpp +++ b/test/test_qr_encode/test_main.cpp @@ -7,9 +7,11 @@ // only correct if every module matches what a full encoder produces for the same version and mask. // // Regressions guarded: -// - any change to placement, interleaving, Reed-Solomon, masking or format bits that moves a module. -// The reference matrix below is Project Nayuki's encoder output for the 73-character link (version 5, -// mask 2, which both encoders choose), and it decodes with zxing; +// - any change to placement, interleaving, Reed-Solomon, masking or format bits that moves a module in +// the two symbols the frame shows. The reference matrices below are Project Nayuki's encoder output for +// the 73-character link with a PIN (version 5, two blocks) and the 62-character link without one +// (version 4, two blocks, filled to capacity), both mask 2 in both encoders, and both decode with zxing. +// Versions 1-3 and 6 and other masks are not checked module by module; // - picking a larger version than needed (a coarser code on 128x64 panels) or a smaller one that the // data overflows, at each version's byte-mode capacity boundary; // - writing past the module buffer instead of rejecting data longer than version 6 holds. @@ -23,9 +25,9 @@ void setUp(void) {} void tearDown(void) {} -static const char *const kLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99&pin=317338"; +static const char *const kPinLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99&pin=317338"; -static const char *const kLinkModules[] = { +static const char *const kPinLinkModules[] = { "1111111000101001111011111001101111111", "1000001000101101010100000101101000001", "1011101010100110101111100011101011101", "1011101011000100110100111111101011101", "1011101011001001000111010000101011101", "1000001011100010001010000001101000001", "1111111010101010101010101010101111111", "0000000010001000100111110011100000000", "1011111001000011001110010101101111100", @@ -40,24 +42,49 @@ static const char *const kLinkModules[] = { "1011101011000001001101110000101111101", "1011101011001011100011101100100101111", "1000001000000111001101110011000101001", "1111111011011101010000100110000000111"}; +static const char *const kNoPinLink = "meshtastic://meshtastic/connections?address=xAC:27:6E:CD:5A:99"; + +static const char *const kNoPinLinkModules[] = { + "111111100110110010111001001111111", "100000100001100110000100001000001", "101110101100001110001101001011101", + "101110101110010111100111001011101", "101110101000001001001001001011101", "100000101111110100100110001000001", + "111111101010101010101010101111111", "000000001000101000011110000000000", "101111100010111001001011101111100", + "010111011001011011111101111001101", "001000101010100101101100010011110", "001110001000001010110111000101111", + "111010100111000010001111010111011", "111111001010110000110101111001001", "110001111000100111100110010100010", + "101000000001001100010111100001110", "100100100110110101111010110011011", "001000001000110010111111101000101", + "000001100100001101101000100001010", "000011000001110010000111101101110", "110010110101111000001001010110001", + "101100011100110111110111001000101", "100110101110100111001000001001010", "101111010010011110011111110100101", + "101110111001111101000010111111011", "000000001011110010111001100010001", "111111100101101110000001101010110", + "100000101111110010101100100011100", "101110101011110011101000111111010", "101110101001011000010011010111011", + "101110101110010101100100101001000", "100000100111100000010101010101100", "111111101000100001001011110000010"}; + static uint8_t modules[QR_ENCODE_BUFFER_SIZE(QR_ENCODE_MAX_VERSION)]; -static void test_connect_link_matches_reference_symbol() +static void assertMatchesReference(const char *link, uint8_t version, uint8_t mask, const char *const *reference) { QrEncode qr; - TEST_ASSERT_TRUE(qrEncodeText(&qr, modules, kLink)); - TEST_ASSERT_EQUAL_UINT8(5, qr.version); - TEST_ASSERT_EQUAL_UINT8(37, qr.size); - TEST_ASSERT_EQUAL_UINT8(2, qr.mask); + TEST_ASSERT_TRUE(qrEncodeText(&qr, modules, link)); + TEST_ASSERT_EQUAL_UINT8(version, qr.version); + TEST_ASSERT_EQUAL_UINT8(QR_ENCODE_SIZE(version), qr.size); + TEST_ASSERT_EQUAL_UINT8(mask, qr.mask); for (uint8_t y = 0; y < qr.size; y++) { - char row[38]; + char row[QR_ENCODE_SIZE(QR_ENCODE_MAX_VERSION) + 1]; for (uint8_t x = 0; x < qr.size; x++) row[x] = qrGetModule(&qr, x, y) ? '1' : '0'; row[qr.size] = '\0'; - TEST_ASSERT_EQUAL_STRING_MESSAGE(kLinkModules[y], row, "module row differs from reference"); + TEST_ASSERT_EQUAL_STRING_MESSAGE(reference[y], row, "module row differs from reference"); } } +static void test_pin_link_matches_reference_symbol() +{ + assertMatchesReference(kPinLink, 5, 2, kPinLinkModules); +} + +static void test_no_pin_link_matches_reference_symbol() +{ + assertMatchesReference(kNoPinLink, 4, 2, kNoPinLinkModules); +} + // Byte-mode capacity at ECC M: 14, 26, 42, 62, 84, 106 characters for versions 1-6. static void test_smallest_version_is_chosen_at_each_capacity_boundary() { @@ -96,7 +123,8 @@ void setup() { initializeTestEnvironment(); UNITY_BEGIN(); - RUN_TEST(test_connect_link_matches_reference_symbol); + RUN_TEST(test_pin_link_matches_reference_symbol); + RUN_TEST(test_no_pin_link_matches_reference_symbol); RUN_TEST(test_smallest_version_is_chosen_at_each_capacity_boundary); RUN_TEST(test_data_beyond_version_6_is_rejected); RUN_TEST(test_getModule_outside_symbol_is_light);