Petlibro Polar Reverse Engineering Notes A developer reverse-engineered the Petlibro Polar (PLAF109/AF109) pet feeder firmware, mapping the ESP32-C3's GPIO and AW9523B I/O expander pins to their functions. The analysis, performed offline with GPT 5.6 Sol, provides a pin map for future replacement firmware, highlighting a GPIO9 conflict with the ROM download strap. 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.