Files
tactility/.claude/skills/tactility-firmware/references/power-system.md
T
Adolfo Reyna 12035f2f39 chore: move firmware skills in-repo + AGENTS.md
- Migrates tactility-firmware and tactility-bluetooth from
  ~/.hermes/skills/ into .claude/skills/ for repo co-location
- Adds AGENTS.md with local workstation context, board table,
  board-direct build rule, Hermes PYTHONPATH pitfall, common
  Tactility pitfalls, and in-repo skill index

Refs: personal Gitea mirror
2026-07-17 09:51:08 -04:00

6.4 KiB

Tactility Power System — IsCharging, Devices, and Flashing

PowerDevice API

Header: Tactility/Include/Tactility/hal/power/PowerDevice.h

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:

auto devices = hal::findDevices<PowerDevice>(hal::Device::Type::Power);
auto power = hal::findFirstDevice<PowerDevice>(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:

grep CONFIG_TT_DEVICE_ID firmware/sdkconfig
ls /dev/cu.usbmodem*

Fixing wrong target:

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.cppheuristic (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:
    std::shared_ptr<PowerDevice> createPower() {
      ChargeFromAdcVoltage::Configuration cfg; cfg.adcMultiplier = 2.11;
      return std::make_shared<EstimatedPower>(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