chore(core): add LED enable/disable functionality
What changed, and why it matters
This commit adds a user-facing setting to turn the device's RGB LED on or off. It is a routine feature addition (a 'chore') with no apparent security relevance. There are no changes that introduce memory corruption, bypass authentication, leak secrets, or alter cryptographic behavior.
No security action required. Treat as normal feature commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch extends the RGB LED HAL driver with enable/disable state tracking, exposes the control through a new MicroPython module (trezorio.rgb_led), adds a persistent storage flag (_DISABLE_RGB_LED), and wires a device-menu toggle in the UI. It also aligns LED color constants across firmware. The code is guarded by USE_RGB_LED feature flags and defaults to enabled. No security-sensitive logic is modified.
Changed components
core/embed/io/rgb_led drivercore/embed/sys/syscall RGB LED syscallscore/embed/upymod/modtrezorio rgb_led modulecore/src/apps/homescreen/device_menu.pycore/src/storage/device.pycore/src/boot.pyInspect captured patch +318 / −30
diff --git a/core/embed/io/rgb_led/inc/io/rgb_led.h b/core/embed/io/rgb_led/inc/io/rgb_led.h
index 8f12c004..824f5ce4 100644
--- a/core/embed/io/rgb_led/inc/io/rgb_led.h
+++ b/core/embed/io/rgb_led/inc/io/rgb_led.h
@@ -30,15 +30,22 @@ void rgb_led_init(void);
// Deinitialize RGB LED driver
void rgb_led_deinit(void);
-#endif
+#endif // KERNEL_MODE
-#define RGBLED_GREEN 0x00FF00
-#define RGBLED_RED 0xFF0000
-#define RGBLED_BLUE 0x0000FF
-#define RGBLED_YELLOW 0xFFFF00
+// Set RGB LED enabled state
+// enabled: true to enable, false to disable
+void rgb_led_set_enabled(bool enabled);
+
+// Get RGB LED enabled state
+bool rgb_led_get_enabled(void);
// Set RGB LED color
// color: 24-bit RGB color, 0x00RRGGBB
void rgb_led_set_color(uint32_t color);
+#define RGBLED_GREEN 0x040D04
+#define RGBLED_RED 0x640603
+#define RGBLED_BLUE 0x050532
+#define RGBLED_YELLOW 0x161000
+
#endif // TREZORHAL_RGB_LED_H
diff --git a/core/embed/io/rgb_led/stm32/rgb_led.c b/core/embed/io/rgb_led/stm32/rgb_led.c
index 5d78290d..ddff616d 100644
--- a/core/embed/io/rgb_led/stm32/rgb_led.c
+++ b/core/embed/io/rgb_led/stm32/rgb_led.c
@@ -12,6 +12,7 @@
typedef struct {
TIM_HandleTypeDef tim;
bool initialized;
+ bool enabled;
} rgb_led_t;
static rgb_led_t g_rgb_led = {0};
@@ -64,6 +65,7 @@ void rgb_led_init(void) {
HAL_TIM_PWM_Start(&drv->tim, TIM_CHANNEL_3);
drv->initialized = true;
+ drv->enabled = true;
}
void rgb_led_deinit(void) {
@@ -82,12 +84,41 @@ void rgb_led_deinit(void) {
drv->initialized = false;
}
+void rgb_led_set_enabled(bool enabled) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ // If the RGB LED is to be disabled, turn off the LED
+ if (!enabled) {
+ rgb_led_set_color(0);
+ }
+
+ drv->enabled = enabled;
+}
+
+bool rgb_led_get_enabled(void) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ return drv->enabled;
+}
+
void rgb_led_set_color(uint32_t color) {
rgb_led_t* drv = &g_rgb_led;
if (!drv->initialized) {
return;
}
+ if (!drv->enabled) {
+ return;
+ }
+
TIM4->CCR1 = ((color >> 16) & 0xFF) * TIMER_PERIOD / 255;
TIM4->CCR2 = ((color >> 8) & 0xFF) * TIMER_PERIOD / 255;
TIM4->CCR3 = (color & 0xFF) * TIMER_PERIOD / 255;
diff --git a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
index 7233bf65..5454794b 100644
--- a/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
+++ b/core/embed/io/rgb_led/stm32u5/rgb_led_lp.c
@@ -46,6 +46,7 @@ typedef struct {
LPTIM_HandleTypeDef tim_1;
LPTIM_HandleTypeDef tim_3;
bool initialized;
+ bool enabled;
} rgb_led_t;
static rgb_led_t g_rgb_led = {0};
@@ -164,6 +165,7 @@ void rgb_led_init(void) {
HAL_GPIO_Init(RGB_LED_BLUE_PORT, &GPIO_InitStructure);
drv->initialized = true;
+ drv->enabled = true;
}
void rgb_led_deinit(void) {
@@ -192,12 +194,41 @@ void rgb_led_deinit(void) {
memset(drv, 0, sizeof(*drv));
}
+void rgb_led_set_enabled(bool enabled) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return;
+ }
+
+ // If the RGB LED is to be disabled, turn off the LED
+ if (!enabled) {
+ rgb_led_set_color(0);
+ }
+
+ drv->enabled = enabled;
+}
+
+bool rgb_led_get_enabled(void) {
+ rgb_led_t* drv = &g_rgb_led;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ return drv->enabled;
+}
+
void rgb_led_set_color(uint32_t color) {
rgb_led_t* drv = &g_rgb_led;
if (!drv->initialized) {
return;
}
+ if (!drv->enabled) {
+ return;
+ }
+
uint32_t red = (color >> 16) & 0xFF;
uint32_t green = (color >> 8) & 0xFF;
uint32_t blue = color & 0xFF;
diff --git a/core/embed/io/rgb_led/unix/rgb_led.c b/core/embed/io/rgb_led/unix/rgb_led.c
index 1a856a54..b5542879 100644
--- a/core/embed/io/rgb_led/unix/rgb_led.c
+++ b/core/embed/io/rgb_led/unix/rgb_led.c
@@ -17,12 +17,76 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <trezor_rtl.h>
+
#include <io/rgb_led.h>
#include <io/unix/sdl_display.h>
#ifdef KERNEL_MODE
-void rgb_led_init(void){};
-void rgb_led_deinit(void){};
-#endif
-void rgb_led_set_color(uint32_t color) { display_rgb_led(color); }
+// Driver state
+typedef struct {
+ bool initialized;
+ bool enabled;
+} rgb_led_driver_t;
+
+// RGB LED driver instance
+static rgb_led_driver_t g_rgb_led_driver = {
+ .initialized = true,
+ .enabled = false,
+};
+
+void rgb_led_init(void) {
+ rgb_led_driver_t *driver = &g_rgb_led_driver;
+
+ // turn the LED off
+ rgb_led_set_color(0);
+
+ driver->initialized = true;
+ driver->enabled = true;
+}
+
+void rgb_led_deinit(void) {
+ rgb_led_driver_t *driver = &g_rgb_led_driver;
+
+ // turn the LED off
+ rgb_led_set_color(0);
+
+ memset(driver, 0, sizeof(rgb_led_driver_t));
+}
+
+void rgb_led_set_enabled(bool enabled) {
+ rgb_led_driver_t *driver = &g_rgb_led_driver;
+
+ if (!driver->initialized) {
+ return;
+ }
+
+ // If the RGB LED is to be disabled, turn off the LED
+ if (!enabled) {
+ rgb_led_set_color(0);
+ }
+
+ driver->enabled = enabled;
+}
+
+bool rgb_led_get_enabled(void) {
+ rgb_led_driver_t *driver = &g_rgb_led_driver;
+
+ if (!driver->initialized) {
+ return false;
+ }
+
+ return driver->enabled;
+}
+
+void rgb_led_set_color(uint32_t color) {
+ rgb_led_driver_t *driver = &g_rgb_led_driver;
+ if (!driver->initialized || !driver->enabled) {
+ return;
+ }
+
+ display_rgb_led(color);
+}
+
+#endif /* KERNEL_MODE */
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index e8d80142..91b2f60e 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -216,7 +216,9 @@ static secbool boot_sequence(void) {
if (state.charging_status == PM_BATTERY_CHARGING) {
// charing screen
- rgb_led_set_color(0x0000FF);
+#ifdef USE_RGB_LED
+ rgb_led_set_color(RGBLED_BLUE);
+#endif
} else {
if (!btn_down && !state.usb_connected && !state.wireless_connected) {
// device in just intended to be turned off
@@ -229,20 +231,24 @@ static secbool boot_sequence(void) {
}
}
+#ifdef USE_RGB_LED
rgb_led_set_color(0);
+#endif
while (pm_turn_on() != PM_OK) {
- rgb_led_set_color(0x400000);
+#ifdef USE_RGB_LED
+ rgb_led_set_color(RGBLED_RED);
systick_delay_ms(400);
rgb_led_set_color(0);
systick_delay_ms(400);
- rgb_led_set_color(0x400000);
+ rgb_led_set_color(RGBLED_RED);
systick_delay_ms(400);
rgb_led_set_color(0);
systick_delay_ms(400);
- rgb_led_set_color(0x400000);
+ rgb_led_set_color(RGBLED_RED);
systick_delay_ms(400);
rgb_led_set_color(0);
+#endif
pm_hibernate();
systick_delay_ms(1000);
reboot_to_off();
diff --git a/core/embed/projects/prodtest/main.c b/core/embed/projects/prodtest/main.c
index 90e9abd0..f5bc24b2 100644
--- a/core/embed/projects/prodtest/main.c
+++ b/core/embed/projects/prodtest/main.c
@@ -338,15 +338,19 @@ int prodtest_main(void) {
} else if (btn_event.event_type == BTN_EVENT_UP) {
if (ticks_expired(btn_deadline)) {
pm_hibernate();
+#ifdef USE_RGB_LED
rgb_led_set_color(RGBLED_YELLOW);
systick_delay_ms(1000);
rgb_led_set_color(0);
+#endif
}
}
}
}
if (button_is_down(BTN_POWER) && ticks_expired(btn_deadline)) {
+#ifdef USE_RGB_LED
rgb_led_set_color(RGBLED_RED);
+#endif
}
#endif
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 718b3fb4..e25ee4ff 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -402,6 +402,9 @@ static void _librust_qstrs(void) {
MP_QSTR_language__changed;
MP_QSTR_language__progress;
MP_QSTR_language__title;
+ MP_QSTR_led__disable;
+ MP_QSTR_led__enable;
+ MP_QSTR_led__title;
MP_QSTR_led_enabled;
MP_QSTR_lines;
MP_QSTR_load_from_flash;
diff --git a/core/embed/rust/src/translations/generated/translated_string.rs b/core/embed/rust/src/translations/generated/translated_string.rs
index bf413e7f..05c4e796 100644
--- a/core/embed/rust/src/translations/generated/translated_string.rs
+++ b/core/embed/rust/src/translations/generated/translated_string.rs
@@ -1478,6 +1478,9 @@ pub enum TranslatedString {
homescreen__firmware_type = 1094, // "Firmware type"
words__off = 1095, // "OFF"
words__on = 1096, // "ON"
+ led__disable = 1097, // "Disable LED?"
+ led__enable = 1098, // "Enable LED?"
+ led__title = 1099, // "LED"
}
impl TranslatedString {
@@ -3275,6 +3278,9 @@ impl TranslatedString {
(Self::homescreen__firmware_type, "Firmware type"),
(Self::words__off, "OFF"),
(Self::words__on, "ON"),
+ (Self::led__disable, "Disable LED?"),
+ (Self::led__enable, "Enable LED?"),
+ (Self::led__title, "LED"),
];
#[cfg(feature = "micropython")]
@@ -3939,6 +3945,9 @@ impl TranslatedString {
(Qstr::MP_QSTR_language__changed, Self::language__changed),
(Qstr::MP_QSTR_language__progress, Self::language__progress),
(Qstr::MP_QSTR_language__title, Self::language__title),
+ (Qstr::MP_QSTR_led__disable, Self::led__disable),
+ (Qstr::MP_QSTR_led__enable, Self::led__enable),
+ (Qstr::MP_QSTR_led__title, Self::led__title),
(Qstr::MP_QSTR_lockscreen__tap_to_connect, Self::lockscreen__tap_to_connect),
(Qstr::MP_QSTR_lockscreen__tap_to_unlock, Self::lockscreen__tap_to_unlock),
(Qstr::MP_QSTR_lockscreen__title_locked, Self::lockscreen__title_locked),
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
index 031e3ce8..55bd62ef 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
@@ -203,7 +203,7 @@ impl DeviceMenuScreen {
device_name: Option<TString<'static>>,
_screen_brightness: Option<TString<'static>>,
_haptic_feedback: Option<bool>,
- led: Option<bool>,
+ led_enabled: Option<bool>,
about_items: Obj,
) -> Result<Self, Error> {
let mut screen = Self {
@@ -219,7 +219,8 @@ impl DeviceMenuScreen {
let about = screen.add_subscreen(Subscreen::AboutScreen);
let regulatory = screen.add_subscreen(Subscreen::RegulatoryScreen);
let security = screen.add_security_menu();
- let device = screen.add_device_menu(device_name, regulatory, about, auto_lock_delay, led);
+ let device =
+ screen.add_device_menu(device_name, regulatory, about, auto_lock_delay, led_enabled);
let settings = screen.add_settings_menu(security, device);
let is_connected = !paired_devices.is_empty(); // FIXME after BLE API has this
@@ -322,7 +323,7 @@ impl DeviceMenuScreen {
regulatory_index: usize,
about_index: usize,
auto_lock_delay: Option<TString<'static>>,
- led: Option<bool>,
+ led_enabled: Option<bool>,
) -> usize {
let mut items: Vec<MenuItem, MEDIUM_MENU_ITEMS> = Vec::new();
if let Some(device_name) = device_name {
@@ -348,18 +349,17 @@ impl DeviceMenuScreen {
unwrap!(items.push(autolock_delay_item));
}
- if let Some(led) = led {
+ if let Some(led_enabled) = led_enabled {
let mut led_item = MenuItem::new(
TR::words__led.into(),
Some(Action::Return(DeviceMenuMsg::LedEnabled)),
);
- let subtext = if led {
- (
+ let subtext = match led_enabled {
+ true => (
TR::words__on.into(),
Some(&theme::TEXT_MENU_ITEM_SUBTITLE_GREEN),
- )
- } else {
- (TR::words__off.into(), None)
+ ),
+ _ => (TR::words__off.into(), None),
};
led_item.with_subtext(Some(subtext));
unwrap!(items.push(led_item));
diff --git a/core/embed/sys/syscall/inc/sys/syscall_numbers.h b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
index 183fa0e4..61fa289f 100644
--- a/core/embed/sys/syscall/inc/sys/syscall_numbers.h
+++ b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -105,6 +105,8 @@ typedef enum {
SYSCALL_TOUCH_GET_EVENT,
+ SYSCALL_RGB_LED_SET_ENABLED,
+ SYSCALL_RGB_LED_GET_ENABLED,
SYSCALL_RGB_LED_SET_COLOR,
SYSCALL_HAPTIC_SET_ENABLED,
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index e93cf857..269787dc 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -454,6 +454,15 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
#endif
#ifdef USE_RGB_LED
+ case SYSCALL_RGB_LED_SET_ENABLED: {
+ bool enabled = (args[0] != 0);
+ rgb_led_set_enabled(enabled);
+ } break;
+
+ case SYSCALL_RGB_LED_GET_ENABLED: {
+ args[0] = rgb_led_get_enabled();
+ } break;
+
case SYSCALL_RGB_LED_SET_COLOR: {
uint32_t color = args[0];
rgb_led_set_color(color);
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index 12e2150e..5802ca0b 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -420,6 +420,15 @@ uint32_t touch_get_event(void) {
#ifdef USE_RGB_LED
#include <io/rgb_led.h>
+
+void rgb_led_set_enabled(bool enabled) {
+ syscall_invoke1((uint32_t)enabled, SYSCALL_RGB_LED_SET_ENABLED);
+}
+
+bool rgb_led_get_enabled(void) {
+ return (bool)syscall_invoke0(SYSCALL_RGB_LED_GET_ENABLED);
+}
+
void rgb_led_set_color(uint32_t color) {
syscall_invoke1(color, SYSCALL_RGB_LED_SET_COLOR);
}
diff --git a/core/embed/upymod/modtrezorio/modtrezorio-rgb_led.h b/core/embed/upymod/modtrezorio/modtrezorio-rgb_led.h
new file mode 100644
index 00000000..1489dbe7
--- /dev/null
+++ b/core/embed/upymod/modtrezorio/modtrezorio-rgb_led.h
@@ -0,0 +1,47 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <io/rgb_led.h>
+
+/// package: trezorio.rgb_led
+
+/// def rgb_led_set_enabled(enable: bool) -> None:
+/// """
+/// Enable/Disable the RGB LED.
+/// """
+STATIC mp_obj_t mod_trezorio_rgb_led_set_enabled(mp_obj_t enable) {
+ rgb_led_set_enabled(mp_obj_is_true(enable));
+ return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mod_trezorio_rgb_led_set_enabled_obj,
+ mod_trezorio_rgb_led_set_enabled);
+
+STATIC const mp_rom_map_elem_t mod_trezorio_rgb_led_globals_table[] = {
+ {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_rgb_led)},
+ {MP_ROM_QSTR(MP_QSTR_rgb_led_set_enabled),
+ MP_ROM_PTR(&mod_trezorio_rgb_led_set_enabled_obj)},
+
+};
+STATIC MP_DEFINE_CONST_DICT(mod_trezorio_rgb_led_globals,
+ mod_trezorio_rgb_led_globals_table);
+
+STATIC const mp_obj_module_t mod_trezorio_rgb_led_module = {
+ .base = {&mp_type_module},
+ .globals = (mp_obj_dict_t *)&mod_trezorio_rgb_led_globals,
+};
diff --git a/core/embed/upymod/modtrezorio/modtrezorio.c b/core/embed/upymod/modtrezorio/modtrezorio.c
index dd2ba548..479a58a4 100644
--- a/core/embed/upymod/modtrezorio/modtrezorio.c
+++ b/core/embed/upymod/modtrezorio/modtrezorio.c
@@ -59,12 +59,15 @@ uint32_t last_touch_sample_time = 0;
#ifdef USE_HAPTIC
#include "modtrezorio-haptic.h"
#endif
+#ifdef USE_RGB_LED
+#include "modtrezorio-rgb_led.h"
+#endif
#ifdef USE_POWER_MANAGER
#include "modtrezorio-pm.h"
#endif
/// package: trezorio.__init__
-/// from . import fatfs, haptic, sdcard, ble, pm
+/// from . import fatfs, haptic, sdcard, ble, pm, rgb_led
/// POLL_READ: int # wait until interface is readable and return read data
/// POLL_WRITE: int # wait until interface is writable
@@ -101,6 +104,10 @@ STATIC const mp_rom_map_elem_t mp_module_trezorio_globals_table[] = {
{MP_ROM_QSTR(MP_QSTR_haptic), MP_ROM_PTR(&mod_trezorio_haptic_module)},
#endif
+#ifdef USE_RGB_LED
+ {MP_ROM_QSTR(MP_QSTR_rgb_led), MP_ROM_PTR(&mod_trezorio_rgb_led_module)},
+#endif
+
#ifdef USE_BLE
{MP_ROM_QSTR(MP_QSTR_BLE_EVENT), MP_ROM_INT(SYSHANDLE_BLE)},
#endif
diff --git a/core/embed/upymod/modtrezorutils/modtrezorutils.c b/core/embed/upymod/modtrezorutils/modtrezorutils.c
index 9c3e3c07..1e224f51 100644
--- a/core/embed/upymod/modtrezorutils/modtrezorutils.c
+++ b/core/embed/upymod/modtrezorutils/modtrezorutils.c
@@ -603,6 +603,8 @@ STATIC mp_obj_tuple_t mod_trezorutils_version_obj = {
/// """Whether the hardware supports backlight brightness control."""
/// USE_HAPTIC: bool
/// """Whether the hardware supports haptic feedback."""
+/// USE_RGB_LED: bool
+/// """Whether the hardware supports RGB LED."""
/// USE_OPTIGA: bool
/// """Whether the hardware supports Optiga secure element."""
/// USE_TROPIC: bool
@@ -709,6 +711,11 @@ STATIC const mp_rom_map_elem_t mp_module_trezorutils_globals_table[] = {
#else
{MP_ROM_QSTR(MP_QSTR_USE_HAPTIC), mp_const_false},
#endif
+#ifdef USE_RGB_LED
+ {MP_ROM_QSTR(MP_QSTR_USE_RGB_LED), mp_const_true},
+#else
+ {MP_ROM_QSTR(MP_QSTR_USE_RGB_LED), mp_const_false},
+#endif
#ifdef USE_OPTIGA
{MP_ROM_QSTR(MP_QSTR_USE_OPTIGA), mp_const_true},
#else
diff --git a/core/mocks/generated/trezorio/__init__.pyi b/core/mocks/generated/trezorio/__init__.pyi
index 287fd95e..1c136e10 100644
--- a/core/mocks/generated/trezorio/__init__.pyi
+++ b/core/mocks/generated/trezorio/__init__.pyi
@@ -166,7 +166,7 @@ class WebUSB:
"""Length of one USB RX packet."""
TX_PACKET_LEN: ClassVar[int]
"""Length of one USB TX packet."""
-from . import fatfs, haptic, sdcard, ble, pm
+from . import fatfs, haptic, sdcard, ble, pm, rgb_led
POLL_READ: int # wait until interface is readable and return read data
POLL_WRITE: int # wait until interface is writable
diff --git a/core/mocks/generated/trezorio/rgb_led.pyi b/core/mocks/generated/trezorio/rgb_led.pyi
new file mode 100644
index 00000000..8844686a
--- /dev/null
+++ b/core/mocks/generated/trezorio/rgb_led.pyi
@@ -0,0 +1,8 @@
+from typing import *
+
+
+# upymod/modtrezorio/modtrezorio-rgb_led.h
+def rgb_led_set_enabled(enable: bool) -> None:
+ """
+ Enable/Disable the RGB LED.
+ """
diff --git a/core/mocks/generated/trezorutils.pyi b/core/mocks/generated/trezorutils.pyi
index 3798ac5f..a45faed5 100644
--- a/core/mocks/generated/trezorutils.pyi
+++ b/core/mocks/generated/trezorutils.pyi
@@ -191,6 +191,8 @@ USE_BACKLIGHT: bool
"""Whether the hardware supports backlight brightness control."""
USE_HAPTIC: bool
"""Whether the hardware supports haptic feedback."""
+USE_RGB_LED: bool
+"""Whether the hardware supports RGB LED."""
USE_OPTIGA: bool
"""Whether the hardware supports Optiga secure element."""
USE_TROPIC: bool
diff --git a/core/mocks/trezortranslate_keys.pyi b/core/mocks/trezortranslate_keys.pyi
index db942c6d..464af9cd 100644
--- a/core/mocks/trezortranslate_keys.pyi
+++ b/core/mocks/trezortranslate_keys.pyi
@@ -433,6 +433,9 @@ class TR:
language__changed: str = "Language changed successfully"
language__progress: str = "Changing language"
language__title: str = "Language settings"
+ led__disable: str = "Disable LED?"
+ led__enable: str = "Enable LED?"
+ led__title: str = "LED"
lockscreen__tap_to_connect: str = "Tap to connect"
lockscreen__tap_to_unlock: str = "Tap to unlock"
lockscreen__title_locked: str = "Locked"
diff --git a/core/src/apps/homescreen/device_menu.py b/core/src/apps/homescreen/device_menu.py
index c8a65a95..ae56c9f8 100644
--- a/core/src/apps/homescreen/device_menu.py
+++ b/core/src/apps/homescreen/device_menu.py
@@ -69,7 +69,11 @@ async def handle_device_menu() -> None:
device_name=device_name,
screen_brightness=None, # TODO implement
haptic_feedback=None, # TODO implement
- led_enabled=None, # TODO implement
+ led_enabled=(
+ storage_device.get_rgb_led()
+ if (storage_device.is_initialized() and utils.USE_RGB_LED)
+ else None
+ ),
about_items=[
(TR.homescreen__firmware_version, firmware_version, False),
(TR.homescreen__firmware_type, firmware_type, False),
@@ -141,7 +145,18 @@ async def handle_device_menu() -> None:
elif menu_result is DeviceMenuResult.HapticFeedback:
pass # TODO implement haptic feedback handling
elif menu_result is DeviceMenuResult.LedEnabled:
- pass # TODO implement led handling
+ from trezor import io
+ from trezor.ui.layouts import confirm_action
+
+ enable = not storage_device.get_rgb_led()
+ await confirm_action(
+ "led__settings",
+ TR.led__title,
+ TR.led__enable if enable else TR.led__disable,
+ )
+
+ io.rgb_led.rgb_led_set_enabled(enable)
+ storage_device.set_rgb_led(enable)
elif menu_result is DeviceMenuResult.WipeDevice:
from trezor.messages import WipeDevice
diff --git a/core/src/boot.py b/core/src/boot.py
index 051aa360..3a0cdedf 100644
--- a/core/src/boot.py
+++ b/core/src/boot.py
@@ -65,6 +65,8 @@ async def bootscreen() -> None:
ui.display.orientation(storage.device.get_rotation())
if utils.USE_HAPTIC:
io.haptic.haptic_set_enabled(storage.device.get_haptic_feedback())
+ if utils.USE_RGB_LED:
+ io.rgb_led.rgb_led_set_enabled(storage.device.get_rgb_led())
lockscreen = Lockscreen(
label=storage.device.get_label(), bootscreen=True
)
diff --git a/core/src/storage/device.py b/core/src/storage/device.py
index 1aca3249..668d6c24 100644
--- a/core/src/storage/device.py
+++ b/core/src/storage/device.py
@@ -41,6 +41,7 @@ if utils.USE_THP:
# unused from python:
# _BRIGHTNESS = const(0x19) # int
_DISABLE_HAPTIC_FEEDBACK = const(0x20) # bool (0x01 or empty)
+_DISABLE_RGB_LED = const(0x21) # bool (0x01 or empty)
SAFETY_CHECK_LEVEL_STRICT : Literal[0] = const(0)
@@ -393,3 +394,17 @@ def get_haptic_feedback() -> bool:
Get haptic feedback enable, default to true if not set.
"""
return not common.get_bool(_NAMESPACE, _DISABLE_HAPTIC_FEEDBACK, True)
+
+
+def set_rgb_led(enable: bool) -> None:
+ """
+ Enable or disable RGB LED.
+ """
+ common.set_bool(_NAMESPACE, _DISABLE_RGB_LED, not enable, True)
+
+
+def get_rgb_led() -> bool:
+ """
+ Get RGB LED enable, default to true if not set.
+ """
+ return not common.get_bool(_NAMESPACE, _DISABLE_RGB_LED, True)
diff --git a/core/src/trezor/utils.py b/core/src/trezor/utils.py
index fa104637..cec8e0d2 100644
--- a/core/src/trezor/utils.py
+++ b/core/src/trezor/utils.py
@@ -17,6 +17,7 @@ from trezorutils import ( # noqa: F401
USE_HAPTIC,
USE_OPTIGA,
USE_POWER_MANAGER,
+ USE_RGB_LED,
USE_SD_CARD,
USE_THP,
USE_TOUCH,
diff --git a/core/translations/en.json b/core/translations/en.json
index df929ab0..e642da98 100644
--- a/core/translations/en.json
+++ b/core/translations/en.json
@@ -500,6 +500,9 @@
"language__changed": "Language changed successfully",
"language__progress": "Changing language",
"language__title": "Language settings",
+ "led__disable": "Disable LED?",
+ "led__enable": "Enable LED?",
+ "led__title": "LED",
"lockscreen__tap_to_connect": "Tap to connect",
"lockscreen__tap_to_unlock": "Tap to unlock",
"lockscreen__title_locked": "Locked",
diff --git a/core/translations/order.json b/core/translations/order.json
index 8f138ae1..19d634a7 100644
--- a/core/translations/order.json
+++ b/core/translations/order.json
@@ -1095,5 +1095,8 @@
"1093": "ble__version",
"1094": "homescreen__firmware_type",
"1095": "words__off",
- "1096": "words__on"
+ "1096": "words__on",
+ "1097": "led__disable",
+ "1098": "led__enable",
+ "1099": "led__title"
}
diff --git a/core/translations/signatures.json b/core/translations/signatures.json
index 098f3601..6b4f7588 100644
--- a/core/translations/signatures.json
+++ b/core/translations/signatures.json
@@ -1,8 +1,8 @@
{
"current": {
- "merkle_root": "23b5c97117589855f7bdda8f991c369f1692f32e1ecc2cb6f94fc7f8a66b137d",
- "datetime": "2025-08-24T18:25:25.894084+00:00",
- "commit": "e20b161bf1df9f3da1b8329310e3d9668b21f07a"
+ "merkle_root": "6bbc691017a335ab66c3f6c3cda05395a86d6cd6d6778d3e28eb27c73dccd387",
+ "datetime": "2025-08-25T08:56:21.339747+00:00",
+ "commit": "13c21082d0c6e9ff634e54f2a31986806f6e8288"
},
"history": [
{
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.