feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host - #18
Merged
Conversation
gets hardware at all The display firmware registered print, delay and millis — nothing else. A program with a screen could not read a sensor, blink a pin, or open a UART; the full hardware surface existed only on the headless host. The natives now live in one shared file (vm/esp/main/ hw_natives.c) both ESP hosts register, so the same program behaves the same whichever firmware is flashed. On the display board the sensors share the panel's I2C pins, so the natives ADOPT the bus the panel driver already owns (panel_i2c_bus + moth_hw_adopt_i2c_bus) rather than fighting it with a second master — verified live: a Dart program scanned all 8 onboard devices and bulk-read their registers through the adopted bus. New natives, all three hosts (the VM gained a public list API so a native can build and read List<int>): - i2cReadBytes/i2cWriteBytes — Wire.requestFrom's shape; empty list / false when the device does not answer; 64-byte clamp - prefsGetInt/prefsSetInt — NVS on boards, in-memory in the simulator; 15-char key limit is NVS's own. Verified on hardware: a push counter survived program swaps and a reboot - servoAttach/servoMicroseconds — 50Hz on LEDC timer 2 (analogWrite's 5kHz and tone's frequency never fight it), 14-bit duty, 500..2500us clamp, two channels Exposing sensor programs on the display host surfaced a real hole: push frames are serviced on the frame hook, and a program looping on delay() with no UI never pumps one — the board received restart frames and never acted. delay() now chunks long waits and polls the push channel, so `mothc run` restarts a sensor program in ~107ms (measured live). Dart layer (built in parallel by a second workstream against the same contract; the natives drift-test is what proves the two agree): I2c.readBytes/writeBytes, I2cDevice equivalents, Prefs, Servo with the degrees mapping in readable Dart. arduino-parity.md's "Not yet" table shrinks to attachInterrupt (the event-loop milestone, stated) and SPI (the display owns the bus on the verified board; an unverifiable claim is worse than a missing feature). The hw_parity golden exercises BOTH layers — raw natives and the classes over them.
i2cBegin, list-API hazard stated correctly micros() and delayMicroseconds() were unregistered on the display firmware — the host this branch exists to give sensors to, and the primitives every bit-banged driver is built from. A program that compiled, analyzed clean, and matched the parity table was answered MPRJ at push time. The timing natives (millis/micros/ delayMicroseconds) now live in hw_natives.c so the two firmwares can never diverge on them again; delay() stays host policy, because the display host's delay also services the push channel. Verified live: delayMicroseconds(1000) measured as 1025us by micros() on the display firmware. The hw_parity golden now calls all three, so their registration is executable on every host rather than resting on a table row. From the same review: noTone on a pin that never played no longer consumes a PWM channel (lookup-only helper); i2cBegin says "bus already started; new pins ignored" instead of silently keeping the old pins; and the list API's GC warning now states the actual hazard — creating a SECOND object while holding an unrooted list — and records that append never collecting is part of the API's promise, not an allocator accident.
shbmx
added a commit
that referenced
this pull request
Aug 15, 2026
feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host
shbmx
added a commit
that referenced
this pull request
Aug 15, 2026
feat(hw): Arduino parity — bulk I2C, prefs, servo, and hardware on the display host
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
i2cReadBytes/i2cWriteBytes+I2c.readBytes/writeBytes) — Wire.requestFrom parity; empty-list/false on no answer.prefsGetInt/SetInt+Prefs) — NVS-backed persistence; EEPROM parity.servoAttach/servoMicroseconds+Servo) — 50Hz on its own LEDC timer; degrees mapping in readable Dart.moth_new_list/append/is_list/length/at) so natives can produce and consumeList<int>.Test plan
mothc run(dogfooding): scanned all 8 onboard I2C devices, bulk-read registers through the adopted panel bus; prefs counter survived pushes and a reboot (3→4); restart of a delay()-looping program in 107ms.