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
version2.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
through0x420ec8cf
- DROM:
0x3c0f0020
through0x3c11bcc7
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.