Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
# mUlt1ACE

> [!IMPORTANT]
> **multiACE is in the running for the Snapmaker U1 Innovation Fund.** The fund backs
> open-source projects for the U1. Community voting is open until Sep 30. Pick your
> favorite projects.
>
> [![Vote here](https://img.shields.io/badge/Vote%20here-0d9488?style=for-the-badge)](https://www.snapmaker.com/innovation-fund#vote)

Started as a SnapACE fork, it has grown to over 5 times the original size, with around 90% of the code now its own and many unique features:

[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/K3K610R4F9)

[![Guides & Downloads](visitbutton.png)](https://postapocalyptic-diy.com/multiace/)

## What's new in multiACE 1.11b
## What's new in multiACE 1.20b-pre (prerelease)

This is a prerelease: new features to try out before the next release. Please report anything odd.

**A big thank you to our new collaborators.** Three developers joined the project and brought their own features with them:

- **Simon-CR** - the ACE2-Open community firmware for the ACE 2 Pro, which lets multiACE read and write spool tags. multiACE now also runs on other Klipper printers, not only the Snapmaker U1 (his Voron is the first one), and a watchdog notices when an ACE silently stops answering and reconnects it.
- **Tareku99** - multiACE as a ready-made package for the paxx Extended Firmware, so the firmware installs and updates it for you. A big thank you to [paxx12](https://github.com/paxx12) for the plan to ship multiACE with the rolling releases of the Extended Firmware.
- **Godless50** - the ACE Pro gets its community firmware: flash it straight from the web UI, and read the tags of your spools on an ACE Pro too. Auto-dry can also follow an external humidity sensor.

**New look for the web UI.** The web UI opens in a new design - the classic one is still there, one click in the Config tab.

Fixes a regression in 1.10b: The Snapmaker and Polymaker vendors in the filament picker show again on firmware 1.6.0 and newer.
**Read spool tags on the ACE Pro.** With the community firmware, press *Read* in the filament picker and the spool's tag is identified and matched to your spool list. (Needs the community firmware on the ACE Pro.)

**More robust connection.** If an ACE drops off, multiACE keeps trying to reconnect it in the background; a disconnected unit is shown as offline instead of a confusing state, and you get no pause when nothing is printing.

**Also:** clearer messages when a spool has no tag code, small web UI and log fixes.


## What's new in multiACE 1.10b
Expand Down Expand Up @@ -664,6 +671,19 @@ All content is reviewed by humans before inclusion.

- **Popstar (forum.snapmaker.com)**
- **the default PLA swap temperature is set to 220 °C**

- **[Simon-CR](https://github.com/Simon-CR)**
- **Other Klipper printers** - multiACE runs on printers other than the Snapmaker U1.
- **Connection watchdog** - notices a silent ACE and reconnects it.
- **ACE2-Open community firmware** for the ACE 2 Pro - reading and writing spool tags.

- **[Tareku99](https://github.com/Tareku99)**
- **paxx Extended Firmware package** - multiACE as a package the firmware installs and updates.

- **[Godless50](https://github.com/Godless50)**
- **ACE Pro firmware flashing** from the web UI.
- **Tag reading on the ACE Pro** through his community firmware.
- **External humidity for auto-dry.**



Expand Down
2 changes: 1 addition & 1 deletion multiace/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.11b
1.20b-pre
10 changes: 0 additions & 10 deletions multiace/config/extended/ace.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,6 @@ pa_sync: true
#tag_write_format: openspool
#tag_write_uid_sku: true

# Gen-1 (ACE Pro) tag tunnel: when true, a slot the unit's own reader could
# not identify gets ONE opportunistic page read through the community
# firmware's RC522 tunnel (CV1.3.87x builds), so third-party spools surface
# with their card UID (OpenSpool tags also with material/colour). false =
# the unit keeps its normal status-only tag path; ACE_TAG_READ remains the
# explicit probe either way. On a unit without the tunnel this option only
# produces one klippy.log line.
# Live: ACE_SET_TAG_TUNNEL ENABLE=0|1
#gen1_tag_tunnel: false

# Humidity-controlled drying. Only an ACE 2 measures; an ACE Pro can only
# FOLLOW a master (picked per unit in the web drying panel). These are the
# defaults; per-unit overrides via ACE_SET_AUTO_DRY (web drying panel).
Expand Down
2 changes: 1 addition & 1 deletion multiace/docs/GEN1_FLASH.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ sequence, but nothing on the wire:

| entry id | image | size | CRC-16 | md5 |
|---|---|---|---|---|
| `1.3.863-opencubic` | OpenCubic ACE 1 Pro CFW v1.1.1 release asset `ACE_V1.3.863_20260716.bin` | 113720 | `0xC110` | `9f7b9a678a96caf98d6a08842d3ff971` |
| `1.3.871-tunnel` | Godless50/ACE-PRO-v1.-NFC-UID asset `ACE_V1.3.863_tunnel_ops.bin` (OpenCubic CFW base + UID stub + RC522 tunnel, reports CV1.3.871) | 114632 | `0x1AC9` | `219df3df77f7c7e1e15a79d580a2379e` |
| `1.3.863-stock` | clean stock `ACE_V1.3.863_20250518.bin` (rollback target) | 105652 | `0xDEFB` | `dcd04589dcadd5b4feab66d33e772531` |

To add an image: flash a dry run with it, read size/CRC/MD5 off the
Expand Down
137 changes: 97 additions & 40 deletions multiace/docs/GEN1_TAG_TUNNEL.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Gen-1 (ACE Pro) tag tunnel - `ACE_TAG_READ`, `ACE_SET_TAG_TUNNEL`
# Gen-1 (ACE Pro) tag tunnel - `ACE_TAG_READ`

The ACE Pro's own reader understands **Anycubic tags only**: a third-party
spool (OpenSpool NDEF, a blank NTAG, a Bambu/Snapmaker MIFARE chip) arrives
Expand Down Expand Up @@ -34,17 +34,25 @@ Companion documents:
NTAG capability container additionally triggers a bounded read of the
OpenSpool user area (pages 4..39), decoded by the shared `ace_rc522`
OpenSpool decoder.
* **Falls back automatically** (when enabled): a slot that is occupied and
whose tag the firmware did not identify - or whose SKU matches no table
entry - gets **one opportunistic read per insert**. The result lands in
* **Reads only on demand.** Every tunnel TRANSCEIVE blocks the firmware
for about 7 s (its lamps blink, its gates flicker), and a full read
holds about ten of them. So nothing reads automatically, neither at
connect nor after an insert (a session during an insert broke the
insert and took the unit off the bus, HW 2026-10-03): a tag is read
only by `ACE_TAG_READ`, i.e. the web Read button. The result lands in
multiACE's own per-unit store, never in `_info_per_ace`. Binding is
**gated on attribution**: the two slots of an antenna pair share one RF
path, so the card UID is offered to the **existing** tag-bind path
(`_spool_bind_by_tag(..., unbind=False)`) **only when the partner slot
(`slot ^ 1`) reads empty in the same status**. An occupied OR
unknown/absent partner still stores and surfaces the read but does not
bind (a wrong first binding has no repair path on a Gen 1); the operator
probe `ACE_TAG_READ` is the explicit override and always binds.
(`slot ^ 1`) reads empty in the same status, or when the partner's
card UID is already known** (its last tunnel read, or a UID code on the
spool bound there). A known partner card is put to sleep with HLTA
during the read, so the next SELECT answers with the other card; a
result can then never be the partner's. An occupied partner with an
unknown card still stores and surfaces the read but does not bind (a
wrong first binding has no repair path on a Gen 1); the operator probe
`ACE_TAG_READ` is the explicit override, always binds and also skips a
known partner card.
`unbind=False` on purpose: a tunnel read must never release a binding
the vendor path owns.
* **Surfaces it.** `get_status`: the slot's `uid` / `tag_format` are filled
Expand Down Expand Up @@ -76,38 +84,89 @@ ACE_TAG_READ ACE=<n> SLOT=<0..3> [PAGE=<n>]
[multiACE] ACE 0 slot 2 page 0: 04 22 52 FC 51 C8 2A 81 32 48 00 00 E1 10 6D 00
[multiACE] ACE 0 slot 2: UID 04225251C82A81 (ntag) - third-party tag
```
* It **moves nothing** (no lane rotation) and is safe while printing; the
tag must face the coil, so a read can fail on a stationary spool.
* It works with the enable flag **off** - the command itself is the
explicit consent. The result feeds the same store/bind/status path as an
automatic read.
* When no card answers and the lane may move (no print running, the slot
feeds no head), it rotates the spool forward in 20 mm steps until a card
answers (at most 650 mm, below the shortest path to a combiner), reads
it and pulls the lane back by the same distance. Otherwise it reads in
place.
* It is the only tunnel read; nothing reads automatically.
* On an ACE 2 the command keeps its previous meaning (rotate + read +
bind); the Gen-1 branch is only taken for a non-V2 protocol.

## The enable flag
## Always on

Automatic fallback traffic needs an explicit opt-in:

```
ACE_SET_TAG_TUNNEL ENABLE=0|1 [PERSIST=0|1]
```

* Default **off** (`#gen1_tag_tunnel: false` in `[ace]`); the setter is
live + write-through, `PERSIST=0` = RAM only until restart.
* With it off, the **only** tunnel traffic is a command the user typed.
* On a unit without the tunnel the flag is inert (one `klippy.log` line,
no console noise, no repeated probes).
`ACE_TAG_READ` uses the tunnel on every ACE Pro whose firmware matches
the tunnel builds (`CV1.3.87x`) **and** answers a probe op through the
stub; a stock unit never receives tunnel traffic, so there is no switch. To go
without it, flash the stock firmware. The former `gen1_tag_tunnel` option
is still read and ignored, so an existing config line does not halt
Klipper; `ACE_SET_TAG_TUNNEL` only reports that the switch is gone.

## Limits

* **Community firmware required.** The tunnel exists only on the
community build `CV1.3.87x` (verified reference `CV1.3.871`). Stock and
the UID-only community image are never touched.
* **The tag must face the coil.** A Gen-1 has no host-side motor control
(`feed_filament`/`unwind_filament` are refused by the stock firmware), so
an automatic read cannot rotate a spool to find the tag. The one attempt
per insert is timed to the firmware's own insert procedure, which already
rotates the spool; if the read misses, use `ACE_TAG_READ` to retry.
* **Searching for the tag.** The tag only answers while it faces the coil.
When `ACE_TAG_READ` finds no card and the lane may
move, it rotates the spool forward (`feed_filament`, 20 mm
steps, at most 650 mm, after the slot reports `ready`), stop at the first
card and pull the lane back by the distance moved (`unwind_filament`;
the ACE Pro has no decoder, so it is the commanded distance). Never
during a print, never on a slot that feeds a head. A read starts only
once no slot of the unit reports `preload` (the firmware's own pull-in,
17-25 s measured). The
pull-back waits up to 60 s while the unit is busy with another slot
(it answers FORBIDDEN then).
* **Unreadable card at the field edge.** A card that answers SELECT but
whose page read fails is re-read further in (+20, +40 mm) and just
before the hit (-20 mm), like the ACE 2 centring.
* **Whose card?** When the partner slot holds an unknown card, the
partner lane is rotated (20 mm steps, at most 200 mm, then back): the
card leaves the field -> it is the partner's (stored for the partner,
our lane is searched with it skipped); it still answers -> ours, bind.
For `ACE_TAG_READ` only; the partner must be idle
and feed no head.
* **Unit-wide wait.** The ACE Pro refuses motor commands while any slot
of the unit still moves, so a search starts only when every slot is
ready or empty.
* **Firmware identity is vetted.** The community firmware reads tags on
its own, but cannot say which bay of the shared antenna answered (or
replays an old record). While the partner slot is occupied, our own
read decides: our OpenSpool read replaces the firmware identity, and a
card of ours whose user pages were ALL read without an OpenSpool record
hides a firmware OpenSpool identity. Until such a read exists (pending,
running, or only the UID came through) the firmware identity stays
shown.
* **Never during an insert.** A tunnel session holds the reader the
firmware needs to pull in and identify a new spool. No session starts
while any slot of the unit reports `preload` or `shifting`, and a
running session stops at its next op when an insert starts or a gate
of the unit changes; the reader is released at once and the read is
retried when the unit is quiet. A session overlapping an insert made
the preload drop the new spool and crashed the ACE off the USB.
Lanes the session moved itself are not counted: the ACE Pro reports
our own search step as `shifting` too, and a real re-insert on such a
lane still flips its gate. While a stopped read waits for its retry,
the slot keeps the identity it showed before.
* **Only real moves count.** A search step counts only when the ACE
answers `success`; `empty` ends the search. A spool pulled or
re-inserted during the search ends it, and the new spool is never
pulled back.
* **Survives a restart.** The reads are persisted (`ace__gen1_tag_reads`,
save_variables) and restored at startup; an empty slot evicts them, a
fresh read replaces them. A spool swapped while the printer was off
keeps the old UID until its card is read again (same as the ACE 2).
* **Never during a print.** A tunnel session holds the unit's reader;
`ACE_TAG_READ` stays available during a print (read in place, no
rotation).
* **A TRANSCEIVE blocks the firmware ~6.9 s** on the tunnel build (every
page read, data or not; FA log 2026-10-03). The reply wait for op 3 is
10 s, and any op without a reply ends the session (release only), so
no further op is queued into a unit that is still blocked.
* **One read per unit at a time.** A manual `ACE_TAG_READ` issued while
another read runs on the same unit is queued behind it (at most 180 s)
instead of being refused.
* **Two antennas, two bays each; reader channel = bit-swap.** Antenna 1
covers slots 0 and 1 (reader channels 0 and 2), antenna 2 covers slots 2
and 3 (channels 1 and 3). The reader **channel** for a slot is
Expand All @@ -116,7 +175,7 @@ ACE_SET_TAG_TUNNEL ENABLE=0|1 [PERSIST=0|1]
`slot ^ 1`, whose channel is `channel ^ 2`. An earlier live run saw
readers 0/2 and 1/3 answer with the same UID - that is the shared RF
path, and the reason the automatic bind is gated on the partner slot
reading empty (see above). Two slots reading the same UID cannot both
reading empty or carrying a known card (see above). Two slots reading the same UID cannot both
bind: the existing duplicate guard refuses the second one, and an
unattributed read is stored but never bound.
* **Read cost.** One page read is ~25 tunnel commands (~0.5-1 s on the
Expand Down Expand Up @@ -148,29 +207,27 @@ packing/signed conversion, the slot -> reader-channel bit-swap map
nothing; a matching-but-silent firmware gets one probe; a dead link times
out bounded), both genuine live captures (`04 22 52 FC ...` and
`53 42 70 E9 ...`) yielding their bytes and UIDs, an OpenSpool NDEF decode
through the reused `ace_rc522` decoder, and the ace.py wiring (flag
gating, one attempt per occupancy, the shared-antenna bind gate - partner
occupied/unknown/absent -> stored but not bound - own store, no
through the reused `ace_rc522` decoder, and the ace.py wiring (one
attempt per occupancy, the shared-antenna bind gate - partner occupied
with an unknown card -> stored but not bound, known card -> halted - own store, no
`_info_per_ace` writes, get_status surfacing, unchanged V2 path).

## Open points for the maintainer

1. **Flag name / default.** Implemented as `gen1_tag_tunnel`, default
`false`, setter `ACE_SET_TAG_TUNNEL`. Happy to rename or default it on.
2. **Surface third-party spools automatically?** The automatic read binds
1. **Surface third-party spools automatically?** The automatic read binds
by card UID only when a table entry already carries that UID **and the
partner slot on the shared antenna reads empty**; it never creates
entries and never releases a vendor binding. If the maintainer prefers
report-only (no bind call at all), the `bind=False` path is already the
non-binding one.
3. **Web UI.** The new `tag_tunnel` status block and the slot `uid` /
2. **Web UI.** The new `tag_tunnel` status block and the slot `uid` /
`tag_format` fill are already in `get_status`; the web backend passes
the slot `uid`/`tag_format` through today. Whether to add a Config tab
toggle and a "third-party tag" badge is a UI decision.
4. **Retry policy.** One attempt per insert is deliberate and cheap; a
3. **Retry policy.** One attempt per insert is deliberate and cheap; a
bounded retry ladder (or a "read on next rotation" hook) would raise the
hit rate on spools whose tag parks away from the coil.
5. **Firmware acceptance.** The version pre-gate accepts `CV1.3.87x`.
4. **Firmware acceptance.** The version pre-gate accepts `CV1.3.87x`.
A later tunnel build (e.g. `CV1.3.872`) would pass the gate; the probe
then decides. If a future build changes the op contract, the constant
needs revisiting (the reference is `CV1.3.871`).
Loading
Loading