adafruit / adafruit/Adafruit_Wippersnapper_Arduino
v2: refcounted shared power-rail/pin ownership + sleep-mode rail handling
- Dominant language
- C++
- Stars
- 54
- Forks
- 56
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 11
Description
### Context
adafruit/Adafruit_Wippersnapper_Arduino#936 added a panel **power-enable rail** to the display Add (`ws.display.Add.power`, a prerequisite `ws.digitalio.Add` — see adafruit/Wippersnapper_Protobuf#208). The PR implements deliberately **two-party** arbitration, per review:
- pin free at Add → the display driver *owns* the rail (enable level before init, released to disable level in its destructor, i.e. display Remove/replace);
- pin already registered with the digitalio controller → **pass through** only if it's an `OUTPUT` already at the enable level, else the Add fails ("Power-enable pin unavailable").
That's correct for the direct display↔digitalio conflict, but it does not generalize. This issue captures the gaps so the design discussion has a home.
### Failure scenarios (current behavior, verified against the code)
Assume one rail (e.g. GPIO15) powers the display **and** I²C sensors **and** NeoPixels, with components added/removed in arbitrary order at check-in or runtime:
| Order of events | What happens today |
|---|---|
| digitalio Add (OUTPUT@enable) → display Add | ✅ pass-through works |
| display Add → **digitalio Add, same pin** | ❌ digitalio never queries display ownership — it reconfigures the pin per its own Add (possibly INPUT or LOW) and kills the panel |
| display owns rail → **display Remove** | ❌ driver dtor drives the rail to disable — I²C sensors / NeoPixels on the same rail are power-cut silently |
| passed-through digitalio pin → **digitalio Remove** | ❌ `DigitalIOHardware` dtor does `LOW + pinMode(INPUT)` — the rail floats and the panel dies with no notification path |
| two displays sharing one rail | ❌ ownership is private to each driver instance; the second display sees the pin as "free", both own it, first Remove powers down the second |
| I²C bus init when `PIN_I2C_POWER` == the rail | ❌ `i2c/hardware.cpp` drives the macro pin directly — it even flips it through `INPUT` to sniff rest polarity (momentary float/brownout), invisible to any check |
| NeoPixel power pin | v2 `pixels` doesn't model a power pin at all |
Root cause: **ownership is pairwise, one-directional, and implicit** — there is no shared registry, no refcount, and time-of-Add-only checking.
### Deep sleep / light sleep (v2 sleep component)
The sleep controller configures wakeups and sleeps; it does **not** tear down component hardware and never calls `gpio_hold_en`:
- **Deep sleep**: the digital GPIO matrix powers down → all non-RTC pins float. An active-high rail sags off (accidentally the right power-saving outcome, by luck). An **`is_inverted` rail may float ON through sleep** (or oscillate, per board pulls) — burning the battery budget the sleep exists to save. Correct handling needs `gpio_hold_en` + `gpio_deep_sleep_hold_en` at the disable level, or an RTC GPIO.
- **Wake from deep sleep = reset** → check-in replay → componentAdds re-run → the add-order nondeterminism above repeats on *every wake cycle*.
- **Light sleep**: GPIO state is retained and execution resumes — the panel stays lit between sensor reads, so no display power saving unless something deliberately drops the rail (policy question).
### Proposed direction
A central **refcounted pin/rail registry** (in the digitalio controller, or a small `PinOwnershipRegistry`):
- `acquire(pin, level [, flags])` / `release(pin)`: same-level acquires bump a refcount (add-order independence falls out for free); a conflicting-level acquire fails the component Add deterministically; the rail is only driven to its disable level when the **last** holder releases.
- **Symmetric**: digitalio Add/Remove, display power/backlight, `PIN_I2C_POWER`, and a future pixels power pin all go through it — no component can stomp or power-cut another's rail.
- **Sleep hook**: a pre-sleep pass over held rails — `gpio_hold` the appropriate level for deep sleep (critical for inverted rails); a future proto policy flag (e.g. `keep_powered_during_sleep`) covers wake-on-display use cases; light sleep policy (blank/power-down the panel?) decided per component.
### Scope
Touches digitalio, display, i2c, pixels, and sleep — hence split out of #936 rather than grown inside a board-support PR.
Refs: adafruit/Adafruit_Wippersnapper_Arduino#936 (review discussion), adafruit/Wippersnapper_Protobuf#208 ("prerequisite components" thread).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor guide
No contributing guide indexed for this repository
Research direction
Trace power ownership through the digitalio controller, ws.display.Add.power, DigitalIOHardware destruction, i2c/hardware.cpp, pixels, and the v2 sleep component. Start by mapping each direct pin operation and its add/remove path, then review the referenced issues for design constraints. Done means the agreed registry and sleep behavior cover every listed component without order-dependent rail changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- arduino, cpp
- Domain
- embedded-iot
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100