# Petlibro Polar Reverse Engineering Notes

> Source: <https://gist.github.com/JakeShirley/919704420b309d5040c7e55aff5a5601>
> Published: 2026-08-24 05:29:20+00:00

note: these findings were done with GPT 5.6 Sol.

This document records static analysis of the stock PLAF109/AF109 application firmware on disk. It is intended as a starting point for a future replacement firmware, not as permission to energize actuators from an incomplete pin map. The analysis was offline; no device or Petlibro service was contacted.

File: `firmware/PLAF109_OTA_020032.bin`

- File SHA-256:
`9e73f904e7f6a09172b7384aa84fa38963ce75ef22bebec89f38ebef5c8dd53d`

- Target: ESP32-C3, revision 0.3 or newer
- Application:
`AF109`

version`2.0.32`

- Build: 2025-04-22 09:46:39, ESP-IDF
`v4.4.6-dirty`

- Image size: 1,247,744 bytes
- Entry point:
`0x40380450`

- IROM:
`0x42000020`

through`0x420ec8cf`

- DROM:
`0x3c0f0020`

through`0x3c11bcc7`

This is only an OTA application image. Values kept in NVS, including the unit's motor-current calibration and thresholds, are not in this file.

The ESP32-C3 directly handles ADC inputs, interrupts, one PWM output, and a
one-wire sound interface. Most actuator outputs are provided by an AW9523B
16-bit I/O expander at I2C address `0x58`

.

The central board initializer at `0x4200d726`

calls these routines in order:

- AW9523B and I2C initialization at
`0x4200d94a`

- cooler/PTC output initialization at
`0x4200d1c0`

- plate motor initialization at
`0x4200d134`

- door motor initialization at
`0x4200d17a`

- button initialization at
`0x4200d09e`

- alarm/status LED initialization at
`0x4200d0f4`

- sensor and 38 kHz LEDC initialization at
`0x4200cdb6`

- ADC initialization at
`0x4200d200`

- sound interface initialization at
`0x4200d6ca`

The AW9523 shadow-register bit writer is at `0x4200d838`

. It updates a 16-bit
RAM shadow and writes AW9523 output registers `0x02`

and `0x03`

through the I2C
helper at `0x4200d816`

.

Confidence in this table is high: the assignments come from immediate values passed to ESP-IDF GPIO, ADC, I2C, and LEDC functions, then were correlated with the firmware's retained log labels.

| ESP GPIO | Direction/peripheral | Recovered purpose | Evidence |
|---|---|---|---|
| 0 | digital input, any-edge | DC/battery power-source detection | polled by `Power_Handle` at `0x4200c692` |
| 1 | ADC1 channel 1 | NTC temperature divider | read by `Read_Ntc_Temp` at `0x4200d41a` |
| 2 | LEDC output | 38 kHz optical/IR emitter, 8-bit timer, duty 127 or 0 | LEDC setup under `0x4200cdb6` |
| 3 | ADC1 channel 3 | door motor current | retained assertion names `ADC1_CHAN3_MOTOR` |
| 4 | ADC1 channel 4 | battery voltage | firmware converts calibrated mV and applies a 2:1 divider factor |
| 5 | ADC2 channel 0 | plate motor current | retained assertion names `ADC2_CHAN0_MOTOR` |
| 6 | digital output | one-wire sound/voice IC data | byte sender at `0x4200d77a` |
| 7 | I2C0 SDA | AW9523B data | pull-up enabled |
| 8 | I2C0 SCL | AW9523B clock | pull-up enabled, 100 kHz bus |
| 9 | digital output | AW9523B reset | held low for 200 ms, then high |
| 10 | interrupt input, pull-up, any-edge | rotating tray microswitch/index pulse | `Plate_Micro_Handle` at `0x4200e332` |
| 18 | interrupt input, any-edge | user/reset button | `Key_init` and `Timer_KeyRestoreHandle` |
| 19 | interrupt input, any-edge | door/close-position signal | retained label `Close_SW_Interrupt` |
| 20 | interrupt input, any-edge | IR/pet-presence receiver | `Infrared_Handle` at `0x4200c596` |

GPIO9 is an important board-specific conflict: it resets the AW9523B in the running application, but it is also the ESP32-C3 ROM download-mode strap. A replacement firmware must release it appropriately after boot.

The firmware treats AW9523 pins as one flat bit index from 0 through 15.

| Bit | AW9523 pin | Recovered purpose | Initial state |
|---|---|---|---|
| 0, 1 | P0.0, P0.1 | hardware revision straps/inputs | read during startup |
| 4, 7 | P0.4, P0.7 | paired existence/optical sensing controls | application controlled |
| 5, 6 | P0.5, P0.6 | rotating plate motor H-bridge inputs | high/high |
| 8, 9 | P1.0, P1.1 | two-channel front/alarm status LED | low/low |
| 12 | P1.4 | cooler/PTC power control | high |
| 14, 15 | P1.6, P1.7 | feeding-door motor H-bridge inputs | high/high |

