- 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
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 hasPower.cppdirect PowerDevice driver on GPIO9 ADC1_CH8, 2x divider 200K/200K, curve-fitting calibration, supportsBatteryVoltage,ChargeLevel, heuristicIsCharging. Matches official LCDWIKI Example_13 and MicroPythonbattery_util.pyPin 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.mdandes3c28p-battery-debug.md. - vs RLCD:
waveshare-esp32-s3-rlcdis 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.001fDrivers/AXP2101/Source/Axp2101Power.cpp— CHARGE_STATUS_CHARGING via AXP2101 registerDevices/m5stack-sticks3/Source/devices/Power.cpp— M5PM1 via I2Cm5pm1_is_chargingDevices/m5stack-tab5/Source/devices/Power.cpp— similar PMICDevices/m5stack-papers3/Source/devices/Power.cppDevices/lilygo-tlora-pager/Source/devices/TpagerPower.cppDevices/simulator/Source/hal/SimulatorPower.cpp— always trueDevices/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/AXP2101binding YAML +bq27220etc 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-core2stubDevices/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
-
Schematic
~/Downloads/2.8inch_IPS_ESP32-S3_ILI9341V_ES3C28P_ES3N28P_V1.0/5-原理图_Schematic/2.8inch_ESP32-S3_Display_Schematic.pdfextraction shows:U2 TP4054_C668215CHRG=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.
-
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_msvia 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.
-
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/1updatePowerStatusIcon()—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 useidf.py -p /dev/cu.usbmodem101 flash