ACNET protocol¶
This page documents the low-level ACNET protocol for advanced users who need to communicate directly with frontends or implement custom logic.
Low-level protocol -- prefer higher-level APIs
This module provides direct access to ACNET protocol primitives. It is strongly recommeded to use the higher-level pacsys APIs instead. Only use AcnetConnection directly when you need custom ACNET interactions.
TCP connection restrictions
acnetd services running on clx and most other central nodes restrict certain tasks from being reachable by TCP.
Specifically, GETS32,RETDAT,FTPMAN,SETS32,SETDAT,DBNEWS,STATES. If you want to access those, you will need to
use AcnetConnectionUDP while running on one of the nodes directly. At that point, you should REALLY know what
you are doing and why.
TCP example¶
Ping an ACNET node (CLX74) to verify connectivity:
import queue
from pacsys.acnet import AcnetConnectionTCP, node_parts
with AcnetConnectionTCP("acsys-proxy.fnal.gov") as conn:
# Look up node address
node = conn.get_node("CLX74")
trunk, n = node_parts(node)
print(f"CLX74 = {node} (trunk={trunk}, node={n})")
# Send ping (request to "ACNET" task with 2-byte payload)
reply_queue = queue.Queue()
conn.send_request(
node=node,
task="ACNET",
data=b"\x00\x00",
reply_handler=reply_queue.put,
timeout=5000,
)
reply = reply_queue.get(timeout=5)
print(f"Reply: status={reply.status}, data={reply.data!r}, last={reply.last}")
API details¶
Parsing data structures¶
from pacsys.acnet import AcnetPacket
packet = AcnetPacket.parse(raw_bytes)
if packet.is_reply():
print(f"Reply from {packet.server_task_name}")
print(f"Status: {packet.status}")
print(f"Data: {packet.data.hex()}")
| Class | Description |
|---|---|
AcnetPacket |
Base class - parse with AcnetPacket.parse(data) |
AcnetRequest |
Incoming request from another task |
AcnetReply |
Reply to a request we sent |
AcnetMessage |
Unsolicited message (no reply expected) |
AcnetCancel |
Cancel notification for an outstanding request |
TCP vs UDP connections¶
AsyncAcnetConnectionBase- Protocol core (commands, dispatch, tracking). Transport-agnostic - subclasses provide framing via abstract methods (_open_transport,_close_transport,_send_frame,_start_read_loop).AsyncAcnetConnectionTCP- TCP stream with 4-byte length-prefix framing and handshake, typically used viaacsys-proxy.fnal.gov:6802.AsyncAcnetConnectionUDP- UDP datagrams (no length prefix); usesasyncio.DatagramProtocolfor receive callbacks.AcnetConnectionTCP/AcnetConnectionUDP- Synchronous wrappers. Run the async core on a dedicated reactor thread and expose a blocking API viarun_coroutine_threadsafe.
The TCP protocol uses length-prefixed framing with 4 message types. UDP protocol is same messages without length prefix:
| Type | Code | Direction | Purpose |
|---|---|---|---|
| PING | 0 | Both | Keepalive (ignored) |
| COMMAND | 1 | Client → acnetd | All commands (connect, send request, cancel, etc.) |
| ACK | 2 | acnetd → Client | Response to every COMMAND |
| DATA | 3 | acnetd → Client | ACNET packets (replies, requests, messages, cancels) |
Every command follows a request/response pattern: the client sends a COMMAND and waits for a single ACK. ACNET data packets (replies from remote nodes) arrive separately as DATA messages. Reply handlers are registered per request ID and called on the reactor thread - consumers must use thread-safe primitives (e.g. queue.Queue, threading.Event) to receive data.
Constants¶
from pacsys.acnet.constants import (
ACNET_PORT, # 6801 - UDP port
ACNET_TCP_PORT, # 6802 - TCP port (for acnetd)
ACNET_HEADER_SIZE, # 18 bytes
# Message flags
ACNET_FLG_USM, # 0x0000 - Unsolicited message
ACNET_FLG_REQ, # 0x0002 - Request
ACNET_FLG_RPY, # 0x0004 - Reply
ACNET_FLG_MLT, # 0x0001 - Multiple reply
ACNET_FLG_CAN, # 0x0200 - Cancel
)
ACNET protocol details¶
When discussing ACNET, people generally refer to the entire control system infrastructure - protocols, frontends, and central services. Here we focus specifically on the ACNET UDP packet protocol.
ACNET is a UDP-based mesh protocol that passes messages between tasks running on nodes.
A node is a computer (physical or VM) that runs either:
acnetd- the ACNET daemon (on central services and some frontends)- Frontend application code (on VME crates, PLCs, etc.)
Each node has a unique address encoded as trunk:node (two bytes). All nodes maintain tables mapping node addresses to IP addresses.
A task is a named process that can send/receive ACNET messages. Task names are 6-character strings encoded using RAD50 (a base-40 encoding that packs 6 chars into 32 bits).
Packet Structure¶
ACNET packets have an 18-byte header followed by variable-length payload:
| Offset | Size | Endian | Field | Description |
|---|---|---|---|---|
| 0 | 2 | LE | flags | Message type and control flags |
| 2 | 2 | LE | status | Status/error code (signed) |
| 4 | 2 | BE | server | Server node (trunk:node) |
| 6 | 2 | BE | client | Client node (trunk:node) |
| 8 | 4 | LE | serverTask | Server task name (RAD50) |
| 12 | 2 | LE | clientTaskId | Client task identifier |
| 14 | 2 | LE | id | Message/request ID |
| 16 | 2 | LE | length | Total packet length |
| 18+ | var | LE | data | Payload |
Note the mixed endianness: node addresses are big-endian, everything else is little-endian.
Message Types¶
The flags field determines the message type:
| Type | Flag Value | Description |
|---|---|---|
| USM | 0x0000 |
Unsolicited message (no reply expected) |
| Request | 0x0002 |
Request (expects reply) |
| Reply | 0x0004 |
Reply to a request |
| Cancel | 0x0200 |
Cancel an outstanding request |
Additional flag bits:
0x0001(MLT) - Multiple replies expected/following- Upper nibble - Reply sequence number (for detecting missed replies)
Request/Reply Flow¶
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Request (flags=0x0002)
S-->>C: Reply (flags=0x0004)
For multiple-reply requests (MLT flag set):
sequenceDiagram
participant C as Client
participant S as Server
C->>S: Request (flags=0x0003)
Note right of C: 0x0002 | 0x0001 = request + MLT
S-->>C: Reply (flags=0x0005, more coming)
S-->>C: Reply (flags=0x0005, more coming)
S-->>C: Reply (flags=0x0004, final)
Node Addressing¶
Node addresses are 16-bit values: (trunk << 8) | node
Examples:
0x0A06= trunk 10, node 60x09CC= trunk 9, node 204
Trunk 230 (0xE6) is reserved for pseudo-nodes (open-access clients).
RAD50 Encoding¶
Task names use RAD50, a base-40 encoding that fits 6 characters into 32 bits:
from pacsys.acnet import rad50
encoded = rad50.encode("DPMD") # -> 0x19001B8D
name = rad50.decode(0x19001B8D) # -> "DPMD " (note spaces)
Character set (40 chars): ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789
Status Codes¶
The status field uses facility-error encoding:
- Low byte: facility code (always positive)
- High byte: error code (signed)
- Negative = failure, zero = success, positive = status or warning (e.g., pending)
Wire Format Notes¶
- No odd-length packets - ACNET does not support odd-length payloads
- Byte swapping - Data is little-endian with even/odd bytes swapped per word.
- Multiple packets per datagram - A single UDP datagram may contain multiple ACNET packets
The byte-swap rule means:
- 2-byte integers appear big-endian on the wire (after swap)
- 4-byte integers have "middle-endian" representation
- Strings like
"MISCBOOT"appear as"IMCSOBTO"on the wire
Common Tasks¶
| Task Name | Purpose |
|---|---|
| DPMD | Data Pool Manager daemon |
| RETDAT | Return data (frontend) |
| SETDAT | Set data (frontend) |
| FTPMAN | Fast Time Plot manager (frontend) - see FTPMAN Protocol |
Further Reading¶
- ACNET Design Note 22 (internal Fermilab documentation)
- FTP Technical Reference - Original FTP protocol documentation