The startup strap decode masks the low two AW9523 input bits. Static control
flow maps value `2`

to hardware `5.1.0`

, value `1`

to `5.2.0`

, and other values
to `5.0.0`

. This agrees with the observed unit reporting hardware `5.1.0`

, but
the electrical pull-up/pull-down arrangement is not present in the OTA image.

The plate motor is a bidirectional DC motor behind two H-bridge inputs on AW bits 5 and 6. The stock firmware uses:

- high/high as
`GPIO_MOTOR_BRAKE`

- high/low and low/high for the two directions
- motor-current feedback on ESP GPIO5 / ADC2 channel 0
- a tray index microswitch on ESP GPIO10

The direction combinations should initially be called direction A and direction B. Static analysis does not establish which is clockwise at the physical tray without a board observation.

`Plate_Micro_Handle`

at `0x4200e332`

advances the current position on each
accepted switch event, wrapping through positions 1, 2, and 3. When the current
position equals the requested position, the routine writes high/high to AW
bits 5 and 6 and changes the plate motor state to the stopped/braked state.
The retained logs include `F1`

, `F2`

, `PLATE_READY_SET_POS`

,
`PLATE_READY_BACK_POS`

, and `SET_PLATE_POS_SERVICE`

, showing that normal
positioning and return/reposition behavior are separate state-machine paths.

The plate controller does not rely on time alone. It watches motor current and
uses retained attributes named `plateStuckCurrent`

and `plateStuckTimeout`

.
On a detected stall it enters `MOTOR_STUCK`

, brakes the motor, and inhibits
normal cooling/feeding transitions. The actual current and timeout values are
loaded from persistent device attributes and cannot be recovered from this OTA
application alone.

The door motor is another bidirectional DC motor behind AW bits 14 and 15.
Like the plate motor, high/high is its initialized stopped/braked state and the
two asymmetric combinations select opposite directions. Firmware logs name
the directions `GPIO_DOOR_MOTOR_FORWARD`

and `GPIO_DOOR_MOTOR_BACK`

; higher
level paths associate them with closing and opening respectively. Verify that
physical mapping before using it in replacement firmware because the motor or
wiring orientation may vary by board revision.

Door movement uses both:

- a position/close signal on ESP GPIO19
- motor-current feedback on ESP GPIO3 / ADC1 channel 3

The state machine has `DOOR_READY_OPEN`

, `DOOR_MOTOR_OPEN_OVER`

,
`DOOR_READY_CLOSE`

, `DOOR_MOTOR_CLOSING`

, `DOOR_MOTOR_CLOSE_OVER`

, and
`MOTOR_STUCK`

states. A door-reset/recovery path samples at approximately
100 ms intervals with a roughly 50-sample bound, about five seconds total.
Retained attributes `doorStuckCurrent`

, `doorStuckTimeout`

,
`doorCheckSignalTime`

, and `doorNotcheckSignalTime`

show that both current and
the position signal participate in protection. Their per-unit values are in
NVS, not this image.

The feeding sequence is explicitly staged as plate movement/thaw, door open, feeding duration, and door close. It is not safe to expose independent raw motor buttons without preserving those interlocks and recovery states.

The only identified refrigeration output is AW bit 12, called `PTC`

by the
firmware. No separate fan output was identified. The temperature input is an
NTC divider on ESP GPIO1 / ADC1 channel 1.

`Read_Ntc_Temp`

at `0x4200d41a`

averages ten ADC readings and applies a standard
10 kOhm, B3950 thermistor calculation using 3.3 V, `1 / 298.15`

, and
`-273.15`

. A retained `tempCoeff`

permits unit calibration. Temperature
publication is held off during a 60-cycle stabilization period.

The cooler controller at `0x4200e672`

contains exact double constants 12.0 and
13.0 degrees C at DROM addresses `0x3c0fae38`

and `0x3c0fae30`

, providing a
1 degree hysteresis band. It also has a 600-call dwell counter and guards for
power source, feeding/plate activity, motor-stuck state, and explicit cool
switch state. Logs include `start cool`

, `NOT_COOL`

, `DL_COOL_STOP_STATUS`

,
`switch plate make cool`

, and `GPIO_PTC_ON`

.

Do not assume the electrical polarity of AW bit 12 yet. Initialization sets it
high, while runtime paths explicitly write both high and low around the
temperature comparisons and guard states. The retained `GPIO_PTC_ON`

log is
not enough by itself to prove whether the expander output or a downstream
transistor is active-low. Confirm polarity with an unpowered continuity check
and then a current-limited measurement before enabling refrigeration.

The ESP32-C3 does not synthesize audio with I2S, DAC, or PWM. GPIO6 drives an
external one-wire sound or voice IC using the byte sender at `0x4200d77a`

.
The recovered waveform is:

- idle high
- 5 ms low start pulse
- eight data bits, least-significant bit first
- bit 0: 1 ms high, then 3 ms low
- bit 1: 3 ms high, then 1 ms low
- return high after the eighth bit

The MQTT/application `AUDIO`

command path at `0x42018988`

sends byte `0x01`

