12035f2f39
- 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
107 lines
6.4 KiB
Markdown
107 lines
6.4 KiB
Markdown
# 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<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:
|
|
```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<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`
|