1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-06-26 09:52:34 +00:00

feat(core): BLE pairing flow

[no changelog]
This commit is contained in:
Martin Milata 2025-04-08 17:05:30 +02:00
parent f077f2d1c1
commit c3afe4c67b
7 changed files with 80 additions and 20 deletions

View File

@ -1,4 +1,5 @@
use crate::{ use crate::{
strutil::TString,
translations::TR, translations::TR,
ui::{ ui::{
component::{Component, Event, EventCtx, Timeout}, component::{Component, Event, EventCtx, Timeout},
@ -128,6 +129,14 @@ impl ActionBar {
Self::new(Mode::PaginateOnly, None, None, None) Self::new(Mode::PaginateOnly, None, None, None)
} }
pub fn new_text_only(text: TString<'static>) -> Self {
Self::new_single(
Button::with_text(text)
.styled(theme::button_always_disabled())
.initially_enabled(false),
)
}
pub fn with_left_short(mut self, short: bool) -> Self { pub fn with_left_short(mut self, short: bool) -> Self {
if let Mode::Double { ref mut left_short } = self.mode { if let Mode::Double { ref mut left_short } = self.mode {
*left_short = short; *left_short = short;

View File

@ -541,6 +541,22 @@ pub const fn button_keyboard_next() -> ButtonStyleSheet {
} }
} }
// Things that look like button but don't do anything.
pub const fn button_always_disabled() -> ButtonStyleSheet {
let style = &ButtonStyle {
font: fonts::FONT_SATOSHI_MEDIUM_26,
text_color: GREY_LIGHT,
button_color: BG,
icon_color: GREY_LIGHT,
background_color: BG,
};
ButtonStyleSheet {
normal: style,
active: style,
disabled: style,
}
}
pub const fn input_mnemonic() -> ButtonStyleSheet { pub const fn input_mnemonic() -> ButtonStyleSheet {
ButtonStyleSheet { ButtonStyleSheet {
normal: &ButtonStyle { normal: &ButtonStyle {

View File

@ -1048,9 +1048,7 @@ impl FirmwareUI for UIEckhart {
ops = ops.text(text, font); ops = ops.text(text, font);
let screen = TextScreen::new(FormattedText::new(ops)) let screen = TextScreen::new(FormattedText::new(ops))
.with_header(Header::new("Pair with new device".into()).with_close_button()) .with_header(Header::new("Pair with new device".into()).with_close_button())
.with_action_bar(ActionBar::new_single(Button::with_text( .with_action_bar(ActionBar::new_text_only("Continue on host".into()));
"Continue on host".into(),
)));
#[cfg(feature = "ble")] #[cfg(feature = "ble")]
let screen = crate::ui::component::BLEHandler::new(screen, true); let screen = crate::ui::component::BLEHandler::new(screen, true);
let layout = RootComponent::new(screen); let layout = RootComponent::new(screen);

View File

@ -124,6 +124,7 @@ Q(apps.management.apply_flags)
Q(apps.management.apply_settings) Q(apps.management.apply_settings)
Q(apps.management.authenticate_device) Q(apps.management.authenticate_device)
Q(apps.management.backup_device) Q(apps.management.backup_device)
Q(apps.management.ble)
Q(apps.management.ble.pair_new_device) Q(apps.management.ble.pair_new_device)
Q(apps.management.ble.unpair) Q(apps.management.ble.unpair)
Q(apps.management.change_language) Q(apps.management.change_language)
@ -168,6 +169,7 @@ Q(benchmark)
Q(benchmarks) Q(benchmarks)
Q(bitcoin) Q(bitcoin)
Q(bitcoinlike) Q(bitcoinlike)
Q(ble)
Q(bolt) Q(bolt)
Q(boot) Q(boot)
Q(cache) Q(cache)

View File

@ -1,6 +1,7 @@
import storage.device import storage.device
import trezorble as ble
import trezorui_api import trezorui_api
from trezor import TR, config, utils from trezor import TR, config, log, utils
from trezor.ui.layouts import interact from trezor.ui.layouts import interact
from trezor.wire import ActionCancelled from trezor.wire import ActionCancelled
from trezorui_api import DeviceMenuResult from trezorui_api import DeviceMenuResult
@ -36,13 +37,19 @@ async def handle_device_menu() -> None:
) )
# MOCK DATA # MOCK DATA
battery_percentage = 22 battery_percentage = 22
paired_devices = ["Trezor Suite"] paired_devices = ["Trezor Suite"] if ble.is_connected() else []
# ### # ###
firmware_version = ".".join(map(str, utils.VERSION)) firmware_version = ".".join(map(str, utils.VERSION))
device_name = storage.device.get_label() or "Trezor" device_name = storage.device.get_label() or "Trezor"
auto_lock_ms = storage.device.get_autolock_delay_ms() auto_lock_ms = storage.device.get_autolock_delay_ms()
auto_lock_delay = strings.format_autolock_duration(auto_lock_ms) auto_lock_delay = strings.format_autolock_duration(auto_lock_ms)
log.debug(
__name__,
f"device menu, BLE state: {ble.connection_flags()} (peers: {ble.peer_count()})",
)
menu_result = await interact( menu_result = await interact(
trezorui_api.show_device_menu( trezorui_api.show_device_menu(
failed_backup=failed_backup, failed_backup=failed_backup,
@ -79,9 +86,11 @@ async def handle_device_menu() -> None:
# It's a tuple with (result_type, index) # It's a tuple with (result_type, index)
result_type, index = menu_result result_type, index = menu_result
if result_type is DeviceMenuResult.DeviceDisconnect: if result_type is DeviceMenuResult.DeviceDisconnect:
raise RuntimeError( from trezor.messages import BleUnpair
f"Device disconnect not implemented, device index: {index}"
) from apps.management.ble.unpair import unpair
await unpair(BleUnpair(all=False)) # FIXME we can only unpair current
else: else:
raise RuntimeError(f"Unknown menu {result_type}, {index}") raise RuntimeError(f"Unknown menu {result_type}, {index}")
else: else:

View File

View File

@ -1,17 +1,43 @@
import trezorble as ble
import trezorui_api import trezorui_api
from trezor.ui.layouts import interact from storage import device as storage_device
from trezor import utils
from trezor.ui.layouts import CONFIRMED, raise_if_not_confirmed
from trezor.wire import ActionCancelled
def _end_pairing() -> None:
if ble.peer_count() > 0:
ble.start_advertising(True, storage_device.get_label())
else:
ble.stop_advertising()
async def pair_new_device() -> None: async def pair_new_device() -> None:
label = "Trezor T3W1" label = storage_device.get_label() or utils.MODEL_FULL_NAME
await interact( ble.start_advertising(False, label)
trezorui_api.show_pairing_device_name(device_name=label), try:
code = await raise_if_not_confirmed(
trezorui_api.show_pairing_device_name(
device_name=label,
),
None, None,
raise_on_cancel=None, # for UI testing
) )
if not isinstance(code, int):
raise ActionCancelled
code = 12345 try:
await interact( result = await raise_if_not_confirmed(
trezorui_api.show_pairing_code(code=f"{code:0>6}"), trezorui_api.show_pairing_code(
code=f"{code:0>6}",
),
None, None,
) )
except Exception:
ble.reject_pairing()
raise
else:
if result is CONFIRMED:
ble.allow_pairing(code)
finally:
_end_pairing()