,
which is therefore the stock one-shot feeding bell command. Other internal
paths also send `0xef`

; its sound or control meaning is unresolved. Persistent
fields include `enableAudio`

and `audioTimes`

, so scheduled feeding sounds can
be suppressed or repeated independently of the one-shot command.

The external sound IC model and its complete command table cannot be derived
from this application image. Identify the package marking or capture GPIO6
while exercising stock firmware before assuming commands other than `0x01`

.

The front indicator uses AW bits 8 and 9. `AlarmLed_init`

at `0x4200d0f4`

initializes both low. A `dl_led`

task drives combinations of the two outputs to
represent states and honors retained settings including `lightSwitch`

,
`enableLight`

, `led_enable`

, `led_mode`

, and start/end hour and minute fields.

The OTA image establishes that this is a two-channel indicator, but not which bit maps to which physical color or whether either channel is active-low. Observe the AW outputs alongside visible stock patterns before naming colors in custom firmware.

- GPIO0 distinguishes external DC and battery operation. Stock logic disables or restricts cooling and networking behavior in battery mode.
- GPIO4 measures battery voltage through a divider; firmware doubles the calibrated millivolt result before estimating charge state.
- GPIO2 generates a 38 kHz, 50-percent-duty carrier and GPIO20 handles an infrared/presence event. This strongly indicates a modulated optical sensing pair used for pet/existence detection.
- AW bits 4 and 7 are toggled together in related existence-detection paths. Their exact external circuits are not identifiable from the OTA image.
- GPIO18 is the user button. The stock code uses debounce/restore timers and includes network/reset behavior, so a custom build should distinguish short and long presses before assigning destructive actions.

Before driving the motors or cooler, a replacement should preserve at least:

- AW9523 reset, I2C shadowing, and stopped output states before task startup.
- high/high motor brake behavior verified against the actual H-bridge.
- tray microswitch debounce and modulo-3 position tracking.
- an explicit homing/reposition procedure for unknown startup position.
- current-based plate and door stall cutoffs, plus hard movement timeouts.
- door position-signal validation while closing.
- motor inhibition after a stall until an attended recovery action.
- cooler hysteresis, minimum dwell time, battery-mode inhibition, and inhibition during motor movement or feeding transitions.
- conservative startup defaults for both LED channels and the sound line.
- retained calibration storage with bounds checking and recoverable defaults.

Do not copy unknown stock NVS values blindly between units. First obtain the
full flash backup described in `CUSTOM_FIRMWARE.md`

, identify the relevant NVS
keys, and correlate them with current-limited bench measurements.

Inspect the image header and segment map:

```
py -3.10 -m esptool image-info firmware\PLAF109_OTA_020032.bin
```

Resolve direct references to retained board-support labels:

```
py -3.10 analysis\esp32c3-xrefs.py `
  firmware\PLAF109_OTA_020032.bin `
  Motor_init Door_Motor_init Ptc_init AlarmLed_init Adc_init `
  Read_Ntc_Temp BSP_aduioInit AW9523B_i2cInit
```

High-value virtual addresses:

| Address | Meaning |
|---|---|
`0x4200c596` |
infrared input handler |
`0x4200c692` |
power-source input handler |
`0x4200cdb6` |
GPIO2 LEDC and sensor setup |
`0x4200d0f4` |
two-channel status LED initialization |
`0x4200d134` |
plate motor initialization |
`0x4200d17a` |
door motor initialization |
`0x4200d1c0` |
PTC/cooler output initialization |
`0x4200d200` |
ADC setup |
`0x4200d41a` |
NTC conversion |
`0x4200d6ca` |
sound GPIO initialization |
`0x4200d77a` |
one-wire sound byte sender |
`0x4200d816` |
AW9523 register write helper |
`0x4200d838` |
AW9523 bit shadow/write helper |
`0x4200d94a` |
I2C and AW9523 initialization |
`0x4200e332` |
tray microswitch and position handler |
`0x4200e672` |
cooler controller |
`0x42018988` |
one-shot `AUDIO` command dispatch |

Useful retained strings include `GPIO_MOTOR_BRAKE`

, `plateStuckCurrent`

,
`doorStuckCurrent`

, `Close_SW_Interrupt`

, `MicroSw_DebounceHandle`

,
`GPIO_PTC_ON`

, `Infrared_Handle`

, `AUDIO`

, `lightSwitch`

, and `audioTimes`

.

- cooler output polarity and downstream power-driver topology
- AW bits 8/9 physical LED colors and polarity
- plate direction A versus physical clockwise/counterclockwise movement
- exact door switch active level and motor orientation on each board revision
- per-unit plate and door stall-current thresholds from NVS
- external sound IC model and commands other than confirmed bell byte
`0x01`

- exact circuits controlled by AW bits 4 and 7
- absolute tray position after startup when no trusted retained position exists

Resolve these with the full flash backup, board photographs and part markings, continuity checks while unpowered, and current-limited logic measurements of stock firmware. Do not begin by connecting normal actuator power to an untested replacement build.
