Skip to content

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. Prefer the higher-level pacsys APIs unless 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}")

Local UDP example

Run this on the same node as acnetd:

from pacsys.acnet import AcnetConnectionUDP

with AcnetConnectionUDP(name="PYTEST") as conn:
    print(conn.name, conn.get_local_node())

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

  • AsyncAcnetConnectionTCP uses acnetd's TCP bridge protocol, normally through acsys-proxy.fnal.gov:6802.
  • AsyncAcnetConnectionUDP uses acnetd's official local UDP client interface. It is restricted to loopback and uses separate command and data sockets.
  • AcnetConnectionTCP and AcnetConnectionUDP are synchronous wrappers around those async implementations.

Both paths share the same raw, big-endian acnetd command and ACK structures. TCP wraps them in a length-prefixed bridge frame:

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)

UDP has no message-type envelope: raw commands and ACKs use the command socket, while raw ACNET packets arrive on the data port advertised by CONNECT. Reply handlers run on the event loop for async connections and on the reactor thread for synchronous wrappers; synchronous consumers should use thread-safe handoff primitives such as queue.Queue or threading.Event.

Constants

from pacsys.acnet.constants import (
    ACNET_PORT,           # 6801 - ACNET mesh traffic
    ACNET_CLIENT_PORT,    # 6802 - local UDP and TCP client interfaces
    ACNET_TCP_PORT,       # compatibility alias for ACNET_CLIENT_PORT
    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). Connection and vnode names that cannot be represented losslessly in RAD50 are rejected.

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 6
  • 0x09CC = 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

  1. Alignment - UDP datagrams are even-sized; an individual packet may declare an odd length and is followed by one alignment byte before the next packet
  2. Byte swapping - Data is little-endian with even/odd bytes swapped per word.
  3. 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
GETS32 / RETDAT Raw frontend reads - see GETS32 and RETDAT
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