fix: restore battery icons (Kconfig) & refactor power logic style
What changed, and why it matters
This commit fixes missing battery icons on several hardware variants of the Blockstream Jade crypto wallet and tidies up power/battery code. The main functional change is that boards without a direct charging-status pin now report 'charging' whenever USB power is detected. That is a UI approximation, not a real charging measurement. There is no direct evidence in the commit of a security vulnerability, but the change could make a device falsely display a charging icon when it is merely plugged into USB without actually charging.
Treat as a low-risk functional/UI fix. Review whether a false 'charging' indication could mislead users about device power state, but no immediate security patch is required. If relying on charging status for any security-critical decision (e.g., prompting before sensitive operations), replace the USB proxy with a real charging IC signal or additional heuristics.
Security signals we found
UI status indicator now derived from USB presence rather than actual charging IC status
Battery voltage measurement used as fallback to infer USB power
No input validation or bounds checking changes introduced
No cryptographic, memory-safety, or authentication code modified
Evidence from the diff
The patch adds ‘select HAS_BATTERY’ to Kconfig for TTGO T-DISPLAY, T-DISPLAYS3, T-DISPLAYS3PROCAMERA, T-WATCH S3, and Waveshare Touch LCD 2 boards, restoring battery UI icons. It also refactors EMA voltage smoothing from integer to float arithmetic across tdisplay.inc, tdisplays3.inc, and wslcdtouch2.inc. The only behavioral change is in power_get_battery_charging(): for tdisplay.inc, tdisplays3.inc, and wslcdtouch2.inc it now returns usb_is_powered() instead of hard-coded false. usb_is_powered() uses TinyUSB mount/suspend state and a battery-voltage fallback. The commit message frames this as a ‘Dummy status’ proxy for charging because the charging IC STAT pin is not wired to a GPIO.
Changed components
main/Kconfig.projbuildmain/power/tdisplay.incmain/power/tdisplays3.incmain/power/wslcdtouch2.incInspect captured patch +37 / −49
diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild
index d3fa788..e080b81 100644
--- a/main/Kconfig.projbuild
+++ b/main/Kconfig.projbuild
@@ -70,17 +70,22 @@ menu "Blockstream Jade"
select HAS_BATTERY
config BOARD_TYPE_TTGO_TDISPLAY
bool "TTGO T-DISPLAY"
+ select HAS_BATTERY
config BOARD_TYPE_TTGO_TDISPLAYS3
bool "TTGO T-DISPLAYS3"
+ select HAS_BATTERY
config BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA
bool "TTGO T-DISPLAYS3PROCAMERA"
select HAS_CAMERA
+ select HAS_BATTERY
config BOARD_TYPE_TTGO_TWATCHS3
bool "TTGO T-WATCH S3"
select HAS_AXP
+ select HAS_BATTERY
config BOARD_TYPE_WS_TOUCH_LCD2
bool "Waveshare Touch LCD 2 (esp32s3)"
select HAS_CAMERA
+ select HAS_BATTERY
select DISPLAY_TOUCHSCREEN
config BOARD_TYPE_QEMU
bool "qemu display via websocket"
diff --git a/main/power/tdisplay.inc b/main/power/tdisplay.inc
index c29ec4e..848ec6f 100644
--- a/main/power/tdisplay.inc
+++ b/main/power/tdisplay.inc
@@ -13,7 +13,8 @@
static adc_oneshot_unit_handle_t adc1_handle = NULL;
static adc_cali_handle_t adc1_cali_chan0_handle = NULL;
-static int ema_voltage = 0;
+
+static float ema_voltage = 0.0f;
esp_err_t power_init(void)
{
@@ -66,8 +67,8 @@ uint16_t power_get_vbat(void)
voltage = (raw_adc * 3300) / 4095;
}
// Apply Estimated Moving Average (EMA)
- const int ema_v = ema_voltage ? ema_voltage : voltage;
- ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1 - BATTERY_EMA_ALPHA) * ema_v);
+ const float ema_v = (ema_voltage > 0) ? ema_voltage : (float)voltage;
+ ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1.0f - BATTERY_EMA_ALPHA) * ema_v);
// T-Display voltage divider is 2 (100k/100k)
return (uint16_t)(ema_voltage * 2);
}
@@ -93,8 +94,8 @@ uint8_t power_get_battery_status(void)
bool power_get_battery_charging(void)
{
// The charging IC STAT pin is not connected to GPIO.
- // Return false to avoid showing "Charging" icon permanently when on USB.
- return false;
+ // Return USB status as a proxy for charging (Dummy status)
+ return usb_is_powered();
}
uint16_t power_get_ibat_charge(void) { return 0; }
@@ -113,4 +114,4 @@ bool usb_is_powered(void)
// applies voltage to the battery rails even without a battery.
const uint16_t vbat = power_get_vbat();
return (vbat < 1000) || (vbat > 4150);
-}
\ No newline at end of file
+}
diff --git a/main/power/tdisplays3.inc b/main/power/tdisplays3.inc
index 739b46f..c8f9438 100644
--- a/main/power/tdisplays3.inc
+++ b/main/power/tdisplays3.inc
@@ -15,7 +15,8 @@
static adc_oneshot_unit_handle_t adc1_handle = NULL;
static adc_cali_handle_t adc1_cali_chan0_handle = NULL;
-static int ema_voltage = 0;
+
+static float ema_voltage = 0.0f;
esp_err_t power_init(void)
{
@@ -67,8 +68,8 @@ uint16_t power_get_vbat(void)
voltage = (raw_adc * 3300) / 4095;
}
// Apply Estimated Moving Average (EMA)
- const int ema_v = ema_voltage ? ema_voltage : voltage;
- ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1 - BATTERY_EMA_ALPHA) * ema_v);
+ const float ema_v = (ema_voltage > 0) ? ema_voltage : (float)voltage;
+ ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1.0f - BATTERY_EMA_ALPHA) * ema_v);
// T-Display S3 voltage divider is 2 (100k/100k)
return (uint16_t)(ema_voltage * 2);
}
@@ -93,9 +94,9 @@ uint8_t power_get_battery_status(void)
bool power_get_battery_charging(void)
{
- // The charging IC STAT pin is not connected to GPIO, so we can't know for sure.
- // Return false to avoid showing "Charging" icon permanently when on USB.
- return false;
+ // The charging IC STAT pin is not connected to GPIO.
+ // Return USB status as a proxy for charging (Dummy status)
+ return usb_is_powered();
}
uint16_t power_get_ibat_charge(void) { return 0; }
@@ -119,4 +120,4 @@ bool usb_is_powered(void)
return true;
}
return false;
-}
\ No newline at end of file
+}
diff --git a/main/power/wslcdtouch2.inc b/main/power/wslcdtouch2.inc
index b2eecc6..720aece 100644
--- a/main/power/wslcdtouch2.inc
+++ b/main/power/wslcdtouch2.inc
@@ -1,4 +1,4 @@
-// Waveshare LCD Touch 2 implementation
+// Waveshare S3 Touch LCD 2 implementation
//
#include <driver/gpio.h>
#include <driver/ledc.h>
@@ -18,7 +18,7 @@
#define BATTERY_ADC_CHANNEL ADC_CHANNEL_4
#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12
-#define BATTERY_EMA_ALPHA 0.3 // estimated moving average smoothing factor
+#define BATTERY_EMA_ALPHA 0.3f // estimated moving average smoothing factor
#define LCD_BL_LEDC_TIMER LEDC_TIMER_0
#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE
@@ -31,7 +31,7 @@
static adc_oneshot_unit_handle_t adc1_handle = NULL;
static adc_cali_handle_t adc1_cali_chan0_handle = NULL;
-static int ema_voltage = 0;
+static float ema_voltage = 0.0f;
static void button_shutdown(void* arg, void* ctx)
{
@@ -48,21 +48,17 @@ static esp_err_t boot_button_init(void)
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
};
-
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = 0,
.active_level = 0,
};
-
button_handle_t btn_handle = NULL;
ESP_ERROR_CHECK(iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn_handle));
ESP_ERROR_CHECK(iot_button_register_cb(btn_handle, BUTTON_LONG_PRESS_HOLD, NULL, button_shutdown, NULL));
-
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
gpio_hold_en(GPIO_NUM_0);
gpio_deep_sleep_hold_en();
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);
-
return ESP_OK;
}
@@ -71,14 +67,12 @@ static esp_err_t brightness_init(void)
gpio_reset_pin(CONFIG_DISPLAY_PIN_BL);
gpio_set_direction(CONFIG_DISPLAY_PIN_BL, GPIO_MODE_OUTPUT);
gpio_set_level(CONFIG_DISPLAY_PIN_BL, 1);
-
ledc_timer_config_t ledc_timer = { .speed_mode = LCD_BL_LEDC_MODE,
.timer_num = LCD_BL_LEDC_TIMER,
.duty_resolution = LCD_BL_LEDC_DUTY_RES,
.freq_hz = LCD_BL_LEDC_FREQUENCY, // Set output frequency at 5 kHz
.clk_cfg = LEDC_AUTO_CLK };
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
-
ledc_channel_config_t ledc_channel = { .speed_mode = LCD_BL_LEDC_MODE,
.channel = LCD_BL_LEDC_CHANNEL,
.timer_sel = LCD_BL_LEDC_TIMER,
@@ -87,7 +81,6 @@ static esp_err_t brightness_init(void)
.duty = 0, // Set duty to 0%
.hpoint = 0 };
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
-
return ESP_OK;
}
@@ -95,23 +88,20 @@ esp_err_t power_init(void)
{
// Use the BOOT button as a sleep/wakeup button
ESP_ERROR_CHECK(boot_button_init());
-
// Initialise backlight brightness
ESP_ERROR_CHECK(brightness_init());
-
// Initialise the ADC to measure battery level
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
JADE_ASSERT(adc1_handle);
-
+ // ADC Config
adc_oneshot_chan_cfg_t config = {
- .bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = BATTERY_ADC_ATTEN,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config));
-
// Use voltage calibration
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
@@ -122,15 +112,12 @@ esp_err_t power_init(void)
JADE_LOGW("ADC calibration not available, continuing without it");
adc1_cali_chan0_handle = NULL;
}
-
return ESP_OK;
}
esp_err_t power_shutdown(void) { esp_deep_sleep_start(); }
-
esp_err_t power_screen_on(void) { return ESP_OK; }
esp_err_t power_screen_off(void) { return ESP_OK; }
-
esp_err_t power_backlight_on(uint8_t brightness)
{
if (brightness < BACKLIGHT_MIN) {
@@ -138,14 +125,11 @@ esp_err_t power_backlight_on(uint8_t brightness)
} else if (brightness > BACKLIGHT_MAX) {
brightness = BACKLIGHT_MAX;
}
-
uint32_t duty = (brightness * (LCD_BL_LEDC_DUTY - 1)) / 5;
ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty));
ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL));
-
return ESP_OK;
}
-
esp_err_t power_backlight_off(void)
{
ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, 0));
@@ -153,26 +137,22 @@ esp_err_t power_backlight_off(void)
return ESP_OK;
}
-
esp_err_t power_camera_on(void) { return ESP_OK; }
esp_err_t power_camera_off(void) { return ESP_OK; }
uint16_t power_get_vbat(void)
{
JADE_ASSERT(adc1_handle);
-
int raw_adc, voltage;
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &raw_adc));
-
if (adc1_cali_chan0_handle) {
adc_cali_raw_to_voltage(adc1_cali_chan0_handle, raw_adc, &voltage);
} else {
- voltage = (raw_adc * 3300) / 4095; // mV
+ voltage = (raw_adc * 3300) / 4095;
}
-
- // apply Estimated Moving Average since the ADC seems to have a lot of noise
- ema_voltage = BATTERY_EMA_ALPHA * voltage + (1 - BATTERY_EMA_ALPHA) * (ema_voltage == 0 ? voltage : ema_voltage);
- /* JADE_LOGI("Raw: %.2dmV, Smoothed: %.2d mV", voltage, ema_voltage); */
+ // Apply Estimated Moving Average (EMA)
+ const float ema_v = (ema_voltage > 0) ? ema_voltage : (float)voltage;
+ ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1.0f - BATTERY_EMA_ALPHA) * ema_v);
return (uint16_t)ema_voltage;
}
@@ -193,10 +173,13 @@ uint8_t power_get_battery_status(void)
return 0;
}
-// The STAT pin on the charging IC isn't exposed to a GPIO so we cannot read it
-// and can't easily tap it with a wire. There is a charging LED though so you
-// can see if the battery is being charged.
-bool power_get_battery_charging(void) { return 0; }
+bool power_get_battery_charging(void)
+{
+ // The charging IC STAT pin is not connected to GPIO.
+ // Return USB status as a proxy for charging (Dummy status)
+ return usb_is_powered();
+}
+
uint16_t power_get_ibat_charge(void) { return 0; }
uint16_t power_get_ibat_discharge(void) { return 0; }
uint16_t power_get_vusb(void) { return 0; }
@@ -212,12 +195,10 @@ bool usb_is_powered(void)
if (tud_mounted() && !tud_suspended()) {
return true;
}
-
// Fallback: If voltage is near zero (<1V) but chip is on,
// we must be running on USB power without a battery.
if (power_get_vbat() < 1000) {
return true;
}
-
return false;
-}
\ No newline at end of file
+}
Why this scored 18/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.