Repository navigation
Redrawn machine description: change bits, description by hash, Reconcile, wiring spec, revive of gone rows - #23
Merged
Merged
Conversation
rc522.h: an optional Irq policy on the mode (using Irq = X) makes transceive() wait on X::wait() instead of polling ComIrqReg; ComIEnReg is enabled only while a command runs and cleared after it, DivIEnReg selects push-pull at init. Modes without it are unchanged. main.cpp: IRQ on D0 (GPIO16, sampled: no interrupt on the ESP8266), RST on D4, counters of the line against ComIrqReg every 5 s. The D4 supply switch and keys x/l are gone. An IRQ on a boot strapping pin (D4) holds it low across a reset of the board and the ROM loops in boot mode (2,x). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
start() returns at once; finish() reads what the command came to. Without an interrupt part the poll runs both in one call, as before. With rc522::Interrupt<Delivery, Observer> (a mode's `using Irq`) the poll starts and returns, and service(row, now) finishes the command when the line is asserted or after 40 ms. The register values (what is enabled while a command runs, push-pull) moved from the sketch to the driver; the App chooses the pin and the delivery (irq::Sampled, irq::IsrFlag, which reject the boot strapping pins). The line against ComIrqReg is checked and a mismatch is reported through the failure edge (Fault: spurious / missed, Timeout); the counters are an Observer in the sketch. Host tests with a modelled IRQ line (spi_irq.cpp) and the delivery pin rules (irq_delivery.cpp). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…al line check and fallback - Without an interrupt part the driver is the polling driver as it was (same AVR image to the byte: test/baselines/rc522_noirq_avr.size); the poll state machine and its per-row state exist only with one. - fail::Services<W, Drivers>: the loop's service(row, now) over every row of the drivers that serve. - rc522::Interrupt<Delivery, Observer, Check, Fallback>: the line check is a part (NoCheck by default). - rc522::PollOnLineFault: a line fault is reported on the failure edge as a kind Recover does not act on (Refused) and that row polls the register from then on, still reading cards; the chip is not initialised. Without it a line fault stays the chip's (Fault, Recover). A chip initialised again tests the line again. Host test in three configurations (fail-fast, fallback, no check); AVR builds with the interrupt part. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
bmp280_machine.h: Machine<W> with nodes temp, press (read-only), cal (device constants) and ctrl, a group of register mimics (get() reads the chip, set() writes it, the defaults are the init); the device's data are static members. Published<Code, Node, node, OnSync<fn>>: an outer node that refers to the inner one (ItemRef) and calls fn(value) from a sync pass when it changed. describe() writes the machine, the published codes, their paths, fields and which ones notify. The sketch uses it for the air sensor: temp= and press= when they move, keys d (description), a (the control group by path), o (set ctrl_meas), r (the defaults). Host test against a simulated chip with the datasheet's worked example (bmp_machine.cpp, mockBmpTwi.h). Needs OneData onsync-r3 (0.1.21) and OneMenu itemref-get. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… old driver goes - Machine<W, Criteria, Mode>: it takes its Criteria (Addr<0x76>), so two sensors are two types with their own statics. - PathRef<Machine, 3, 1> / PublishedAt: an outer node reaches its inner node by a compile-time path (any node, a leaf of a group too); ItemRef stays for standalone objects. The image is the same as with ItemRef. - Capture on the register nodes keeps the last set (also while the part is gone); restore() replays it in order, then calls the tail; forget() drops it; the defaults are the init. On discovery and on return the part is validated (chip id and calibration hash): validated, the capture is replayed; otherwise the slot is dropped and the defaults are the init. - The driver is under fail::DevEdge over the bus edge (Mode::checked): each poll reads the control registers back, a register that lost its capture is Corrupt (Recover -> reinit), a part that stops answering is retried and probed, and reinit validates it. - Uses oneData::ReadOnly (OneData readonly-public, 0.1.22). The compensation multiplies where the datasheet shifts a possibly negative value (undefined before C++20); bmp280.h, the polling driver it replaced, is removed. - Host tests: capture, reset behind the host's back, unplug with a set while gone, another part, another chip id (bmp_capture.cpp); two machines, paths and a published leaf (bmp_machine.cpp). build_spi1.sh now fails on a test's exit status. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- link/frame.h: the request/response framing of role/link.h (op, u16 length, payload; status, u16 length, payload) for a device whose ops answer with a payload; python/onemachine's StreamLink and CtypesLink talk to it unchanged. - examples/spi/src/tree_ops.h: ops over the published nodes: d (the description), v (get by code), w (set by code, through the node: its limits, its capture, its register), n (the changes since the last n, from a fail::Buffer<8>: the newest are refused and counted when it is full, and the reply says how many were missed). Nodes carry Limits; the description lists ranges; PathRef forwards set(). - examples/spi: env d1_mini_link (-DONEMACHINE_LINK) puts the link on the serial port instead of the log; op f is one fault key; the RFID card is a code that notifies; air/config and air/ctrl_meas are published. - python/onemachine/tree.py: Tree(link): the codes from the description, m.temp, m.air.ctrl_meas = 0x27 (refused here when read-only or out of range), m.changes(), m.missed. - test/link: the consumer against the air sensor on a simulated chip, over a pipe and through ctypes (wired into build_spi1.sh); examples/spi/rig_session.py is the same on the real board. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
# Conflicts: # examples/spi/src/bmp280.h
…nd Python tests - Status: every published code is bound to a row (Machine::status() reads it from the registry: Alive, Stale or Gone). It is listed in 'd' (a status column), carried in every 'v' reply (the status first, then the value; a part that is not alive answers its last value: a register its capture, a sensor its last reading), and a change of it is queued like a value change (bit 7 of the code number, the code that stands for the row). Python: m.status(code), m.reading(code) -> Reading(value, status), m.temp raises Stale (with the last value), changes() has a Change(code, status=...) for every code of the part. - One link: role::Link's app hook can answer with a payload (`payload = true`: describe() for op 'd', request() for the others, role::LinkReply). link/frame.h is gone; the spi example's tree ops (d v w n f) are served by role::Link<role::Machine<>, ...>. The role layer is unchanged: the AVR role builds (no link, link, tuned + link) are byte-identical to before, and the role suite passes. - CI: the suites job clones OneData, OneMenu and what OneMenu includes (the branch name if it exists, else main) and sets up Python, so build_spi1.sh runs the machine tests and test/link/check_tree.py. - Machine header: nodes must not be touched from static initialisers (template inline statics initialise in no defined order). - The old polling bmp280.h and its compensation test are gone (merge of main; the machine's own compensation is covered by the machine tests). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…chine builds against them) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mplate parameters are as they were A defaulted extra template parameter changed the instantiation names in the image (not the code), which a symbol baseline shows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… sizes.sh for the full balance avr_tree.cpp builds the BMP280 machine under the spi example's failure edges on the real AVR TWI core, bare or with the link and tree ops (-DLINK). sizes.sh prints flash and RAM for each variant on AVR and on the ESP8266 (examples/spi via PlatformIO). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…mber; only the card queues A state code (temp, press, a register, a row's status) is one pending bit (bmpm::StateChanges): a change sets it and counts the machine's sequence number. 'n' takes the sequence number of the last reply the consumer got, and answers each pending code once with its value as it is now, then the events (the card, still a fail::Buffer<8> that refuses the newest and counts it). A consumer whose number is not the last one answered missed a reply: every code is sent (resync). The Python Tree sends its number, follows 'more', and counts resyncs. check_tree.py: a burst of 100 temperature changes is one record with the latest value, while 13 cards in the same burst keep the oldest 8 and count 5; a lost reply gives a resync with every code. test/link/build.sh also runs the device under ASan+UBSan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…s every bit set A record of 'n' carries the code's row status with its value (u8 status << 6 | code, i32), so a status change sets the same bit as a value change; kind 3 marks an event. On a sequence mismatch the device sets every bit and answers as any other 'n'. The Python Tree announces a row's status when it differs from the last one it told, and a value whenever its bit was set by the value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…d output 'd' answers `hash <fnv>` and each code's status; the hash is FNV-1a over the description's text without the status, folded at compile time by bmpm::Walk (now constexpr-capable: a put may fold, and may leave out the status). examples/spi/describe.cpp writes <hash>.txt from the same types the firmware is built from (src/air_tree.h: the codes, the published nodes, the card's line, the sensor's Criteria), checking its text against the fold; describe.py runs it as a PlatformIO post script into .pio/build/<env>/description. -DONEMACHINE_DESC_TEXT (env d1_mini_link_text) keeps the text. The Python Tree takes descriptions=<dir>, loads <hash>.txt and checks its hash; without it, a readable error names the hash and the fallback. test/link/build.sh runs check_tree.py on both builds (pipe, ctypes, ASan+UBSan). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…t builds are Round 6 to the byte noinline on the text walk made the text build larger (AVR 13,440 B, Round 6 13,278 B), so bmpm::Walk is back to Round 6's code and bmpm::HashWalk (constexpr, without the status) folds the hash and prints the build output. The App's card text is one macro (AIRTREE_CARD_TEXT), a literal at the App's text path as before; a published value's notify function is passed as a constexpr pointer, so OnSync<&say<Code, 2>> is Round 6's type. With the build stamps pinned, the AVR bare and text images and the ESP8266 d1_mini and d1_mini_link_text images are byte-identical to Round 6. check_tree.py, on the text build: the description the device sends, without its statuses, is the generator's file (the two walks cannot drift apart unnoticed; a one-word change to HashWalk fails it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…sters Each register holds what is wanted of it (desired(), its default from the start: RegAt's regDef below it); set() changes it and writes the chip, also while the part is gone (the last intent). The machine applies what is wanted when the part is found or comes back validated, and resets it to the defaults for another part (D13/D22); the driver's canary compares the read-back with desired(). This replaces Capture (its value and `have` flag), the restore/forget chain and RestoreEnd, and the separate defaults-as-init step. The Round 4/6 scenarios pass unchanged (bmp_capture.cpp: found, set, soft reset behind the host's back, unplug with a set while gone, replug, another part, another chip id; check_tree.py over pipe, ctypes and ASan+UBSan); bmp_capture.cpp reads desired() where it read captured() and known(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…nd a diff against the device's description examples/spi/wiring/rig.toml is the rig's wiring by hand (TOML: tomllib, no dependency): the buses' pins, the parts in the App's machine order, and the codes published as code = "part/node". python/onemachine/wiring.py reads the board's pins from OneChip (esp8266Device.h: each D pin's GPIO and what its comment says, boot strapping or no interrupt), checks the wiring (each pin once; no IRQ on a strap pin; no ISR on GPIO16; a part on its driver's bus, at an address and with ids its driver knows; published nodes that exist), emits the App's composition, and diffs the spec against the device's 'd' (tree._parse). The drivers' manifests are a table in wiring.py, a stand-in for a declaration beside each driver. test/wiring/check_wiring.py: rig.toml is sound; the 16 lines it emits are all in examples/spi/src/main.cpp; the simulated device's description is what it wires; another address, another order of machines or of codes are each told; three faults give three sentences. rig_irq_d4.toml (the RC522's IRQ on D4) is refused with the reason (a strap pin: a pending IRQ holds GPIO2 low and the ESP8266 boots into ROM mode), and the same wiring emitted anyway is rejected by the C++ (irq::Esp8266Line). Run from test/discover/build_spi1.sh. No firmware changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…escription's hash By hash (the default), a code in 'v' and 'w' is u32 the description's hash and u8 the code's number: a request under another hash is refused with role::LinkBadHash and nothing is written; a name is BadLength. The names are only in the build output: on the AVR image none of the codes' names is left, and the ESP8266 link build by hash leaves out the log lines that carry them (its log goes nowhere; SPI_LOG_NAMES), so they leave its image too. The text build (-DONEMACHINE_DESC_TEXT) keeps names in v and w with Round 6's code: with the stamps pinned, the bare and text images on AVR and ESP8266 are still byte-identical to Round 6. The Python Tree sends numbers under the hash it read (Tree.hash) and names otherwise; a BadHash says the device is another build (refresh()). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
…ests; the strap rule by who drives the line Each driver declares what it needs beside the driver (discover/manifest.h): bmpm::Manifest (i2c, 0x76/0x77, ids 0x58/0x60, which the machine's Driver and BME entry now read) and rc522::Manifest (spi, its IdSet, its lines: CS and RST driven by the MCU, IRQ by the chip). The buses' lines have roles too (I2C SDA shared; SPI MISO the part's). The rig's pins are one Wiring in air_tree.h over OneChip's Esp8266Pins (OneChip rnd/redrawn-6), the sketch takes Twi, the SPI slots, RST and the IRQ from it, and air_tree.h checks it at compile time: a line the part drives or shares is not on a strap pin; the MCU's may be (D3 and D8 as chip selects). irq_esp8266.h reads strap() and hasIrq() from OneChip. The description by hash gains a wiring section (the build output's only: the device pays nothing, the hash covers it): wiring wemos-d1-mini / i2c sda 4 shared scl 5 mcu / spi ... / rfid rc522 cs 15 mcu rst 2 mcu irq 16 device / empty cs 0 mcu / air bmp280 at 0x76 describe.cpp also writes facts.txt (the board's pins and facts, the buses' lines, each manifest with the BMP280's nodes walked from its machine), and python/onemachine/wiring.py reads it instead of OneChip's comments and its own table; its strap rule goes by role, and diff compares the pins. emit writes a Wiring that air_tree.h includes in place of its own (-DAIRTREE_WIRING): built, the rig's gives the same description to the hash. test/wiring/check_wiring.py: the facts; the emitted Wiring's description is the hand-written one's; the device's hash is the build output's; the wiring lines; another address, order of machines or codes, and two MCU lines swapped between strap pins are each told; SDA on D3 is refused, RST on D3 is not; the IRQ on D4 is refused with the reason and its emitted Wiring does not build. test/discover/build_spi1.sh now fails on the link step's and the wiring step's own exit status. With the stamps pinned the bare and text images (AVR, ESP8266) are Round 6's to the byte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GDeNB62nMDNgj6xceZUWwx
# Conflicts: # test/link/build.sh
…rawn-all # Conflicts: # examples/spi/README.md # python/onemachine/tree.py # test/link/build.sh # test/link/check_tree.py
… address An app that declares `revive = true` can call World::reviveGone(): each Gone device row's declared address (I2C address, SPI slot) is checked again by the same entries, and a part that answers takes its row again (its init runs, consumers are bound). Without `revive` nothing changes: found() calls reg.add as before and the AVR baselines are byte-identical. The spi example calls it every 5 s. README and rig.toml note the 10k series resistor on the RC522's SS line (D8 must be low at reset). Verified: test/discover/revive.cpp (native, ASan+UBSan), all suites, and on the D1 mini rig (unplug, wait past Gone, replug: Alive again without a reset, wanted ctrl_meas written back). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
main carries Round 6 re-cut as #19-#22 plus the scripts follow-up; their changes since the Round 6 tip (c49ba6f) are applied on top of rnd/redrawn-all: the spi_irq, services and payload_check tests, the role_link AVR baseline, the test tools (baseline, lastok, selftest) and the build scripts failing on their own status. The machine header note names Reconcile where main's names Capture/RestoreEnd. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WEBoG3kUwkuTW9Yc2HtQhK
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.
Requested by Roy · project thread
Before: a consumer of the spi example could lose the latest value under a burst of changes (the change queue refused the newest), the device sent its whole text description and looked codes up by name, restore was three parts (capture, canary, restore chain), wiring mistakes were found on the board, and a part that went
goneneeded a reset to come back.After: state changes are one pending bit per code plus a sequence number (the latest value always arrives; only the card's events queue).
danswers with a description hash,v/wuse the hash and code numbers, and the text description is generated at build time. OneReconcilepart holds each register's desired value and re-checks identity before writing it back. A wiring spec (examples/spi/wiring/rig.toml) is checked, turned into the App's wiring and diffed against the device's description; a line the part drives on a boot strap pin is refused before the build and at compile time. An app that opts in withrevive = truecan callWorld::reviveGone(), which re-checks agonerow's declared address only.These are experiments 1 to 6 of the redrawn design (decisions D40-D48) plus the revive fix from the Round 7 rig check, as tested on the rig on
rnd/redrawn-all(661d9fb), with main (#19-#22) merged in. Merge OneChip #3 first: this PR reads itsesp8266Pins.h.Cost (web agent's measurements on
rnd/redrawn-all, before revive): link build by hash is 10,840 B flash / 286 B RAM on ATmega328P, −2,438 B / −225 B against the Round 6 link; ESP8266 289,355 B / 29,636 B, −3,736 B / −296 B. Revive adds +340 B / +4 B on the ESP8266 image when used; withoutrevivethe AVR baselines are byte-identical.How
examples/spi/src/tree_ops.h,python/onemachine/tree.py: pending bits (status in the top two bits of the code byte, 5-byte records), resync on a sequence mismatch, description by hash, code numbers.examples/spi/src/air_tree.h,describe.cpp,describe.py: one shared composition; a host generator writes<hash>.txtandfacts.txtand refuses on a hash mismatch.examples/spi/src/bmp280_machine.h:Reconcilereplaces Capture; a read-back mismatch goes through the identity check first.include/oneMachine/discover/manifest.h: who drives each line (Drive::Mcu,Device,Shared); driver manifests for the BMP280 and RC522;python/onemachine/wiring.py(check, emit, diff).include/oneMachine/discover/registry.h,driver.h:reviveGone()reuses the row's slot.test/link(burst, resync, both description modes, ASan),test/wiring,test/discover/revive.cpp.Verified: host suites on the merged tree here (
native.sh,build_spi1.sh,test/wiring; AVR builds not available in this environment, CI runs them). On the D1 mini rig (local agent, 661d9fb): description by hash and wiring diffok, change bits under real changes, unplug/replug status, Reconcile after a soft reset and a set while unplugged, normal boots with a card on the reader, and a BMP280 backalive31.6 s aftergonewith no reset and itsctrl_measkept. Not verified on hardware: the RC522 revive path, and a BME280 swap (none on hand; host test covers id 0x60).🤖 Generated with Claude Code
https://claude.ai/code/session_01WEBoG3kUwkuTW9Yc2HtQhK
Generated by Claude Code