Three independent, optional circuits, none of them required to use this device. Build whichever ones are useful to you. Two of them (heart rate on the PM3's screen, and detecting when the PM3 powers itself back on) connect through a standard male 3.5mm TRS (tip-ring-sleeve) plug wired to this device and plugged straight into the PM3's own HR jack: no disassembly of the PM3 is needed, and unplugging it removes the feature entirely. Both can share one plug or use separate ones. The third (putting the PM3 to sleep) is a switch built onto this device's own board and does not touch the PM3 at all.
| Contact | Role | Direction |
|---|---|---|
| Sleeve | Ground | - |
| Ring | Heart rate signal | Device drives it |
| Tip | Wake-sense signal | Device reads it |
Ring and tip are unrelated signals with different electrical characteristics. Do not wire either one the way the other section below describes; see each section for what is safe.
With a strap paired to this device over Bluetooth (Connections tab), driving the jack's ring contact makes that heart rate show up on the PM3's own screen too, not just in apps connected over Bluetooth.
Wiring: a bare GPIO wire straight to the plug's ring lead, sleeve to ground. No transistor or protection component is required. Ring is a high-impedance input designed for a receiver's logic-level pulses, well within what the ESP32's 3.3V GPIO drive already expects.
If you want extra protection for the PM3 anyway, put a small resistor (around 470Ω, anywhere from 330Ω to 1kΩ) in series on the wire. It does not change the signal ring sees, it just limits current in a wiring-slip or plug-insertion short. Stay at or below 1kΩ so it does not weaken the pulse if ring turns out to have a weak internal pulldown. Do not add a resistor divider to ground; it is unnecessary and only weakens the pulse.
Enable it: set the hr_jack_pin YAML option to the GPIO you
wired. Inert with no effect until a strap is paired from the Connections tab.
The PM3 never sleeps while it can see 5V on its USB cable, so the only way to let it sleep is to remove that 5V. Software cannot do this on its own: the board's 5V rail is joined to the USB-C connector's VBUS pin by a plain solder link, so the 5V is there whenever this device is powered, whatever the USB controller is told. This circuit puts a switch in that link. Without it, the "PM3 auto sleep" setting stays greyed out.
The switch sits in the 5V supply only. The USB data wires are untouched, so nothing about normal operation changes, and the switch is on by default: if this device is off, resetting, or has crashed, the PM3 keeps its power.
Board modification, the same for either build below: find the 0Ω
solder jumper silkscreened USB-OTG on the ESP32-S3-DevKitC-1. It is what connects
the board's 5V rail to the USB-C connector's VBUS pin. Remove it and wire the two exposed pads
into the circuit: the pad on the 5V rail side becomes 5V IN, the pad on the connector
side becomes VBUS OUT.
The TPS2117 is sold as a power multiplexer, a part that picks between two supplies, but it works as a plain on/off switch when you only give it one. Tying its MODE pin to ground puts it in "diode mode", where the PR1 pin becomes an on/off control: PR1 low passes the input through to the output, PR1 high disconnects the output entirely. This is a configuration the datasheet's own truth table covers, not a trick.
Parts: a TPS2117 (the DRLR package is the common one), R1 100kΩ, and C1 1µF. A second 1µF capacitor across the input is worth adding.
Connections, one row per pin:
| Pin | Connect to |
|---|---|
| VIN1 (3) | 5V IN |
| VOUT (2 and 7) | VBUS OUT, and one end of C1 |
| PR1 (4) | ESP32 GPIO, and one end of R1 |
| MODE (5) | GND, which is what selects diode mode |
| VIN2 (6) | GND (the second input is unused) |
| GND (1) | GND, and the other ends of R1 and C1 |
| ST (8) | nothing, leave it unconnected |
R1 is what makes it fail-safe. PR1 turns the output on when it is low, so pulling it down holds the PM3 powered whenever the GPIO is not actively driving: before the firmware has booted, during a reset, and if the firmware ever hangs. C1 is not optional; the chip needs it to ramp the output up smoothly instead of snapping it on.
The chip also brings its own current limiting, reverse-current blocking and soft start, and its on-resistance is lower than the discrete build's, so the PM3 sees a slightly cleaner 5V.
The one catch is size: the package is about 2.1mm by 1.6mm with 8 pins, which is difficult to solder by hand. A small breakout board for it makes the job much easier and is the way to go unless you are comfortable with fine-pitch work.
Enable it: set the usb_power_pin YAML option to the GPIO you
wired, and set inverted: true on it, because this build turns the power on with a
LOW.
Larger, easier to solder parts, at the cost of a few more of them. Everything here is in an 8-pin package with ordinary spacing, or through-hole.
Parts:
| Part | Value |
|---|---|
| Q1 | FDS8958A (one package containing both a P-channel and an N-channel MOSFET) |
| R1 | 100kΩ, holds the switch off unless told otherwise |
| R2 | 1kΩ, feeds the N-channel's pull-down into the P-channel's gate |
Connections, one row per node. Everything listed in a row joins together at that point:
| Node | Joins |
|---|---|
| 5V IN (jumper pad, rail side) | Q1 P-channel source, one end of R1 |
| P-channel gate | other end of R1, one end of R2 |
| Gate drive | other end of R2 to Q1 N-channel drain |
| VBUS OUT (jumper pad, connector side) | Q1 P-channel drain |
| N-channel gate | ESP32 GPIO, directly |
| GND | Q1 N-channel source |
The P-channel MOSFET is the actual switch, carrying the PM3's 5V. It needs its gate pulled to ground to conduct, which a 3.3V GPIO cannot do directly against a 5V source, so the N-channel MOSFET in the same package does it. R1 pulls the gate up to 5V, which holds the switch off; the N-channel pulls it down, which turns the switch on.
The GPIO drives the N-channel gate directly, with no pull resistor of its own. That gate floats whenever the GPIO is not actively driving it (before the firmware has booted, during a reset, or if the firmware ever hangs), so the switch has no defined state through those windows. That is an accepted trade-off, not an oversight: a PM3 that briefly loses USB power keeps running on its own battery, and this device just re-enumerates it once the GPIO is driving again. If you want the old fail-safe-on behaviour back, add a 100kΩ resistor from the N-channel gate to 3.3V.
Check the FDS8958A datasheet for which package pin is which. It is a dual MOSFET in an 8-pin package with two gates, two sources and two drains, and the two drains each use two pins to carry current. If you would rather use through-hole parts you already have, a P-channel MOSFET plus a small NPN transistor (a 2N3904, the same part as the sense circuit below) in place of the N-channel half works identically.
Enable it: set the usb_power_pin YAML option to the GPIO you
wired. This build turns the power on with a HIGH, so it needs no inverted: flag.
The FDS8958A's SO-8 pinout doubles both drains across two pins each, and happens to put the P-channel's source and gate on adjacent pins, so R1 needs no wire run at all. Here is one way to lay it out by hand on an SO-8-to-DIP breakout, no separate perfboard needed.
Either way, the "PM3 auto sleep" setting (Settings tab) becomes available once the pin is configured, and is on by default.
One caution: cutting the 5V drops power to everything on the bus, including the USB hub and an ANT+ stick if you have one. That is intended. Do not feed the hub from its own power supply to work around it, since the PM3 would then keep seeing 5V from the hub and never sleep.
Once the circuit above cuts USB power and the PM3 falls asleep, USB power comes back automatically the moment a Bluetooth app connects, or if this device restarts, so most people never need this circuit at all. Without it, the one case it does not cover is rowing with no Bluetooth app open at all, for example ANT+ only: there is then no way to tell when the PM3 has powered itself back on again while USB power is off, so you have to press the PM3's power button yourself and then restart this device. This circuit senses that automatically instead.
The jack's tip contact is not the HR signal. It idles at roughly 7-7.5V open circuit, sourced through approximately 1.5kΩ of internal impedance, whenever the PM3's own electronics are powered, by USB or its own battery, and decays to 0V over a few seconds once it loses power. This must never be driven or read by a bare GPIO. Build the sense circuit below instead, which tolerates the exact voltage varying somewhat without needing to be re-derived.
Parts:
| Part | Value |
|---|---|
| Q1 | Small-signal NPN, e.g. 2N3904 or BC547 |
| R1 | 100kΩ |
| R2 | 470kΩ |
Q1's base-emitter junction clamps at about 0.7V no matter what tip's actual voltage is, so the GPIO is never exposed to it directly. R1 keeps the load on the PM3's tip pin to roughly 60-70µA, negligible against its own internal impedance. The resulting signal is active-low: the GPIO reads LOW while the PM3 is powered, HIGH (via the pull-up) while it is not.
Enable it: set the tip_wake_pin YAML option to the GPIO you
wired. This circuit only earns its keep alongside the power switch above: without that, USB
power is never cut, the PM3 never sleeps, and there is nothing here to detect. The same is
true if you have built the switch but turned the "PM3 auto sleep" setting off.
Three parts, no board needed. Q1 is TO-92, but the two recommended parts do not share a pinout: viewed top-down with the flat face toward you and legs pointing down, the 2N3904 reads Emitter, Base, Collector left to right, while the BC547 reads Collector, Base, Emitter - mirrored. The base is always the middle leg on both; only emitter and collector swap sides. The diagram below shows both wirings side by side, since one generic "E-B-C" label would be wrong for whichever part is not a 2N3904.