# Tactility Power System — IsCharging, Devices, and Flashing ## PowerDevice API Header: `Tactility/Include/Tactility/hal/power/PowerDevice.h` ```cpp enum class MetricType { IsCharging, Current, BatteryVoltage, ChargeLevel }; union MetricData { int32_t valueAsInt32; uint32_t valueAsUint32; uint8_t valueAsUint8; bool valueAsBool; }; virtual bool supportsMetric(MetricType) const = 0; virtual bool getMetric(MetricType, MetricData&) = 0; ``` Finding devices: ```cpp auto devices = hal::findDevices(hal::Device::Type::Power); auto power = hal::findFirstDevice(hal::Device::Type::Power); ``` ## Family Board: ES3C28P - **Device ID**: `es3c28p` — LCDWIKI 2.8" 240x320 color IPS, ESP32-S3R8, 16MB flash, OCT PSRAM 120MHz, SD, Bluetooth, TinyUSB - **device path**: `Devices/es3c28p/` — now has `Power.cpp` direct PowerDevice driver on **GPIO9 ADC1_CH8**, 2x divider 200K/200K, curve-fitting calibration, supports `BatteryVoltage`, `ChargeLevel`, heuristic `IsCharging`. Matches official LCDWIKI Example_13 and MicroPython `battery_util.py` Pin 9. - **Charging HW limitation**: TP4054 charger IC U2 CHRG pin -> R12 3.3K -> LED -> GND, **NOT routed to ESP32 GPIO**. No VBUS detect GPIO. Therefore true hardware IsCharging impossible; implemented heuristic: <500mV or >5000mV wall-powered -> charging true; >=4150mV CV hold -> charging true; rising +10mV/2s twice -> charging true. See `es3c28p-official-pin.md` and `es3c28p-battery-debug.md`. - **vs RLCD**: `waveshare-esp32-s3-rlcd` is 4.2" monochrome ST7305, compact uiDensity, different pins (SDMMC routed CLK=38 CMD=21 D0=39 D3=17). Git history: added alongside ES3C28P in July 2026. Symptoms of wrong flash: mono UI, no color. - **Port**: `/dev/cu.usbmodem101` (sometimes 1101) - **Partition**: `partitions-16mb-with-sd.csv`, free ~31% (2.87MB bin / 4MB partition) - **Display driver**: ILI9341 via SPI When user says "color screen esp32" → `es3c28p`. When says "not right board, I have RLCD" → `waveshare-esp32-s3-rlcd`. Always verify via: ```bash grep CONFIG_TT_DEVICE_ID firmware/sdkconfig ls /dev/cu.usbmodem* ``` Fixing wrong target: ```bash python device.py es3c28p # and ensure Hermes PYTHONPATH not hijacking IDF python (see SKILL.md Build section) ``` ## IsCharging Support (grep result 2026-07-11 + updated 2026-07-11 for ES3C28P) HAS IsCharging (hardware or heuristic): - `Drivers/AXP192/Source/Axp192.cpp` — charge current > 0.001f - `Drivers/AXP2101/Source/Axp2101Power.cpp` — CHARGE_STATUS_CHARGING via AXP2101 register - `Devices/m5stack-sticks3/Source/devices/Power.cpp` — M5PM1 via I2C `m5pm1_is_charging` - `Devices/m5stack-tab5/Source/devices/Power.cpp` — similar PMIC - `Devices/m5stack-papers3/Source/devices/Power.cpp` - `Devices/lilygo-tlora-pager/Source/devices/TpagerPower.cpp` - `Devices/simulator/Source/hal/SimulatorPower.cpp` — always true - `Devices/es3c28p/Source/devices/Power.cpp` — **heuristic** (since TP4054 CHRG not wired): <500/>5000 wall, >=4150 high-voltage, rising trend detection. Works ~80% but not instant. - `Drivers/AXP2101` binding YAML + `bq27220` etc may indirectly support but check above NO IsCharging (EstimatedPower only BatteryVoltage+ChargeLevel): - `Devices/lilygo-tdeck/Source/devices/Power.cpp`: ```cpp std::shared_ptr createPower() { ChargeFromAdcVoltage::Configuration cfg; cfg.adcMultiplier = 2.11; return std::make_shared(cfg); } ``` - `Devices/lilygo-tdisplay-s3`, `lilygo-thmi`, `heltec-wifi-lora-32-v3`, `cyd-e32r32p`, `guition-jc1060p470ciwy`, `m5stack-stackchan`, `m5stack-core2` stub - `Devices/generic-esp32`, `generic-esp32s3` — no Power device at all (module.cpp empty start/stop) ### EstimatedPower Source ``` firmware/Drivers/EstimatedPower/ Source/EstimatedPower.h/.cpp Source/ChargeFromAdcVoltage.h/.cpp Source/ChargeFromVoltage.h/.cpp ``` EstimatedPower::supportsMetric = BatteryVoltage, ChargeLevel only. ChargeFromVoltage estimates 0-100% linear interpolation between min 3.2V max 4.2V (configurable). ADC: adc_oneshot, 15 samples averaged, multiplier * (1000*refV/4096)*raw. ## Why IsCharging Is Not Reliable on ES3C28P 1. Schematic `~/Downloads/2.8inch_IPS_ESP32-S3_ILI9341V_ES3C28P_ES3N28P_V1.0/5-原理图_Schematic/2.8inch_ESP32-S3_Display_Schematic.pdf` extraction shows: - `U2 TP4054_C668215` CHRG=1 -> R12 3.3K -> LED -> GND, NOT to ESP32 - No STDBY, no VBUS GPIO to ESP32 — VBUS only goes to power path Q3 SL2305 P-MOS auto-switch - Only BAT_ADC = GPIO9 available. 2. Therefore hardware cannot tell charging vs high battery. Options: - **HW mod**: solder wire from TP4054 CHRG pin 1 to free GPIO (GPIO3/21) with 10K pull-up to 3.3V, read LOW=charging. - **Heuristic** (implemented): track `last_vbat_mv`, `last_read_ms` via FreeRTOS ticks, `rising_count` >=2 -> charging; `falling_count` >3 -> not charging; high-voltage thresholds as above. - For screensaver disable-when-charging: best-effort but not instant; user reported "charging state not recognized properly" because original >4350 only threshold never hit during normal 3700-4200 charging. 3. User's final question "So there is no way to know if the board is charging?" — answer: **No true way without HW mod** on this board revision. ## Statusbar Battery Rendering — Native Location `Tactility/Source/service/statusbar/Statusbar.cpp`: - `getPowerStatusIcon()` — if charging → BOLT icon (`LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_BOLT`), else thresholds → FULL/6/5/4/3/2/1 - `updatePowerStatusIcon()` — `std::format("{}%", charge)` → `statusbar_set_battery_text()` + visibility - Called from 1s periodic timer in StatusbarService. Needs LVGL lock. If custom board returns N/A for ChargeLevel, statusbar hides battery. Fix = ensure PowerDevice exists in `createDevices()`. **User preference 2026-07-11**: Battery belongs in overall status bar next to WiFi, NOT in WiFi Manage View tab. WiFi tab `battery_label` attempt was reverted per user: "I don't need the battery level on the wifi, that makes no sense. I wanted on the overall status bar". Always check native path before custom UI. ## Flash Log Reference (ES3C28P successful flash 2026-07-11) - Port `/dev/cu.usbmodem101`, 16MB flash, 80MHz - `Wrote 2863136 bytes (1653045 compressed) at 0x00010000 in 19.9s @ 1149.4 kbit/s` - Files: bootloader.bin 0x0, partition-table.bin 0x8000, Tactility.bin 0x10000, system.bin 0x410000 - Final builds with GPIO9 driver: 2.87-2.88 MB, 31% free - Same pattern for `build-all.py` — but for single board use `idf.py -p /dev/cu.usbmodem101 flash`