Two ACE 2 Pro units, firmware V1.1.31, chained through their own chain ports on a single USB
connection, enumerated to distinct addresses and commanded individually. No USB hub, no per-unit
power sequencing, no firmware modification.
Everything below is measured. Nothing here is new protocol analysis — the protocol was already fully understood by others (see Credit). What was missing was whether it actually works with real hardware sharing one bus.
Discussion: multiACE #120 — where this was reported, following the request in #62 for exactly this finding.
The ACE 2 Pro protocol has always had the pieces for multi-unit operation: DISCOVER_DEVICE (0) and
ASSIGN_DEVICE_ID (1) are implemented in the firmware, the frame carries a device address, and
replies OR in bit 7. That much was already documented.
What nobody had confirmed is whether several units physically sharing one bus behave. Every unit
boots at address 0, so a DISCOVER broadcast reaches all of them at once — and if they answer on top
of one another, enumeration never gets off the ground. The existing research notes are explicit that
this was untested, flagging another project as reported to run multiple units on one adapter with
the caveat "check this before relying on it".
In practice the field had settled on a workaround: give each unit its own USB adapter through a hub, which sidesteps the shared bus entirely. That works, but it is one adapter and one cable per unit, and it leaves each unit's chain port unused.
It turns out the chain ports work. This note reports what two units did on one bus, so the question can stop being open.
DISCOVER @0 -> two valid frames, one per unit
uid <first> (3 x uint32; redacted, they are hardware serials)
uid <second>
ASSIGN(<first>, 1) -> ack
DISCOVER @0 -> one reply: only the still-unassigned unit remains
ASSIGN(<second>,2) -> ack
GET_INFO @1 -> addr 0x81, "V1.1.31" 3/3 rounds
GET_INFO @2 -> addr 0x82, "V1.1.31" 3/3 rounds
DISCOVER @0 -> silence; the bus is clean
Addressing behaved exactly as the existing analysis describes: the address byte selects the unit and
replies OR in bit 7 (0x81, 0x82 observed).
Both units at address 0, answering the same broadcast. Two runs — 60 rounds with per-byte timestamping, then 300 rounds for the rate.
| Rounds returning two CRC-valid frames | 300 / 300, zero CRC errors |
| Round-trip | ≈30 ms |
| Latency of the first reply | 5.4 – 43.1 ms, median 18.2 |
| Gap between the two replies | 0 – 41.7 ms, median 12.6 |
| Which unit replies first | random — 27 / 33 across 60 rounds, no fixed order |
| Frame airtime at 230400 baud | 27 bytes ≈ 1.2 ms |
Replies serialise; they do not overlap. Extrapolating the measured bound by pair count — Anycubic printers take at most four units:
| units | collision rate per round |
|---|---|
| 2 | 0 observed in 300; 95% upper bound 0.99% |
| 3 | ≤ 2.95% |
| 4 | ≤ 5.82% |
This is worth stating plainly rather than glossing. A model in which each unit simply answers at an uncorrelated moment inside the observed ~40 ms window predicts roughly 18 collisions in 300 rounds. Zero occurred. Something is actively keeping the units out of each other's way — carrier sense before driving the bus would be the obvious way to do it — but this was not investigated and remains unknown.
Reply latency also never exceeded ~43 ms across either run, so there appears to be an upper bound on how long a unit will wait before answering — but that bound is an observation, not an explanation.
Treat the numbers as measured facts and the cause as an open question. If the serialisation turns out to be incidental on some other bus topology or unit count, the collision figures above do not transfer.
A round does not need every unit to answer cleanly. It needs one.
loop:
DISCOVER @0
take ANY one valid uid from the reply
ASSIGN(uid, next free address) # that unit leaves address 0
until DISCOVER @0 is silent
Each assignment removes a talker from address 0, so contention strictly decreases and the last unit enumerates alone. A collided round costs a retry and nothing else — a corrupted frame fails CRC and is discarded, which is the correct failure mode. Step 3 of §1 shows the thinning directly.
This is why the ≤5.8% four-unit figure does not need to be small: it is the probability of a retry, not of a failure.
Two units both replying "V1.1.31" proves nothing — the strings are identical. Three independent
discriminators:
Addressed DISCOVER returns that unit's own uid. Each address returned exactly the uid that had
been assigned to it, and never the other unit's. (UIDs are per-device hardware serials and are
redacted here; what matters is that they were distinct and stable.)
DISCOVER @1 -> addr 0x81 uid of the unit assigned to 1
DISCOVER @2 -> addr 0x82 uid of the unit assigned to 2
(DISCOVER order at address 0 is random from round to round, so which physical unit lands on which
address depends on who answered first that time — which is itself part of the result.)
Distinct physical readings. One unit had recently run a dryer cycle; the other had never been heated. Stable across repeated rounds:
| ptc1 | ptc2 | env | humidity | |
|---|---|---|---|---|
| @1 — never heated | 21.07 | 20.28 | 22.65 | 61.9% |
| @2 — ran a dryer cycle earlier | 25.14 | 29.19 | 24.22 | 52.4% |
Visual, and reversed. FLASH_LED addressed to one unit only, components = 0x0F, slow1 = 4,
loop = 2 — run twice, targeting a different unit each time:
| addressed | observed |
|---|---|
| the chained unit | it blinked five times and repeated twice; the USB-side unit stayed dark |
| the USB-side unit | it blinked; the chained unit stayed dark |
Five blinks is slow1 + 1 and two repeats is loop — the programmed pattern, on the addressed unit
and only on it.
Running it in both directions matters: a single-direction result could be explained by something about the topology always favouring one end. Swapping the target and getting the mirror image rules that out. This is selection by address, not an artefact.
- Address every assigned unit at least once every 3.5 s, forever — including while later units are still being enumerated. A unit that goes quiet reverts to address 0 and starts answering broadcasts meant for enumeration. Verified: a unit assigned to 5 answered on 5; after 4.2 s of silence, 5 was dead and 0 answered again.
- The
ASSIGNacknowledgement arrives on the OLD address. Receive filtering switches to the new address immediately, but the ack for the assigning frame still carries the old one. A client awaiting it at the new address times out on a successful assignment. - A failed
ASSIGNis total silence, indistinguishable from an absent device. There is no error frame; the unit simply does not answer. - Addresses do not survive a power cycle. Re-enumerate on every connect.
A working implementation of all of this is in ace2_daisy_chain.py.
Two ACE 2 Pro units, firmware V1.1.31, one CH343 USB link, units chained unit-to-unit. Every figure
above was captured over a live serial link; nothing is inferred. Experiment design and analysis of the
results used AI assistance.
Worth recording because it shaped the work: the expectation going in was that this would fail — that two units both sitting at address 0 would answer a broadcast on top of each other and make enumeration impossible. The very first measurement refuted that, and the tell was easy to miss: the order of the two replies alternated between rounds. Units talking over each other corrupt each other; they do not take turns.
Not established: the arbitration mechanism; behaviour with three or four units — only two were available; whether these figures hold on a different chain length, cable, or firmware version.
Not claimed as new: the protocol, the frame format, the DISCOVER/ASSIGN handshake, the address
byte semantics, or anything else about how the ACE 2 Pro communicates.
- hakimio did the foundational work and everything here rests on it.
His research gist carries the
protocol itself, the
ace_com.protoschema, an interactive shell, the OTA updater and the MCU firmware analysis; he also publishes a working ACE 2 driver. Before that research the ACE 2 Pro was a black box — the generation change to RS-485, 230400 baud and Protocol Buffers meant no earlier project could talk to it at all. - Simon-CR/ace2-pro-firmware-research —
the research notes that build on hakimio's work, including the decoded MCU descriptors and the
subsystem analysis. It is that repository's
docs/13which flags multi-unit as reported but unverified, and this note is the answer to that specific caveat. - Kobra-S1/ACEPRO — reported to do bus discovery with multiple units, the report that prompted the check.
This document contributes measurement of one thing those notes flagged as unverified, and nothing else.
No affiliation with or endorsement by Anycubic. Contains no firmware binary, no decompiler output and no reproduction of device program code.