Skip to content

Commit b87003c

Browse files
committed
fw: Add dynamic backlight feature with ALS-based intensity control
Implements user-toggleable dynamic backlight that automatically adjusts backlight intensity based on ambient light sensor readings. When enabled, backlight intensity scales linearly between 5% (Low) and the user's configured max intensity based on ambient light condition. Note: Threshold values (20/120) are initial estimates and will likely need to be adjusted based on real-world testing and user feedback Signed-off-by: Joshua Jun <joshuajun@proton.me>
1 parent 89bf800 commit b87003c

8 files changed

Lines changed: 140 additions & 3 deletions

File tree

platform/platform_capabilities.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
'HAS_FLASH_OTP',
6060
'HAS_VIBE_AW86225',
6161
'HAS_PBLBOOT',
62+
'HAS_DYNAMIC_BACKLIGHT',
6263
}
6364

6465
board_capability_dicts = [
@@ -365,6 +366,7 @@
365366
'HAS_ALS_W1160',
366367
'HAS_MAGNETOMETER',
367368
'HAS_PBLBOOT',
369+
'HAS_DYNAMIC_BACKLIGHT',
368370
},
369371
},
370372
]

src/fw/apps/system_apps/settings/settings_display.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ enum SettingsDisplayItem {
166166
SettingsDisplayBacklightMode,
167167
SettingsDisplayMotionSensor,
168168
SettingsDisplayAmbientSensor,
169+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
170+
SettingsDisplayDynamicIntensity,
171+
#endif
169172
SettingsDisplayBacklightIntensity,
170173
SettingsDisplayBacklightTimeout,
171174
#if PLATFORM_SPALDING
@@ -208,6 +211,11 @@ static void prv_select_click_cb(SettingsCallbacks *context, uint16_t row) {
208211
case SettingsDisplayAmbientSensor:
209212
light_toggle_ambient_sensor_enabled();
210213
break;
214+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
215+
case SettingsDisplayDynamicIntensity:
216+
backlight_set_dynamic_intensity_enabled(!backlight_is_dynamic_intensity_enabled());
217+
break;
218+
#endif
211219
case SettingsDisplayBacklightIntensity:
212220
prv_intensity_menu_push(data);
213221
break;
@@ -266,8 +274,26 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
266274
subtitle = i18n_noop("Off");
267275
}
268276
break;
277+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
278+
case SettingsDisplayDynamicIntensity:
279+
title = i18n_noop("Dynamic Backlight");
280+
if (backlight_is_dynamic_intensity_enabled()) {
281+
subtitle = i18n_noop("On");
282+
} else {
283+
subtitle = i18n_noop("Off");
284+
}
285+
break;
286+
#endif
269287
case SettingsDisplayBacklightIntensity:
288+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
289+
if (backlight_is_dynamic_intensity_enabled()) {
290+
title = i18n_noop("Max Intensity");
291+
} else {
292+
title = i18n_noop("Intensity");
293+
}
294+
#else
270295
title = i18n_noop("Intensity");
296+
#endif
271297
subtitle = s_intensity_labels[prv_intensity_get_selection_index()];
272298
break;
273299
case SettingsDisplayBacklightTimeout:

src/fw/board/board_sf32lb52.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ typedef struct {
131131
//ambient light config
132132
uint32_t ambient_light_dark_threshold;
133133
uint32_t ambient_k_delta_threshold;
134+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
135+
//dynamic backlight thresholds
136+
uint32_t dynamic_backlight_min_threshold;
137+
uint32_t dynamic_backlight_max_threshold;
138+
#endif
134139
} BoardConfig;
135140

136141
typedef struct {

src/fw/board/boards/board_obelix.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,10 @@ const BoardConfig BOARD_CONFIG = {
546546
.backlight_on_percent = 25,
547547
.ambient_light_dark_threshold = 150,
548548
.ambient_k_delta_threshold = 25,
549+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
550+
.dynamic_backlight_min_threshold = 20,
551+
.dynamic_backlight_max_threshold = 120,
552+
#endif
549553
};
550554

551555
const BoardConfigButton BOARD_CONFIG_BUTTON = {

src/fw/services/common/light.c

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ static bool s_user_controlled_state;
8585
//! For temporary disabling backlight (ie: low power mode)
8686
static bool s_backlight_allowed = false;
8787

88+
//! Cached ambient light level captured when backlight turns on (to avoid feedback from backlight illuminating sensor)
89+
static uint32_t s_cached_ambient_light_level = 0;
90+
91+
//! Starting intensity for fade-out (captured when fade begins)
92+
static uint16_t s_fade_start_intensity = 0;
93+
94+
//! Fade step size (calculated once at start of fade to avoid rounding jitter)
95+
static uint16_t s_fade_step_size = 0;
96+
8897
//! Mutex to guard all the above state. We have a pattern of taking the lock in the public functions and assuming
8998
//! it's already taken in the prv_ functions.
9099
static PebbleMutex *s_mutex;
@@ -100,11 +109,54 @@ static void light_timer_callback(void *data) {
100109
static uint16_t prv_backlight_get_intensity(void) {
101110
// low_power_mode backlight intensity (25% of max brightness)
102111
const uint16_t backlight_low_power_intensity = (BACKLIGHT_BRIGHTNESS_MAX * (uint32_t)25) / 100;
103-
return low_power_is_active() ? backlight_low_power_intensity : backlight_get_intensity();
112+
113+
if (low_power_is_active()) {
114+
return backlight_low_power_intensity;
115+
}
116+
117+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT && !defined(RECOVERY_FW)
118+
// Dynamic backlight: adjust intensity based on ambient light sensor
119+
if (backlight_is_dynamic_intensity_enabled()) {
120+
// Use cached light level to avoid feedback from backlight illuminating the sensor
121+
uint32_t light_level = s_cached_ambient_light_level;
122+
uint16_t user_max_intensity = backlight_get_intensity();
123+
124+
// Low intensity is always 5% (the "Low" setting)
125+
const uint16_t low_intensity = (BACKLIGHT_BRIGHTNESS_MAX * (uint32_t)5) / 100;
126+
127+
// Get thresholds from board config
128+
const uint32_t min_light_threshold = BOARD_CONFIG.dynamic_backlight_min_threshold;
129+
const uint32_t max_light_threshold = BOARD_CONFIG.dynamic_backlight_max_threshold;
130+
131+
// If below minimum threshold, return low intensity
132+
if (light_level < min_light_threshold) {
133+
return low_intensity;
134+
}
135+
136+
// Clamp light level to max threshold
137+
if (light_level > max_light_threshold) {
138+
light_level = max_light_threshold;
139+
}
140+
141+
// Scale linearly from low_intensity to user_max_intensity based on ambient light
142+
// Adjusted to start scaling from min_light_threshold
143+
uint32_t dynamic_intensity = low_intensity +
144+
((user_max_intensity - low_intensity) * (light_level - min_light_threshold)) /
145+
(max_light_threshold - min_light_threshold);
146+
147+
return (uint16_t)dynamic_intensity;
148+
}
149+
#endif
150+
151+
return backlight_get_intensity();
104152
}
105153

106154
static void prv_change_brightness(int32_t new_brightness) {
107-
const uint16_t HALF_BRIGHTNESS = (prv_backlight_get_intensity() - BACKLIGHT_BRIGHTNESS_OFF) / 2;
155+
// Use fade start intensity during fading, otherwise get current intensity
156+
uint16_t reference_intensity = (s_light_state == LIGHT_STATE_ON_FADING && s_fade_start_intensity > 0)
157+
? s_fade_start_intensity
158+
: prv_backlight_get_intensity();
159+
const uint16_t HALF_BRIGHTNESS = (reference_intensity - BACKLIGHT_BRIGHTNESS_OFF) / 2;
108160

109161
// update the debug stats
110162
if (new_brightness > HALF_BRIGHTNESS && s_current_brightness <= HALF_BRIGHTNESS) {
@@ -126,10 +178,19 @@ static void prv_change_brightness(int32_t new_brightness) {
126178
}
127179

128180
static void prv_change_state(BacklightState new_state) {
181+
BacklightState old_state = s_light_state;
129182
s_light_state = new_state;
130183

184+
// Capture ambient light level when transitioning from OFF to ON states
185+
// This prevents feedback from the backlight illuminating the sensor
186+
if ((new_state == LIGHT_STATE_ON || new_state == LIGHT_STATE_ON_TIMED) &&
187+
s_current_brightness == BACKLIGHT_BRIGHTNESS_OFF) {
188+
s_cached_ambient_light_level = ambient_light_get_light_level();
189+
}
190+
131191
// Calculate the new brightness and reset any timers based on our state.
132192
int32_t new_brightness = 0;
193+
133194
switch (new_state) {
134195
case LIGHT_STATE_ON:
135196
new_brightness = prv_backlight_get_intensity();
@@ -143,7 +204,12 @@ static void prv_change_state(BacklightState new_state) {
143204
light_timer_callback, NULL, 0 /* flags */);
144205
break;
145206
case LIGHT_STATE_ON_FADING:
146-
new_brightness = s_current_brightness - (prv_backlight_get_intensity() / LIGHT_FADE_STEPS);
207+
// Capture the starting intensity only when we first enter fading state
208+
if (old_state != LIGHT_STATE_ON_FADING) {
209+
s_fade_start_intensity = s_current_brightness;
210+
s_fade_step_size = s_fade_start_intensity / LIGHT_FADE_STEPS;
211+
}
212+
new_brightness = s_current_brightness - s_fade_step_size;
147213

148214
if (new_brightness <= BACKLIGHT_BRIGHTNESS_OFF) {
149215
// Done fading!
@@ -193,6 +259,9 @@ void light_init(void) {
193259
s_timer_id = new_timer_create();
194260
s_num_buttons_down = 0;
195261
s_user_controlled_state = false;
262+
s_cached_ambient_light_level = 0;
263+
s_fade_start_intensity = 0;
264+
s_fade_step_size = 0;
196265
s_mutex = mutex_create();
197266
}
198267

src/fw/shell/normal/prefs.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ static bool s_backlight_motion_enabled = true;
7878
#define PREF_KEY_MOTION_SENSITIVITY "motionSensitivity"
7979
static uint8_t s_motion_sensitivity = 100; // Default to maximum sensitivity (100%)
8080

81+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
82+
#define PREF_KEY_BACKLIGHT_DYNAMIC_INTENSITY "lightDynamicIntensity"
83+
static bool s_backlight_dynamic_intensity_enabled = false;
84+
#endif
85+
8186
#define PREF_KEY_BACKLIGHT_AMBIENT_THRESHOLD "lightAmbientThreshold"
8287
static uint32_t s_backlight_ambient_threshold = 0; // default set from board config in shell_prefs_init()
8388

@@ -278,6 +283,13 @@ static bool prv_set_s_backlight_motion_enabled(bool *enabled) {
278283
return true;
279284
}
280285

286+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
287+
static bool prv_set_s_backlight_dynamic_intensity_enabled(bool *enabled) {
288+
s_backlight_dynamic_intensity_enabled = *enabled;
289+
return true;
290+
}
291+
#endif
292+
281293
static bool prv_set_s_motion_sensitivity(uint8_t *sensitivity) {
282294
// Clamp sensitivity to 0-100 range
283295
if (*sensitivity > 100) {
@@ -880,6 +892,16 @@ void backlight_set_motion_enabled(bool enable) {
880892
prv_pref_set(PREF_KEY_BACKLIGHT_MOTION, &enable, sizeof(enable));
881893
}
882894

895+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
896+
bool backlight_is_dynamic_intensity_enabled(void) {
897+
return s_backlight_dynamic_intensity_enabled;
898+
}
899+
900+
void backlight_set_dynamic_intensity_enabled(bool enable) {
901+
prv_pref_set(PREF_KEY_BACKLIGHT_DYNAMIC_INTENSITY, &enable, sizeof(enable));
902+
}
903+
#endif
904+
883905
uint8_t shell_prefs_get_motion_sensitivity(void) {
884906
return s_motion_sensitivity;
885907
}

src/fw/shell/normal/prefs_values.h.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
PREFS_MACRO(PREF_KEY_BACKLIGHT_INTENSITY, s_backlight_intensity)
1010
PREFS_MACRO(PREF_KEY_BACKLIGHT_MOTION, s_backlight_motion_enabled)
1111
PREFS_MACRO(PREF_KEY_MOTION_SENSITIVITY, s_motion_sensitivity)
12+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
13+
PREFS_MACRO(PREF_KEY_BACKLIGHT_DYNAMIC_INTENSITY, s_backlight_dynamic_intensity_enabled)
14+
#endif
1215
PREFS_MACRO(PREF_KEY_BACKLIGHT_AMBIENT_THRESHOLD, s_backlight_ambient_threshold)
1316
PREFS_MACRO(PREF_KEY_STATIONARY, s_stationary_mode_enabled)
1417
PREFS_MACRO(PREF_KEY_DEFAULT_WORKER, s_default_worker)

src/fw/shell/prefs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ void backlight_set_intensity_percent(uint8_t intensity_percent);
8989
bool backlight_is_motion_enabled(void);
9090
void backlight_set_motion_enabled(bool enable);
9191

92+
#if CAPABILITY_HAS_DYNAMIC_BACKLIGHT
93+
// Dynamic backlight intensity based on ambient light sensor
94+
bool backlight_is_dynamic_intensity_enabled(void);
95+
void backlight_set_dynamic_intensity_enabled(bool enable);
96+
#endif
97+
9298
// Motion sensitivity for accelerometer shake detection (0-100, lower = less sensitive)
9399
// Only available on platforms with LSM6DSO (Asterix, Obelix)
94100
uint8_t shell_prefs_get_motion_sensitivity(void);

0 commit comments

Comments
 (0)