FreeCAD/DXF to .rd conversion, named upload to controller memory with idle check and read-back verification, read-only status/file listing, and a fake controller for dry runs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
3.6 KiB
Ruida UDP protocol notes
What these scripts rely on, where it came from, and what was confirmed against a real RDC644x-class controller (60 W CO2 laser, firmware reporting 49 stored files).
Transport
- UDP. Host sends from port 40200 to the controller's port 50200.
- Every byte in both directions is swizzled with a per-model "magic" value. Ours is
0x88 (the common value for 644x boards over UDP).
meerk40t.ruida.rdjob.swizzles_lut(magic)returns the encode/decode tables. If the controller never ACKs anything, the magic may differ; RDWorks.rdfiles from the same machine use the same magic, and MeerK40t can guess it from one. - Host datagram = 2-byte big-endian sum of the swizzled payload bytes, then the payload. At most 998 payload bytes per datagram.
- The controller answers each datagram with a single byte: ACK
CC(C6on the wire), NAKCF, ENQCE(keep-alive). Wait for the ACK before sending the next datagram (3 s timeout). - Read commands get an ACK followed by a separate, swizzled reply datagram (no checksum prefix).
Commands are bytes ≥ 0x80
A command starts with a byte ≥ 0x80; its arguments are all < 0x80 (7-bit encodings). That makes
a stream self-delimiting: split at every byte ≥ 0x80. Integers are big-endian 7-bit groups:
encode14 (2 bytes), encode32/decodeu35 (5 bytes). Coordinates are micrometres.
Storing a job in memory (rdupload.py)
Follows LibLaserCut / VisiCut's Ruida driver. Insert two commands right after the job's
preamble (D8 12, E6 01, F0):
E8 02 file transfer: store, don't run
E7 01 <name> 00 file name, ASCII, 1-8 chars, null-terminated
then send the whole job (ending D7, end of file) as datagrams. Drop any E5 05 (file sum);
LibLaserCut sends none and the controller accepted the file without it.
Confirmed: the file appears in the panel's file list and runs correctly (clean cut, settings as encoded). Confirmed failure mode: uploading while a job is running gets every packet ACKed and the file silently discarded.
Read-only queries (rdlist.py)
| Send | Reply | Notes |
|---|---|---|
DA 00 04 00 |
DA 01 04 00 <u35> |
Status bits. Bit 0 = job running: confirmed (0x20018401 mid-cut, 0x2001x600/0x2403x600 idle). Bit 24 = moving, bit 1 = part end (from MeerK40t, unconfirmed). Other bits change while idle; ignore them. |
DA 00 02 00 |
DA 01 02 00 <u35> |
MeerK40t's emulator says 21/22/23 = running/idle/paused. Ours read 33313 whether cutting or idle: useless here. |
DA 00 02 05 |
DA 01 02 05 <u35> |
"Total doc number". Read 0 with 49 files stored. Don't trust it. |
E8 01 <n14> |
E8 01 <n14> NAME 00 |
Name of stored file n, 1-based, oldest first. Past the last file the controller doesn't reply at all; that timeout is the end-of-list marker. Confirmed. |
Commands this skill must never send
| Bytes | Effect |
|---|---|
D8 00 |
Start process (runs the current job) |
E8 03 <n14> |
Select document n: starts it |
E8 00 ... |
Delete document(s); E8 00 00 00 00 00 deletes all |
E8 04 <n14> |
Calculate document time (harmless, but not needed) |
A5 ... |
Panel keys (frame, origin, jog, pulse) |
D9 ..., 88, 89 |
Moves |
rdlist.py enforces this with an exact-match whitelist checked before the socket.
Sources
- MeerK40t (
meerk40t/ruida/): command table, swizzle, emulator, status bits. MIT. - LibLaserCut (VisiCut) Ruida driver: file-transfer header and UDP framing.
- Everything marked "confirmed" was observed on the real controller; the rest is from those sources and is labelled as such.