James Cupps — open-source developer


About

C:\CUPPS> type ABOUT.TXT

Tools that solve real problems, mostly.
Check out the projects below or type HELP for commands.


Contact

C:\CUPPS> type CONTACT.TXT

GitHub:   github.com/jamesccupps
Email:    jamesccupps@proton.me

APOGEE P2 Protocol — Full Technical Reference

C:\CUPPS> type P2-PROTOCOL.TXT | more

APOGEE P2 (Protocol II) — Technical Reference — full text, click to expand

APOGEE P2 (Protocol II) — Technical Reference

Table of Contents

Summary

P2 ("Protocol II") is the application-layer network protocol of the Siemens APOGEE building-automation system — the language supervisory workstations and field panels use to exchange point values, change-of-value (COV) reports, alarms, schedules, control-program (PPCL) source, trend data, and node-routing information. This reference describes P2 as it runs over TCP/IP, the transport used by all Ethernet-attached APOGEE installations.

At a glance:

  • Transport. TCP, default port 5033 — every field panel and the supervisor listens here; some installations add a second supervisor-side listener on 5034. Frame semantics derive from a frame's contents and direction, never from the TCP port that carried it.
  • Peer model. On the backbone (the BLN, also called the ALN) every member — supervisor and panel alike — is an equal node, and any node may originate traffic. "Request" and "response" describe the role of a frame, not of a node.
  • Frame. Big-endian throughout: u32 total_len | u32 message_type (low byte = message class) | u32 sequence | u8 direction | four NUL-terminated ASCII routing slots [BLN, dst-node, BLN, src-node] | (request/push frames only) u16 opcode | body. The opcode is present only on direction == 0x00 frames; a response is matched to its request by the echoed sequence number.
  • Dialect. The message class is a legacy/modern pair fixed by a panel's firmware generation — data 0x33/0x34, second channel 0x2E/0x2F, peer carriers 0x29/0x2A — not by direction. A client reads a panel's firmware once (via CABINET_DISPLAY, opcode 0x010C) and selects the dialect and the string encoding (ASCII vs RAD-50) from it; there is no on-wire negotiation.
  • Operations. A 2-byte function code (the "AP2 function code") selects the operation; about 630 are defined and 135 are observed on the wire in the reference corpus. The high-volume operations are the COV value push (0x0274), the liveness/identity heartbeat (0x4640), point command and read (0x0240 / 0x0220), COV subscribe/unsubscribe (0x0271 / 0x0273), and the database upload/replication family.
  • Encoding. Strings are length-prefixed TLVs (01 00 <len> <ascii>); analog values are IEEE-754 single-precision big-endian floats; command priority rides a scope tag. Bodies are ordered, positionally-typed field structures (ASDUs).
  • Control logic. Panels run a resident, line-numbered control language — Powers Process Control Language (PPCL); the supervisor reads, edits, and uploads it over P2 but never executes it (§14).
  • Security. P2 carries no authentication and no encryption; the only admission gate is a matching BLN name. Any party with network reachability and the BLN name can read points, command points, and reconfigure panels. §17 covers the implications and an owner-operator hardening posture.

The sections that follow specify each layer: transport and ports (§2, §4), framing (§6) and encoding primitives (§8), addressing and identity (§3), discovery / liveness / replication (§5), the operation catalog (§9) and body structures (§10), the point model (§11), COV (§12) and alarms (§13), PPCL (§14), scheduling (§15), database and firmware (§16), and security considerations (§17).

1. Introduction, Scope, Lineage & Conventions

1.1 What P2 is

P2 ("Protocol II") is the application-layer trunk protocol of the Siemens APOGEE building-automation system. It is the language that supervisory workstations and field panels use to exchange point values, change-of-value (COV) reports, alarms, schedules, control-program (PPCL) source, trend data, and node-routing information across a building's automation network. [D/I]

P2 is a peer protocol: on the network tier where it runs, every member — supervisor and field panel alike — is an equal node, any node may originate traffic, and there is no single bus master. [W][D] At the wire level the protocol is symmetric: the same frame format carries traffic in both directions, and the terms "request" and "response" describe the role of a frame, not the role of the node that emitted it (see §6 for framing, §7 for the session model). [W]

This document specifies P2 as it operates over TCP/IP, which is the transport used by all current (Ethernet-attached) APOGEE installations. A reader who works through this reference can implement a conformant P2 peer — a client that establishes sessions, reads and writes points, receives COV reports, enumerates panel contents, and participates in node-routing exchange — interoperating on a live P2 network exactly as a supervisor or panel does. The two normative conformance levels (minimal client, full peer) are defined in §1.6. [I]

Within the APOGEE product family the protocol is also written "PII" — the same "Protocol II" — so "P2", "PII", and "Protocol II" all denote this one protocol. [D/I]

1.2 The protocol family — P1, P2, BACnet coexistence

An APOGEE installation layers several distinct protocols. An implementer must keep them separate; this document specifies only P2.

Protocol Tier Role In scope here
P2 (Protocol II) BLN / backbone Peer network among supervisors and field panels; the subject of this document. Yes
P1 (Protocol I) FLN / fieldbus Master/slave polling bus beneath a single panel, carrying terminal-equipment controllers (TEC) and unitary controllers (UC). A panel mediates P1; P1 device data is reached through the panel using P2 opcodes, never as a separate TCP transport. Reached via P2 (§2.2, §3.8), not specified at the byte level
BACnet/IP & BACnet MSTP Coexisting on later panels A separate, standardized protocol stack present on DXR/BACnet-capable panels and on the modern supervisor. It uses its own ports, framing, segmentation (TSM), and object model — none of which are P2. Out of scope

The vendor's own topology metadata tags a network backbone as BLN Type="BLN_PII" (a Protocol-II backbone) and a fieldbus as FLN Type="FLN_P1" (a Protocol-I fieldbus), and distinguishes an Ethernet BLN (EBLN) from a BACnet BLN (BBLN). [S][D] On a mixed site, the same physical panel may carry both a P2 (EBLN) presence and a BACnet (BBLN) presence; the two are independent stacks selected at the panel. [D] BACnet coexistence is noted here only so an implementer can recognize and ignore BACnet traffic; the BACnet stack, its segmentation, and its object model are explicitly out of scope of this reference. [I]

1.3 Audience and goal

The intended reader is an engineer implementing a compatible P2 device or client — for example, an owner-operator building a monitoring tool, a gateway, or a replacement supervisor for legacy hardware they own and operate. The goal of the document is implementation completeness: every layer needed to put a conformant P2 node on the wire — transport, framing, encoding primitives, addressing, the session handshake, the operation/opcode catalog, the point model, replication and discovery, COV/alarms, error codes, and the on-wire form of PPCL programs — is specified here. [I]

Where a claim derives from observable wire behavior, from the protocol's type-system definitions, or from vendor documentation, that provenance is recorded with an evidence tag (§1.4), so each statement is traceable to how it was established. [I]

1.4 Document conventions

1.4.1 Evidence tags

Every non-trivial claim carries an inline tag, at the end of the sentence or in the final column of a table, recording how it was established:

Tag Meaning
[W] Wire-verified. Directly observed in a packet capture or opcode census of live P2 traffic. Ground truth for byte-level claims.
[S] Struct/metadata-derived. From the protocol's own type system — the function-code enumeration, the ASDU body-structure definitions, and the value enums (priorities, point types, COV masks, native types, node states, etc.). Definitional truth for names, field order, and constant values.
[D] Doc-sourced. Taken from vendor help text, manuals, or templates. Reliable for behavior, topology, timing, and semantics — but not for byte-level wire layout.
[I] Inferred / synthesis. Reasoned from one or more of the above.
[OPEN] Not yet confirmed. A specific gap that needs a capture or a live test to close; flagged explicitly rather than papered over.

A field-layout table is tagged [S] when its field order and types come from the ASDU structure definitions, and [W] when the layout has additionally been confirmed on the wire. A behavioral claim tagged [D] must never be presented as a byte-level wire fact; where the byte offsets behind a documented behavior are not pinned, the gap is tagged [OPEN].

1.4.2 Byte, integer, and field conventions

  • Hexadecimal byte values are written 0xNN. A run of literal bytes is written space-separated, e.g. 01 00 04.
  • All multi-byte integers in the P2 header and in body integer fields are big-endian. The header length, message-type, and sequence fields are unsigned 32-bit big-endian (u32 BE); the opcode and the error code are unsigned 16-bit big-endian (u16 BE). [W]
  • Analog values are IEEE-754 single-precision (32-bit) floats in big-endian byte order (f32, big-endian). [W]
  • Byte/field layouts are presented as tables of the form:
Offset Field Type Value/Notes [tag]

Offsets are relative to the start of the structure under discussion (frame, body, or sub-structure); each table states its origin.

1.4.3 Terminology

The following terms are used consistently throughout (cross-referenced in detail in §2 and §3):

  • BLN — Building Level Network: the P2 peer backbone. Later product generations also call this the ALN (Automation Level Network); the two names denote the same trunk and the same wire field. [D]
  • FLN — Floor/Field Level Network: the P1 fieldbus sub-bus beneath one panel. [D]
  • CEC — the controller/panel exec (a field panel as an addressable executive). [S][D]
  • P2 = Protocol II — the BLN/backbone protocol (this document). P1 = Protocol I — the FLN/fieldbus protocol. [S][D]
  • PXC / MEC / PXM — field-panel hardware families (modern modular/compact controllers, the modular equipment controller, and the panel-mount operator/controller variant). Legacy panel families include SCU, RCU, MBC, and MEC. [S][D]
  • TEC — terminal equipment controller (a P1/FLN field device); UC — unitary controller. [S][D]
  • AEM — APOGEE Ethernet Microserver (a serial-to-TCP terminal server that tunnels a serial P2 byte stream over TCP; §2.1.4). [D]
  • EPing — the Ethernet-BLN liveness/discovery ping (the 0x4640-class heartbeat at the protocol layer; §5 and §10). [S][D]
  • PPCL — Powers Process Control Language, the line-numbered control language a field panel executes (§14). [S][D]
  • AP2 function code — the name for the 2-byte wire opcode (also abbreviated fc, "function code"; §9.1). [S]

1.4.4 Scope summary

In scope: the P2 wire protocol over TCP/IP and the adjacent layers an implementer must understand to use it — transport and ports, framing and encoding, addressing and identity, the session handshake, the full operation/opcode catalog, the point model, replication/discovery, COV/alarms, error codes, and PPCL-over-the-wire.

Out of scope: the BACnet protocol stack (BACnet/IP and BACnet MSTP) that coexists on later panels; the serial-era physical layer except as lineage context (§1.5); and any management-station-internal supervisor-to-station RPC that does not appear on the panel wire. [I]

1.5 Lineage (informative)

This subsection is informative; nothing in it is required for interoperation. It orients an implementer to why the protocol is shaped as it is.

Powers → Landis & Gyr → Siemens. P2's data model and much of its vocabulary descend from the Powers System 600 of the 1980s and its Protocol II — the peer ("token-passing") network discipline of that era — as distinct from Protocol I, the master/slave polling discipline that survives conceptually as the FLN sub-bus beneath a panel. [D/I] The product line passed through Landis & Gyr (and a parallel Staefa Control System branch) before consolidating under Siemens' APOGEE, which is why conventions from several BAS lineages coexist in the modern system. [D/I]

PPCL. The control language a panel executes is Powers Process Control Language, carrying the Powers name forward into the modern product. PPCL is specified, to the extent needed to read and write programs over the wire, in §14. [D]

The model is stable; the encoding and transport evolved. The point taxonomy, the linear analog calibration (engineering value = digitized value × slope + intercept), the command-priority ladder, the point-condition/COV mask, and the peer network model are all carried forward essentially unchanged from the Powers/Protocol-II era. [S][D] What changed is the wire. The original serial-era constructs — the RS-485 multidrop trunk, the circulating media-access token, packed numeric point addressing, RAD-50 name packing, and a serial-link CRC — are not part of P2 over TCP: the modern transport is TCP, integrity and ordering are provided by TCP itself, there is no token because there is no shared bus to arbitrate, and routing is by NUL-terminated ASCII name rather than by packed numeric address. [W][I] The peer-equality concept of Protocol II survives (panels originate their own connections, pushes, and routing advertisements) while the serial mechanism that once implemented it does not. An implementer building for TCP MUST NOT implement RS-485 token arbitration, the serial CRC, or numeric wire addressing; these survive only inside a panel's internal database and CLI reports (§5.5). [I]

A note on string encoding across the lineage. Name/string encoding is a per-firmware-revision, per-platform property, not a per-frame one. Early (pre-IP) field-controller and supervisor revisions pack names in RAD-50 — three characters per 16-bit word, over the 40-symbol alphabet " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789" (index 0 = space, 1–26 = A–Z, 27 = $, 28 = ., 29 = ?, 30–39 = 09), packed as ((c0×40)+c1)×40+c2. [S] All P2/IP revisions use plain ASCII in the length-prefixed TLVs and NUL-terminated routing slots of §6 and §8. Whether a given platform uses RAD-50 or ASCII is keyed by firmware/platform class (a per-platform STRING_TYPE of RAD50 vs ASCII). [S][D] A peer presenting RAD-50-packed names is a pre-IP revision and is out of scope for a TCP/5033 implementation, which is ASCII throughout. [I]

1.6 Conformance levels

Two conformance levels are defined.

  • Minimal client (read-mostly). MUST implement: the Ethernet/IP transport (TCP/5033, §4.1); the frame format (§6) including the four NUL-terminated routing slots and the rule that the opcode is present only when the direction byte is 0x00, parsed by scanning the slots rather than at a fixed offset (§6.4); the service model and error handling (§7); the encoding primitives (§8); a session via EBLN_PING 0x4640 (§5.1, §10.6); and the read/COV opcodes POINT_LOG_VALUE 0x0220, COV_ENABLE/COV_DISABLE/COV_ANNUNCIATE 0x0271/0x0273/0x0274, and CABINET_DISPLAY 0x010C. This is sufficient to discover a panel's identity and read live point values.
  • Full peer. Additionally MUST implement: commanded writes with priority (POINT_CMD_VALUE 0x0240 / POINT_CMD_PRIORITY 0x0241, §8.2, §12.4) honoring the command-priority >= gate and release semantics; the upload family (UPL_ALL_*, §9); node-table participation and global-data replication (EBLN_REPL_* 0x46330x4636, §5.3); and PPCL transfer (§14). A full peer that emits node-management or cabinet-lifecycle opcodes (§16.6, §17.4) MUST treat them as destructive.

Both levels MUST NOT depend on a multicast discovery beacon — none exists (§5.2) — and MUST treat the BLN-name slot as the only admission gate (§3.4, §6.4).


2. Architecture & Layering

2.1 The layered stack

A P2/IP node is best understood as a small stack of layers. From bottom to top:

  +-------------------------------------------------------------+
  | Application / ASDU layer                                    |
  |   AP2 function code (2-byte opcode) + typed ASDU body       |  §9, §10, §8
  +-------------------------------------------------------------+
  | Transport layer                                            |
  |   unicast TCP, canonical port 5033 (full mesh among peers)  |  §2.1
  +-------------------------------------------------------------+
  | Physical / datalink (one of)                               |
  |   - Ethernet (TCP/IP) ............ the modern, in-scope path |
  |   - RS-485 serial BLN ............ legacy multidrop trunk    |  §2.1.3
  |   - P1 / FLN fieldbus ............ sub-bus beneath a panel   |  §2.2
  |   - AEM serial-over-TCP tunnel ... serial P2 wrapped in TCP  |  §2.1.4
  +-------------------------------------------------------------+

The same application/ASDU layer rides over whichever datalink a node uses; this document specifies the application layer and the TCP/IP transport. The serial, FLN, and AEM paths are described only enough to delimit them from the TCP wire. [I]

2.1.1 TCP/5033 — the canonical transport

P2 runs over TCP, and TCP port 5033 is the canonical and default P2 port. [W][D] Every node that participates in the BLN — the supervisor and every field panel — listens on TCP/5033 for inbound P2 connections, so the BLN is a full mesh at the transport layer: any node may open a connection to any other node's 5033 listener, and a communicating pair commonly holds two connections, one initiated in each direction (§7.3). [W]

The port is configurable but must be set identically across all panels and the supervisor on a given BLN; the vendor exposes a small fixed set of selectable "Transport Server Port" slots, defaulting to 5033, applied uniformly BLN-wide. [D] An implementer should default to 5033 and treat any other value as site configuration. A P2/IP deployment additionally requires, at the network layer, that every node reach every other node by its actual IP address — network address translation between panels and the supervisor is not supported; each node must present a stable, directly reachable address. [D]

2.1.2 TCP/5034 — an optional second supervisor-side listener (NOT a protocol-standard port)

Some installations run a supervisor that also listens on TCP/5034 as a second listener distinct from the canonical 5033. A client MUST NOT assume it exists and MUST default to 5033. [W] Where it appears, only the supervisor host listens on 5034; field panels never do — the listener set on 5033 is the full mesh, while 5034 is a star into one supervisor box. On the wire, 5034 carries the panel→supervisor push/announce (reverse) channel: panels open a connection to the supervisor's 5034 listener and push COV/value frames there, while the supervisor reaches panels on their 5033 listeners (§7.3). Whether 5034 is a distinct co-installed supervisor product or simply the same supervisor's second listener for that reverse channel is not determinable from traffic alone — either way it is deployment-specific configuration, not a protocol-standard port. [W] The protocol carried on 5034 is identical to that on 5033 — the same frames, opcodes, and semantics — and unsolicited pushes (COV, alarms) are port-agnostic: a push rides whichever connection/port the receiving supervisor is listening on, and the port number does not change the meaning of any frame. [W]

The decisive evidence that 5034 is not a protocol feature: a supervisor's identity string commonly carries a |5034 suffix (e.g. DCC-SVR|5034), and that exact suffixed identity appears in thousands of frames captured on a 5033-only connection, while the literal text 5033 appears in zero frames anywhere. The supervisor stamps its HOST|PORT identity into the routing slot regardless of which TCP port carries the frame — so the |PORT suffix is part of the identity string, not a live port indicator (§2.1.5, §6.4). [W] A conformant implementation derives all frame semantics from the frame's direction and contents, never from the TCP port that carried it, and defaults to 5033 only (exposing any other port as explicit configuration). [I]

Before Ethernet, the BLN was a terminated RS-485 multidrop trunk (8 data bits, no parity, 1 stop bit), with up to a few trunks (trunk numbers 0–3) and a firmware-tiered baud rate (e.g. 38400 baud where all firmware on the trunk is recent enough, otherwise 19200; modem links lower). [D] The serial BLN implements the same peer model and the same data model as the Ethernet BLN, but with the serial-era mechanics (token, CRC, packed addressing) noted in §1.5. It is not the subject of this document; an implementer targeting TCP/5033 does not implement it. [I]

2.1.4 The AEM tunnel (serial-over-TCP, context only)

The APOGEE Ethernet Microserver (AEM) is a Lantronix-class serial-to-TCP terminal server that tunnels an existing serial P2 byte stream verbatim over TCP (commonly channel 1 ≈ TCP 3001 for P2, channel 2 ≈ TCP 3002 for the HMI console; 8/N/1). [D] The AEM does not define a new framing layer — it is a transport wrapper around the serial stream, so the bytes inside an AEM tunnel are the serial-P2 bytes, not the native TCP/5033 P2 frames of §3. An implementer targeting native P2/IP does not interact with the AEM path; it is documented only to explain the AEM socket construct present in the vendor datalink layer and to warn that an AEM-bearing port (≈3001) is a different byte stream from a 5033 listener. [I]

2.1.5 The |port identity-suffix convention

A node's source-identity string — carried in the source-node routing slot and exchanged at session establishment — commonly appears as NAME|PORT, e.g. P2SCAN|5033 or DCC-SVR|5034. The |PORT portion is part of the identity string (a HOST|PORT disambiguator that lets one host present distinct identities); it is not a network-layer port indicator and not a field delimiter. [W][D] Handling rules: when building a frame, emit the full literal identity including the suffix and do not split on |; when parsing, accept both the suffixed and bare forms (responses and routing-table entries frequently return the bare NAME) and match identities with the suffix tolerated on either side. [W] See §6.4 for the identity model.

2.1.6 No multicast discovery beacon

P2 has no multicast presence or discovery beacon that a client can listen for to find nodes. [W][D] There is no periodic UDP multicast that a node emits to announce itself for discovery. A multicast group exists in the protocol, but only as an optional, default-disabled peer failure-detection (liveness) feature: when an operator explicitly enables it, panels join a configured Ethernet-BLN availability multicast group (the vendor-documented default group is 234.5.6.7, UDP port 8, up to four group/port pairs per panel) solely to detect peer failure — not to advertise presence for discovery. [D] It is off by default and must not be relied upon for node discovery; an implementation MUST NOT implement a "beacon listener" as a discovery mechanism, because there is no P2 discovery beacon to receive. Node discovery is performed by connecting to known addresses on TCP/5033 and exchanging frames, and on mixed sites by the BACnet-side and Ethernet-BLN discovery exchanges of §10. [I]

Correction of a common misattribution. Traffic to the multicast group 233.89.188.1 (UDP/10001), sometimes mistaken for an "APOGEE multicast beacon," is not Siemens-sourced. On at least one analyzed network it originates entirely from a Ubiquiti/UniFi gateway (gateway source IP and OUI, 4-byte payload, ~10.5 s cadence); Siemens devices on the same segment emit only BACnet/IP and ARP, never to that group or port. The 233.89.188.1 "beacon" does not exist as a P2 feature. Do not implement or rely on it. [W]

2.1.7 Surrounding network services

A P2/IP node typically depends on several platform services around the P2 port itself: name resolution (DNS) to resolve node names to addresses, address assignment (BootP/DHCP) where dynamic addressing is used, file transfer (FTP) and a console (Telnet and/or a serial CLI) for firmware administration, and management (SNMP) for monitoring. [D] These are platform services around the P2 node and are outside the P2 wire protocol; only TCP/5033 (and, where present, the site-specific 5034) carries P2 itself. [I]

2.2 Node roles and the object hierarchy

2.2.1 Node roles

Role Examples Behavior on the BLN
Field panel (CEC) PXC (modular/compact), MEC, PXM; legacy SCU, RCU, MBC A peer node. Listens on 5033, answers reads/writes/commands/enumerations addressed to it, and originates its own traffic — COV reports, alarm reports, routing-table pushes, and heartbeats — on its own initiative. Mediates the P1/FLN devices beneath it. [W][D]
Supervisor Insight (legacy), Desigo CC / GMS, WCIS, DataMate A peer node that manages one or more panels: it opens outbound connections to each panel to poll, read, write, command, and enumerate, and it accepts inbound connections (including panel pushes). On the BLN the supervisor is "node 100" conceptually (the management station; §5.4). [W][D]
FLN device TEC, UC (and other P1 field controllers) Not a P2 peer. Lives in a separate namespace on the P1 fieldbus behind one panel; reached only through that panel via the FLN browse/enumerate opcodes (§9). [S][D]

Every P2 node — supervisor or panel — is a peer: there is no single master that owns the network. A panel originates traffic without a prior request, using the request encoding (direction byte 0x00), and a supervisor likewise originates reads, writes, and commands; both directions use the identical frame format (§6). [W]

2.2.2 The object hierarchy

P2 addresses entities by name through a five-level hierarchy, broadest to narrowest:

  BLN  (Building Level Network — the peer backbone; one BLN System Name)
   └─ Site  (a grouping container inside a BLN; affects liveness/replication cadence, not admission)
       └─ Node / CEC  (a supervisor or field panel — the addressable executive)
           └─ Point  (a logical point name on that node; may group 1–4 subpoints)
               └─ FLN device / drop  (a P1 sub-device on the panel's fieldbus — a separate namespace)

This hierarchy is corroborated by the vendor's own object model, which nests BLN → CEC → Point at the database and codec layers (a building network contains controllers, which contain points), with a point team abstraction whose default member is the logical point. [S] Addressing detail for each level is specified in §3; the FLN device level is reached through the panel and is not carried in the four routing slots (§6.4). A panel and the supervisor are each a Node (equivalently a CEC, the controller/panel exec). [S]

2.2.3 Point teams

A "point team" groups the physical/virtual subpoints that make up one logical point. A logical point name may bind 1 to 4 subpoints under a single name; the team's default member is the logical point a client normally reads or commands, and a panel's point-team metadata maps the team's members to their subpoint indices, link types, and engineering-unit scaling. [S][D] FLN sub-device subpoints derive their names from the parent point name plus a suffix describing the subpoint. The point team is the unit a panel uploads when a supervisor enumerates a device's points (§9), and the FLN/TEC point-team templates (the vendor's .ptd device-family library) define, per device family, which subpoint indices exist and what each means. [S][D] Full point-model detail is in §11.

2.3 Service model (informative)

This subsection is informative orientation, not part of the normative wire specification. It describes the request/response service model that the wire framing of §6–§10 realizes; nothing here is required to build a conformant node.

2.3.1 Confirmed and event services

P2 follows the classic ISO/OSI confirmed-service idiom: a Request at the originator becomes an Indication at the receiver, and a Response at the receiver becomes a Confirm back at the originator, with the operation's payload carried as an ASDU (Application Service Data Unit). Operations fall into two service classes, which the implementer sees on the wire as the direction byte (§6.3):

  • Confirmed service — a request that expects a matching response, correlated by the sequence number.
  • Event service — an asynchronous, unsolicited indication with no prior request: a panel-originated push (COV report, alarm, routing advertisement, heartbeat).

On the wire, both a confirmed request and an event push are emitted with direction == 0x00 and carry an opcode (§6.3); the difference is only whether the peer was expecting them. This confirmed-vs-event distinction is the origin of the wire's request/push-vs-response structure — for example, COV is a register/cancel subscription with an asynchronous annunciate indication (§11, §12). [I]

2.3.2 Dispatch

A practical consequence for an implementer: the wire opcode is the primary dispatch key on a request/push frame, but a robust receiver dispatches on the opcode together with the body shape and direction, because the same opcode can select different operations by body and direction (§3.7). A management station may use a different internal index for an operation, but only the wire opcode appears on the wire; a conformant peer keys solely on the wire opcode (plus body and direction). [I]

3. Topology & Addressing

P2 (Protocol II) is the backbone protocol of an APOGEE automation network. This section defines the network tiers a P2 deployment is built from, the forms a BLN (Building Level Network) can take, the way nodes and points are named and numbered, how those identifiers are carried on the wire versus how they live in a node's database, how traffic crosses from one BLN to another, and the documented size limits of the architecture. Frame-level encoding of the routing identifiers is specified in §6 (frame encoding); the session admission rules that gate membership are in the handshake section; this section is about structure and naming, not bytes on the wire except where the structure dictates them.

3.1 Network tiers

A P2 installation is organized into three stacked network tiers. P2 is the protocol of the middle tier.

Tier Name Role Protocol on the tier [tag]
Top MLN — Management Level Network Supervisory workstations (Insight, Desigo CC, WCIS, DataMate); operator presentation, database of record, cross-BLN brokering Supervisor-internal RPC (DCE/MS-RPC), not P2 [D]
Middle BLN — Building Level Network (also "ALN", Automation Level Network in later product generations) Peer network of field panels and the supervisor's BLN proxy; carries point data, alarms, schedules, control-program data, node-routing P2 (Protocol II) [D][S]
Bottom FLN — Field/Floor Level Network Sub-bus beneath one panel, carrying field controllers (TEC, lab/unitary controllers) polled master/slave by the panel P1 (Protocol I) fieldbus, or BACnet MS/TP on later panels [D]

The two upper names matter to an implementer because the supervisor's RPC layer is a different protocol from the wire P2 stack. Supervisor-internal command classes (the __Rpc*/CPI vocabulary used between a workstation and its station services) operate above P2; they are not what travels between panels. The wire-level command vocabulary is the AP2 function-code set (see §8). When this document says "P2 wire" it means the BLN tier exclusively. [I]

The vendor's own name for the BLN-tier protocol is "APOGEE PII protocol" — Protocol II — confirming P2 = Protocol II, the Powers/Landis-&-Gyr System 600 lineage carried into the IP era. [S][D]

3.2 BLN forms

A single logical BLN can be realized over three different physical media. P2 framing and the AP2 function-code semantics are identical across all three; only the datalink underneath differs.

3.2.1 Ethernet BLN (EBLN)

The modern form. All panels and the supervisor sit on a switched IP network and speak P2 over TCP/5033 (see §4 for transport). Every node listens on TCP/5033, so an EBLN is a full mesh at the transport layer: any node may open a connection to any other node. [W][D]

EBLN discovery is performed by the vendor's "EPing" (Ethernet Ping) mechanism, an application-layer liveness/discovery exchange (AP2 function code 0x4640 EBLN_PING), not by any multicast beacon (see §3.7). The discovery engine reports each BLN's own name during the exchange, so the BLN name is obtainable from the EPing dialog rather than from a passive broadcast. [S][D]

The vendor's discovery layer distinguishes EBLN (Ethernet BLN) from BBLN (BACnet BLN). They are separate stacks (see §3.2.4). [S]

3.2.2 Serial RS-485 BLN

The legacy form. Panels are wired on a terminated, 2-wire RS-485 multidrop trunk; the supervisor reaches them through a serial port (or, historically, a dial-up modem with the ESC fallback protocol). [D]

Property Value [tag]
Signalling RS-485 multidrop, terminated at both ends [D]
Framing 8 data bits, no parity, 1 stop (8/N/1) [D]
Trunk numbering Trunk 0–3 (the "LAN"/trunk number, §3.3) [D]
Baud, all-firmware-modern 38400 if every node's firmware is ≥ rev 2.2 [D]
Baud, mixed firmware 19200 (fallback when any node is older) [D]
Baud over modem ≤ 19200 [D]

Each link class on a panel carries its own configurable baud — the vendor exposes separate BLN_BAUD_RATE, FLN_BAUD_RATE, HMI_BAUD_RATE, and MSTP_BAUD_RATE settings. The BLN baud is uniform across a serial trunk; FLN and HMI bauds are per-link. [S][D]

3.2.3 Remote P2 RS-485 via AEM (transport-tunneled serial)

A serial RS-485 BLN segment can be carried over IP by an AEM (APOGEE Ethernet Microserver) — a Lantronix-class serial-to-TCP terminal server. The AEM tunnels the existing serial P2 byte stream verbatim over TCP (channel 1 ≈ TCP/3001 = P2, channel 2 ≈ TCP/3002 = HMI; 8/N/1). The AEM defines no new RS-485 framing — it is a transport wrapper only, so a P2 implementation that speaks the serial byte stream sees an AEM-tunneled link as an ordinary serial BLN reached over a TCP socket. An alternate P2-bearing TCP port (3001) therefore exists at AEM sites; it is not the canonical 5033. [D][I]

3.2.4 BACnet BLN (BBLN) — separate stack, noted only

Later product generations support a BACnet BLN (BBLN): panels that are BACnet-native (or dual P2+BACnet, e.g. DXR-class controllers) and present themselves over BACnet/IP (UDP/47808, I-Am/Who-Is) rather than over P2. The discovery engine has distinct HandleAddEBlnRequest (P2) vs HandleAddBBlnRequest (BACnet, via IAm) paths. [S][D]

A BBLN is out of scope for this document: it is a different protocol stack with a different addressing model, different framing, and a different security surface. The only cross-tier fact a P2 implementer needs is the negative one in §3.5: there is no BACnet→P2 routing path. A point on a BBLN is not reachable through a P2 frame and vice versa. [D][I]

3.2.5 The logical BLN is self-organizing

Regardless of medium, a BLN is a logical peer group, not just a wire. Membership is gated by the BLN System Name (see §3.4.1): two nodes exchange traffic only if their BLN names match exactly. Within a matched-name group the BLN self-organizes — each node maintains a node-name table (the roster of known peers) that auto-replicates BLN-wide, so every node converges on the same membership view. Peers gate communication to nodes the table marks dead. This is why the membership unit is the name, not the cable: an Ethernet panel and a serial-trunk panel that share a BLN name and a route are members of the same logical BLN. [D][S]

3.3 The point-address 3-tuple: LAN / Drop / Address

Every point on a P2 network has a canonical numeric coordinate, the LAN / Drop / Address 3-tuple. This is the addressing model the vendor's own database export uses for every point record (PointLan / PointDrop / PointAddress). [D]

Component Database field Meaning Range / form [tag]
LAN PointLan The BLN / trunk number the point's panel lives on trunk 0–3 on serial; the BLN identifier on Ethernet [D]
Drop PointDrop The panel / node number within the BLN (the "drop" on the trunk) 0–99 panel range; 100 = supervisor (§3.4.4) [D][S]
Address PointAddress The point / subpoint index within that panel's database point index [D]

The 3-tuple is the conceptual identity of a point and the form in which point databases store it. It is not the wire routing key — P2/IP routes frames by NUL-terminated node name strings in the four routing slots (§6), and reads/writes a point by its logical point name carried in the request body, not by the numeric tuple (see §11 for the point model). An implementer uses LAN/Drop/Address to reason about and index topology, and uses names on the wire. [W][S][I]

The vendor's database tags the tuple's network types explicitly: a BLN is BLN_PII (Protocol II backbone) and an FLN is FLN_P1 (Protocol 1 fieldbus), confirming the two-protocol stack the tuple spans. [D]

3.3.1 Node numbering: serial integer vs. Ethernet name

A node has two parallel identities depending on the BLN medium.

  • Serial node integer. On an RS-485 BLN a node is a numbered drop, 0–100: panels occupy 0–99 and the supervisor/management station takes 100 (one number is consumed by the supervisor). The protocol carries a set of resident liveness points named NODE0..NODE99 — one per possible drop — whose value reflects that drop's online/failed/ready state. These resident points are how a node's liveness becomes a readable point value to the rest of the BLN. [D][S][I]

  • Ethernet pingable DNS node name. On an EBLN a node is identified by a DNS-resolvable node name of ≤ 30 characters (see §3.4.2). The Ethernet node is reached by resolving its name to an IP and connecting on TCP/5033; the numeric drop survives as a conceptual coordinate (and in the database tuple) but is not the wire address. [D][W]

A practical consequence for an implementer: the same logical panel can be referenced as a serial drop integer (in legacy databases and in NODEnn liveness points) and as a DNS node name (on the Ethernet wire). They denote the same node. [I]

3.3.2 Node name length: ≤ 30 wire, ≤ 15 where RAD-50 packed

Node-name length has two regimes, keyed to the firmware's string encoding (cross-ref §8 for the RAD-50 codec and the platform encoding table):

  • ASCII regime (all P2/IP revisions): node names are plain ASCII, ≤ 30 characters, NUL-terminated in the routing slots. [W][S]
  • RAD-50-packed regime (pre-IP / legacy field-controller and supervisor revisions): names are packed three characters per 16-bit word from the 40-symbol RAD-50 alphabet, which budgets a node-name field to ≤ 15 characters. A system name is modeled as Base + Suffix, with the base length-bounded, corroborating the ≤15-character node-name basis. The documented 15-character node-name limit is also why the session handshake's ≥15-character self-identity acceptance threshold lands where it does (cross-ref the handshake admission rules). [S][D]

An implementer targeting a TCP/5033 (EBLN) deployment uses ASCII throughout and may use the full ≤30-character node name; the ≤15 limit applies only to legacy RAD-50 platforms and to fields that retain the packed budget. A peer presenting RAD-50-packed names is a pre-IP revision and is out of scope for an ASCII TCP/5033 client. [I]

3.4 Named scopes and their constraints

P2 addresses entities by name. Five named scopes nest from broadest to narrowest:

BLN (Building Level Network)
 └─ Site
     └─ Node (panel / supervisor)
         └─ Point (logical point name)
             └─ FLN device / drop (sub-bus, separate namespace)

The wire-level enforcement asymmetry of these names (BLN strict, destination node case-insensitive, source identity format-shaped) is specified with the handshake; this section gives the naming constraints themselves.

3.4.1 BLN name

Property Value [tag]
Encoding ASCII, NUL-terminated on the wire [W]
Maximum length 30 characters [D]
Permitted characters Letters and digits [D]
Forbidden Periods, spaces, underscores, other special characters [D]
Case sensitivity MUST match exactly across all nodes; treat as case-significant [W][D]
Role Membership gate — two nodes exchange P2 traffic only if BLN names are identical [W][D]

The BLN name is fixed for the life of a network and appears twice in every frame's routing slots (slots 0 and 2; see §6). Later product generations call the same trunk the ALN (Automation Level Network); the wire name field is unchanged. [W][D]

3.4.2 Node name

Property Value [tag]
Encoding ASCII, NUL-terminated on the wire (≤15 RAD-50 packed, §3.3.2) [W][S]
Maximum length 30 characters (Ethernet/field-panel node names) [D]
Permitted characters Letters, digits, dash (-) [D]
Case sensitivity Not case sensitive; a node MAY render its own name in a different case in a reply (cosmetic, not structural) [W][D]

A supervisor / management-station identity is shaped differently from a panel node name: it is a composite HOST|PORT token (e.g. DCC-SVR|5034, or the placeholder P2SCAN|5033). The |PORT suffix is part of the identity string — a HOST|PORT disambiguator that lets one host present distinct identities — and is not a connect-address. The supervisor stamps the same suffixed identity into its source slot regardless of which TCP port the frame actually rides (a frame seen on TCP/5033 routinely carries a source identity suffixed |5034). The suffix number may coincide with a real listener on the host, but a client MUST treat the whole token as one opaque identity, MUST NOT parse the suffix to choose a TCP port, and derives the connect port from configuration (defaulting to 5033). [W][I]

3.4.3 Site name

The Site name is a grouping container inside a BLN. It scopes the cadence of background liveness/replication traffic between physical locations (intrasite vs. intersite EPing periods, §3.6) but does not gate data exchange and is not validated during the handshake. [D][W]

Property Value [tag]
Encoding ASCII, NUL-terminated on the wire [W]
Permitted characters Letters and digits [D]
Forbidden Periods, spaces, special characters [D]
Case sensitivity Not case sensitive [D]

A node accepts a peer whose Site name differs from or is unknown to it, provided the BLN name matches and the destination node is known. Site grouping additionally appears in a node's database as a container — observed test injections created orphan site groupings (e.g. <DIAGSITE>) in a panel's field-panel log, so the Site is a real on-node structure, not merely a cadence selector. [W][D]

3.4.4 Node numbering and the management station

A node has a conceptual numeric identity 0–99 for field panels; number 100 is reserved for the management station (supervisor/host), which sits above the panel range. This is the conceptual node-id model from the protocol's lineage. On the P2/IP wire, routing is by node NAME, not number — a client places the node's name string in a routing slot, never a numeric id. The 0–99/100 model remains useful for reasoning about topology (and is the Drop of §3.3) but is not a wire field on EBLN. [D][S][I]

3.5 Inter-BLN routing

P2 panels do not talk panel-to-panel across BLN boundaries. A panel on BLN-A cannot directly address a panel on BLN-B; the BLN name is a hard membership gate (§3.4.1), and a frame whose BLN name does not match the receiving panel's own is rejected at the transport layer (TCP RST on a field panel). [W][D]

Cross-BLN data flow is brokered by the supervisor, via its CrossTrunkService. The supervisor is a member of (or a proxy onto) multiple BLNs and relays data between them — BLN-A ⇄ supervisor ⇄ BLN-B. The four routing slots in every frame ([BLN, destination-node, BLN, source-node], see §6) carry exactly the information this brokering needs: the BLN name in slots 0/2 scopes which trunk the frame belongs to, and the source/destination node names in slots 1/3 identify the endpoints. When the supervisor relays a frame across BLNs it is operating on these slots. The repeated-BLN structure of the four slots is the wire expression of the per-trunk scoping that makes brokered cross-trunk routing possible. [W][I]

Two hard constraints bound cross-BLN traffic:

  • No BACnet→P2 path. A point on a BACnet BLN (§3.2.4) is not reachable through a P2 frame; the supervisor brokers within and between P2 BLNs, but BACnet and P2 are separate stacks with no protocol-level bridge in the P2 direction. [D][I]
  • ~300-point cross-BLN COV-share cap. The number of points that can be shared across BLNs by change-of-value subscription is bounded at approximately 300 points per the cross-BLN sharing limit. Beyond this cap, cross-BLN COV sharing is not available; an implementer must not assume unbounded cross-trunk COV propagation. (COV mechanics are in §12.) [D]

A node-eviction operation exists at the node-management layer, by which a node can be force-evicted from the BLN roster; it is a node-table mutation, documented here for topology completeness and flagged as destructive (it removes a node from the operating membership view). It is not a routing facility and is not part of normal traffic. [S][D]

3.6 Liveness and replication cadence (topology layer)

BLN membership is maintained by two background mechanisms whose cadence is topology-relevant (the per-frame timing is in the session/COV sections, not here):

  • EPing (Ethernet liveness/discovery), two-tier: intrasite period 10 s, timeout 5 s; intersite period 60 s, timeout 5 s (intersite period configurable up to ~900 s). The Site name (§3.4.3) selects which tier a peer relationship uses. [D]
  • Node-table replication: notify ~10 s, poll ~30 s, full cycle ~75 s, holdback ~10 s, tombstone 86400 s (24 h) before a removed entry is reaped. The node-name table auto-replicates BLN-wide so all members converge on one roster. [D]

The 24-hour tombstone and the auto-replication are why stale roster entries persist and propagate across a BLN — relevant to the accumulation behavior discussed in the findings, and the reason an implementer should expect a roster to contain entries for nodes not currently reachable. [D][I]

3.6.1 Node states

A node-table entry carries a state. The node-state vocabulary (the values an entry can hold, and the events that transition it) is the liveness taxonomy: [S]

State Meaning [tag]
defined Configured/known but not yet live [S]
ready Online and serving [S]
failed Was live, now unreachable [S]
offline Administratively offline [S]
remote Reached via another trunk/broker (cross-BLN) [S]
extended_timeout Slow-link timeout regime (e.g. modem) [S]
orderly_removed Cleanly de-registered [S]
no_cov_links Live but no COV subscriptions established [S]
TIU_cabinet Terminal-interface-unit cabinet class [S]
unknown_protocol / p3_protocol_detected Peer speaking an unrecognized / P3 protocol [S]

Node-table transition events include node_added, node_removed, node_failed, node_ostracized, node_coldstarted, node_made_online/node_made_offline, node_made_ext_timeout, and node_make_ready. The ostracized and coldstarted events correspond to the destructive node-management operations (§9.3, and the cabinet-coldstart opcodes in §9). [S]

3.7 No multicast discovery beacon

P2 has no multicast presence/discovery beacon. There is no periodic broadcast a node emits to announce itself and nothing to passively listen for to enumerate P2 nodes. Discovery is active: connect to known addresses on TCP/5033 and exchange EPing/IdentifyBlock frames (§3.2.1). [W][D]

Two clarifications an implementer must not get wrong:

  1. An optional, default-DISABLED multicast failure-detection feature exists. When an operator explicitly enables it, panels join a configured multicast group/port (the real Ethernet-BLN availability group is 234.5.6.7, UDP port 8; up to four group/port pairs per panel) solely to detect peer failure faster. It is a liveness heartbeat among already-known peers, off by default, and is not a discovery beacon — a fresh node is not discoverable by listening for it. [D][S]

  2. The 233.89.188.1 "APOGEE multicast beacon" does not exist. Traffic to 233.89.188.1:10001 observed on test networks is UniFi-gateway-sourced (source IP = the gateway's own, Ubiquiti OUI, 4-byte 01 00 00 00 payload, ~10.5 s cadence), not Siemens. Siemens devices on the same segment emit only BACnet/IP and ARP. Any tooling that labels 233.89.188.1:10001 as a Siemens P2 beacon is misattributing gateway traffic. A P2 implementation MUST NOT implement a beacon listener as a discovery mechanism, because there is no P2 beacon to receive. [W]

3.8 FLN / P1 fieldbus hanging off a panel

Beneath an individual panel hangs a Field Level Network — a sub-bus of field controllers the panel polls master/slave. FLN device points are a separate namespace from BLN points: they are not addressed through the four P2 routing slots; the parent panel mediates all access to them. [D][W]

Property Value [tag]
Medium RS-485, 2-wire [D]
Devices per FLN trunk ≤ 32 (drop 0–31) [D]
FLN trunks per panel up to 3 (4 on NCRS-class panels) [D]
FLN protocol P1 (Protocol 1), or BACnet MS/TP on later panels (Fln_type = P1 / MSTP) [S][D]
Discovery P1WhoAreYou poll on the FLN; BLN→FLN route-through via the parent panel [D]
Auto-discover baud 1200 (MMI 1200–38400) [D]

FLN device classes are enumerated by the vendor FLN_Device_Type set: TEC (terminal equipment controller), TCU, UC (unitary controller), PXM, DPU/MPU, P1BIM (P1 Bus Interface Module), GATEWAY/FLOAT_GATEWAY, GLOBAL_IO, and FSCS. [S]

An implementer reaches FLN data by establishing a session to the parent panel (§ handshake) and issuing the FLN browse/enumerate opcodes of the 0x09xx upload family (UPL_ALL_TEC, etc.; see §9 and §10). FLN-scoped operations ride a session-carrier message class on the wire (§6). The panel returns the FLN device's point data on behalf of the field controller; there is no direct P2 transport to an FLN device. An FLN point is selected by its drop number on the addressed FLN plus its subpoint index — this is the lowest level of the LAN/Drop/Address tuple (§3.3) and the FLN-device tail of the named-scope hierarchy (§3.4). [W][S][I]

3.9 Documented topology limits

The architectural maxima below are deployment limits, not wire-protocol constants; they bound what a conformant deployment may contain and are useful sanity checks for an implementer sizing tables or validating configuration. [D]

Limit Value Scope [tag]
BLNs per supervisor 64 one supervisor manages up to 64 BLNs [D]
Panels per BLN ~99 RS-485 panels + the supervisor per BLN (the 0–99 drop range, §3.4.4) [D]
FLN trunks per panel up to 3 (4 on NCRS) per panel [D]
Devices per FLN trunk 32 (drop 0–31) per FLN trunk [D]
Panels per workstation ~1000 total across all managed BLNs [D]
Ethernet connections per workstation 64 concurrent EBLN connections [D]
Remote auto-dial BLNs 300 dial-up reachable BLNs [D]
Cross-BLN COV-share ~300 points total points shareable across BLN boundaries (§3.5) [D]
Concurrent peer sessions per panel small, firmware-dependent per panel TCP/5033 listener [D][I]

These figures derive from vendor topology documentation; treat them as the documented ceiling, not a guarantee that a given firmware enforces each one identically. Where a finding or operation depends on a limit (e.g. the cross-BLN COV cap), the dependency is called out at the relevant section. [D][I]

P2 (Protocol II) is a logical application protocol that has been carried over several physical media across its lineage. The dominant modern transport is Ethernet/IP (the Ethernet BLN, or EBLN); the original and still-supported transport is a serial RS-485 multidrop trunk (the dedicated serial BLN). Below P2's BLN tier sits the field bus, P1 (Protocol I) over RS-485, addressed by route-through from the BLN. This section defines what is known about each medium at the physical and datalink layers. The byte-level frame grammar (length prefix, routing slots, opcode, ASDU body) is medium-independent and is specified in the framing section (see §6); on the serial media that same logical frame is carried inside a medium-specific link framing whose exact bytes are not established here.

4.1 Ethernet/IP transport (the canonical P2 transport)

Native P2 over IP runs on TCP. Every BLN member — the supervisor and every field panel — listens for inbound P2 connections on a single configurable unicast Transport Server Port, default TCP/5033 [W][D]. The BLN is therefore a full mesh at the transport layer: any node may open a TCP connection to any other node's listener (see §7). The port is observed carrying P2 in the capture corpus on 5033 and (at sites with a second supervisor listener) 5034 [W].

Property Value Tag
Transport TCP, connection-oriented, length-prefixed frames [W]
Default port TCP/5033 (slot 1) [W][D]
Configurable port slots Up to 8 selectable listener-port slots per panel (TCP Port 1 = default 5033 … TCP Port 8 = default blank) [D]
BLN-wide uniformity The active Transport Server Port must be identical across every panel and the supervisor on a given BLN; a node on a different port cannot participate [D]
Addressing constraint NAT is not supported between peers — every node must be reachable by its real IP address (the BLN mesh and the replicated node-name/IP table both assume direct real-IP reachability, §5.3) [D]

The 8-slot model means a panel can be told to listen on an alternate port (e.g. to deconflict with a co-resident supervisor product on the same host, which is the origin of the site-specific TCP/5034 second listener); slot 1 always defaults to 5033 and is the canonical port. A client should default to 5033 and treat any other value as site configuration. See §2.1.5 for why the |PORT suffix that appears inside identity strings (e.g. NODE1|5034) is part of the identity string and is not a live port indicator — the same suffixed identity rides whichever TCP port actually carried the frame [W].

The 5034 listener and the two-connection pattern. In the observed Desigo deployment, 5034 is not merely a "site-specific second port" — it is the supervisor-side inbound listener for the reverse (panel→supervisor) channel: field panels listen on 5033 for the supervisor's poll/command channel, while the supervisor listens on 5034 for node-originated push/value traffic and node announcements. Each node-pair therefore maintains (at least) two TCP connections — supervisor→panel:5033 and panel→supervisor:5034 — one opened in each direction (§7.3). The exact supervisor port assignment is deployment-specific (it can equally be 5033, and the 8-slot model permits other values), but the two-listener / two-connection model is the structural pattern, independent of the specific port numbers. [W]

4.1.1 Surrounding observable service footprint

A P2/IP panel is a small embedded host that exposes platform services around the P2 port. These are not P2 and carry no P2 frames; they are listed only as the observable footprint of a real panel, useful for fingerprinting and for understanding what else is on the wire. Only TCP/5033 (and optionally a second supervisor listener) carries P2 itself.

Service Port(s) Role on a panel Tag
FTP TCP/20, TCP/21 firmware / database file transfer [D]
Telnet TCP/23 firmware-admin CLI (node-name table, field-panel reports, point-look) [W][D]
DNS UDP/53 (client) resolves Ethernet node names to IP addresses (Ethernet BLN uses DNS names, §3.4.2) [D]
BootP / DHCP UDP/67, UDP/68 address assignment where dynamic addressing is used [D]
SNMP UDP/161, UDP/162 platform monitoring / traps [D]
P2 multicast (availability) UDP/8, group 234.5.6.7 off by default peer-liveness heartbeat — see §5.2 [D]

The Telnet CLI is a significant secondary observation surface: panel-side node-name-table reports, field-panel reports, and point-look output are read over Telnet, and these have been used to corroborate wire findings (e.g. confirming a registered identity appears as a Permanent node-table entry) [W]. The CLI is not part of the P2 wire protocol.

4.2 AEM serial-to-TCP tunnel (a second observable P2-bearing TCP port)

The AEM (APOGEE Ethernet Microserver) is a Lantronix-class serial-to-TCP terminal server that bridges a legacy serial BLN onto IP by tunneling the panel's serial RS-232 byte stream verbatim over TCP [D]. To the supervisor the tunneled BLN appears as an always-connected remote BLN.

Property Value Tag
Device class Lantronix-class terminal server (e.g. AEM100 / AEM200); Local_1> admin prompt [D]
Channel 1 (Remote ALN / P2) default TCP/3001, default 38400 bps (BLN baud tied to panel firmware) [D]
Channel 2 (HMI / setup) default TCP/3002, 9600–115200 bps [D]
Serial line params 8/N/1, no hardware flow control [D]
Fallback addressing AutoIP 169.254.x when no DHCP; SNMP/Telnet/TFTP/HTTP admin services present [D]

Critically, the AEM does not define new P2 framing [D]. It encapsulates the existing serial P2 byte stream inside a TCP connection — the same bytes that would have crossed the RS-485 trunk, wrapped in TCP. Channel 1 (TCP/3001) is therefore a second observable P2-bearing TCP port distinct from native 5033, but the application bytes inside it are serial-BLN P2, not the IP-native framing of §6 (the IP-native handshake/heartbeat opcode behavior is specific to the EBLN stack). An implementer treating an AEM Channel-1 stream must speak the serial-BLN dialect, not assume the TCP/5033 IP-native conventions.

The original P2 BLN is a terminated, multidrop RS-485-style two-wire trunk — the Powers "dedicated BLN" — tapped via a Trunk Interface (TI / TI2, e.g. part 538-670) at the workstation COM port.

Property Value Tag
Medium Terminated RS-485 multidrop, two-wire, terminated at both ends [D]
Line params 8/N/1 (8 data bits, no parity, 1 stop) [D]
Trunk numbering Trunk number 0–3 (the LAN/trunk field of the address model, §3.3) [D]
Capacity Up to 99 RS-485 panels per BLN + 1 workstation node [D]
Baud (firmware-tiered) 38400 if all panels are firmware ≥ 2.2; 19200 if any panel is older (e.g. FW 12.5 / 1.5) [D]
Modem / Auto-Dial path ≤ 19200 baud, software flow control [D]
MMI / tool port up to 115200 on newer panels [D]
Backward-compat timing a per-panel Extended Timeout flag exists for panels older than firmware Rev 2.1 — direct evidence that link timeouts are firmware-version-dependent [D]

Observed operator-terminal (MMI/HyperTerminal) capture of a legacy panel shows the LAN bus speed reported as 4800 baud for LAN #1–#3 on that panel, confirming the serial-BLN baud is a per-panel configurable parameter at the operator layer [W][D]. The firmware-tiered default (19200 legacy / 38400 modern) is the documented network-wide rule; individual panels may be configured otherwise.

4.4 FLN / P1 field bus (RS-485 two-wire)

Beneath each panel sits the Field Level Network (FLN), running P1 (Powers Protocol I) over a two-wire differential RS-485 trunk. FLN device points live in a separate namespace from BLN points and are reached by route-through from the hosting panel (§5.5); the panel is the gateway/master polling its FLN devices.

Property Value Tag
Medium 2-wire differential RS-485 ("FLN TRUNK", labeled + / − / Shield), with a per-controller communication-status LED (BST) [D]
Capacity per trunk ≤ 32 devices, drop addresses 0–31 [D]
Trunks per panel 3 P1 FLN trunks per APOGEE panel (4 on NCRS-class) [D]
Device identity drop number + application number (see §5.5) [D]
Discovery transaction P1WhoAreYou (see §5.5); a mismatch yields a Failed P1WhoAreYou error [D]
Auto-discover baud default 1200 baud [D]
MMI / tool-port baud 1200–38400 (options 1200/2400/4800/9600/19200/38400) [D]

On a modular panel the RS-485 FLN trunks are provided either by built-in ports or by an add-on RS-485 FLN expansion module — Siemens' PXX-485.3 carries "three RS-485 P1 FLN connections OR one MS/TP FLN connection" per the public PXC Modular Series datasheet. This module sits on the panel's downstream (field) bus and is wholly separate from the upstream ALN/supervisor link that carries P2 — it has no bearing on the P2 wire dialect (§6.6). [D]

A panel may alternatively host a BACnet MS/TP field bus in place of P1 (Fln_type_enum: P1 = 0, MSTP = 1) [S]. Which one is available tracks the panel's firmware track, not the hardware: proprietary-P2/APOGEE firmware (the subject of this spec) drives a P1 FLN only, while the separate BACnet firmware build of the same hardware adds the MS/TP option. MS/TP is a different protocol stack and is out of scope for this P2 specification; only the P1/FLN bus is treated here. [D]

4.5 Open items — serial and field-bus framing

[OPEN] Serial-BLN P2 link framing bytes. Only the line parameters (8/N/1, baud tiers, trunk numbering) are established for the dedicated serial BLN. The byte-level link framing that carries a P2 logical frame over RS-485 (start/sync delimiting, address byte, CRC/checksum, the medium's segmentation) is not observed in the present evidence and is not specified here. An AEM Channel-1 capture (TCP/3001) would expose these bytes, since the AEM tunnels the serial stream verbatim.

[OPEN] FLN/P1 frame bytes. The P1 fieldbus discovery transaction (P1WhoAreYou), addressing (drop + application number), physical layer (RS-485 2-wire), and baud are documented, but the P1 frame byte layout itself is unobserved. The on-wire P1 frame structure, the WhoAreYou request/response bytes, and the per-poll cadence/retry behavior require a P1-bus capture or a route-through capture from the BLN.

[OPEN] P2 segmentation thresholds and reassembly rules. The ~256-byte figure from vendor connection-test material is a connection-test ping size, not a maximum P2 data-packet cap — single P2 frames are observed with total_len up to ≈1,587 bytes (a ~1,530-byte body of packed records plus header+slots) in one response, so a single frame's total_len is bounded only by the u32 length field (§6.7) [W]. The P2 application layer still carries an explicit more-follows segmentation flag and segment-mapping at the ASDU layer for results large enough to exceed an implementation ceiling [S], but the exact segmentation threshold and the more-follows flag offset/reassembly rules are not pinned on the wire. Needs a multi-segment upload capture for confirmation.


5. Discovery, Liveness & Replication

A P2 BLN is a self-organizing peer network. There is no central registry: nodes find and monitor one another using a liveness probe (EPing), an optional multicast availability channel, and a node-name/IP table that auto-replicates across the whole BLN. Below the BLN, the panel discovers its FLN devices with the P1WhoAreYou transaction. This section defines each mechanism, its wire opcode where one exists, and its documented timing.

5.1 EPing — Ethernet-BLN availability probe (liveness, not a beacon)

The Ethernet BLN's availability/liveness primitive is EPing (Ethernet Ping). On the wire EPing is the AP2 function code 0x4640 (AP2_EBLN_PING) [S], and 0x4640 is one of the two highest-frequency opcodes in the capture corpus (second only to the COV value-push 0x0274) — the steady ~10-second per-peer heartbeat, present in the tens of thousands of frames in any multi-hour supervisor capture [W]. The same 0x4640 opcode also serves as the session establish / IdentifyBlock exchange and the in-session keepalive (see §6, §7): establishing presence and proving continued liveness are the same operation, which is why this one opcode is so prominent in the traffic. 0x4640 is observed under message classes 0x29/0x2A/0x2E/0x2F/0x33/0x34 and on both observed ports 5033/5034. Its success-response body length is variable — 35 + node-name length (so 40 B for a 5-char name, 41 B for 6, 45 B for 10, 47 B for 12 — all observed); a parser MUST treat the length as variable, not as one of two fixed sizes. Every 0x4640 request in the corpus drew a reply (no no-response outcome was observed on the wire). [W]

On the wire the EBLN_PING (0x4640) request and response bodies each carry, in order: TLV(node-name) + TLV(site) + TLV(BLN-name), then the eBLN_Node trailer — five 1-byte boolean flags (failed, ready, replication_online, reresolve_all, reresolve_unresolved; observed 00 01 01 00 00), a u32 spare (0), a u32 baseTime — an absolute Unix-epoch wall-clock timestamp (e.g. 0x6A3D… ≈ a 2026 date/time; it advances frame-to-frame because it tracks wall-clock, not an uptime/tick counter), a u16 timezone offset, and a u8 DST flag. [W] This is the eBLN_Node identity element of §10.6 realized on the wire — the node/site/BLN triple is the same identity the access gate checks (§6).

EPing is two-tier, distinguishing peers in the same physical site from peers across a slower inter-site link:

Tier Probe period (default) Timeout (default) Notes Tag
Intrasite 10 s 5 s the canonical ~10 s heartbeat seen on the wire [W][D]
Intersite 60 s 5 s configurable up to 900 s; the supervisor scales the intersite period upward as panel count grows (≈ 60 + N) [D]

A node that stops seeing a peer's EPing within the timeout treats that peer as unavailable and gates communication to it (§5.3). These are discovery/liveness-layer periods, not P2 frame-level retry counts or per-frame ACK timeouts — the latter are not established (see §5.6 open items).

Note: EPing/0x4640 is a unicast TCP exchange between known peers; it is not a broadcast or a discovery beacon. Discovery of a peer's existence comes from the replicated node-name/IP table (§5.3), not from passively listening for EPing.

5.2 Multicast availability channel (optional, off by default) — and the beacon myth

P2 supports an optional IP-multicast availability/failure-detection channel layered over the unicast TCP transport. It is disabled by default and activates only when an operator configures a multicast port.

Property Value Tag
Default group 234.5.6.7 [D]
Default UDP port 8 [D]
Default state DISABLED (enabled only by configuring the multicast port) [D]
Capacity up to 4 multicast address/port pairs per panel [D]
BLN-wide rule the group/port must match across all panels and the supervisor [D]
Purpose peer-to-peer availability/failure detection (group-optimized liveness), not node discovery [D]
Related opcodes 0x462C AP2_EBLN_FP_MULTICAST_CONFIGURE, 0x463D AP2_EBLN_MULTICAST_DISPLAY [S]

This is a peer-liveness heartbeat group, not a discovery beacon: a node does not announce itself to the world for newcomers to find, and a P2 implementation must not implement a multicast beacon-listener as a discovery mechanism — there is no such beacon to receive. Discovery is via the replicated node table (§5.3).

Beacon-myth correction. A previously asserted "APOGEE multicast beacon at 233.89.188.1" does not exist as a P2 mechanism [I]. That assertion was a misattribution: the traffic in question was unrelated gateway/network-appliance traffic (UniFi-class), mislabeled by an early dissector. The only multicast P2 uses is the off-by-default availability group at 234.5.6.7 / UDP 8 above. Independent confirmations: the vendor field-panel/LocalNet configuration documents 234.5.6.7:8 as the group [D]; and on a live network with multicast disabled, no 233.89.188.1 P2 traffic is present in capture [W]. Any tool keyed to 233.89.188.1 for P2 discovery is wrong.

5.3 Node-name-table replication (the self-organizing BLN)

BLN membership is carried in a node-name table (name ↔ IP, with state) that is entered once on any one panel and auto-replicates to every panel on the BLN [D]. Each panel continuously monitors every other peer and temporarily disables communication to any peer that becomes unavailable, resuming when the peer returns [D]. This is the application-layer membership model built on top of the EPing/multicast liveness layer — there is no central master; the table is the shared, replicated source of truth for "who is on this BLN and at what IP."

The replication of shared/global data (the node-name table, plus alarm destinations, user accounts, state-text tables, etc.) runs on a notify + poll + cycle schedule with dead-node tombstoning:

Timer Intrasite default Intersite default Tag
Notification 10 s 10 + N s [D]
Polling 30 s 180 + N s [D]
Cycle 75 s 75 + N s [D]
Holdback delay 10 s 10 s [D]
Tombstone lifetime 86400 s (24 h) 86400 s (24 h) [D]

(N scales with node count.) A dead node's entry persists for 24 hours (tombstone lifetime) before it expires. This replicated background traffic — distinct from per-point change-of-value (see §12) — accounts for the steady low-rate 0x46xx replication frames seen alongside the EPing heartbeat.

5.3.1 Replication opcodes

The replication exchange is a small family of AP2 function codes. In the current capture corpus only 0x4634 (EBLN_REPL_PULL) is routinely wire-observed (thousands of frames); the other four appear only rarely (empty bodies) in the broader capture set and did not fire during captures that contained live database changes — so they are documented from the type system, not confirmed as the delta-carrier here (see step 3 below):

Opcode AP2 name Role Status in corpus Tag
0x4633 AP2_EBLN_REPL_NOTIFY "I have changes" notification to peers rare; empty bodies; did not fire on live DB change [S]
0x4634 AP2_EBLN_REPL_PULL pull replicated data (carries the node-table version digest; leaks node membership) routinely observed (the dominant replication opcode) [W]
0x4635 AP2_EBLN_REPL_PULL_MORE continuation pull (segmented replication payload) rare; empty bodies (≈130 frames) [W]
0x4636 AP2_EBLN_REPL_CHANGES changed-records delta exchange rare; empty bodies (≈179 frames); did not fire on live DB change [W]
0x464C AP2_EBLN_REPL_DIAG_NODELIST replication-diagnostic node list observed once [W]

0x4634 (EBLN_REPL_PULL) is the mechanism by which a node advertises/obtains the current node roster — the same roster that constitutes BLN membership; its request carries the full version digest and the matched response is an empty (0 B) success ack (§5.3 step 3). The actual changed-record delta propagation observed on the wire rides the DBCHANGE_*/UPL_ADDED_*/UPL_DEL_* family on the second channel, not these 0x463x opcodes.

Note on naming: an earlier behavioral label for 0x4634 was "PushRoutingTable" (from its observed effect of conveying the node/routing roster). The function-code enumeration names it AP2_EBLN_REPL_PULL [S]; the roster-bearing behavior is consistent (a pull of replicated routing/membership data). This reference uses the enum name; the behavioral description (roster transfer) is unchanged.

EBLN_REPL_PULL (0x4634) body — the replicated node table as a versioned digest. The body is the full node-name table carried as a version-vector digest, wire-confirmed [W]: an 8-byte header (00 00 00 00, then a 2-byte table-level version, then a 2-byte entry count) followed by one entry per node, each a TLV(01 00 <len> <node-name>) immediately followed by a u32 per-node version. A $paneldefault entry leads, then every BLN node and the supervisor (the supervisor participates as ordinary node entries — e.g. both its SUP and SUP|5034 identities appear).

offset 0:  00 00 00 00  <u16 table-version>  <u16 entry-count>     -- 8-byte header
then, per node entry:
   01 00 <len> "<node-name>"      -- name TLV
   <u32 per-node version>         -- this node's current change generation
first entry:  ... "$paneldefault"
then:         every BLN node + the supervisor identity(ies)
finally:   00 00 00 00                -- 4-byte trailer/terminator after the last entry

The u32 after each name is that node's change generation — it increments whenever that node alters its replicated state. Crucially, the version is a per-node convergence counter, not a timestamp, and it does not grow during a quiet capture. Across a 3-hour supervisor capture (1,653 pull digests) each node's advertised version was constant in time within any one observer's view, but differed by vantage: the supervisor advertised a lower per-node version than the panels did for the same node (observed panel−supervisor offsets ranged ≈ +1,781 to +3,564). That gap is a snapshot of how far the supervisor's replicated copy lags the panels' current state, i.e. the mesh's convergence state at capture time — it is not a per-second increment. (An earlier reading that took a node's min-to-max version across digests as growth-over-time was conflating two vantages' digests, not two points in time.) A burst of growth would appear only when a node's database actually changes (add/rename/command), bumping its own counter and the header table-version; these captures were largely quiescent. The 2-byte table-level version tracks the table as a whole, and the entry set reflects the advertising node's own view of membership, so roster length can differ between nodes.

How a name added on one panel reaches all of them. This is masterless anti-entropy gossip over the full mesh (§7.3), reconciled by version comparison — no central coordinator:

  1. You add (or rename) a node entry on panel A. A inserts the row, bumps its own per-node version (and the header table-version).
  2. On its next replication round A sends its full digest to its peers via EBLN_REPL_PULL (0x4634) — observed flowing mutually in every direction (panel→panel, panel→supervisor, supervisor→panel), so every node periodically advertises its whole table to every other node. [W]
  3. Each receiver compares the advertised per-node versions against its own. If they already match, it answers with an empty (0-byte) success ACK — the steady-state case (every matched 0x4634 response in the capture was empty: the digest request carries the full version vector, the response is just an ack). [W] If a peer is behind on some node (A's version is newer, or A carries an entry the peer lacks), it fetches the delta. Two delta paths are seen. The commonly observed one, during live database activity, is the ordinary database-change opcode family flowing on the 0x2E/0x2F second channel: DBCHANGE_* (e.g. 0x0956 DBCHANGE_CONTROLLER), UPL_ADDED_* (0x0971 POINT, 0x0974 TREND, 0x0976 TEC, …), and UPL_DEL_* (0x0961/0x0964/0x0966/…) — i.e. the changed records propagate as the same add/delete operations the supervisor uses, addressed peer→peer. [W] The dedicated replication-delta opcodes — EBLN_REPL_NOTIFY (0x4633, "I have changes"), EBLN_REPL_CHANGES (0x4636, changed records), EBLN_REPL_PULL_MORE (0x4635, paging) — also exist and appear in the corpus, but rarely and with empty bodies, and did not fire during captures that contained live DB changes; treat them as the formal mechanism while recognizing that observed record propagation went via the DBCHANGE_*/UPL_ADDED_*/UPL_DEL_* family. [W][S]
  4. Each peer that merges the new entry then re-advertises it on its own next round, so the addition ripples across the entire mesh until every panel and the supervisor converge — typically within a poll cycle or two given the ~10 s notify / ~30 s poll cadence above. A removed entry lingers as a tombstone for ~24 h before it is reaped, which is also how a deletion propagates without a node that missed the change silently re-adding it.

Because the key is the exact node-name string, adding a name that differs only in case or spelling creates a separate replicated entry rather than updating the existing one — the capture shows two co-existing supervisor rows differing only in case, each independently versioned and mesh-replicated. Obtaining this roster via 0x4634 is also the pre-auth membership-disclosure path of §17.3.

5.3.2 EBLN configuration & node-management opcodes (struct-derived)

Adjacent to the replication family is the full EBLN configuration/management opcode block. These set or display the very identity, IP, port, BLN/site name, multicast, and host-table values that the replication layer then propagates. They are definitional from the vendor function-code enum [S]; most are not seen in the read-only capture corpus (they are write/config operations). Several are destructive or identity-mutating and must be treated accordingly by any tooling (document, do not exercise blindly):

Opcode AP2 name Effect Tag
0x461F AP2_EBLN_FP_NAMES_DISPLAY display field-panel names [S]
0x4620 AP2_EBLN_FP_NAME_SET set a panel's node name (identity mutation) [S]
0x4621 AP2_EBLN_FP_IP_CONFIGURE set a panel's IP (network mutation) [S]
0x4622 AP2_EBLN_FP_TCP_PORTS_CONFIGURE set the TCP Transport Server Port slots (§4.1) [S]
0x4628 / 0x4629 AP2_EBLN_TRUNK_SETTINGS_REPLACE / _DISPLAY replace / display trunk settings [S]
0x462A AP2_EBLN_FP_SITE_NAME_SET set Site name [S]
0x462B AP2_EBLN_FP_BLN_NAME_SET set BLN name (changes membership gate, §3.4/§6) [S]
0x462C / 0x463D AP2_EBLN_FP_MULTICAST_CONFIGURE / AP2_EBLN_MULTICAST_DISPLAY configure / display multicast (§5.2) [S]
0x462D / 0x462E / 0x462F AP2_EBLN_HOSTTABLE_ENTRY_ADD / _REMOVE / _DISPLAY add / remove / display host-table (node-name/IP) entries [S]
0x4638 AP2_EBLN_MAC_ADDRESS_SET set MAC address [S]
0x4644 / 0x4645 AP2_EBLN_TELNET_ENABLE / _DISABLE enable / disable the Telnet admin CLI (0x4644 wire-observed, count 1; 0x4645 [S]) [W]

The host-table add/remove pair (0x462D/0x462E) is the explicit write path for the node-name table whose contents §5.3 says auto-replicate BLN-wide. Companion node-state operations in the 0x003x range govern membership at the routing layer: 0x0032 AP2_REMOTE_NODE_CHECK, 0x0033 AP2_GET_COMPLETE_NODE_STATE, 0x0034 AP2_SET_NODE_STATE, 0x0035 AP2_SET_COMPLETE_NODE_STATE, and 0x0030/0x0031 AP2_SET_GLOBAL_DATA / AP2_GET_GLOBAL_DATA [S]. The node lifecycle these drive is enumerated in Node_table_event_enum — including node_added, node_failed, and node_ostracized (a force-evict of a node from the BLN, a node-availability denial mechanism) [S].

Security note (struct-derived, not a wire claim). The EBLN config block and the node-state setters are full, unauthenticated-protocol write paths to a panel's network identity, BLN membership, port configuration, multicast, host table, and Telnet enablement, gated only by the BLN-name admission check (see §6). 0x4620/0x4621/0x462B/0x4638 (name/IP/BLN/MAC mutation), the host-table writers (0x462D/0x462E), the node-state setters (0x0034/0x0035), and node-ostracize are destructive to availability and identity. They are documented here for completeness; legitimate tooling for owner-operators should treat them as read-only-by-default and must not exercise the mutating members against production panels.

5.4 What a fresh client learns, and what it cannot

Because membership lives in the replicated node table rather than a beacon, a client's discovery path is:

  • Liveness of a known peer: send/observe EPing (0x4640) and watch the ~10 s cadence (§5.1) [W].
  • Membership / roster: obtain the node roster via the replication pull (0x4634) once admitted, or read it from a panel's host-table display (0x462F) / Telnet node-name-table report [W][S].
  • Cold discovery of an unknown site: there is no P2 broadcast that enumerates nodes for an unauthenticated newcomer. With multicast off (the default) and no replicated table access (admission requires the correct BLN name, §6), a fresh client cannot passively discover P2 nodes; it must know addresses to connect to. This read-only posture is by design and is consistent across the evidence [W][I].

5.5 FLN discovery (P1WhoAreYou)

Below the BLN, a panel discovers and identifies the devices on each of its P1 FLN trunks using the P1WhoAreYou transaction [D]. An FLN device's identity is its drop number + application number: the drop number (0–31, §4.4) locates it on the bus, and the application number identifies which point-team/application it runs (the supervisor reads the device's application self-ID — conventionally subpoint 2, "APPLICATION" — to learn which point map the device implements; see the point-model section). A definition must match both the FLN device's system name and its address; a mismatch produces a Failed P1WhoAreYou error [D].

Access from the BLN to an FLN device is route-through: a client connects to the hosting BLN panel (over P2/TCP) and then addresses the device behind it; the panel polls its FLN device over P1 and returns the data (see §3 / the addressing section). The FLN-scoped browse/enumerate operations on the BLN side are the 0x09xx upload/enumerate family and the FLN-topology query 0x5301 AP2_GET_FLN_TOPOLOGY [S]; FLN scan enable/disable is 0x4630/0x4631 (AP2_FLN_SCAN_ENABLE / _DISABLE) [S]. The route-through model is why BLN-to-FLN access never appears as a separate transport — it is always layered on an established P2 node session.

5.6 Open items — discovery & FLN timing

[OPEN] FLN/P1 wire bytes. P1WhoAreYou, FLN addressing (drop + application number), and the route-through model are documented at the behavioral level, but the P1 frame bytes — the WhoAreYou request/response layout and the per-device poll cadence — are unobserved. Needs a P1-bus or route-through capture.

[OPEN] Per-frame retry/ACK timing. All established timing is at the discovery/replication layer (EPing periods/timeouts; replication notify/poll/cycle; the binary Extended-Timeout pre-Rev-2.1 compatibility flag). P2 frame-level retry counts and per-frame ACK/sequence timeouts are not established and should not be inferred from the discovery-layer periods.

[OPEN] Exact replication payload offsets. The replication opcodes (0x46330x4636) and their roles are wire-confirmed, but the byte-level layout of the pull-response roster and the changed-records delta (record framing, per-entry fields, the COV/condition block carried in change records) is not fully mapped from the read-only corpus. The vendor ASDU structures (AP2_Ebln_Repl_*) define the field set [S]; confirming the exact on-wire offsets needs a capture of an active add/remove replication event.

6. Frame Format (Wire)

This section defines the P2 on-wire frame: the exact byte layout, the message-type discriminator, the direction byte, the four routing slots, the opcode field, and segmentation. P2 ("Protocol II", the APOGEE BLN/backbone protocol) runs over TCP/5033 (see §4/§3 for transport and addressing). The frame format is uniform across all message classes and both protocol dialects; only body conventions and session role differ. Every multi-byte integer in the header is big-endian (lengths and the sequence number are big-endian u32; the opcode and the error code are big-endian u16; analog values in bodies are IEEE-754 big-endian f32).

6.1 Frame byte layout

A frame is a fixed 13-byte header, followed by four NUL-terminated ASCII routing slots, followed — only on request/push frames — by a 2-byte AP2 function code (the opcode), followed by an opcode-specific body. [W]

Offset Field Type Value/Notes Tag
0 total_len u32 BE Total frame length, including these 4 bytes (self-inclusive). [W]
4 msg_type u32 BE High three bytes always 0x00 0x00 0x00; low byte is the message-class discriminator (§6.2). [W]
8 sequence u32 BE Per-connection request sequence; echoed verbatim in the matching response (§6.5). [W]
12 dir u8 Direction byte: 0x00 request/push, 0x01 success, 0x05 error (§6.3). [W]
13 slot[0] ASCIIZ BLN name. [W]
slot[1] ASCIIZ Destination node name (on a request) / source node name (on a response). [W]
slot[2] ASCIIZ BLN name (identical content to slot[0]). [W]
slot[3] ASCIIZ Source node / self identity (on a request) / destination (on a response). [W]
S opcode u16 BE The 2-byte AP2 function code. Present if and only if dir == 0x00 (§6.4). [W]
S+2 body bytes Opcode-specific request body, or — on a response — the result payload / 2-byte error tail. [W]

total_len is self-inclusive: it equals the exact on-wire byte count of the frame counting the four length bytes. For a body-less success response the value is 13 + (sum of the four NUL-terminated slot lengths including their NUL terminators). [W]

S is the variable post-slot offset at which the opcode (request) or body (response) begins. It is variable because the four routing slots are variable-length strings. A parser MUST locate S by scanning forward from offset 13 and counting four NUL terminators; it MUST NOT assume a fixed offset. The four slots are the only variable-length region of the header, so once four NULs have been consumed the parser is positioned at the opcode (if dir == 0x00) or at the response body. [W] A fixed-offset opcode parser is wrong and fabricates phantom values. [W]

6.1.1 Framing over TCP

Framing is a pure length prefix over the reassembled TCP byte stream — no record delimiter, no escape mechanism. A receiver reads four bytes, decodes total_len, then reads total_len − 4 further bytes to complete the frame. A single TCP segment may carry several P2 frames, and one P2 frame may span multiple segments; a receiver MUST buffer across segment boundaries until a whole frame has arrived. [W] An implementation should reject any frame whose declared total_len exceeds a sane ceiling (for example 65536 bytes) before buffering, to bound memory against a malformed or hostile length prefix. [I]

6.1.2 Annotated hex example

A legacy-dialect (msg_type low byte 0x33) ReadShort-class request, addressed from supervisor identity P2SCAN|5033 on BLN BLNNAME to destination node NODE1, carrying opcode 0x0220 (POINT_LOG_VALUE, the modern compact read) with a SYST read-scope prefix. All values are sanitized placeholders; the body grammar of a given opcode is defined in §8.

00 00 00 5E   total_len  = 0x5E = 94 bytes (self-inclusive)            [W]
00 00 00 33   msg_type   = low byte 0x33 (legacy DATA dialect)         [W]
00 00 1A 2F   sequence   = 0x00001A2F (per-connection, big-endian)     [W]
00            dir        = 0x00 (request — opcode IS present)          [W]
42 4C 4E 4E 41 4D 45 00              slot[0] "BLNNAME"\0   (BLN)        [W]
4E 4F 44 45 31 00                    slot[1] "NODE1"\0     (dst node)  [W]
42 4C 4E 4E 41 4D 45 00              slot[2] "BLNNAME"\0   (BLN again) [W]
50 32 53 43 41 4E 7C 35 30 33 33 00  slot[3] "P2SCAN|5033"\0 (src id)  [W]
02 20            opcode = 0x0220 (POINT_LOG_VALUE / ReadShort)         [W]
01 00 04 53 59 53 54                 SYST scope-name TLV               [W]
00 3F FF FF FF                       scope selector: scope_byte 0x00 (read) + mask [W]
00 00                                reserved (2 bytes)                [W]
01 00 0A 43 54 4C 52 30 31 2E 30 30 31   name TLV part 1 "CTLR01.001" (10) [W]
01 00 07 52 4F 4F 4D 54 4D 50            name TLV part 2 "ROOMTMP"     (7)  [W]
00 00 01 00 00 01 00 00              read trailer (8 bytes)            [W]

The two BLN-name slots are byte-identical; both MUST be populated. The opcode sits immediately after the NUL terminator of slot[3]. On the matching response (§6.3) dir becomes 0x01, slots [1] and [3] swap, and the two opcode bytes are absent — the response body begins immediately after slot[3]'s NUL. See §6.4 for the consequences of reading post-slot bytes off a response frame.

6.2 The msg_type discriminator (message class / dialect)

msg_type is a 32-bit field whose top three bytes are always 0x00 0x00 0x00; only the low byte varies. The low byte is a per-transaction message-class / dialect selector. The header layout and the four-slot routing structure are identical for every value — only body conventions and session role differ. Six low-byte values are observed; 0x40 is never observed in any capture and is not a P2 message class. [W]

Low byte Name Role Tag
0x33 DATA, legacy dialect Operational data traffic, legacy dialect. The dominant class — the great majority of all request classes. [W]
0x34 DATA, modern dialect Operational data traffic, modern dialect. Byte-for-byte a 0x33 frame with the low byte incremented; carries the same opcode set. [W]
0x2E second channel, legacy Announce / reverse / DB-sync carrier for legacy-firmware panels (pairs with 0x33 data). Carries identity (0x4640), DB-change/replication records, and alarm prints; may also carry an entire transaction in "single-type carrier" mode. [W]
0x2F second channel, modern The modern-firmware counterpart of 0x2E (pairs with 0x34 data) — same role, selected by panel generation, not by direction. [W]
0x29 session carrier Lowest-volume session carrier; appears only at the very start of a connection at low sequence numbers, accompanying session establishment. [W]
0x2A peer-session carrier Session carrier observed in peer-to-peer (panel↔panel) sessions; carries the EBLN_PING (0x4640) identity exchange like the other carriers. [W]

Class-frequency profile (shape, reproducible per-capture; absolute totals are deployment-dependent): 0x33 (legacy data) dominates, then the legacy second-channel 0x2E, then the modern data dialect 0x34, then the modern second-channel 0x2F; the session carrier 0x29 is small and the peer carrier 0x2A appears only in panel-to-panel (mirror) captures. 0x2E consistently outnumbers 0x34 — the second channel runs on every legacy panel, while 0x34 runs only on the (typically few) modern-firmware panels. Representative single 3-hour supervisor capture (≈163,700 P2 frames): 0x33 ≈ 130,000; 0x2E ≈ 18,900; 0x34 ≈ 11,500; 0x2F ≈ 3,300. (A panel-side mirror capture additionally shows the peer carriers 0x29/0x2A.) [W]

The session / second-channel band. The low-byte values 0x290x2F form a session / second-channel band (0x29 peer maintenance carrier, 0x2A peer COV-subscribe carrier, 0x2E legacy second channel, 0x2F modern second channel) — all of which carry the EBLN_PING (0x4640) identity exchange — as distinct from the data band 0x33 (legacy dialect) / 0x34 (modern dialect) that carries the operational opcode set. The pairing within the band is by panel firmware generation, not by session role or direction (§6.6): a legacy panel uses 0x2E in both directions, a modern panel uses 0x2F in both. [W]

  • 0x2E = the legacy panel's second channel — the 0x4640 identity exchange plus the panel→supervisor announce and DB-change/replication records and alarm prints (§5.3, §13). [W]
  • 0x2F = the modern panel's second channel — the same role as 0x2E, selected by firmware generation. The identity body is TLV(node-name) + TLV(site) + TLV(BLN-name) (the same triple as the 0x4640 eBLN_Node body, §5.1, §10.6). [W]
  • 0x29 / 0x2A = the peer (panel↔panel) carriers of the same exchange (maintenance / COV-subscribe), visible only from a panel-side mirror. [W]

The residual nuance — which peer is treated as primary vs secondary within a panel↔panel 0x29/0x2A session (and on what criterion the primary is chosen) — is not pinned. The role/initiator meaning of the band itself is established. [W][OPEN]

Parsing vs emitting. 0x33 and 0x34 are interchangeable for parsing — a receiver treats them identically. Only the emit-side dialect choice must match the peer. When a client must discover a peer's dialect, the candidate set is exactly {0x33, 0x34}, probed in that order; the carriers 0x2E/0x2F/0x29/0x2A are session/second-channel carriers, not data dialects, and MUST NOT be iterated during dialect detection. Dialect is not negotiated by a handshake message; it is a fixed per-peer property of the panel's model + firmware generation (§6.6) — legacy panels speak 0x33+0x2E, the newer platform speaks 0x34+0x2F — and is best discovered by fingerprinting the peer via CABINET_DISPLAY (0x010C, §10.5) rather than blind-probing. [W]

The second channel (0x2E/0x2F) carries more than identity. Between a supervisor and a panel, the 0x2E (legacy) / 0x2F (modern) class is the announce / reverse / DB-sync channel: besides the 0x4640 identity exchange it carries database-change and replication records (DBCHANGE_*, UPL_ADDED_*, UPL_DEL_*) and alarm prints (ALARM_PRINT 0x0508) flowing panel→supervisor. The peer (panel↔panel) carriers 0x29 (maintenance: heartbeat + replication gossip + COV management) and 0x2A (panel↔panel COV subscription) are visible only from a panel-side mirror, never from the supervisor's own vantage. [W]

Second-channel session establishment. Session establishment is not a distinct opcode or message class — it rides the second channel as the 0x4640 (EBLN_PING / IdentifyBlock, §7) identity exchange. When the source identity in slot[3] matches a node already in the receiver's peer list, the receiver replies with a distinct, short (~48-byte) second-channel response that is effectively an outbound peer offer back to the peer — different from the passive ~91-byte 0x33 DATA-dialect response returned to a freshly-accepted (wildcard) identity. [W] Critically, this second-channel identity path accepts an arbitrary slot[3] identity (it is not bound by the data-service identity-length gate of §7); empirically it has accepted identities at lengths 10 and 15. [W]

0x2F is the modern counterpart to 0x2E — a per-panel-platform pair, not a direction. 0x2F is to 0x2E exactly what 0x34 is to 0x33: the modern-platform form of the second channel. The choice between the pair is fixed by the panel's firmware generation (§6.6), not by traffic direction — a legacy panel uses 0x2E in both directions (supervisor→panel and panel→supervisor) and a modern panel uses 0x2F in both. An earlier reading that mapped 0x2E to the supervisor→panel direction and 0x2F to the panel→supervisor direction was an artifact of a small capture in which the one modern panel happened to be the main reverse-pushing node; a three-hour fleet capture shows the split tracks the panel, not the direction. [W] A client that does not originate peer-to-peer connect offers need not emit 0x2F, but MUST accept and correlate inbound 0x2F frames by sequence, and MUST emit the pair member that matches the peer's generation. [W]

Connection modes. The second-channel class chosen for a connection follows the panel generation; within that, two usage patterns appear, by whether the carrier hands off to the data class:

Mode Carrier (second-channel) class Data class Applies to Tag
Legacy 0x2E 0x33 older-firmware panels [W]
Modern 0x2F 0x34 newer-firmware panels [W]
Single-type carrier 0x2E or 0x2F (every frame stays on the carrier class) schedule/program edits and alarm bursts, on either generation [W]

In the first two rows the carrier class establishes/keepalives the session (via 0x4640) and carries the panel→supervisor announce + DB-change records, while operational reads/writes ride the matching data class; in the single-type-carrier pattern a whole transaction (e.g. a schedule edit or an alarm burst) stays on the carrier class start to finish. A parser distinguishes a session-establishment frame from an operational frame by inspecting the two bytes after the routing slots on a dir == 0x00 frame: if they equal 0x4640, the frame is a session establish/keepalive; otherwise they are an ordinary opcode. [W]

6.3 The direction byte

The single byte dir at offset 12 selects the meaning of everything after the routing slots. Exactly three values are defined. [W]

dir Meaning Post-slot content Tag
0x00 request / unsolicited push a 2-byte opcode followed by the request body [W]
0x01 success response a result body (may be empty); no opcode [W]
0x05 error response exactly 2 bytes — a u16 BE error code (§7.2); no opcode [W]

Observed corpus distribution: 0x00 ≈ 262,142; 0x01 ≈ 261,466; 0x05 ≈ 7,103. [W]

The governing rule is: the opcode field is present if and only if dir == 0x00. A response of either kind carries no opcode. An unsolicited push — for example a change-of-value report (opcode 0x0274, COV_ANNUNCIATE) or an 0x4640 heartbeat — uses dir == 0x00 exactly like a request, carries an opcode and body, and is acknowledged by the peer with a dir == 0x01 empty success. [W] The dir == 0x05 error response is the panel's application-layer error path; it is distinct from a TCP-level reset (which signals a wrong-BLN rejection or a fault, §7) and from the comm-status flag inside a value block (which signals device health). [W][I]

6.4 Routing slots

The four slots carry the routing identity of the frame as NUL-terminated ASCII strings. They carry no length prefix — distinct from the length-prefixed TLV form (01 00 <len>) used inside bodies; the two MUST NOT be conflated. [W]

On a request (dir == 0x00, including an unsolicited push) the slot order is:

slot[0] = BLN name           (the building-level-network name)
slot[1] = destination node   (the node being addressed)
slot[2] = BLN name           (identical content to slot[0])
slot[3] = source node / self identity (the sender)

On a response (dir == 0x01 or 0x05) the destination and source contents swap, so each frame's slot[1] always names that frame's destination. A request addressed [BLNNAME, NODE1, BLNNAME, P2SCAN] produces a reply addressed [BLNNAME, P2SCAN, BLNNAME, NODE1]. Slots 0 and 2 are stable (both the BLN name); slots 1 and 3 reverse. The BLN name appears twice in every frame; both copies carry the same value, and a conformant node MUST populate both. [W] The vendor framing logs the routing as name/channel/trunk/cabinet, with the trunk corresponding to the BLN; the doubled [BLN, dst, BLN, src] shape reflects an inter-BLN cross-trunk broker in the routing layer. [S]

Implementation note: node names may differ in letter case between a request and its reply (e.g. node1 vs NODE1). Case variation is cosmetic and does not affect routing. [W]

Slot meaning and validation. The three identity fields are enforced unequally. The BLN name is the membership gate — two nodes exchange P2 traffic only if their BLN names are identical. [W]

Field Typical enforcement Wrong-value symptom Tag
BLN name (slots 0/2) strict, case-sensitive TCP RST (panel) or graceful FIN (supervisor listener); no application processing, no node-table side effect [W]
destination node (slot 1) case-insensitive match against the known-peer list frame silently dropped, connection stays up [W]
source identity (slot 3) deployment-dependent; format-shaped, not authenticated ignored on permissive configs; silently dropped on configs that enforce a peer list [W]

A wrong-BLN handshake is footprint-free: in a controlled 24-handshake test (8 wrong-BLN variants × 3 reps) the panel returned TCP RST for every attempt, with zero data responses, zero silent drops, and zero new NODE NAME TABLE entries. [W] Registration is gated by BLN-correctness, not by data-service acceptance: a right-BLN handshake with a novel slot[3] identity writes a Permanent NODE NAME TABLE entry at the sender's IP even when the panel refuses to serve data at the 5033 data layer (the data-service identity check is a separate, later gate — see §7). [W]

The |port identity suffix. A source identity in slot[3] commonly appears as NAME|PORT, for example P2SCAN|5033 or DCC-SVR|5034. The |PORT portion is part of the identity string — a HOST|PORT disambiguator that lets one host present distinct identities — and is not a network-layer port indicator and not a field delimiter. [W] Decisive evidence: a supervisor stamps DCC-SVR|5034 into slot[3] in thousands of frames captured on a 5033-only link, and the literal 5033 never appears in any slot. The same suffixed identity rides whatever TCP port carried the frame. [W] Handling: when building a frame, emit the full literal identity including the suffix and do not split on |; when parsing, accept both the suffixed and bare forms — responses and routing-table entries frequently return the bare NAME without the suffix. [W]

Opcode presence warning. The opcode is present only when dir == 0x00. Reading the two post-slot bytes off a 0x01 success or a 0x05 error frame fabricates phantom opcodes — the values 0x0100, 0x0200, 0x0300, 0x0400 seen by such a parser are response-payload artifacts, not function codes. Any census, dissector, or scanner that tallies opcodes off response frames is inventing them. A robust dispatcher keys on (opcode, body shape) together, because several opcodes are polymorphic — the same value selects different operations by body shape, scope tag, and direction (e.g. 00 FF read trailer vs 00 00 command trailer on the same addressing grammar). [W] Some opcodes carry a 2-byte sub-field immediately after the opcode (e.g. 00 00 or 00 01) before the body; its presence is opcode-specific. The session opcode 0x4640 carries no such sub-field — its TLVs begin immediately after the opcode. [W]

6.5 Sequence number and request/response pairing

sequence is a per-connection 32-bit (big-endian) value. A response — success (0x01) or error (0x05) — echoes the sequence of the request it answers. This echo is the only correlation between a request and its reply: because a response carries no opcode, the requester recovers the meaning of a response body by looking up the opcode of the outstanding request bearing the same sequence. A sender increments sequence per request; each TCP connection has an independent sequence-number space. [W]

The vendor datalink maintains three change counters — Master / Primary / Secondary (ChangeMseq / ChangePseq / ChangeSseq) — underneath this single wire field. [S] These map to multiple independent sequence spaces by role/channel: a node's outbound-request counter, its COV/value push counter, and each peer-channel counter are distinct number lines, not one shared counter. [W] A sender should initialize sequence to a random value rather than 0/1. The normative correlation rule is exact echo: a response carries the verbatim sequence of the request it answers, and a robust requester matches on that exact value against its set of outstanding requests. [W]

Pipelining is real and confirmed. Two overlapping live captures show the request sequence and the reverse-direction response sequence carrying the identical value range — the response echoes the request's sequence verbatim, which is exactly how a client matches a reply to its outstanding request — and up to 9 requests outstanding observed before their responses arrive (9 is a capture-bound maximum, not a protocol limit; treat it as "at least 9"). A reply may therefore lag a request by a small number of steps (never arrive ahead of it); a sliding-window matcher keyed on exact sequence is the correct implementation, not merely defensive accommodation. [W]

Implementation note — each TCP connection has its own narrow counter at a distinct base. Every TCP connection carries its own sequence space, and that space is narrow — no single connection's 0x33/0x34 sequence was observed spanning more than ≈0.04M over a 3-hour capture. The wide ranges you see when you (wrongly) merge a host-pair's two connections are an artifact: a supervisor↔panel pair runs two concurrent connections (the panel's :5033 data-listen channel and the supervisor's :5034 push/command channel), each at a very different base. On one multi-panel supervisor capture: legacy panel node6's :5033 data channel ran ≈3.300M–3.338M while its own :5034 channel ran ≈7.087M–7.123M; a second legacy panel's :5034 channel sat near ≈0.311M; the modern panel's :5033 (0x34) ran ≈3.300M–3.338M and its :5034 ran ≈4.372M–4.373M. Distinct, widely-separated bases per connection refute a single global counter shared across connections — if one counter fed every link they would share one contiguous range, which they plainly do not. [W] Within a single connection the value increments by ≥1 with small gaps (the node's other concurrent channels/operations consume intervening values), so a receiver MUST treat sequence as opaque per-connection match data and MUST NOT assume a strict +1 increment. A reconnecting node resumes its counter (the first frame on a freshly-opened TCP connection continues the prior high value, it does not reset to 0/1). [W] The normative rule remains exact echo: a response carries the request's sequence verbatim; match on that against the set of outstanding requests on that connection. [W]

6.6 Legacy and modern dialect differences

The legacy dialect (0x33) and modern dialect (0x34) are byte-compatible. For the same operation the only required byte difference is the msg_type low byte:

legacy:  <total_len> 00 00 00 33 <sequence> 00  <slots> <opcode> <body...>
modern:  <total_len> 00 00 00 34 <sequence> 00  <slots> <opcode> <body...>

The header layout, four-slot routing, opcode position, direction semantics, TLV grammar, scope tag, f32 encoding, and error tail are all identical across the two dialects. The two carry the same operational opcode set and the same value encoding — a side-by-side check of COV_ANNUNCIATE payloads on both dialects returns identical big-endian f32 values; only the msg_type low byte differs. [W]

Dialect is a stable per-peer property fixed by the panel's firmware generation — not a per-transaction choice, and not by hardware type. In a single supervisor capture spanning a nine-panel fleet, each panel used exactly one dialect pair for its entire lifetime in the capture, and the split tracked firmware generation on identical hardware: all nine were the same hardware platform (a modular-cabinet controller — PXME in the firmware identity string), but the eight on an older firmware build (a 2013-era revision) spoke legacy 0x33 DATA + 0x2E on their second channel, while the one on a newer firmware build (a 2019-era revision) spoke modern 0x34 DATA + 0x2F exclusively. [W] The panel's firmware revision string, version, and build date are all readable in one round-trip via CABINET_DISPLAY (0x010C, §10.5), so a client SHOULD fingerprint the peer and select the dialect from its firmware generation rather than blind-probing. As a fallback when the firmware is unknown, the candidate set is exactly {0x33, 0x34} (§6.2), legacy first; for parsing, the two are interchangeable (a receiver treats them identically). Note this legacy↔modern split is a revision within the proprietary-P2 firmware line (a later 2.x build emits the modern classes) — it is not a protocol-family change, and is distinct from the separate "P2 vs BACnet firmware" choice the same hardware platform can be ordered with (§10.5); a BACnet-firmware unit is not a P2 node at all and does not appear on the P2 BLN. Opcode coverage overlaps but is not identical: most high-volume operations occur in both, while certain single-shot and legacy-supervisor operations occur only in the legacy dialect. [W]

Implementation note: name/string encoding is a per-firmware-revision property, not a per-frame one. Early (pre-IP) revisions pack names in RAD-50 — three characters per 16-bit word over the 40-symbol alphabet " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789" (index 0 = space, 1–26 = A–Z, 27 = $, 28 = ., 29 = ?, 30–39 = 09), packed as ((c0 × 40) + c1) × 40 + c2. [S] All P2/IP revisions use plain ASCII in the length-prefixed TLVs and NUL-terminated slots. A node uses one string encoding for its whole revision; the firmware/platform identity selects the encoding (the vendor's per-platform STRING_TYPE = RAD50 or ASCII). A peer presenting RAD-50-packed names is a pre-IP revision, out of scope for a TCP/5033 ASCII implementation. [S][D]

The per-peer capability model — how one client speaks to every panel generation. Dialect and encoding are not negotiated on the wire; there is no capability handshake, no version byte exchanged per transaction, no feature bits. Instead the encoding and dialect are learned once per peer, at connect, from the peer's firmware revision, and then applied statically for the life of the session. A conformant multi-generation client (this is exactly how a supervisor talks to a mixed fleet) keeps a small per-peer record — { device generation, string encoding, message-class dialect } — and fills it in the first time it reaches a peer:

  1. Learn. Read the peer's firmware revision once at connect — CABINET_DISPLAY (0x010C, §10.5) returns the revision string, version, build date, and platform in a single empty-request round-trip (a pre-IP serial peer is identified by its revision-table entry instead). The revision string is the the single defining input. [W]
  2. Classify. Map the revision to a device generation, which is purely a function of the revision value: a field-bus (FLN/P1) device, a modern APOGEE Ethernet panel (revision at or above the APOGEE threshold), a legacy / pre-APOGEE panel (revision below it), a LON-integrated panel, or a BACnet-firmware unit (which is not a P2 node at all and never appears on the P2 BLN, §10.5). This classification is the same one the field's own supervisor makes, and it is derivable entirely from the observable revision — no probing required. [S][I]
  3. Select encoding. The generation selects the string encoding — pre-IP revisions = RAD-50, all IP/APOGEE revisions = ASCII (above) — via the vendor's per-revision STRING_TYPE. [S][D]
  4. Select dialect. The generation selects the message-class dialect — modern 0x34/0x2F for the newer-firmware generation, legacy 0x33/0x2E otherwise (§6.2). The selector reduces to a single bit driven by the stored per-peer generation, not by anything on the wire. [W][I]
  5. Cache and reuse. The record is stored against the peer and reused for every subsequent frame; it is refreshed only when the peer's firmware identity changes. [I]

The practical consequence for an implementer: you do not blind-probe {0x33, 0x34} or guess the encoding. You issue one CABINET_DISPLAY, classify the generation from the returned revision, and from that one read you know both how to encode names (RAD-50 vs ASCII) and which dialect to frame in — for that peer, permanently. This learned-once-per-peer record is the entire mechanism behind "it just works with every controller on the BLN." [W][S][I]

6.7 Segmentation

P2 carries large payloads across multiple frames via a more-follows / segment mechanism in the application layer. The application layer exposes a more-follows segmentation flag and MapSegment / GetSegBufferCopy segment-buffer machinery; a partial reply sets more-follows and the requester pulls the next segment (the replication family EBLN_REPL_PULL / EBLN_REPL_PULL_MORE / EBLN_REPL_CHANGES carries explicit srce_cycle_number + srce_cycle_pdu_number segment cursors and a more_data : BOOLEAN_ field for exactly this). [S]

The ~256-byte figure is NOT a frame-size cap. A previously cited "roughly 256 bytes as the maximum P2 data packet" is a vendor connection-test ping figure, not a limit on how large a single P2 frame may be. [D] On the wire a single P2 frame's total_len is bounded only by the header's u32 length field (implementations cap at a sane ceiling); the largest single legitimate frame observed in the corpus has total_len1,587 bytes — roughly a 1,530-byte body of packed TLV(name)+value records plus the 13-byte header and the NUL-terminated routing slots. (A raw census maximum of 65,536 B is a stream-desynchronization artifact, not a real frame.) Large multi-record read/trend responses arrive as one large frame, not as a 256-byte-segmented exchange. [W]

The observed continuation mechanism for UPL_ALL_* is application-layer cursoring, not a frame-level more-follows flag. Bulk enumerations (UPL_ALL_POINT 0x0981, UPL_ALL_TEC 0x0986, UPL_ALL_EQS_CMD_TABLE 0x0988, …) are paginated by request: each request carries the last-returned object's name as a cursor (a TLV(name) after the application-number TLV), and the response returns the next object's record. To walk a list the client re-issues the opcode with the previous response's object name until the panel returns end-of-list. A controller with a long point list (e.g. an AHU returning consecutive ~1514-byte records) is walked the same way — each response is one near-MTU frame and the next record is fetched by cursor, not by a continuation bit. Across every UPL_ALL_* capture the panel kept each response to a single frame (frame ≤ ~1,587 B) and the client advanced by cursor; the data-channel more-follows flag was not observed firing. [W] The codec has a more-follows mechanism [S] and the EBLN_REPL_* replication bodies use explicit segment cursors (above), but for ordinary 0x33/0x34 reads the on-wire pattern is cursored request/response pagination capped near one MTU.

[OPEN] Whether a single object whose record exceeds the ~1,587 B frame ceiling triggers the codec's frame-level more-follows flag (and at what byte) is still unpinned — no single record that large was captured; every large result was continued by cursor instead. [S][OPEN]

P2 segmentation is distinct from the BACnet-side Transport Segment Management (TSML) layer (High/Low halves, per-TSM invokeID/userID, ReqACK, out-of-order expected/received reassembly) that rides over the BACnet IPC connection. TSML is a different stack and is out of scope for the P2 wire. [S]


7. Service Model & Message Types

7.1 The ASDU service model

P2 is an ISO/OSI-style application protocol. The vendor codec frames every transaction as a service primitive carrying an ASDU (Application Service Data Unit) as its payload, with the classic four-primitive shape: Request → Indication on the originator/receiver pair, and Response → Confirm on the reply path. [S] The 2-byte wire opcode is the AP2 function code (a 16-bit word); a management station may use a different internal command index for the same operation, but the wire carries the AP2 function code only. [S]

The model has two transaction shapes, which map directly onto the direction byte (§6.3):

Shape Wire pattern Direction bytes Tag
Confirmed (request → response) client sends opcode + body (dir 0x00); peer replies with success body (dir 0x01) or error tail (dir 0x05), same sequence 0x000x01/0x05 [W][S]
Event (async indication) originator pushes opcode + body (dir 0x00) on its own initiative; peer acknowledges with an empty success (dir 0x01) 0x000x01 [W][S]

The stack-selector layer's read primitives confirm the split: ReadConfirmedInd (confirmed request/response) versus ReadEventInd (asynchronous event indication). [S] The dominant Event-class transaction is the change-of-value push 0x0274 (COV_ANNUNCIATE): 121,446 frames in the corpus — almost all acknowledged with a 0-byte success, a small minority unanswered. [W] COV itself is a register/cancel subscription — a peer arms COV with COV_ENABLE (0x0271) / disarms with COV_DISABLE (0x0273), after which the panel emits COV_ANNUNCIATE events. [W][S] The session/heartbeat opcode 0x4640 (EBLN_PING) is a Confirmed transaction reused as the ~10-second in-session keepalive (its ASDU body is a single eBLN_Node element). [W][S]

A second corroboration of the Confirmed model: bulk database transfer (the upload / UPL_ALL_* and DBCHANGE families) proceeds record-by-record, each record drawing its own success-or-fault reply. This per-record success/fault pattern is the Confirmed request/response loop applied iteratively, and it is what the per-record dir == 0x01 / dir == 0x05 outcomes in upload captures show. [W][S]

7.2 Success vs error responses

A response correlates to its request solely by the echoed sequence (§6.5) and never carries an opcode (§6.4).

  • Success (dir == 0x01). Carries a result body that may be empty. Many command/acknowledge operations reply with a 0-byte success (the corpus shows ok:0B for 0x0240 POINT_CMD_VALUE, 0x0273 COV_DISABLE, 0x4634 replication, and others). Read/enumerate operations reply with a sized body whose length varies with the embedded name/value TLVs and firmware. [W]
  • Error (dir == 0x05). Carries exactly two bytes: a big-endian u16 error code, and nothing else. The error frame is the panel's application-layer rejection path; it is distinct from a TCP RST/FIN (transport-layer wrong-BLN rejection) and from a "no response at all" (the V2-class DoS signature, where the trigger draws neither an error nor a success before the panel's TCP stack goes silent). [W]

Worked error example. A TREND_DATA_DISPLAY (0x0295) request naming a point that has no trend log returns dir == 0x05 with the 2-byte tail 0x0003 ("not found", §7.2.2) — the same not-found code returned for any named object that does not exist. [W]

Reliability and acknowledgment. P2 runs over TCP and relies on TCP for in-order delivery and retransmission; there is no application-layer retransmit and no separate app-layer ACK frame. On a busy link ~1.2% of frames are TCP retransmissions — handled entirely by the transport, invisible to the P2 codec. [W] The application-level acknowledgment of a request is its dir == 0x01 success (or dir == 0x05 error) response, matched to the request by the echoed sequence (§6.5). A peer that needs to know a request was received and acted on waits for that sequence-matched response; nothing else on the wire confirms receipt. [W]

Measured round-trip latency. Median application round-trip (request dir == 0x00 → matched response dir == 0x01/0x05, by echoed sequence) on a busy live link, with p95 < ~2× the median and up to 9 requests pipelined outstanding (§6.5):

Opcode Operation Median RTT Tag
0x4640 EBLN_PING (heartbeat/identity) ≈ 6 ms [W]
0x0274 COV_ANNUNCIATE (value push ack) ≈ 6 ms [W]
0x0271 / 0x0273 COV_ENABLE / COV_DISABLE ≈ 10–12 ms [W]
0x0240 POINT_CMD_VALUE (write) ≈ 18 ms [W]
0x0220 POINT_LOG_VALUE (read) ≈ 25 ms [W]
0x0295 TREND_DATA_DISPLAY (trend retrieval) ≈ 53 ms [W]

These figures are deployment- and link-dependent; they are illustrative of relative cost, not absolute guarantees. The ordering is the durable observation: pings and COV are cheap, point read/write are mid-cost, and trend retrieval is the slowest by an order of magnitude over pings. [W]

7.2.1 Error classes (vendor AP2 error taxonomy)

The vendor codec defines a fixed set of AP2 error classes (C++ RTTI types in the codec, mirrored as named members in the managed type system). These are the definitional error categories; the wire carries a numeric code (§7.2.2) that maps onto them. [S]

Error class Meaning Tag
not supported The opcode / function code is not supported (or not permitted) in context. [S]
AP2BadTagValue A TLV tag/value in the request body is malformed or out of range. [S]
AP2BadPacketLength The declared/observed packet length is inconsistent with the parsed body. [S]
AP2BadNoOfElements An element-count field in the body does not match the elements present. [S]
AP2EncodeOverflow The response would overflow the encode buffer. [S]
AP2BufferTextOverflow A text/string field would overflow its buffer. [S]
AP2Error Base/generic AP2 error. [S]
AP2MappingError An AP2↔CPI function-code mapping failure. [S]

7.2.2 Observed wire error codes

These 2-byte codes appear as the dir == 0x05 error tail in the corpus. Distribution across 530,715 P2 frames (7,103 of which are error responses): 0x0003 ≈ 6,860; 0x00AC ≈ 163; 0x0E15 ≈ 40; 0x0002 ≈ 30; 0x0E11 ≈ 6; 0x0E12 ≈ 3; 0x0009 ≈ 1. [W]

Wire code Observed meaning Class correspondence Tag
0x0003 not found — named point/device/object does not exist (also returned for an "unrecognized opcode" probe) not yet pinned to a single AP2 class [W] / [OPEN]
0x00AC not supported — opcode/operation refused in context (0x00AC = decimal 172, the not-supported class) [W] / [I]
0x0002 out of scope — operation rejected for the addressed scope (seen on POINT_CMD_ALARM 0x0244 / CMD_ALARM_DISABLE 0x0247 against a point lacking alarm capability) not yet pinned to a single AP2 class [W] / [OPEN]
0x0E11 already exists — object/record creation collided with an existing entry (seen on POINT_ADD_LAI) not yet pinned to a single AP2 class [W] / [OPEN]
0x0E15 not commandable — point cannot be commanded (seen on POINT_CMD_VALUE) not yet pinned to a single AP2 class [W] / [OPEN]
0x0E12 (observed a few times in the corpus; an already-exists/0x0E11-adjacent record-state rejection, precise meaning not established) not yet pinned to a single AP2 class [W] / [OPEN]
0x0009 (observed once in the corpus; precise meaning not established) not yet pinned to a single AP2 class [W] / [OPEN]

[OPEN] The exact code↔class pairing is wire-confirmed only for 0x00AC (not supported). The remaining wire codes (0x0003, 0x0002, 0x0E11, 0x0E12, 0x0E15, 0x0009) are observed as raw 2-byte tails with empirically-derived meanings from the operations that produced them, but are not yet mapped to a specific named AP2 error class from a capture. Pinning the full table requires either a codec-level decode of the error-class→code constants or controlled captures eliciting each class with a known stimulus. [W][OPEN]

7.2.3 Implementation guidance

A parser MUST inspect dir before interpreting any post-slot bytes. An error tail and a value block can both begin 00 03: read as a value block, 0x0003 ("not found") would fabricate a phantom value where the peer actually reported an error. [W] A robust client treats dir == 0x05 as an application-layer "operation failed with code N," distinct from transport-level RST/FIN (connection rejected/closed) and from silence (no reply). A 0-byte dir == 0x01 is a positive acknowledgement, not an empty/failed result. [W][I]

7.3 Connection and session model

P2 runs over TCP with a role-asymmetric, multi-connection topology. A field panel listens for P2 on TCP/5033 (§4.1); a supervisor listens on its own P2 port(s) for node-originated traffic — in the observed Desigo deployment the supervisor listens on TCP/5034 for panel push/value traffic and also accepts node announcements there (§4.1, §2.1.2). Exact supervisor port assignment is deployment-specific, but the two-listener / two-connection pattern below is the model. [W]

Two connections per node-pair. Each side opens a TCP connection to the other side's listener, so a communicating pair maintains at least two long-lived TCP connections, one per direction:

  • supervisor → panel:5033 — the supervisor's poll/command channel.
  • panel → supervisor:listener — the panel's announcement + COV/value-push channel.

The message classes a given pair uses are fixed by the panel's firmware generation, not by which of these two connections a frame rides: a legacy panel uses 0x33 data + 0x2E second-channel in both directions, a modern panel uses 0x34 + 0x2F (§6.2/§6.6). The second channel (0x2E/0x2F) is where the panel's announce/identity and DB-change/replication records flow toward the supervisor.

These connections are long-lived; reconnects (panel reboot, link flap, supervisor restart) add further connections over the life of a capture. [W]

The BLN is a full peer mesh. Every node holds live P2 sessions with the supervisor and with every other node on the BLN — panel↔panel sessions run over panel:5033 just like the supervisor's poll channel (carried by the 0x29/0x2A peer-session band, §6.2). This realizes the self-organizing logical BLN of §5.3 at the connection layer. Inter-panel traffic is only visible at a panel's own switch port, never from the supervisor's vantage — a capture taken at the supervisor sees supervisor↔panel traffic but none of the panel↔panel mesh. [W]

Connection establishment (observed at SYN level). A panel-side switch-port mirror captures the TCP handshakes directly: panels open connections to port 5033 — both to the supervisor (panel → supervisor:5033) and to each other (panel → peer-panel:5033) — and the listener answers with SYN-ACK. In one mirror window a single panel dialed five different peer panels' :5033 plus the supervisor, and other panels dialed back to it, in waves (initial connect, then reconnects). This is the peer mesh forming at the transport layer. The supervisor's own outbound connections to panels are off-vantage in a supervisor-side capture (it is the SYN-ACK side there), so "who initiates" is only directly observable for the connections that cross the mirrored port; what is established is that every panel both listens on 5033 and dials 5033 on its peers. [W] A reconnecting node does not reset its sequence — the first frame on a freshly-opened TCP connection continues the node's prior high sequence value for that channel (§6.5). The first application traffic on a fresh data path depends on the channel role: on a supervisor→panel poll channel (TCP/5033) to a legacy panel it is a periodic database-change poll (0x0959/DBCHANGE family) at a ~5-second cadence; on a panel→supervisor push channel (e.g. TCP/5034) it is typically a COV push/enable (0x0274/0x0271). The constant across all fresh streams is the 0x4640 (EBLN_PING) identity exchange. There is no dedicated P2 "connect handshake" opcode — the TCP handshake plus the message-class context (§6.2) is the session. [W]

                       supervisor "SUP"  (192.0.2.10)
                       listener: TCP/5034
                          ▲              │
        panel→SUP push    │              │  SUP→panel:5033 poll/command
        (announce/COV)    │              ▼
                    ┌─────┴──────┐  ┌────────────┐
                    │   NODE1    │  │   NODE2    │
                    │ (192.0.2.21)│ │(192.0.2.22)│
                    │ TCP/5033   │  │ TCP/5033   │
                    └─────┬──────┘  └─────┬──────┘
                          │   panel↔panel  │
                          └────────────────┘
                    NODE1:5033 ↔ NODE2:5033 (peer mesh,
                    0x29/0x2A band — invisible at SUP)

Each arrow is an independent TCP connection with its own sequence space (§6.5).

8. Body Encoding Primitives

Request and response bodies (everything after the 2-byte AP2 function code on a request frame, or after the routing slots on a response frame) are assembled from a small, fixed set of encoding primitives. This section defines each primitive at the byte level. The opcode/operation grammars in §9 are built entirely from these; the point-model value forms they feed are detailed in §11. All multi-byte integers in P2 bodies are big-endian unless explicitly stated otherwise; header length and sequence fields are big-endian u32, and analog f32 values are big-endian (see §8.3). [W][S]

The primitives are:

Primitive Role Subsection
String TLV Tagged, length-prefixed string/name/scope field §8.1
Scope tag + command priority Addressing prefix that also sets command priority and read/write path §8.2
Numeric value field Integer and f32 value encodings §8.3
String character encoding RAD-50 vs ASCII codec selection §8.4
ASDU field convention How ordered typed fields realize on the wire §8.5

8.1 The string TLV [W]

The universal container for a string, name, or scope-name field is a tag-length-value (TLV) structure. It is the single most common primitive in any P2 body. [W]

Offset Field Type Value/Notes [tag]
0 tag 2 bytes Fixed 2-byte prefix 0x01 0x00 [W]
2 length u8 Number of content bytes that follow (0–255) [W]
3 content length bytes Usually ASCII text; occasionally binary [W]

The tag is the literal prefix 0x01 0x00. The third byte is a one-byte length, and exactly that many content bytes follow. Worked examples (verbatim wire bytes): [W]

01 00 04 53 59 53 54         ->  "SYST"   (length 4)
01 00 0A <10 content bytes>  ->  a 10-byte name
01 00 00                     ->  ""       (empty TLV, length 0)

Properties an implementer must honor:

  • Content is NOT NUL-terminated. The length byte alone bounds the content. This is the inverse of the four routing slots in the header (see §6), which are NUL-terminated and carry no length prefix. The two MUST NOT be conflated. [W]
  • A single TLV caps at 255 content bytes because the length is one byte. A payload longer than 255 bytes is carried as multiple consecutive TLVs. Standard P2 name fields (point names, node names, BLN names, descriptors) are all well under this cap; only free-text and table dumps approach it. [W]
  • The empty TLV 01 00 00 (length 0) is common and serves as a positional placeholder inside request bodies — reserving or separating a field that carries no value in a given request (for example a device-name slot left empty to address a BLN-virtual point). [W]

Notation used throughout §9: S<n> abbreviates a string TLV 01 00 <n> <n content bytes>, and S() abbreviates the empty placeholder TLV 01 00 00. [I]

The TLV's TEXT_ content is what realizes a logical name on the wire. In the vendor's own ASDU field model (§8.5) a name field decomposes into a name_space selector plus name and suffix TEXT_ fields; on the wire each TEXT_ field is a string TLV, and a multi-component name is carried as consecutive TLVs. [S]

8.2 Scope tag and command priority [W][S]

Most addressable requests open with a scope tag: a scope-name string TLV (§8.1) immediately followed by a 5-byte selector of the form <scope_byte> 3F FF FF FF. [W]

01 00 04 53 59 53 54   23 3F FF FF FF
|------- TLV "SYST" --| |- selector -|
                        scope_byte=0x23, then wildcard mask 3F FF FF FF

The trailing 3F FF FF FF is a fixed wildcard mask. The leading scope_byte is the command priority at which the operation acts — it is not a separate flag. The same byte both sets the priority a write commands the target at, and (through the read-vs-write handler split) selects the body grammar that follows. [W][S]

Defined scope-name TLVs observed on the wire: [W]

Scope TLV Use
"NONE" The default scope for routine point operations addressed by full point name — both reads and the bulk of commands. In a 3-hour supervisor capture it was the scope on all 13,796 POINT_CMD_VALUE writes (zero "SYST"); a separate 4-minute command capture showed 444 writes split 439 NONE / 5 SYST (the SYST cases carrying scope_byte = 0x23). It typically carries scope_byte = 0x00, with the command's effective priority carried instead in the trailing priority byte after the value (§12.4). [W]
"SYST" System / whole-panel scope, routed through the panel's system-scope handler. Its scope_byte is the operation's command priority: system-scope writes (e.g. 0x4221, 0x0295, 0x0240/0x0241/0x0244) carry 0x23 (OPER), while the system-scope read path — dominated by 0x0220 POINT_LOG_VALUE — carries 0x01 (tec_ovrd) or 0x00 (none). By raw frame count across the corpus the most common SYST scope_byte is 0x01, not 0x23; 0x23 is dominant only on write opcodes. Reserved for system-level points (e.g. panel day/night mode); ordinary point commands use "NONE". Many system-scoped operations require this tag and reject the request if it is absent. [W]
"CC" Command-and-control opcode family; typically carries scope_byte = 0x23 (OPER). [W]

8.2.1 The command-priority ladder [S]

The scope_byte takes its values from the User_command_priority enumeration, the command-priority ladder. Higher numeric value = higher priority; acceptance of a command at a point is a >= gate against the priority already held at that point. [S]

Name Value (dec) Value (hex) Notes [tag]
none 0 0x00 The read path; no command authority. Read-style operations carry 0x00. [W][S]
tec_ovrd 1 0x01 TEC (terminal-equipment-controller) local override. [S]
pdl 5 0x05 PDL (process/peer data link) program command. [S]
host_2 10 0x0A Host band level 2. [S]
host_3 15 0x0F Host band level 3. [S]
host_4 20 0x14 Host band level 4. [S]
host_5 25 0x19 Host band level 5. [S]
host_6 30 0x1E Host band level 6. [S]
emer 32 0x20 Emergency. [S]
smoke 34 0x22 Smoke control. [S]
oper 35 0x23 Operator — highest. The system-scope write priority; "SYST" with 0x23 routes the write path. [W][S]

The same five priority names are exposed in the PPCL control language as the literals @NONE, @PDL, @EMER, @SMOKE, @OPER, and the field-controller priority field is a single byte (GetPriority -> BYTE), confirming scope_byte is exactly the 1-byte command priority. [D]

Priority semantics an implementer must honor:

  • A write commands the target at the priority in scope_byte; a read uses 0x00 (none). The read-vs-write body shape for a polymorphic scoped opcode is discriminated by scope_byte (0x23 write vs 0x00 read) together with traffic direction — never by TCP port and never by the message class. [W][S]
  • Priority is held until released. A command placed at a given priority holds the point at that priority. A held priority MUST be explicitly released (commanded back to none) before a lower-authority source — for example a PPCL program commanding at NONE — can reclaim control of the point. A PPCL NONE command does not override a higher held priority. [D][I]

8.2.2 Point priority overlay (BACnet 16-level band) [S]

The Point_priority_enum superset extends the command ladder of §8.2.1 with the BACnet 16-level priority array, values 101–116 (bacnet_1bacnet_16), where bacnet_1 (101) is highest within that band. Later, BACnet-capable controllers also expose an MMI/BACnet priority band in the 8–16 region (APP_PRIORITY / MMI_PRIORITY enums on BACnet-TEC platforms). A point on such a controller resolves its effective command from the combined ladder: the legacy P2 command priorities (0–35) and the BACnet array (101–116) coexist in one priority space, with the same higher-value-wins >= arbitration. The classic P2 wire scope_byte carries only the 0–35 legacy band; the 101–116 band is reached through the point's BACnet command path on controllers that implement it. [S][I]

8.3 Numeric value fields [W][S]

8.3.1 Integers [W]

Integer fields embedded in bodies are big-endian. Both 16-bit (u16 BE) and 32-bit (u32 BE) fields occur — 2-byte error codes, 2-byte record markers and element counts, and 4-byte counts or identifiers in enumeration and routing bodies. (Header total_len, msg_type, and sequence are u32 BE; the AP2 function code and the error code are u16 BE — see §6.) The vendor field model uses the signed/unsigned 8/16/32 widths of the Native_type_enum (§8.3.3), and counts are typically UNSIGNED16 / UNSIGNED_16, e.g. an array length preceding a variable-length element list. [W][S]

8.3.2 Floating-point (analog) values [W][S]

Analog point values are IEEE-754 single-precision (32-bit) floats in big-endian byte order (f32 BE), embedded directly in the body with no scaling on the wire. Example: the bytes 41 EB 3C 0C decode to 29.404. Runs of f32 values appear inside multi-value response bodies (setpoint and limit tables, schedule data). [W]

Values are raw engineering units as the point holds them; any slope/intercept conversion to display units is applied by the client from a per-point table, not from any wire field. Each analog point carries a Slope and Intercept (and a Sensor Type) in its configuration; display value = slope * raw + intercept, with the device-range ↔ signal-range mapping (PointDeviceHigh/LowPointSignalHigh/Low) defining the conversion. This metadata is not on the value wire; it lives in the point database. (See §11 for the full point-read value-block layout that wraps the f32.) [D][S]

This BLN/P2 f32 value form is distinct from the FLN/P1 raw-count analog representation: an FLN field device reports an analog as an integer raw count whose width is governed by P1MaxRange (most commonly an 8-bit count, 0–255), which the parent panel converts to engineering units using the point-team scaling before presenting the point as a P2 f32. An implementer reading P2-over-TCP sees the f32 form; the raw-count form appears only across the P1 fieldbus and in FLN-device point definitions (cross-ref §11). [D][I]

8.3.3 Native primitive types [S]

The vendor's Native_type_enum enumerates the primitive value types a point or field may take. An implementer mapping a typed field to wire bytes uses this set:

Value Name Wire meaning [tag]
0 NVT_TYPE_UNKNOWN unknown/unset [S]
1 NVT_TYPE_SIGNED_CHAR signed 8-bit [S]
2 NVT_TYPE_UNSIGNED_CHAR unsigned 8-bit [S]
3 NVT_TYPE_SIGNED_SHORT signed 16-bit BE [S]
4 NVT_TYPE_UNSIGNED_SHORT unsigned 16-bit BE [S]
5 NVT_TYPE_SIGNED_LONG signed 32-bit BE [S]
6 NVT_TYPE_UNSIGNED_LONG unsigned 32-bit BE [S]
7 NVT_TYPE_ENUM enumerated (multistate); maps to an enum-table id (§11) [S]
8 NVT_TYPE_ARRAY array; a count followed by repeated elements [S]
9 NVT_TYPE_STRUCT structure; ordered typed fields (§8.5) [S]
10 NVT_TYPE_UNION union [S]
11 NVT_TYPE_BITF bit field [S]
12 NVT_TYPE_FLOAT IEEE-754 f32 BE — the analog value type (§8.3.2) [W][S]
13 NVT_TYPE_SIGNED_QUAD signed 64-bit BE [S]
14 NVT_TYPE_REFERENCE reference/handle [S]
15 NVT_TYPE_UNSIGNED_QUAD unsigned 64-bit BE [S]

The analog wire value (§8.3.2) is NVT_TYPE_FLOAT (12). The width and byte order of every other primitive follows the table above, big-endian for all multi-byte forms. [S]

8.3.4 Date/time stamp [W]

Event records (alarm prints, §13.6, and trend/log samples) carry an absolute timestamp as a packed 8-byte calendar field, one byte per component, in order:

<year-1900> <month> <day> <day-of-week> <hour> <minute> <second> <centisecond>
   u8         u8     u8        u8         u8      u8       u8         u8

The year byte is the calendar year minus 1900 (so 0x7E = 126 → 2026); month is 1–12, day is 1–31, day-of-week is 1 = Monday … 7 = Sunday, then 0-based hour (0–23) / minute (0–59) / second (0–59) and a trailing hundredths-of-a-second byte. Worked example: 7E 06 19 04 0C 3B 23 48 → 2026-06-25, day-of-week 4 (Thursday — which is the true weekday of that date, an independent check on the field order), 12:59:35.72. [W] A record may carry more than one such stamp — an alarm report (§13.6) carries three: an event time, a reference time, and a created/configured time; a second observed stamp 7C 01 1E 02 10 0B 0A 59 decodes to 2024-01-30, day-of-week 2 = Tuesday, 16:11:10, again weekday-consistent (a recurring "created" base). [W] This wire form is distinct from the eBLN_Node baseTime/offset clock-sync fields (§10.6), which are a separate u32 time base plus a u16 zone offset used for replication time alignment, not a calendar stamp. [W][S]

8.4 String character encoding — RAD-50 vs ASCII [S][D]

String content inside a TLV (§8.1) is carried in one of two character encodings, selected per firmware platform, not per frame. The encoding in force is a fixed property of the device's firmware revision (STRING_TYPE = RAD50 | ASCII, keyed per platform class in the firmware revision library). A node uses one string encoding for its whole revision. [D]

8.4.1 The RAD-50 codec [S]

RAD-50 packs three characters into a single 16-bit word, drawing from a fixed 40-character alphabet. The alphabet (index → character) is: [S]

index:  0                                              39
char:   " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789"
Index range Characters
0 space (' ')
1–26 AZ
27 $
28 .
29 ?
30–39 09

Three characters c0 c1 c2 (each mapped to its index 0–39) pack into one unsigned 16-bit word: [S]

word = ((c0 * 40) + c1) * 40 + c2

The maximum packed word is 39*40*40 + 39*40 + 39 = 63999 (0xF9FF), so every RAD-50 word fits a u16. To unpack, decode c2 = word % 40, c1 = (word / 40) % 40, c0 = (word / 40 / 40) % 40, then map each index back through the alphabet. Names whose character count is not a multiple of 3 are padded with the space character (index 0) in the trailing position(s). The packing word is stored in the platform's native word order; RAD-50 appears only on pre-IP field-controller and supervisor revisions, so an implementer targeting P2-over-TCP normally encounters ASCII (§8.4.2) and treats a RAD-50-packed peer as an out-of-scope legacy revision. [S][I]

Only the 40 alphabet characters are representable: uppercase letters, digits, space, and the three symbols $ . ?. Lowercase and other punctuation cannot be RAD-50-encoded, which is consistent with the uppercase-only, restricted character sets of legacy name fields. [S][I]

8.4.2 ASCII [W][D]

All P2/IP revisions carry plain ASCII in both the length-prefixed TLVs (§8.1) and the NUL-terminated routing slots (§6). This is the encoding observed throughout the TCP/5033 wire traffic. A TCP/5033 implementation is ASCII end-to-end; it does not need a RAD-50 codec to interoperate with current panels. [W][D]

8.4.3 Field length budgets by encoding [D]

String fields have encoding-dependent capacity budgets. Name fields (point name, node name) on RAD-50 platforms are constrained to roughly a 12-character budget (and ~30 for longer object-name fields); free-text fields such as a point Description run to roughly 60 ASCII characters. Concrete name-field limits used by the protocol model: a logical point name up to 15 characters (historically a 6-character field; shorter names interoperate everywhere), a point descriptor up to 12 characters (display-only), and an object/display name up to 30 characters. Node and BLN names also fall in the ~30-character band. These budgets are firmware-platform properties; an implementer should treat the larger limit as the worst case and not assume a fixed width. The ~15-character node-name truncation is consistent with the Base + Suffix decomposition of a system name (the name is split into a base and a suffix, with the base length-bounded). [D][I]

Codec-enforced byte maxima (cross-validated). The ASCII serializer applies a fixed maximum byte length per string field, and the same caps recur across dozens of independent operation bodies — so they are reliable worst-case widths for an encoder, not per-opcode accidents. The dominant cap is 31 bytes (0x1F) for object / point / program-name fields (seen on the command, COV, cabinet, EBLN, and enum families alike — it is by far the most common string cap; this is the byte width of the 30-character object-name field above, i.e. 30 usable characters within a 31-byte budget), with 13 bytes for a short descriptor or secondary name (the 12-character descriptor above in its field budget), roughly 21 bytes for an operator credential / logon identity, and a free-text band of ~248–257 bytes for message text, license strings, point descriptions, and PPCL program-line text. (This 31-byte object-name cap is distinct from the ≤15-byte node-name limit above — they are different fields: the node name is the access-gate identity in the routing slot / node-name table, while the 31-byte cap governs point, object, and program names inside the body.) An encoder SHOULD truncate to these maxima before framing; an over-length field is the most common reason a panel silently rejects an otherwise well-formed write. [D][I]

8.5 ASDU field convention [S]

A P2 body is an Application Service Data Unit (ASDU) — the payload the request/response service carries. Each ASDU body is a sequence of ordered, typed fields in declaration order (an AsnBase-style structure: there are 1,144 such request/response/sub-type structures defined in the vendor type system). There is no per-field tag/name on the wire beyond what each primitive carries; field identity is positional, fixed by the structure definition for that opcode. A parser walks an ASDU left to right, consuming each field per its declared type. [S]

Field-type-to-primitive mapping:

ASDU field type Wire realization Primitive
TEXT_ A string TLV 01 00 <len> <bytes> §8.1
FLOAT_ f32 BE (4 bytes) §8.3.2
UNSIGNED8 / UNSIGNED_8 / BOOLEAN_ u8 (1 byte) §8.3.1
UNSIGNED16 / UNSIGNED_16 u16 BE (2 bytes) §8.3.1
signed/unsigned 32/64 per Native_type_enum width, BE §8.3.3
<EnumName> the enum's underlying integer (typically u8 or u16 BE) §8.3.3
<SubStruct> that sub-structure's ordered fields, inlined §8.5 (recursive)
<Type>[] a UNSIGNED_16 count followed by that many elements §8.3.1 + element

Composite name fields decompose into a name_space selector plus TEXT_ components, each component a string TLV on the wire:

  • Name_single = name_space, name (TEXT_), suffix (TEXT_) — addresses one named object. [S]
  • Name_response = name_space, name, suffix — the echoed name in a reply. [S]
  • Name_search = name_space, name_pattern, suffix_pattern, plus last_name_space/last_name/last_suffix for resumable/paged browse. [S]

A worked structural example — the point-command-value request — shows the convention end to end: [S]

AP2_Point_Cmd_Value_Request =
    user_profile   : User_profile      ; sub-structure (inlined fields)
    name_search    : Name_search       ; name_space + name/suffix TLVs (+ resume fields)
    point_value    : Point_value       ; the f32 value block
    point_priority : Point_priority     ; the command priority (§8.2.1 ladder)

On the wire this serializes as the sub-structure's fields, then the name TLV(s), then the f32 value, then the priority field — in that order, positionally. The command priority here is the same ladder value that a scoped request carries in its scope_byte (§8.2.1); whether the priority rides as a scope-tag selector byte or as a trailing typed field depends on the specific opcode's structure, but the value space is identical. [S][I]

Layout-precision note. ASDU field order and type are definitional truth from the vendor type system ([S]). For the COV annunciate condition/priority block specifically, the offsets are now pinned: the Annunciate_request defines exactly ten status fields after the value and the wire block is exactly ten bytes, so the mapping is one byte per field in schema order (§12.3.3) — position/size/order are [W]/[S], and only the asserted values of the alarm/flag/priority bytes remain to be confirmed from an alarmed capture. For other composite blocks (e.g. the per-type value arms of All_points, and the replication change-record framing), the exact interior byte offsets are still [OPEN] pending a labeled capture or codec confirmation; treat byte-offset claims for those as inferred until then.

For the byte-level grammar of each opcode's request and response ASDU, see §9; for the point-model structures (value blocks, multistate enum tables, FLN-device subpoints, slope/intercept scaling) these primitives compose into, see §11.

9. Function-Code (Opcode) Catalog

Every P2 request, panel-originated push, and response is dispatched on a single 2-byte field, the AP2 function code (the wire opcode). This section catalogs that field. §10 then gives the body (ASDU) structures that follow each opcode.

9.1 The AP2 function code

The AP2 function code is a 16-bit big-endian value that sits on the wire immediately after the fourth NUL-terminated routing slot, in direction == 0x00 frames (requests and panel-originated async pushes). It is the dispatch key the panel exec (CEC) uses to select the operation; the bytes following it are the operation's ASDU body (§10). The opcode is meaningful only in direction == 0x00 frames — reading the post-slot bytes off a 0x01 success or 0x05 error response yields response-payload bytes, not an opcode, and fabricates phantom values (see §6.4). [W][S]

The complete command vocabulary of the protocol is defined by the vendor's AP2_Function_Code enumeration: 641 named members across 630 distinct opcode values (a handful of values carry two names — historical aliases such as AP2_DUMMY_CMD/AP2_REV_STRING at 0x0100, or the CONTROLLER/TEC doublets). The enum's numeric values are the wire opcodes: cross-checking every opcode seen on the wire against the enum, all matched exactly. [S]

Of the 630 defined values, 135 are observed in the capture corpus (530,715 P2 frames across 108 captures); the remaining 495 are defined-but-unobserved (overwhelmingly configuration, database-management, upload, and BACnet/LON-integration operations that a passive supervisor↔panel capture does not exercise). Every defined opcode is enumerable from the catalog below; "not observed" means absent from this corpus, not undefined. The corpus combines passive supervisor↔panel and panel↔panel site captures with a smaller set of active read/enumeration test captures; opcode counts are corpus frequencies, not a claim about steady-state operation. [W][S]

Rows that were seen on the wire are tagged [W] and carry their frame count; defined-but-unobserved rows are tagged [S] (struct/metadata-derived from the vendor enum — definitional truth). Wire counts in the catalog come from the corpus census; per-opcode response shapes, error tails, and message-class distributions are in §9.7.

9.2 Naming, families, and value layout

Opcode names follow the form AP2_<FAMILY>_<operation>. The value space is grouped: contiguous blocks share a family and an operation-style suffix vocabulary that recurs across families:

  • _LOG / _DISPLAY / _LOOK / _QUERY_*reads (return data, no state change).
  • _ADD / _REMOVE / _MODIFY / _COPY / _REPLACE / _DELETEdefinition / configuration writes.
  • _CMD_*runtime command (commands a live point or object).
  • _ENABLE / _DISABLE — toggle.
  • UPL_ALL_* / UPL_ADDED_* / UPL_DEL_* — the three-phase upload (bulk read of a whole object class / incremental adds / incremental deletes) used for supervisor↔panel database synchronization.
  • DBCHANGE_* — database-change notification (panel tells the supervisor a class changed).

The high byte of the opcode loosely tracks the family band: 0x00xx node/cabinet/license, 0x02xx point and COV, 0x03xx session/database/user/EMS/ENVELOPE, 0x04xx enum/alarm/calendar/language, 0x09xx upload + DBCHANGE, 0x28xx/0x38xx RACS, 0x40xx team/PPCL/program, 0x42xx TEC/UC, 0x43xx–0x44xx LON, 0x46xx EBLN + session keepalive, 0x48xx–0x4Bxx BACnet integration, 0x50xx EQS, 0x53xx I/O-module / FLN-topology / flash / HOA, 0x70xx web. The catalog (§9.5) is grouped by these families and sorted by value within each.

9.3 Destructive and sensitive operations

A subset of opcodes mutate panel firmware state, node-table membership, point values, or device configuration. These are flagged DESTRUCTIVE in the catalog and must be blocklisted in any read-only scanner or bridge (refuse to emit, even behind a flag). They are documented here for completeness; this is a reference, not an exploitation aid, and no attack code is given. [S/W]

Panel lifecycle (reboot / firmware). AP2_CABINET_COLDSTART (0x010A) and AP2_CABINET_WARMSTART (0x010B) reboot the panel; AP2_CABINET_BOOT_MONITOR (0x0108) drops it to the boot monitor. Correction of record: 0x010A is the cold-start (reboot) command — not a benign "GetRevString sibling" as an earlier behavioral reading guessed; the firmware-revision string read is AP2_CABINET_DISPLAY (0x010C, §10.5). The cold-start display/history operations are distinct from the restart commands: AP2_CABINET_COLDSTART_DISPLAY (0x012A) is read-only, while AP2_CABINET_COLDSTART_CLEAR_HISTORY (0x012B) clears the cold-start history and is therefore state-changing (flag it; not read-only). [S]

Node-table / cabinet membership. AP2_CABINET_ADD (0x0041), AP2_CABINET_REMOVE (0x0042), AP2_CABINET_ONLINE (0x0046), AP2_CABINET_OFFLINE (0x0047), AP2_SET_NODE_STATE (0x0034), AP2_SET_COMPLETE_NODE_STATE (0x0035). These add, remove, force-online, force-offline, and evict a cabinet's node-table membership; the eviction case force-removes a peer from the logical BLN (a node-eviction denial-of-service mechanism). Adding/removing entries here is the same node-table surface that registration touches (see §5 addressing and the registration-vs-impersonation footprint asymmetry). [S][I]

EBLN panel reconfiguration. AP2_EBLN_FP_NAME_SET (0x4620), AP2_EBLN_FP_IP_CONFIGURE (0x4621), AP2_EBLN_FP_SITE_NAME_SET (0x462A), AP2_EBLN_FP_BLN_NAME_SET (0x462B), AP2_EBLN_FP_MULTICAST_CONFIGURE (0x462C), AP2_EBLN_HOSTTABLE_ENTRY_ADD/REMOVE (0x462D/0x462E), AP2_EBLN_MAC_ADDRESS_SET (0x4638), AP2_EBLN_TELNET_ENABLE/DISABLE (0x4644/0x4645). These rewrite the panel's network identity (IP, MAC, names) and management surface. [S]

Point command (write). AP2_POINT_CMD_VALUE (0x0240) and AP2_POINT_CMD_PRIORITY (0x0241) command a live point's value/priority — the primary actuation write path (§10.3). High wire volume (32,044 / 101) confirms these as the routine supervisor write opcodes. The full AP2_POINT_CMD_* band (0x0240–0x024E) includes alarm-state forcing, limit setting, totalizer/trouble commands, and RELEASE. [W]

Database / flash / memory / license / COLBAS. AP2_RESTORE_FLASH_DBASE (0x5331, overwrites the panel flash DB), AP2_CLEAR_FLASH_DBASE (0x5332, erases it), AP2_CABINET_MEMORY_MODIFY (0x012F, direct memory write), AP2_LICENSE_MANAGER_DELETE/DELETE_ALL (0x0111/0x0112), AP2_COLBAS_WRITE/ABORT (0x4A03/0x4A04). AP2_BACKUP_FLASH_DBASE (0x5330) is a read/export and is not destructive. [S]

Correction of record (COV pair). AP2_COV_ENABLE (0x0271) and AP2_COV_DISABLE (0x0273) are the change-of-value subscription enable/disable pair — not "ReadExtended"/"PointExistenceProbe" as an earlier behavioral reading labeled them. AP2_COV_ANNUNCIATE (0x0274) is the actual COV report (the highest-volume opcode in the corpus, 121,446 frames), and AP2_COV_DELETE_STUB (0x0272) tears down a subscription stub. These are not destructive but are corrected here because the wrong names propagated into earlier notes. [W][S]

9.4 Family overview

  • NODE/STATE (0x0030–0x0035, 0x5301/0x5304, logger-state) — BLN global data get/set, remote node check, node-state get/set, FLN/MEC-expansion topology query. The destructive members are the set-node-state pair.
  • CABINET (0x003E–0x0131, plus report-descriptor) — per-panel identity, lifecycle, and link configuration: timeouts, add/remove/online/offline, boot-monitor/cold/warm start, CABINET_DISPLAY (the firmware/identity block, §10.5), per-link baud setters (MMI/FLN1-3/BLN), P-bus and modem state, memory display/modify/available, cold-start history.
  • BLN/DIAG (0x005B/0x005C) — BLN diagnostic counter display and reset.
  • LICENSE (0x010F–0x0116) — license manager display/add/delete/db-change/message-send (FlexLM-style feature licensing).
  • ROUTING (0x0136, 0x030E, 0x0310) — P2 route, route-object, P-bus poll. Inter-BLN/cross-trunk brokering rides this family (the source of the multi-slot [BLN,dst,BLN,src] routing seen in cross-BLN frames).
  • PBUS (0x0140–0x0143) — peripheral-bus module display, diagnostics reset, line test.
  • POINT (0x0200–0x0309) — the point lifecycle: typed POINT_ADD_* per L-type, the POINT_LOG_* read family, the POINT_CMD_* command family (writes), modify/look/remove/definition, totalizer enable/disable/display, set-prefix, save. Read core is POINT_LOG_VALUE (0x0220); write core is POINT_CMD_VALUE (0x0240).
  • COV (0x0271–0x0275) — change-of-value subscription enable/disable/delete-stub, the annunciate report, and the COV cross-reference display.
  • MONITOR (0x0280–0x0282) — name-based monitor add/remove/start (point watch lists).
  • TREND (0x0290–0x02A9) — trend setup add/delete/enable/disable, data and definition display, multipoint, copy/modify/look, query families, archive setup/upload, and the trend-event subfamily. Data read is TREND_DATA_DISPLAY (0x0295, §10).
  • TOD/TIME (0x0301/0x0302, 0x4500–0x450F) — panel time display/set/software-clock, and the time-of-day scheduling point/command add/remove/enable/disable/display family.
  • MISC (0x0303–0x0311) — message send, quick keys, development toggle, print-error.
  • SESSION (0x0304/0x0305) — LOGON_CEC / LOGOFF_CEC operator session establish/teardown at the panel exec.
  • DATABASE (0x0307/0x0308/0x030B) — whole-database load/save and tape trailer (legacy archive).
  • PPCL (0x030A, 0x4100–0x4138) — Powers Process Control Language program editing: add/edit/remove/enable/disable lines, clear trace, program log/search/query/display (incl. unresolved-reference display), modify/copy/setup-modify/look lines, PDL reset/init/display, and the PROGRAM_* wrapper ops. PPCL is the panel's resident control-logic language layered over P2.
  • COLBAS (0x030D, 0x4A00–0x4A06) — COLBAS scripting: immediate/connect/disconnect/write/abort/upload. The write/abort members are destructive.
  • P1/FLN (0x030F–0x0317, 0x4230–0x4232) — Protocol-I fieldbus operations: P1 poll/route/line-test/reset-counters, FLN scan enable/disable, P1 diagnostics log. P1 is the fieldbus tier below P2 (§see topology).
  • ENVELOPE (0x0316, 0x031B–0x0320) — message-envelope open/close for destination, text, and user lists (alarm/report routing envelopes).
  • LOGGER (0x0325/0x0327) — event-logger and buffer-alarm setup.
  • USER/ACCESS (0x0330–0x0358) — user-account log/display/add/modify/copy/delete/look and db get/replace; access-group log/modify/db get/replace. The operator-credential database.
  • EMS (0x0360–0x0368) — Energy Management System dial enable/disable, db replace/get/display, entry replace, dial-flags / destinations get, print.
  • ENUM (0x0401–0x040E) — enumeration-type and enumeration-element add/delete/modify/display/look/log and db get/replace. Backs LENUM multistate text tables.
  • ALARM (0x0500–0x056A) — alarm setup add/remove/copy/modify/display, alarm point query lists, alarm ack (+ pending query), the alarm-mode subfamily (mode add/copy/list-by-*/definition/modify/query/delete), the category subfamily (0x0540–0x054D: add/remove/descriptor/dial+print enable/disable/db-get/log/nodes-append/nodes-remove/query/default-db-get/replace), and the alarm-message subfamily (0x0560–0x056A: look/enable/disable/delete/copy/add/query/log/modify).
  • CAL/DST (0x0600–0x0615) — calendar date/db add/reset/display/get (holiday spec + other), and daylight-saving year/db add/delete/display/get.
  • LANGUAGE (0x0900–0x0902) — localized string/prompt get and report-data (the multilingual UI string service).
  • UPL (0x0950–0x09C3, 0x4131–0x4133) — the bulk upload engine: DOWNLOAD_ME, and per-object-class UPL_DEL_* / UPL_ADDED_* / UPL_ALL_* triplets for point, alarm-setup, alarm-mode, trend, PPCL, TEC, EQS (zone/cmd-table/mode-sched/override), loop, alarm-message, SSTO (general/start/stop/night), port, partner, UC, TOD point/cmd, LON, command/miscdata report, MSTP-device, and program. This is how a supervisor pulls a panel's whole configured database.
  • DBCHANGE (0x0951–0x09C0, 0x4130, 0x5356) — the panel→supervisor change-notification mirror of the upload classes (point, alarm, trend, PPCL, controller, EQS, SSTO, port, partner, UC, TOD, LON, reports, MSTP-device, program, HOA-map).
  • RACS (0x2824, 0x3800–0x382A) — Remote Access / Communication System partner, port, and system add/copy/delete/disable/display/enable/log/look/modify/statlog (dial-up / WAN inter-site connectivity management).
  • TEAM (0x4000–0x4018) — point-team / application descriptor and member operations: team-desc and member-desc add (analog/digital/enum/LPACI/L2SL), team/member/report log/list, descriptor uploads and db-changes. A "point team" is a logical point whose default member is the logical point value (see §11 point model).
  • TEC (0x4200–0x4225) — Terminal Equipment Controller (FLN device) operations: controller/TEC log/add/copy/modify/remove/look/query/definition, member/report log, and the local/remote init-value log/set/restore/initialize/update family (TEC application init values).
  • UC (0x4241–0x4249) — Unitary Controller add/remove/look/member-log.
  • LON (0x4300–0x4452) — LonWorks integration: device log/add/modify/remove, member/report log, init-value family, diagnostics, send-service-pin, get/set-domain, request-wink, status-clear, agent db export/import, peak-db-clear.
  • EBLN (0x461F–0x464C) — Ethernet-BLN management: field-panel names display, name/IP/TCP-ports/site/BLN/multicast/MAC set/configure, host-table add/remove/display, trunk-settings replace/display, the replication subfamily (notify/pull/pull-more/changes/diag-nodelist), point-location-get, MII configure/display, IP/ports/multicast/MAC display, telnet enable/disable, and EBLN_PING (0x4640) — the session-establish/keepalive opcode (see §9.6 and §10.6).
  • WEB (0x465D, 0x700C) — embedded web-server and ApogeeEdit get-state.
  • BACNET (0x4821–0x4B03) — BACnet-side integration carried over P2: BBMD add/remove/display, object-id log, application-priority and device-name replace/remove/display, COV-table, BACnet trend-log, MSTP-device (BNMSTP) family, and BNEEO. This is the BACnet bridge configuration surface, distinct from the native P2 point model.
  • EQS (0x5000–0x5054) — Equipment Scheduling: zone, command-table, mode-entry, and override add/modify/remove/look/enable/disable; display variants; zone log; and the SSTO (Start-Stop Time Optimization) setup/look/display/reset/enable/disable subfamily.
  • IO (0x5300/0x5303/0x5305) — global and local I/O-module display (incl. MEC expansion bus).
  • FLASH (0x5330–0x5332) — flash-database backup (read), restore (overwrite), clear (erase).
  • HOA (0x5351/0x5354/0x5355) — Hand-Off-Auto map modify/look/add (physical HOA-switch mapping).

9.5 The catalog

The catalog below is generated by joining the vendor AP2_Function_Code enum (the 630 distinct opcode values) with the corpus census (135 wire-observed values, by frame count). Columns: hex opcode, name(s), observed wire count (or -), notes (destructive flag where applicable), and evidence tag ([W] wire-observed, [S] enum-defined). Within each family, rows are sorted by opcode value. The 16 wire values that do not appear here — 0x0000, 0x0002, 0x0453, 0x0510, 0x0C44, 0x4443, 0x4641, 0x4642, 0x4643, 0x4647, 0x464A, 0x464B, 0x464D, 0x464E, 0x464F, 0x4650 — are not real function codes: 0x0000/0x0002 are below the enum's first defined value, 0x0C44/0x4443 are slot-walk misalignments of 0x4640, and the rest are single-shot speculative probe artifacts from active test captures (they resolve to no enum member and should be treated as parser noise, not undocumented operations). [W][S]

Family: NODE/STATE

0xHEX Name Observed (count) Notes Tag
0x0030 AP2_SET_GLOBAL_DATA - [S]
0x0031 AP2_GET_GLOBAL_DATA - [S]
0x0032 AP2_REMOTE_NODE_CHECK - [S]
0x0033 AP2_GET_COMPLETE_NODE_STATE - [S]
0x0034 AP2_SET_NODE_STATE - DESTRUCTIVE (set node state) [S]
0x0035 AP2_SET_COMPLETE_NODE_STATE - DESTRUCTIVE (set complete node state) [S]
0x0326 AP2_GET_LOGGER_STATE - [S]
0x0328 AP2_GET_BUFFERALARM_STATE - [S]
0x5301 AP2_GET_FLN_TOPOLOGY - [S]
0x5304 AP2_GET_MEC_EXPBUS_TOPOLOGY - [S]

Family: CABINET

0xHEX Name Observed (count) Notes Tag
0x003E AP2_CABINET_TIMEOUT_NORMAL - [S]
0x003F AP2_CABINET_TIMEOUT_EXTENDED - [S]
0x0041 AP2_CABINET_ADD - DESTRUCTIVE (add cabinet to node table) [S]
0x0042 AP2_CABINET_REMOVE - DESTRUCTIVE (remove cabinet from node table) [S]
0x0044 AP2_CABINET_MAKE_READY - [S]
0x0046 AP2_CABINET_ONLINE - DESTRUCTIVE (force cabinet online) [S]
0x0047 AP2_CABINET_OFFLINE - DESTRUCTIVE (force cabinet offline) [S]
0x0050 AP2_DISK_LOG 142 [W]
0x0051 AP2_DISK_ADD - [S]
0x0058 AP2_REPORT_PRINTER_LOG - [S]
0x0059 AP2_REPORT_PRINTER_ADD - [S]
0x0100 AP2_DUMMY_CMD / AP2_REV_STRING 9 [W]
0x0108 AP2_CABINET_BOOT_MONITOR - DESTRUCTIVE (reboot to boot monitor) [S]
0x010A AP2_CABINET_COLDSTART 1 DESTRUCTIVE (panel cold start (reboot)) [W]
0x010B AP2_CABINET_WARMSTART - DESTRUCTIVE (panel warm start (reboot)) [S]
0x010C AP2_CABINET_DISPLAY 250 [W]
0x010D AP2_SERVICES_RENDERED - [S]
0x010E AP2_SERVICES_RENDERED_CHANGED - [S]
0x0120 AP2_CABINET_SET_MMI1_BAUDRATE - [S]
0x0121 AP2_CABINET_SET_MMI2_BAUDRATE - [S]
0x0123 AP2_CABINET_SET_FLN1_BAUDRATE - [S]
0x0124 AP2_CABINET_SET_FLN2_BAUDRATE - [S]
0x0125 AP2_CABINET_SET_FLN3_BAUDRATE - [S]
0x0126 AP2_CABINET_SET_BLN_BAUDRATE - [S]
0x0127 AP2_CABINET_SET_PBUS_STATE - [S]
0x0128 AP2_CABINET_SET_BLN_ADDRESS - [S]
0x0129 AP2_CABINET_SET_MODEM_STATE - [S]
0x012A AP2_CABINET_COLDSTART_DISPLAY - [S]
0x012B AP2_CABINET_COLDSTART_CLEAR_HISTORY - [S]
0x012F AP2_CABINET_MEMORY_MODIFY - DESTRUCTIVE (modify panel memory) [S]
0x0130 AP2_CABINET_MEMORY_DISPLAY - [S]
0x0131 AP2_CABINET_MEMORY_AVAILABLE - [S]
0x400E AP2_REPORT_DESC_ADD - [S]
0x4011 AP2_REPORT_DESC_UPLOAD 24 [W]

Family: BLN/DIAG

0xHEX Name Observed (count) Notes Tag
0x005B AP2_BLN_DIAGNOSTICS_DISPLAY - [S]
0x005C AP2_RESET_BLN_DIAGNOSTIC_COUNTERS - [S]

Family: LICENSE

0xHEX Name Observed (count) Notes Tag
0x010F AP2_LICENSE_MANAGER_DISPLAY - [S]
0x0110 AP2_LICENSE_MANAGER_ADD - [S]
0x0111 AP2_LICENSE_MANAGER_DELETE - DESTRUCTIVE (delete license) [S]
0x0112 AP2_LICENSE_MANAGER_DELETE_ALL - DESTRUCTIVE (delete all licenses) [S]
0x0113 AP2_LICENSE_MANAGER_DBCHANGE - [S]
0x0114 AP2_LICENSE_MANAGER_DISPLAY_LICENSE - [S]
0x0116 AP2_LICENSE_MANAGER_MESSAGE_SEND - [S]

Family: ROUTING

0xHEX Name Observed (count) Notes Tag
0x0136 AP2_P2_ROUTE - [S]
0x030E AP2_ROUTE_OBJECT - [S]
0x0310 AP2_PB_POLL - [S]

Family: PBUS

0xHEX Name Observed (count) Notes Tag
0x0140 AP2_PBUS_MODULE_DISPLAY - [S]
0x0142 AP2_PBUS_DIAGS_RESET - [S]
0x0143 AP2_PBUS_LINETEST - [S]

Family: POINT

0xHEX Name Observed (count) Notes Tag
0x0200 AP2_POINT_ADD 6 [W]
0x0201 AP2_POINT_ADD_LDO - [S]
0x0202 AP2_POINT_ADD_LDI - [S]
0x0203 AP2_POINT_ADD_LAO 4 [W]
0x0204 AP2_POINT_ADD_LAI 12 [W]
0x0205 AP2_POINT_ADD_L2SL - [S]
0x0206 AP2_POINT_ADD_L2SP - [S]
0x0207 AP2_POINT_ADD_LFSSL - [S]
0x0208 AP2_POINT_ADD_LFSSP - [S]
0x0209 AP2_POINT_ADD_LOOAL - [S]
0x020A AP2_POINT_ADD_LOOAP - [S]
0x020B AP2_POINT_ADD_LPACI - [S]
0x020C AP2_POINT_ADD_LDAO - [S]
0x020D AP2_POINT_ADD_LFMSSL - [S]
0x020E AP2_POINT_ADD_LFMSSP - [S]
0x020F AP2_POINT_ADD_LENUM - [S]
0x0220 AP2_POINT_LOG_VALUE 8011 [W]
0x0221 AP2_POINT_LOG_ALARM - [S]
0x0222 AP2_POINT_LOG_CTRL_STAT - [S]
0x0223 AP2_POINT_LOG_FAILED - [S]
0x0224 AP2_POINT_LOG_TOTAL - [S]
0x0225 AP2_POINT_LOG_PRIORITY - [S]
0x0226 AP2_POINT_LOG_DISABLED - [S]
0x0227 AP2_POINT_LOG_TYPE - [S]
0x0228 AP2_POINT_LOG_TROUBLE - [S]
0x0229 AP2_POINT_LOG_ANY - [S]
0x022A AP2_POINT_LOG_ODSB - [S]
0x022B AP2_POINT_LOG_PDSB - [S]
0x022C AP2_POINT_LOG_ALARM_CMD - [S]
0x0240 AP2_POINT_CMD_VALUE 32044 DESTRUCTIVE (point command (write value)) [W]
0x0241 AP2_POINT_CMD_PRIORITY 101 DESTRUCTIVE (point command (write priority)) [W]
0x0242 AP2_POINT_CMD_ENABLE - [S]
0x0243 AP2_POINT_CMD_DISABLE - [S]
0x0244 AP2_POINT_CMD_ALARM 29 [W]
0x0245 AP2_POINT_CMD_NORMAL 7 [W]
0x0246 AP2_POINT_CMD_ALARM_ENABLE 3 [W]
0x0247 AP2_POINT_CMD_ALARM_DISABLE 5 [W]
0x0248 AP2_POINT_CMD_INIT_LPACI - [S]
0x0249 AP2_POINT_CMD_LOWLIMIT - [S]
0x024A AP2_POINT_CMD_HIGHLIMIT - [S]
0x024B AP2_POINT_CMD_TOTALIZER - [S]
0x024C AP2_POINT_CMD_INTO_TROUBLE - [S]
0x024D AP2_POINT_CMD_OUTOF_TROUBLE - [S]
0x024E AP2_POINT_CMD_RELEASE - [S]
0x0260 AP2_POINT_MODIFY 4 [W]
0x0261 AP2_POINT_LOOK - [S]
0x0262 AP2_POINT_DEFINITION_DISPLAY - [S]
0x0263 AP2_POINT_REMOVE 10 [W]
0x0264 AP2_POINT_DEFINITION_BYADDR_DISPLAY - [S]
0x0265 AP2_POINT_QUERY_NAME - [S]
0x02E0 AP2_POINT_TOTAL_ENABLE - [S]
0x02E1 AP2_POINT_TOTAL_DISABLE - [S]
0x02E2 AP2_POINT_TOTAL_DISPLAY - [S]
0x0300 AP2_POINT_SET_PREFIX - [S]
0x0309 AP2_POINT_SAVE - [S]

Family: COV

0xHEX Name Observed (count) Notes Tag
0x0271 AP2_COV_ENABLE 9695 [W]
0x0272 AP2_COV_DELETE_STUB 259 [W]
0x0273 AP2_COV_DISABLE 7408 [W]
0x0274 AP2_COV_ANNUNCIATE 121446 [W]
0x0275 AP2_XREF_COV_DISPLAY - [S]

Family: MONITOR

0xHEX Name Observed (count) Notes Tag
0x0280 AP2_MONITOR_ADD_NAME - [S]
0x0281 AP2_MONITOR_REMOVE_NAME - [S]
0x0282 AP2_MONITOR_START - [S]

Family: TREND

0xHEX Name Observed (count) Notes Tag
0x0290 AP2_TREND_SETUP_ADD - [S]
0x0291 AP2_TREND_SETUP_DELETE 16 [W]
0x0292 AP2_TREND_ENABLE - [S]
0x0293 AP2_TREND_DISABLE - [S]
0x0294 AP2_TREND_SETUP_LOG 69 [W]
0x0295 AP2_TREND_DATA_DISPLAY 297 [W]
0x0296 AP2_TREND_DEFINITION_DISPLAY - [S]
0x0297 AP2_TREND_MULTIPOINT_DISPLAY - [S]
0x0298 AP2_TREND_SETUP_MODIFY - [S]
0x0299 AP2_TREND_MODIFY - [S]
0x029A AP2_TREND_SETUP_COPY - [S]
0x029B AP2_TREND_COPY - [S]
0x029C AP2_TREND_LOOK - [S]
0x029D AP2_TREND_QUERY_SINGLE_NAME - [S]
0x029E AP2_TREND_QUERY_NAMES - [S]
0x029F AP2_TREND_QUERY_TRENDS - [S]
0x02A0 AP2_TREND_ARC_SETUP - [S]
0x02A1 AP2_TREND_ARC_DATA_UPLOAD - [S]
0x02A2 AP2_TREND_ARC_UPLOAD_ME - [S]
0x02A5 AP2_TREND_EVENT_SETUP_ADD - [S]
0x02A6 AP2_TREND_EVENT_MODIFY - [S]
0x02A7 AP2_TREND_EVENT_COPY - [S]
0x02A8 AP2_TREND_EVENT_ARC_SETUP 10 [W]
0x02A9 AP2_TREND_EVENT_ARC_ENABLE - [S]

Family: TOD/TIME

0xHEX Name Observed (count) Notes Tag
0x0301 AP2_TIME_DISPLAY / AP2_TIME_SOFTWARE - [S]
0x0302 AP2_TIME_DISPLAY_CLOCK / AP2_TIME_SET 2 [W]
0x4500 AP2_TOD_POINT_ADD 10 [W]
0x4501 AP2_TOD_POINT_REMOVE - [S]
0x4502 AP2_TOD_POINT_ENABLE - [S]
0x4503 AP2_TOD_POINT_DISABLE - [S]
0x4504 AP2_TOD_CMD_ADD - [S]
0x4505 AP2_TOD_CMD_REMOVE - [S]
0x4506 AP2_TOD_CMD_DISABLE - [S]
0x450E AP2_TOD_POINT_DISPLAY - [S]
0x450F AP2_TOD_CMD_DISPLAY - [S]

Family: MISC

0xHEX Name Observed (count) Notes Tag
0x0303 AP2_MESSAGE_SEND / AP2_MESSAGE - [S]
0x0306 AP2_QUICK_KEYS - [S]
0x030C AP2_TOGGLE_DEVELOPMENT - [S]
0x0311 AP2_PRINT_ERROR - [S]

Family: SESSION

0xHEX Name Observed (count) Notes Tag
0x0304 AP2_LOGON_CEC - [S]
0x0305 AP2_LOGOFF_CEC - [S]

Family: DATABASE

0xHEX Name Observed (count) Notes Tag
0x0307 AP2_LOAD_DATABASE - [S]
0x0308 AP2_SAVE_DATABASE - [S]
0x030B AP2_TAPE_TRAILER - [S]

Family: PPCL

0xHEX Name Observed (count) Notes Tag
0x030A AP2_PPCL_SAVE - [S]
0x4100 AP2_PPCL_ADD_LINE 8 [W]
0x4101 AP2_PPCL_EDIT_LINE - [S]
0x4103 AP2_PPCL_REMOVE_LINES 4 [W]
0x4104 AP2_PPCL_ENABLE_LINES 8 [W]
0x4105 AP2_PPCL_DISABLE_LINES 2 [W]
0x4106 AP2_PPCL_CLEAR_TRACE 5 [W]
0x4107 AP2_PPCL_PROGRAM_LOG 8 [W]
0x4108 AP2_PPCL_SEARCH_NAME_TYPE - [S]
0x4109 AP2_PPCL_QUERY_PROGRAM - [S]
0x410A AP2_PPCL_PROGRAM_DISPLAY - [S]
0x410B AP2_PPCL_MODIFY_LINE - [S]
0x410C AP2_PPCL_COPY_LINE - [S]
0x410D AP2_PPCL_SETUP_MODIFY_LINE - [S]
0x410E AP2_PPCL_LOOK_LINES - [S]
0x410F AP2_PPCL_PDL_RESET - [S]
0x4110 AP2_PPCL_PDL_INIT - [S]
0x4111 AP2_PPCL_PDL_DISPLAY - [S]
0x412A AP2_PPCL_PROGRAM_DISPLAY_UNRESOLVED - [S]
0x4134 AP2_PROGRAM_ADD - [S]
0x4135 AP2_PROGRAM_REMOVE - [S]
0x4137 AP2_PROGRAM_LOG - [S]
0x4138 AP2_PROGRAM_MODIFY - [S]

Family: COLBAS

0xHEX Name Observed (count) Notes Tag
0x030D AP2_COLBAS_TEST - [S]
0x4A00 AP2_COLBAS_IMMEDIATE - [S]
0x4A01 AP2_COLBAS_CONNECT - [S]
0x4A02 AP2_COLBAS_DISCONNECT - [S]
0x4A03 AP2_COLBAS_WRITE - DESTRUCTIVE (COLBAS write) [S]
0x4A04 AP2_COLBAS_ABORT - DESTRUCTIVE (COLBAS abort) [S]
0x4A05 AP2_COLBAS_UPLOAD_BEGIN - [S]
0x4A06 AP2_COLBAS_UPLOAD_CONTINUE - [S]

Family: P1/FLN

0xHEX Name Observed (count) Notes Tag
0x030F AP2_P1_POLL - [S]
0x0313 AP2_P1_ROUTE - [S]
0x0314 AP2_P1_LINETEST - [S]
0x0317 AP2_P1_RESET_COUNTERS - [S]
0x4230 AP2_FLN_SCAN_ENABLE - [S]
0x4231 AP2_FLN_SCAN_DISABLE - [S]
0x4232 AP2_P1_DIAGNOSTICS_LOG - [S]

Family: ENVELOPE

0xHEX Name Observed (count) Notes Tag
0x0316 AP2_OPEN_ENVELOPE - [S]
0x031B AP2_ENVELOPE_OPEN_DEST - [S]
0x031C AP2_ENVELOPE_CLOSE_DEST - [S]
0x031D AP2_ENVELOPE_OPEN_TEXT - [S]
0x031E AP2_ENVELOPE_CLOSE_TEXT - [S]
0x031F AP2_ENVELOPE_OPEN_USERS - [S]
0x0320 AP2_ENVELOPE_CLOSE_USERS - [S]

Family: LOGGER

0xHEX Name Observed (count) Notes Tag
0x0325 AP2_SETUP_LOGGER - [S]
0x0327 AP2_SETUP_BUFFERALARM - [S]

Family: USER/ACCESS

0xHEX Name Observed (count) Notes Tag
0x0330 AP2_USER_ACCT_LOG - [S]
0x0331 AP2_USER_ACCT_DISPLAY - [S]
0x0332 AP2_USER_ACCT_ADD - [S]
0x0333 AP2_USER_ACCT_MODIFY - [S]
0x0334 AP2_USER_ACCT_COPY - [S]
0x0335 AP2_USER_ACCT_DELETE - [S]
0x0336 AP2_USER_ACCT_LOOK - [S]
0x0337 AP2_USER_ACCT_DB_GET - [S]
0x0338 AP2_USER_ACCT_DB_REPLACE - [S]
0x0350 AP2_ACCESS_GROUPS_LOG - [S]
0x0353 AP2_ACCESS_GROUPS_MODIFY - [S]
0x0357 AP2_ACCESS_GROUPS_DB_GET - [S]
0x0358 AP2_ACCESS_GROUPS_DB_REPLACE - [S]

Family: EMS

0xHEX Name Observed (count) Notes Tag
0x0360 AP2_EMS_DIAL_ENABLE - [S]
0x0361 AP2_EMS_DIAL_DISABLE - [S]
0x0362 AP2_EMS_DB_REPLACE - [S]
0x0363 AP2_EMS_DB_GET - [S]
0x0364 AP2_EMS_DB_DISPLAY - [S]
0x0365 AP2_EMS_ENTRY_REPLACE - [S]
0x0366 AP2_EMS_DB_GET_DIALFLAGS - [S]
0x0367 AP2_EMS_DB_GET_DESTINATIONS - [S]
0x0368 AP2_EMS_PRINT 3 [W]

Family: ENUM

0xHEX Name Observed (count) Notes Tag
0x0401 AP2_ENUM_TYPE_ADD - [S]
0x0402 AP2_ENUM_TYPE_DELETE - [S]
0x0403 AP2_ENUM_TYPE_DB_DELETE - [S]
0x0404 AP2_ENUM_TYPE_DISPLAY - [S]
0x0405 AP2_ENUM_TYPE_LOOK - [S]
0x0406 AP2_ENUM_TYPE_LOG - [S]
0x0407 AP2_ENUM_ELEMENT_ADD - [S]
0x0408 AP2_ENUM_ELEMENT_DELETE - [S]
0x0409 AP2_ENUM_ELEMENT_MODIFY - [S]
0x040A AP2_ENUM_TYPE_DB_GET 30 [W]
0x040B AP2_ENUM_TYPE_DB_REPLACE - [S]
0x040E AP2_ENUM_TYPE_REPLACE - [S]

Family: ALARM

0xHEX Name Observed (count) Notes Tag
0x0500 AP2_ALARM_SETUP - [S]
0x0501 AP2_ALARM_REMOVE - [S]
0x0502 AP2_ALARM_POINT_QUERY_LIST_EALARMABLE - [S]
0x0503 AP2_ALARM_POINT_QUERY_REC_EALARMABLE - [S]
0x0504 AP2_ALARM_POINT_SETUP_QUERY_LIST - [S]
0x0505 AP2_ALARM_POINT_SETUP_QUERY_RECORD - [S]
0x0506 AP2_ALARM_SETUP_COPY - [S]
0x0507 AP2_ALARM_SETUP_MODIFY - [S]
0x0508 AP2_ALARM_PRINT 75 [W]
0x0509 AP2_ALARM_ACK 11 [W]
0x050A AP2_ALARM_ACK_PENDING_QUERY_LIST - [S]
0x050B AP2_ALARM_SETUP_DISPLAY_BY_MODE - [S]
0x050C AP2_ALARM_SETUP_DISPLAY_BY_CATEGORY - [S]
0x050D AP2_ALARM_SETUP_DISPLAY - [S]
0x0520 AP2_ALARM_MODE_ADD 1 [W]
0x0521 AP2_ALARM_MODE_COPY - [S]
0x0522 AP2_ALARM_MODE_LISTBY_SETPOINT_NAME - [S]
0x0523 AP2_ALARM_MODE_LISTBY_PRIORITY - [S]
0x0524 AP2_ALARM_MODE_LISTBY_SETPOINT_VALUE - [S]
0x0525 AP2_ALARM_MODE_DEFINITION_DISPLAY - [S]
0x0526 AP2_ALARM_MODE_LOOK - [S]
0x0528 AP2_ALARM_MODE_MODIFY - [S]
0x0529 AP2_ALARM_MODE_QUERY_RECORD - [S]
0x052B AP2_ALARM_MODE_DELETE - [S]
0x052C AP2_ALARM_MODE_LISTBY_CATEGORY - [S]
0x052D AP2_ALARM_MODE_LISTBY_MESSAGE - [S]
0x0530 AP2_ALARM_MODE_QUERY_LIST - [S]
0x0540 AP2_CATEGORY_ADD 1 [W]
0x0541 AP2_CATEGORY_REMOVE 14 [W]
0x0542 AP2_CATEGORY_DESCRIPTOR 1 [W]
0x0543 AP2_CATEGORY_ENABLE_DIAL 1 [W]
0x0544 AP2_CATEGORY_ENABLE_PRINT 1 [W]
0x0545 AP2_CATEGORY_DIAL_DISABLE 1 [W]
0x0546 AP2_CATEGORY_PRINT_DISABLE 1 [W]
0x0547 AP2_CATEGORY_DB_GET 1 [W]
0x0548 AP2_CATEGORY_LOG 1 [W]
0x0549 AP2_CATEGORY_NODES_APPEND 10 [W]
0x054A AP2_CATEGORY_NODES_REMOVE 1 [W]
0x054B AP2_CATEGORY_QUERY_LIST 1 [W]
0x054C AP2_CATEGORY_DEFAULT_DB_GET 1 [W]
0x054D AP2_CATEGORY_REPLACE 1 [W]
0x0560 AP2_ALARM_MESSAGE_LOOK 1 [W]
0x0561 AP2_ALARM_MESSAGE_ENABLE 1 [W]
0x0562 AP2_ALARM_MESSAGE_DISABLE 1 [W]
0x0563 AP2_ALARM_MESSAGE_DELETE 1 [W]
0x0564 AP2_ALARM_MESSAGE_COPY 1 [W]
0x0565 AP2_ALARM_MESSAGE_ADD 1 [W]
0x0566 AP2_ALARM_MESSAGE_QUERY_RECORD 1 [W]
0x0567 AP2_ALARM_MESSAGE_LOG 1 [W]
0x0568 AP2_ALARM_MESSAGE_QUERY_LIST 1 [W]
0x056A AP2_ALARM_MESSAGE_MODIFY - [S]

Family: CAL/DST

0xHEX Name Observed (count) Notes Tag
0x0600 AP2_CAL_DATE_ADD - [S]
0x0601 AP2_CAL_DATE_RESET - [S]
0x0602 AP2_CAL_DB_ADD - [S]
0x0603 AP2_CAL_DB_RESET - [S]
0x0604 AP2_CAL_DB_DISPLAY - [S]
0x0605 AP2_CAL_DB_GET_HOL_SPEC - [S]
0x0606 AP2_CAL_DB_GET_OTHER 45 [W]
0x0610 AP2_DST_YEAR_ADD - [S]
0x0611 AP2_DST_YEAR_DELETE - [S]
0x0612 AP2_DST_DB_ADD - [S]
0x0613 AP2_DST_DB_DELETE - [S]
0x0614 AP2_DST_DB_DISPLAY - [S]
0x0615 AP2_DST_DB_GET - [S]

Family: LANGUAGE

0xHEX Name Observed (count) Notes Tag
0x0900 AP2_LANGUAGE_GET_STRING - [S]
0x0901 AP2_LANGUAGE_GET_PROMPT - [S]
0x0902 AP2_LANGUAGE_REPORT_DATA - [S]

Family: UPL

0xHEX Name Observed (count) Notes Tag
0x0950 AP2_DOWNLOAD_ME - [S]
0x0961 AP2_UPL_DEL_POINT 50 [W]
0x0962 AP2_UPL_DEL_ALARM_SETUP - [S]
0x0963 AP2_UPL_DEL_ALARM_MODE - [S]
0x0964 AP2_UPL_DEL_TREND 30 [W]
0x0965 AP2_UPL_DEL_PPCL 20 [W]
0x0966 AP2_UPL_DEL_TEC 20 [W]
0x0967 AP2_UPL_DEL_EQS_ZONE 6 [W]
0x0968 AP2_UPL_DEL_EQS_CMD_TABLE - [S]
0x0969 AP2_UPL_DEL_EQS_MODE_SCHED 38 [W]
0x096A AP2_UPL_DEL_LOOP - [S]
0x096B AP2_UPL_DEL_ALARM_MESSAGE - [S]
0x0971 AP2_UPL_ADDED_POINT 84 [W]
0x0972 AP2_UPL_ADDED_ALARM_SETUP - [S]
0x0973 AP2_UPL_ADDED_ALARM_MODE - [S]
0x0974 AP2_UPL_ADDED_TREND 46 [W]
0x0975 AP2_UPL_ADDED_PPCL 28 [W]
0x0976 AP2_UPL_ADDED_TEC 54 [W]
0x0977 AP2_UPL_ADDED_EQS_ZONE 10 [W]
0x0978 AP2_UPL_ADDED_EQS_CMD_TABLE - [S]
0x0979 AP2_UPL_ADDED_EQS_MODE_SCHED 32 [W]
0x097A AP2_UPL_ADDED_LOOP - [S]
0x097B AP2_UPL_ADDED_ALARM_MESSAGE - [S]
0x097C AP2_UPL_ADDED_SSTO_GENERAL 8 [W]
0x097D AP2_UPL_ADDED_SSTO_START 8 [W]
0x097E AP2_UPL_ADDED_SSTO_STOP 8 [W]
0x097F AP2_UPL_ADDED_SSTO_NIGHT 8 [W]
0x0981 AP2_UPL_ALL_POINT 16183 [W]
0x0982 AP2_UPL_ALL_ALARM_SETUP 65 [W]
0x0983 AP2_UPL_ALL_ALARM_MODE 61 [W]
0x0984 AP2_UPL_ALL_TREND 487 [W]
0x0985 AP2_UPL_ALL_PPCL 3661 [W]
0x0986 AP2_UPL_ALL_TEC 1498 [W]
0x0987 AP2_UPL_ALL_EQS_ZONE 127 [W]
0x0988 AP2_UPL_ALL_EQS_CMD_TABLE 630 [W]
0x0989 AP2_UPL_ALL_EQS_MODE_SCHED 145 [W]
0x098B AP2_UPL_ALL_ALARM_MESSAGE 24 [W]
0x098C AP2_UPL_ALL_SSTO_GENERAL 94 [W]
0x098D AP2_UPL_ALL_SSTO_START 94 [W]
0x098E AP2_UPL_ALL_SSTO_STOP 94 [W]
0x098F AP2_UPL_ALL_SSTO_NIGHT 94 [W]
0x099D AP2_UPL_DEL_PORT - [S]
0x099E AP2_UPL_ADDED_PORT - [S]
0x099F AP2_UPL_ALL_PORT 144 [W]
0x09A1 AP2_UPL_DEL_PARTNER - [S]
0x09A2 AP2_UPL_ADDED_PARTNER - [S]
0x09A3 AP2_UPL_ALL_PARTNER 24 [W]
0x09A5 AP2_UPL_DEL_EQS_OVERRIDE - [S]
0x09A6 AP2_UPL_ADDED_EQS_OVERRIDE - [S]
0x09A7 AP2_UPL_ALL_EQS_OVERRIDE 24 [W]
0x09A9 AP2_UPL_DEL_UC - [S]
0x09AA AP2_UPL_ADDED_UC - [S]
0x09AB AP2_UPL_ALL_UC 24 [W]
0x09B1 AP2_UPL_DEL_TOD_POINT - [S]
0x09B2 AP2_UPL_ADDED_TOD_POINT - [S]
0x09B3 AP2_UPL_ALL_TOD_POINT - [S]
0x09B5 AP2_UPL_DEL_TOD_CMD - [S]
0x09B6 AP2_UPL_ADDED_TOD_CMD - [S]
0x09B7 AP2_UPL_ALL_TOD_CMD - [S]
0x09B9 AP2_UPL_DEL_LON - [S]
0x09BA AP2_UPL_ADDED_LON - [S]
0x09BB AP2_UPL_ALL_LON 24 [W]
0x09BD AP2_UPLD_COMND_REPORT - [S]
0x09BF AP2_UPLD_MISCDATA_REPORT - [S]
0x09C1 AP2_UPL_DEL_MSTP_DEVICE - [S]
0x09C2 AP2_UPL_ADDED_MSTP_DEVICE - [S]
0x09C3 AP2_UPL_ALL_MSTP_DEVICE 2 [W]
0x4131 AP2_UPL_DEL_PROGRAM - [S]
0x4132 AP2_UPL_ADDED_PROGRAM - [S]
0x4133 AP2_UPL_ALL_PROGRAM 24 [W]

Family: DBCHANGE

0xHEX Name Observed (count) Notes Tag
0x0951 AP2_DBCHANGE_POINT 30 [W]
0x0952 AP2_DBCHANGE_ALARM_SETUP - [S]
0x0953 AP2_DBCHANGE_ALARM_MODE - [S]
0x0954 AP2_DBCHANGE_TREND 16 [W]
0x0955 AP2_DBCHANGE_PPCL 16 [W]
0x0956 AP2_DBCHANGE_CONTROLLER 13 [W]
0x0957 AP2_DBCHANGE_EQS_ZONE 4 [W]
0x0958 AP2_DBCHANGE_EQS_CMD_TABLE - [S]
0x0959 AP2_DBCHANGE_EQS_MODE_SCHED 20 [W]
0x095A AP2_DBCHANGE_LOOP - [S]
0x095B AP2_DBCHANGE_ALARM_MESSAGE - [S]
0x095C AP2_DBCHANGE_SSTO_GENERAL 4 [W]
0x095D AP2_DBCHANGE_SSTO_START 4 [W]
0x095E AP2_DBCHANGE_SSTO_STOP 4 [W]
0x095F AP2_DBCHANGE_SSTO_NIGHT 4 [W]
0x099C AP2_DBCHANGE_PORT - [S]
0x09A0 AP2_DBCHANGE_PARTNER - [S]
0x09A4 AP2_DBCHANGE_EQS_OVERRIDE - [S]
0x09A8 AP2_DBCHANGE_UC - [S]
0x09B0 AP2_DBCHANGE_TOD_POINT - [S]
0x09B4 AP2_DBCHANGE_TOD_CMD - [S]
0x09B8 AP2_DBCHANGE_LON - [S]
0x09BC AP2_DBCHANGE_COMMAND_REPORT - [S]
0x09BE AP2_DBCHANGE_MISCDATA_REPORT - [S]
0x09C0 AP2_DBCHANGE_MSTP_DEVICE - [S]
0x4130 AP2_DBCHANGE_PROGRAM - [S]
0x5356 AP2_DBCHANGE_HOA_MAP - [S]

Family: RACS

0xHEX Name Observed (count) Notes Tag
0x2824 AP2_RACS_SYSTEM_DISPLAY - [S]
0x3800 AP2_RACS_PARTNER_ADD - [S]
0x3801 AP2_RACS_PARTNER_COPY - [S]
0x3802 AP2_RACS_PARTNER_DELETE - [S]
0x3803 AP2_RACS_PARTNER_DISABLE - [S]
0x3804 AP2_RACS_PARTNER_DISPLAY - [S]
0x3805 AP2_RACS_PARTNER_ENABLE - [S]
0x3806 AP2_RACS_PARTNER_LOG - [S]
0x3807 AP2_RACS_PARTNER_LOOK - [S]
0x3808 AP2_RACS_PARTNER_MODIFY - [S]
0x3809 AP2_RACS_PARTNER_STATLOG - [S]
0x380A AP2_RACS_PARTNER_STATLOG_RESET - [S]
0x3810 AP2_RACS_PORT_ADD - [S]
0x3811 AP2_RACS_PORT_COPY - [S]
0x3812 AP2_RACS_PORT_DELETE - [S]
0x3813 AP2_RACS_PORT_DISABLE - [S]
0x3814 AP2_RACS_PORT_DISPLAY - [S]
0x3815 AP2_RACS_PORT_ENABLE - [S]
0x3816 AP2_RACS_PORT_LOG - [S]
0x3817 AP2_RACS_PORT_LOOK - [S]
0x3818 AP2_RACS_PORT_MODIFY - [S]
0x3819 AP2_RACS_PORT_STATLOG - [S]
0x381A AP2_RACS_PORT_STATLOG_RESET - [S]
0x3820 AP2_RACS_SYSTEM_ADD - [S]
0x3821 AP2_RACS_SYSTEM_COPY - [S]
0x3822 AP2_RACS_SYSTEM_DELETE - [S]
0x3823 AP2_RACS_SYSTEM_DISABLE - [S]
0x3825 AP2_RACS_SYSTEM_ENABLE - [S]
0x3826 AP2_RACS_SYSTEM_LOG - [S]
0x3827 AP2_RACS_SYSTEM_LOOK - [S]
0x3828 AP2_RACS_SYSTEM_MODIFY - [S]
0x3829 AP2_RACS_SYSTEM_STATLOG - [S]
0x382A AP2_RACS_SYSTEM_STATLOG_RESET - [S]

Family: TEAM

0xHEX Name Observed (count) Notes Tag
0x4000 AP2_TEAM_LOG / AP2_APPLICATION_LOG - [S]
0x4001 AP2_TEAM_DESC_ADD / AP2_APPLICATION_DISPLAY - [S]
0x4002 AP2_MEMBER_DESC_ADD_ANALOG - [S]
0x4003 AP2_MEMBER_DESC_ADD_DIGITAL - [S]
0x4004 AP2_MEMBER_DESC_ADD_ENUM - [S]
0x4005 AP2_MEMBER_DESC_ADD_LPACI - [S]
0x4006 AP2_MEMBER_DESC_ADD_L2SL - [S]
0x400B AP2_TEAM_MEMBER_LOG - [S]
0x400C AP2_TEAM_REPORT_LOG - [S]
0x400D AP2_TEAM_REPORT_LIST - [S]
0x400F AP2_TEAM_DESC_UPLOAD 48 [W]
0x4010 AP2_MEMBER_DESC_UPLOAD 24 [W]
0x4015 AP2_TEAM_DESC_DB_CHANGE - [S]
0x4016 AP2_TEAM_MEMBER_DB_CHANGE - [S]
0x4017 AP2_TEAM_DESC_UPLOAD_ADDED - [S]
0x4018 AP2_TEAM_MEMBER_UPLOAD_ADDED - [S]

Family: TEC

0xHEX Name Observed (count) Notes Tag
0x4200 AP2_CONTROLLER_LOG / AP2_TEC_LOG 366 [W]
0x4201 AP2_TEC_ADD - [S]
0x4202 AP2_TEC_COPY - [S]
0x4203 AP2_TEC_MODIFY / AP2_CONTROLLER_MODIFY - [S]
0x4204 AP2_CONTROLLER_REMOVE / AP2_TEC_REMOVE - [S]
0x4205 AP2_TEC_LOOK / AP2_CONTROLLER_LOOK - [S]
0x4206 AP2_TEC_QUERY_RECORD / AP2_CONTROLLER_QUERY - [S]
0x4207 AP2_TEC_QUERY_LIST - [S]
0x4208 AP2_TEC_DEFINITION - [S]
0x4210 AP2_TEC_MEMBER_LOG - [S]
0x4211 AP2_TEC_REPORT_LOG - [S]
0x4212 AP2_TEC_REPORT_QUERY_LIST - [S]
0x4220 AP2_TEC_LOCAL_INIT_VALUE_LOG 1 [W]
0x4221 AP2_TEC_REMOTE_INIT_VALUE_LOG 1776 [W]
0x4222 AP2_TEC_SET_INIT_VALUE 74 [W]
0x4223 AP2_TEC_RESTORE_INIT_VALUE - [S]
0x4224 AP2_TEC_INITIALIZE 3 [W]
0x4225 AP2_TEC_UPDATE_LOCAL_INIT_VALUES 6 [W]

Family: UC

0xHEX Name Observed (count) Notes Tag
0x4241 AP2_UC_ADD - [S]
0x4244 AP2_UC_REMOVE - [S]
0x4245 AP2_UC_LOOK - [S]
0x4249 AP2_UC_MEMBER_LOG - [S]

Family: LON

0xHEX Name Observed (count) Notes Tag
0x4300 AP2_LON_LOG - [S]
0x4301 AP2_LON_ADD - [S]
0x4303 AP2_LON_MODIFY - [S]
0x4304 AP2_LON_REMOVE - [S]
0x4310 AP2_LON_MEMBER_LOG - [S]
0x4311 AP2_LON_REPORT_LOG - [S]
0x4320 AP2_LON_LOCAL_INIT_VALUE_LOG - [S]
0x4321 AP2_LON_REMOTE_INIT_VALUE_LOG - [S]
0x4322 AP2_LON_SET_INIT_VALUE - [S]
0x4323 AP2_LON_RESTORE_INIT_VALUE - [S]
0x4324 AP2_LON_INITIALIZE - [S]
0x4325 AP2_LON_UPDATE_LOCAL_INIT_VALUES - [S]
0x4332 AP2_LON_DIAGNOSTICS_LOG - [S]
0x4401 AP2_LON_SEND_SERVICE_PIN - [S]
0x4402 AP2_LON_GET_DOMAIN - [S]
0x4403 AP2_LON_SET_DOMAIN - [S]
0x4404 AP2_LON_REQUEST_WINK - [S]
0x440B AP2_LON_STATUS_CLEAR - [S]
0x4450 AP2_LON_PKCMSAGTSRVDBEXPORT - [S]
0x4451 AP2_LON_PKCMSAGTSRVDBIMPORT - [S]
0x4452 AP2_LON_PEAK_DB_CLEAR - [S]

Family: EBLN

0xHEX Name Observed (count) Notes Tag
0x461F AP2_EBLN_FP_NAMES_DISPLAY - [S]
0x4620 AP2_EBLN_FP_NAME_SET - DESTRUCTIVE (rename field panel) [S]
0x4621 AP2_EBLN_FP_IP_CONFIGURE - DESTRUCTIVE (reconfigure panel IP) [S]
0x4622 AP2_EBLN_FP_TCP_PORTS_CONFIGURE - [S]
0x4628 AP2_EBLN_TRUNK_SETTINGS_REPLACE - [S]
0x4629 AP2_EBLN_TRUNK_SETTINGS_DISPLAY - [S]
0x462A AP2_EBLN_FP_SITE_NAME_SET - DESTRUCTIVE (set site name) [S]
0x462B AP2_EBLN_FP_BLN_NAME_SET - DESTRUCTIVE (set BLN name) [S]
0x462C AP2_EBLN_FP_MULTICAST_CONFIGURE - DESTRUCTIVE (reconfigure multicast) [S]
0x462D AP2_EBLN_HOSTTABLE_ENTRY_ADD - DESTRUCTIVE (add host-table entry) [S]
0x462E AP2_EBLN_HOSTTABLE_ENTRY_REMOVE - DESTRUCTIVE (remove host-table entry) [S]
0x462F AP2_EBLN_HOSTTABLE_DISPLAY - [S]
0x4633 AP2_EBLN_REPL_NOTIFY 23 [W]
0x4634 AP2_EBLN_REPL_PULL 3938 [W]
0x4635 AP2_EBLN_REPL_PULL_MORE 130 [W]
0x4636 AP2_EBLN_REPL_CHANGES 179 [W]
0x4637 AP2_EBLN_POINT_LOCATION_GET - [S]
0x4638 AP2_EBLN_MAC_ADDRESS_SET - DESTRUCTIVE (set MAC address) [S]
0x4639 AP2_EBLN_MII_CONFIGURE - [S]
0x463A AP2_EBLN_MII_DISPLAY - [S]
0x463B AP2_EBLN_IP_DISPLAY - [S]
0x463C AP2_EBLN_PORTS_DISPLAY - [S]
0x463D AP2_EBLN_MULTICAST_DISPLAY - [S]
0x463E AP2_EBLN_MAC_ADDRESS_DISPLAY - [S]
0x4640 AP2_EBLN_PING 51155 [W]
0x4644 AP2_EBLN_TELNET_ENABLE 1 DESTRUCTIVE (enable telnet) [W]
0x4645 AP2_EBLN_TELNET_DISABLE - DESTRUCTIVE (disable telnet) [S]
0x464C AP2_EBLN_REPL_DIAG_NODELIST 1 [W]

Family: WEB

0xHEX Name Observed (count) Notes Tag
0x465D AP2_WEBSERVER_GET_STATE - [S]
0x700C AP2_WS_APOGEEEDIT_GET_STATE - [S]

Family: BACNET

0xHEX Name Observed (count) Notes Tag
0x4821 AP2_BAC_DBCHANGE_BBMD - [S]
0x4822 AP2_BAC_UPL_DEL_BBMD - [S]
0x4823 AP2_BAC_UPL_ADDED_BBMD - [S]
0x4824 AP2_BAC_UPL_ALL_BBMD - [S]
0x4825 AP2_BAC_BBMD_ADD - [S]
0x4826 AP2_BAC_BBMD_REMOVE - [S]
0x4827 AP2_BAC_BBMD_DISPLAY - [S]
0x4828 AP2_BAC_BBMD_REMOVE_ALL - [S]
0x4829 AP2_BAC_OBJECT_ID_LOG - [S]
0x482A AP2_BAC_APPLICATION_PRIORITY_REPLACE - [S]
0x482B AP2_BAC_APPLICATION_PRIORITY_REMOVE - [S]
0x482C AP2_BAC_APPLICATION_PRIORITY_DISPLAY - [S]
0x482E AP2_BAC_DEVICE_NAME_REPLACE - [S]
0x482F AP2_BAC_DEVICE_NAME_REMOVE - [S]
0x4830 AP2_BAC_DBCHANGE_COVTAB - [S]
0x4831 AP2_BAC_UPL_DEL_COVTAB - [S]
0x4832 AP2_BAC_UPL_ADDED_COVTAB - [S]
0x4833 AP2_BAC_UPL_ALL_COVTAB - [S]
0x4834 AP2_BAC_COVTAB_ADD - [S]
0x4835 AP2_BAC_COVTAB_REMOVE - [S]
0x4837 AP2_BAC_COVTAB_REMOVE_ALL - [S]
0x4838 AP2_BAC_TREND_LOG_ADD - [S]
0x4839 AP2_BAC_TREND_LOG_DELETE - [S]
0x483A AP2_BAC_TREND_LOG_MODIFY - [S]
0x4842 AP2_BAC_TREND_LOG_LOG - [S]
0x4843 AP2_BAC_TREND_DBCHANGE - [S]
0x4844 AP2_BAC_TREND_UPL_DELETED - [S]
0x4845 AP2_BAC_TREND_UPL_ADDED - [S]
0x4846 AP2_BAC_TREND_UPL_ALL - [S]
0x4877 AP2_BAC_DBCHANGE - [S]
0x4878 AP2_BAC_UPLOAD_ADDED - [S]
0x4879 AP2_BAC_UPLOAD_DELETED - [S]
0x4960 AP2_BACNET_SET_MSTP - [S]
0x4961 AP2_BACNET_SET_FLN_TYPE - [S]
0x4963 AP2_BNMSTP_ADD - [S]
0x4965 AP2_BNMSTP_MODIFY - [S]
0x4966 AP2_BNMSTP_REMOVE - [S]
0x4967 AP2_BNMSTP_LOOK - [S]
0x496B AP2_BNMSTP_MEMBER_LOG - [S]
0x496E AP2_BNMSTP_LOCAL_INIT_VALUE_LOG - [S]
0x4970 AP2_BNMSTP_SET_INIT_VALUE - [S]
0x4971 AP2_BNMSTP_RESTORE_INIT_VALUE - [S]
0x4972 AP2_BNMSTP_INITIALIZE - [S]
0x4973 AP2_BNMSTP_UPDATE_LOCAL_INIT_VALUES - [S]
0x4B01 AP2_BNEEO_ADD - [S]
0x4B02 AP2_BNEEO_REMOVE - [S]
0x4B03 AP2_BNEEO_LOOK - [S]

Family: EQS

0xHEX Name Observed (count) Notes Tag
0x5000 AP2_EQS_ZONE_ADD 4 [W]
0x5001 AP2_EQS_ZONE_REMOVE 4 [W]
0x5002 AP2_EQS_ZONE_MODIFY - [S]
0x5003 AP2_EQS_ZONE_LOOK 84 [W]
0x5004 AP2_EQS_ZONE_ENABLE - [S]
0x5005 AP2_EQS_ZONE_DISABLE - [S]
0x5018 AP2_EQS_CMD_TABLE_ENTRY_ADD - [S]
0x5019 AP2_EQS_CMD_TABLE_ENTRY_MODIFY - [S]
0x501A AP2_EQS_CMD_TABLE_ENTRY_REMOVE - [S]
0x501B AP2_EQS_CMD_TABLE_ENTRY_LOOK - [S]
0x5020 AP2_EQS_MODE_ENTRY_ADD 12 [W]
0x5021 AP2_EQS_MODE_ENTRY_MODIFY - [S]
0x5022 AP2_EQS_MODE_ENTRY_REMOVE 10 [W]
0x5023 AP2_EQS_MODE_ENTRY_LOOK - [S]
0x5024 AP2_EQS_MODE_ENTRY_ENABLE - [S]
0x5025 AP2_EQS_MODE_ENTRY_DISABLE - [S]
0x5028 AP2_EQS_OVERRIDE_ADD - [S]
0x5029 AP2_EQS_OVERRIDE_MODIFY - [S]
0x502A AP2_EQS_OVERRIDE_REMOVE - [S]
0x502B AP2_EQS_OVERRIDE_LOOK - [S]
0x5035 AP2_EQS_DISPLAY_ZONE - [S]
0x5036 AP2_EQS_DISPLAY_MODE_ENTRY - [S]
0x5037 AP2_EQS_DISPLAY_CMD_TABLE - [S]
0x5038 AP2_EQS_ZONE_LOG 30 [W]
0x5039 AP2_EQS_DISPLAY_OVERRIDES - [S]
0x503A AP2_EQS_SSTO_SETUP_GENERAL 2 [W]
0x503B AP2_EQS_SSTO_SETUP_START 2 [W]
0x503C AP2_EQS_SSTO_SETUP_STOP 2 [W]
0x503D AP2_EQS_SSTO_SETUP_NIGHT 2 [W]
0x503E AP2_EQS_SSTO_LOOK_GENERAL - [S]
0x503F AP2_EQS_SSTO_LOOK_START - [S]
0x5040 AP2_EQS_SSTO_LOOK_STOP - [S]
0x5041 AP2_EQS_SSTO_LOOK_NIGHT - [S]
0x5042 AP2_EQS_SSTO_RESET - [S]
0x5043 AP2_EQS_SSTO_ENABLE - [S]
0x5044 AP2_EQS_SSTO_DISABLE - [S]
0x5050 AP2_EQS_SSTO_DISPLAY_GENERAL - [S]
0x5051 AP2_EQS_SSTO_DISPLAY_START - [S]
0x5052 AP2_EQS_SSTO_DISPLAY_STOP - [S]
0x5053 AP2_EQS_SSTO_DISPLAY_NIGHT - [S]
0x5054 AP2_EQS_MEMBER_LOG - [S]

Family: IO

0xHEX Name Observed (count) Notes Tag
0x5300 AP2_GLOBAL_IO_MODULE_DISPLAY - [S]
0x5303 AP2_GLOBAL_IO_MODULE_DISPLAY_MEC_EXPBUS - [S]
0x5305 AP2_LOCAL_IO_MODULE_DISPLAY - [S]

Family: FLASH

0xHEX Name Observed (count) Notes Tag
0x5330 AP2_BACKUP_FLASH_DBASE - flash-database read/export (non-destructive) [S]
0x5331 AP2_RESTORE_FLASH_DBASE - DESTRUCTIVE (restore flash database (overwrite)) [S]
0x5332 AP2_CLEAR_FLASH_DBASE - DESTRUCTIVE (clear flash database (erase)) [S]

Family: HOA

0xHEX Name Observed (count) Notes Tag
0x5351 AP2_HOA_MAP_MODIFY - [S]
0x5354 AP2_HOA_MAP_LOOK 31 [W]
0x5355 AP2_HOA_MAP_ADD - [S]

9.6 The session/keepalive opcode 0x4640 (EBLN_PING)

AP2_EBLN_PING (0x4640) is the most cross-cutting opcode in the corpus: it is the only opcode that appears under every session/second-channel and data message class — 0x29, 0x2A, 0x2E, 0x2F, 0x33, and 0x34 (see §9.7) — and it serves three roles. As session establish it is the IdentifyBlock handshake carrying the peer's identity slots; as keepalive it recurs on a ~10-second cadence (the EPing / Ethernet-Ping liveness heartbeat); and its request/response body is the eBLN_Node block (§10.6). It is also the opcode whose malformed slot-walk produces the 0x0C44/0x4443 phantoms in §9.5. Its response length is variable and deterministic — 35 + node-name length (40/41/45/47 B observed for 5/6/10/12-char names) — so a parser must treat 0x4640 response-length as variable; every request in the corpus drew a reply (§9.7). [W][S]

9.7 Wire behavior: message classes, directions, error tails

The corpus distribution grounds the catalog in observed behavior:

  • Message classes (msg_type low byte). Corpus distribution: 0x33 legacy data dialect (433,425 frames), 0x34 modern data dialect (43,668), 0x2E legacy 2nd-channel / announce + DB-sync (43,780), 0x2F modern 2nd-channel (7,322), 0x29 peer maintenance carrier (2,200), 0x2A peer COV-subscribe carrier (296). These six are the only valid message classes (the legacy/modern pairs of §6.2/§6.6); any other low-byte value is parser noise from a desynced stream. Most high-volume operations occur in both 0x33 and 0x34; certain single-shot and legacy-supervisor operations occur only in 0x33. [W]
  • Direction byte. 0x00 request/push (262,142), 0x01 success response (261,466), 0x05 error response (7,103). A success response carries the operation's payload after the routing slots; an error response carries exactly a 2-byte error code and nothing else. [W]
  • Error tail values (the 2-byte code on a 0x05 response). 0x0003 not-found / unrecognized-opcode (6,860×), 0x00AC not-supported (163×, the not-supported error class), 0x0E15 not-commandable (40×), 0x0002 out-of-scope (30×), 0x0E11 already-exists (6×), 0x0E12 (3×), 0x0009 (1×). An opcode the panel does not implement returns 0x05 ... 00 03; this is how the defined-but-unimplemented-on-this-firmware opcodes announce themselves on the wire. [W]
  • Per-opcode response shapes. Response lengths vary by opcode and by the addressed object; representative shapes from the census (opcode → success-response sizes): 0x010C → 230/231 B (firmware/identity block, §10.5); 0x0220 (read) → 126/138 B or err 0003 when the point is absent; 0x0271 (COV enable) → 96/108 B; 0x0274 (annunciate) → 0 B (acknowledged, no payload); 0x4640 → 40/41/45/47 B (= 35 + node-name length); 0x0981 (enumerate points) → 88/115/118 B. The "0 B success" pattern (direction 0x01, empty body) is a bare acknowledgement used by push/command/replication opcodes. [W]

10. Message Body Structures

10.1 Encoding convention

The bytes after the opcode are the operation's ASDU (Application Service Data Unit; the service model is ISO/OSI-style Request → Indication on the receiver, Response → Confirm back). The complete set of bodies is defined as 1,144 distinct ordered-field ASDU structures in the protocol's type system (spanning the request and response forms — on the order of 614 *_Request and 638 *_Response named forms, which share common sub-types, so the named forms outnumber the distinct structures) — each an ordered list of typed fields. This section documents the encoding rules, then the ~30 most important structures in field tables; the full set is enumerable from the structure library, so any opcode in §9 has a known body shape even where not detailed here. [S]

Field types in the structure tables map to wire encodings as follows. These are the [S] definitional types; the [W] encodings (TLV framing, f32, scope tag) are established from captures (cross-reference §8 of the framing sections):

ASDU type Wire encoding Notes
TEXT_ string TLV 01 00 <len> <bytes> 1-byte length, ASCII content, not NUL-terminated inside the TLV; empty = 01 00 00. [W]
UNSIGNED8 / UNSIGNED_8 1 byte [S]
UNSIGNED16 / UNSIGNED_16 2 bytes, big-endian counts/markers/error codes are u16 BE. [W]
UNSIGNED32 4 bytes, big-endian sequence numbers, identifiers. [W]
SHORT_ / LONG_ 2 / 4 bytes, big-endian, signed [S]
FLOAT_ 4 bytes IEEE-754, big-endian analog values; raw engineering units, no scaling on the wire. [W]
BOOLEAN_ 1 byte (0/1) [S]
NULL_ 0 bytes a present-but-empty CHOICE alternative or placeholder. [S]
DATE_TIME / DATE_ / TIME_ packed date/time block [S]
<Name> (capitalized) nested sub-structure expanded inline; shared sub-types defined once in §10.2. [S]
<Name>[] repeating array preceded by a nrOf<name> u16 BE count field. [S]
<Enum> 1–4 byte integer value space per the enum tables (priorities, point types, cov masks, node states, etc.). [S]

Two structural conventions recur. CHOICE / tagged union: several structures begin with a tag_ : UNSIGNED_8 followed by one alternative per possible type (e.g. All_points, Alarm_object, Physical_address_Lenum); the tag selects which one alternative is actually present on the wire. Counted array: a nrOf<x> : UNSIGNED_16 immediately precedes its <x> : <T>[] array. [S]

10.2 Shared sub-types

These sub-types appear inside many request/response bodies and are defined once here. Field order is the wire order.

User_profile — the requester's credential/priority context, prepended to most addressable requests. [S]

# Field Type Meaning
1 user_logon TEXT_ operator logon name
2 point_priority Point_priority (enum) command priority of this requester
3 access_class Access_class access-rights class

Name_search — the addressing/search key in read and command requests (selects the target point/object by name + suffix within a namespace). [S]

# Field Type Meaning
1 name_space Name_space (enum) system / user / any (0 / 1 / 0xFFFF)
2 name_pattern TEXT_ point/object base name (may be a pattern)
3 suffix_pattern TEXT_ name suffix (the Base + Suffix split; see naming §)
4 last_name_space Name_space (enum) resume key for paged search
5 last_name TEXT_ resume key
6 last_suffix TEXT_ resume key

Name_response — how a returned object names itself. [S]

# Field Type Meaning
1 name_space Name_space (enum) namespace of the returned name
2 name TEXT_ base name
3 suffix TEXT_ suffix

All_points — the polymorphic point-value body (CHOICE). The tag_ byte selects exactly one L-type alternative; the alternative's layout is the per-type value/quality block. [S]

# Field Type Meaning
1 tag_ UNSIGNED_8 selects the point type (maps to Point_type enum: 1=ldi … 24=ppcl_lai)
2..17 ldi / ldo / lai / lao / l2sl / looap / lpaci / l2sp / looal / lfssl / lfssp / ldao / lenum / lfmsl / lfmsp / ppcl_lai per-type value block exactly one present, per tag_; the analog types carry an FLOAT_ value, digital/state types carry state + quality

Physical_address_Lenum — optional LENUM physical address (CHOICE: not_present : NULL_ or present : present_), trailing many point responses. [S]

Point_priority (enum, 1 byte on the wire) — the command-priority ladder shared by writes and the scope tag: none=0, tec_ovrd=1, pdl=5, host_2=10, host_3=15, host_4=20, host_5=25, host_6=30, emer=32 (0x20), smoke=34 (0x22), oper=35 (0x23), plus the BACnet 16-level band 101–116. The User_command_priority enum is the same ladder truncated to the operator-reachable subset. [S]

Point_value — the typed value carried in commands and trend samples; its concrete encoding is the per-type block selected by point type (analog = FLOAT_ BE; digital/enum = small integer state). [S]

Cov_mask (enum) — the COV condition bitmask: data=0, failure=1, alarm=2, service=3, priority=4, TCU=5, temp_all=6, proof_on=7. Selects which change classes a subscription annunciates. [S]

10.3 Read / command / COV core

AP2_POINT_LOG_VALUE request (0x0220) — present-value read. [S][W]

# Field Type Meaning
1 user_profile User_profile requester context
2 name_search Name_search target point selector

AP2_POINT_LOG_VALUE response (0x0220) — present-value reply (this same response shape backs UPL_ALL_POINT, POINT_LOG_ALARM, and COV_ENABLE responses). [S][W]

# Field Type Meaning
1 name_response Name_response the point's own name
2 point All_points typed value + quality block (analog → f32 BE)
3 lenum_address Physical_address_Lenum optional LENUM address
4 point_extension2 Point_extension2 optional extension block

On the wire the value block sits inside the All_points alternative: an f32 BE value preceded by a 4-byte quality sentinel (3F FF FF Fx good, 00 00 00 00 otherwise) and then a 3-byte group 00 <comm-status> <point-subtype> — the comm-status byte is 0 (live) / 1 (stale), and the trailing byte is a 1-byte point sub-type code (observed 0x02/0x04/0x06, and 0x01). [W]

AP2_POINT_CMD_VALUE request (0x0240) — point command / write (DESTRUCTIVE). [S][W]

# Field Type Meaning
1 user_profile User_profile requester (carries command priority)
2 name_search Name_search target point
3 point_value Point_value the value to command (analog f32 BE; digital/enum state)
4 point_priority Point_priority (enum) priority at which to command

AP2_POINT_CMD_PRIORITY request (0x0241) — command at a priority without changing value / release a priority level (DESTRUCTIVE). [S][W]

# Field Type Meaning
1 user_profile User_profile requester
2 name_search Name_search target point
3 point_priority Point_priority (enum) priority to assert/release

AP2_COV_ENABLE request (0x0271) / AP2_COV_DISABLE request (0x0273) — subscribe / unsubscribe to change-of-value on a point. Identical body. [S][W]

# Field Type Meaning
1 name_response Name_response target point name
2 cov_mask Cov_mask (enum) which change classes to (un)subscribe

The COV-enable response returns the point's All_points value block (same shape as the read response). On the wire the enable/disable pair is also distinguishable by a 2-byte trailer (00 FF enable vs 00 00 disable). [W]

AP2_COV_ANNUNCIATE request (0x0274) — the panel-originated COV report (highest-volume opcode, 121,446 frames; pushed on direction == 0x00, acknowledged with a 0-byte success). [S][W]

# Field Type Meaning
1 nrOfannunciate_request UNSIGNED_16 count of annunciate records that follow
2 annunciate_request Annunciate_request[] the records (see below)

Annunciate_request (one COV record) — the per-point change payload. [S]

# Field Type Meaning
1 name_response Name_response the changed point
2 value FLOAT_ new value (f32 BE)
3 point_priority Point_priority (enum) active command priority
4 control_status Control_status (enum) remote / tool-override / by-priority / manual / etc.
5 out_of_service BOOLEAN_ point OOS
6 failed BOOLEAN_ comm-failed
7 proof_on BOOLEAN_ proof active
8 operator_disabled BOOLEAN_ operator-disabled
9 program_disabled BOOLEAN_ PPCL-disabled
10 commanded_to_alarm BOOLEAN_ forced alarm
11 alarm_state Alarm_state (enum) normal / alarm / high / low / trouble
12 alarm_priority Alarm_priority (enum) priority_0..6

The boolean run (fields 5–10) is the wire source of the point operating-state taxonomy (Normal / Failed / Out-of-Service / Proofing / Alarm-by-Command / Operator-Disabled / Program-Disabled); these are the semantic meaning of the COV condition bits. [S][I]

10.4 The point model (Point_base)

Point_base is the rich point-definition structure returned by point-look/definition reads; it shows the full point model the protocol carries. [S]

# Field Type Meaning
1 point_type Point_type (enum) L-type (ldi/ldo/lai/lao/l2sl/looap/lpaci/…/lenum)
2 nrOfnames UNSIGNED_16 count of names
3 names Name_response[] the point's name(s)
4 point_descriptor Point_descriptor free-text descriptor
5 access_class Access_class access-rights class
6 out_of_service BOOLEAN_ OOS flag
7 failed BOOLEAN_ comm-failed flag
8 control_status Control_status (enum) control source
9 point_value Point_value current value
10 point_priority Point_priority (enum) active priority
11 point_totalizer Point_totalizer accumulator (LPACI)
12 alarm_object Alarm_object alarm configuration (CHOICE by alarm-object type)

The point types are the L-type vocabulary: ldi=Digital Input, ldo=Digital Output, lai=Analog Input, lao=Analog Output, l2sl/l2sp=2-State Latched/Pulsed, looal/looap=On/Off/Auto Latched/Pulsed, lfssl/lfssp=Fast-Slow-Speed Latched/Pulsed, lpaci=Pulse-Accumulator/Counter Input, ldao=Dual Analog Output, lenum=Enumerated, lfmssl/lfmssp=Fast-Multi-Speed variants, ppcl_lai=PPCL-resident analog. Analog points carry per-point Slope + Intercept for engineering-unit conversion, applied by the client from the point table — not carried on each value frame. [S][I]

10.5 CABINET_DISPLAY — firmware / identity block (0x010C)

AP2_CABINET_DISPLAY response (0x010C) is the panel's full firmware-and-identity block — the single richest unauthenticated read in the protocol (230/231 B on the wire; observed returning a firmware string such as PME1252 / PXME V<rev> APOGEE / <link date>). Field order is the wire order, and the layout below is now wire-confirmed — the on-wire field order matches the struct in this section exactly. [W]

# Field Type Meaning
1 revstring TEXT_ firmware revision string
2 firmwaretype TEXT_ firmware type / platform (e.g. PXME)
3 linktime TEXT_ firmware link/build timestamp
4 firmware_checksum UNSIGNED16 firmware checksum
5–35 config_byte1 … config_byte31 UNSIGNED8 (byte8/byte9 BOOLEAN_) 31 configuration bytes; byte10=alarm_config, byte11=report_config (Cabinet_report_config enum)
36 config_checksum UNSIGNED8 config-block checksum
37 battery_state UNSIGNED16 backup-battery state
38 node_name TEXT_ this panel's node name
39 site_name TEXT_ site name
40 bln_name TEXT_ BLN name
41 ip_addr_settings IP_Address_Settings DHCP/DNS flags, app-port list, multicast list, SMTP, telnet-enabled, dynamic-DNS
42 configured_mii_data MII_Data configured link speed/duplex
43 negotiated_mii_data MII_Data negotiated link speed/duplex
44 link_status Link_Status (enum) Up / Down
45 configured_mac_address Mac_Address configured MAC
46 hardware_mac_address Mac_Address burned-in MAC
47 bacnetSettings BACnet_Settings BACnet/IP config
48 BACnetMSTPLANSettings BACnet_MSTP_LAN_Settings MSTP LAN config
49 bacnet_ip_aln_choice BOOLEAN_ IP vs MSTP ALN selection
50 ip_network_number UNSIGNED16 BACnet IP network number
51 BACnetMSTPALNSettings BACnet_MSTP_ALN_Settings MSTP ALN config

Sub-types referenced: IP_Address_Settings { dhcp, dns, nrOfapp_ports:u16, app_ports:u16[], nrOfmulticast:u16, multicast:Multicast_Entry[], smtp_server, telnet_enabled:bool, dynamic_dns }; MII_Data { mii_speed:Mii_Speed, mii_duplex:Mii_Duplex }; Mac_Address { nrOfaddress:u16, address:u8[] }. This single response enumerates firmware revision, build time, node/site/BLN identity, IP/MII/MAC, and BACnet settings — the entire device fingerprint. [S]

Annotated wire example (sanitized). A 230-byte 0x010C response decodes leading TLVs in the field order above: [W]

01 00 08 "PME1252 "                       -> revstring     (field 1, TEXT_ len 8)
01 00 13 "PXME V2.8.10 APOGEE"            -> firmwaretype  (field 2, TEXT_ len 0x13)
01 00 14 "Oct 28 2013 12:31:01"           -> linktime      (field 3, TEXT_ len 0x14)
<firmware_checksum u16> <31 config bytes> <config_checksum> <battery_state u16>
                                          -> fields 4–37 (checksum / config block)
01 00 05 <node-name>                      -> node_name     (field 38, TEXT_)
01 00 03 <site>                           -> site_name     (field 39, TEXT_)
01 00 07 <BLN-name>                       -> bln_name      (field 40, TEXT_)
<ip_addr_settings> <configured/negotiated MII> <link_status>
<configured/hardware MAC> <BACnet settings>
                                          -> fields 41–51 (IP / MII / MAC / BACnet)

The leading three TLVs (revstring / firmwaretype / linktime) and the trailing node/site/BLN TLVs followed by the IP/MII/MAC/BACnet settings match the struct field order one-for-one. [W]

Fleet fingerprinting — and it predicts the wire dialect. The banner's first three TLVs are a firmware revision string (e.g. PME1252, PME1300 — firmware-build identifiers, not hardware model numbers), a hardware-platform + firmware-version string (e.g. PXME V2.8.10 APOGEE, where PXME is the modular-cabinet platform code and V2.8.10 the firmware version), and the build date. Because the request body is empty and the response carries all of this plus identity in one round-trip, 0x010C is the canonical way to inventory a BLN: issue it to each known node and read back the firmware generation. Across a nine-panel fleet — all the same hardware platform — the split was purely by firmware: eight on a 2013-era revision (V2.8.10) and one on a 2019-era revision (V2.8.18). That split is exactly the legacy/modern message-class split (§6.2/§6.6): the older-firmware panels speak legacy 0x33/0x2E, the newer-firmware panel speaks modern 0x34/0x2F. So a client can read a panel's firmware generation from 0x010C and select the correct dialect before sending any data-class frame, rather than blind-probing {0x33, 0x34}. The build date / version string is the reliable generation discriminator. [W]

Reading the hardware model (informative). These modular panels carry a printed model such as PXC100-PE96.A (Siemens APOGEE part number; per the public PXC Modular Series datasheet, "PXC Modular, P2, TX-I/O, 96-node"). The part number decodes as: PXC = Programmable Controller (Modular class) · 100/00 = with / without the TX-I/O island bus · PE vs E = protocol firmware ordered on the box: PE = P2/APOGEE, E = BACnet · 96 = FLN-node capacity · .A = hardware revision. The same physical platform thus ships as either a P2 controller or a BACnet controller depending on the firmware it is ordered/loaded with — the protocol is a firmware property, not a different chassis. The CABINET_DISPLAY PXME … platform string is the firmware's internal name for this modular hardware. So 0x010C gives you both the firmware generation (legacy vs modern P2) and, via the version/APOGEE-vs-BACnet banner text, which protocol stack the box is running. [D]

10.6 Session / EBLN node block (0x4640)

AP2_eBLN_Ping request and response (0x4640) both carry the eBLN_Node block — the peer-identity and liveness/replication state exchanged on session establish and on the ~10 s keepalive. [S][W]

eBLN_Node [S]

# Field Type Meaning
1 node_name TEXT_ the peer's node name
2 site_name TEXT_ site name
3 bln_name TEXT_ BLN name (the access-gate identity; §see addressing)
4 failed BOOLEAN_ peer failed
5 ready BOOLEAN_ peer ready
6 replication_online BOOLEAN_ replication active
7 reresolve_all BOOLEAN_ request full re-resolve
8 reresolve_unresolved BOOLEAN_ request partial re-resolve
9 spare1 UNSIGNED32 reserved
10 baseTime UNSIGNED32 time base (clock-sync)
11 offset UNSIGNED16 DST/zone offset
12 dst_flag BOOLEAN_ daylight-saving active

The bln_name field here is the same value the access gate checks: a peer presenting the wrong BLN is rejected at the network layer; the node/site/bln triple is also echoed back in CABINET_DISPLAY (§10.5). [W][I]

10.7 Node / BLN management bodies

Node_Choice (target selector for node-management ops) — CHOICE: all_nodes : NULL_ or single_node : UNSIGNED8 (node number). Used by the SET_NODE_STATE / cabinet online-offline family. [S]

Cov_data (BLN COV-share table entry) — destination_panel : UNSIGNED16 + cov_mask : Cov_mask. Defines which panel receives which change classes (the cross-BLN COV share). [S]

The EBLN host-table, MII, IP, and MAC display/configure ops (0x462D–0x463E) carry the corresponding IP_Address_Settings / MII_Data / Mac_Address sub-types from §10.5, and the host-table entry add/remove ops carry a host name + IP. The replication family (0x4633–0x4636, 0x464C) carries Repl_Cmd_Type (unknown/add/delete) records and node-roster grains (Grain_Type enum: node_list_entry, storage_node_db, user_acc_entry, hosttbl_entry, addresstbl_entry, …). [S]

10.8 Upload / PPCL / TEC / trend / alarm representatives

AP2_UPL_ALL_POINT request (0x0981) — bulk point upload; body is a single name_search : Name_search (the class/range selector). Response reuses the POINT_LOG_VALUE response shape (§10.3), iterated. The whole UPL_ALL_* family (0x0982–0x09C3) follows the same pattern: a Name_search/*_search request, a per-record response paged via the last_* resume keys. [S][W]

AP2_Ppcl_Program_Display response (0x410A) — a single PPCL_data record (programs are returned line-by-line). [S]

PPCL_data (one PPCL line) [S]

# Field Type Meaning
1 name_space Name_space (enum) program namespace
2 name TEXT_ program name
3 line_status TEXT_ rendered status
4 line_text TEXT_ the PPCL source line
5 line_number UNSIGNED16 line number
6 line_enabled BOOLEAN_ line enabled
7 line_traced BOOLEAN_ trace flag
8 line_unresolved BOOLEAN_ has unresolved reference
9 line_failed BOOLEAN_ line failed
10 line_looped BOOLEAN_ in a loop

PPCL statement keywords (the PPCL_statement_type enum, 71 members: WHOPLOOP/WHOPON/WHOPOFF/WHOPGOTO/WHOPGOSUB/WHOPIF/WHOPTHEN/WHOPELSE/WHOPALARM/WHOPENALM/WHOPHLIMIT/WHOPLLIMIT/WHOPTOD/WHOPHOLIDAY/…) are the token vocabulary of the line text. [S]

AP2_TREND_DATA_DISPLAY request (0x0295) [S]

# Field Type Meaning
1 user_profile User_profile requester
2 name_search Name_search target trend point
3 trend_specifier Trend_specifier { number_of_samples:u16, trend_type:Trend_type }
4 last_sequence_number UNSIGNED32 resume key (paging)
5 last_date_time DATE_TIME resume key
6 max_samples UNSIGNED16 cap on returned samples

AP2_TREND_DATA_DISPLAY response (0x0295)name_response + point : All_points + nrOftrend_data:u16 + trend_data : Trend_data[] + lenum_address + nrOftrend_dst:u16 + trend_dst : Trend_dst[]. Each Trend_data sample: sequence_number:u32, time:DATE_TIME, point_value:Point_value, point_priority, out_of_service:bool, failed:bool, control_status, alarm_priority, acked:bool, in_alarm:bool, in_trouble:bool, commanded_to_alarm:bool, operator_disabled:bool, program_disabled:bool, proof_pending:bool. [S]

AP2_TEC_REMOTE_INIT_VALUE_LOG (0x4221) and the TEC family carry a TEC_body plus TEC_valid (no/yes/maybe) and the FLN-device init-value records — the FLN/Terminal-Equipment-Controller application init-value model. (1,776 frames observed; one of the routine FLN reads.) [S][W]

Alarm representative — AP2_POINT_LOG_ALARM response (0x0221) reuses the name_response + point : All_points + lenum_address + point_extension2 shape; the alarm configuration itself is the Alarm_object CHOICE (tagged by Alarm_object_type: no_alarming / std_digital / std_single_analog / std_analog / enhanced_ / bacnet_alarm_), each alternative carrying Alarm_level[] records (offset:FLOAT_, alarm_priority, category:u8, msg_number:u16). [S]

10.9 The full structure set is enumerable

The ~30 structures above cover the read/command/COV core, the firmware/identity block, the session block, and one representative of each remaining major family (upload, PPCL, trend, TEC, alarm, node-management). The remaining ~1,110 structures follow the same conventions — ordered typed fields, the shared sub-types of §10.2, counted arrays, and CHOICE unions — and each maps one-to-one to a *_Request / *_Response pair for an opcode in the §9 catalog. For any opcode not detailed here, its body is the like-named ASDU structure (e.g. AP2_<name>_Request / AP2_<name>_Response), readable from the structure library with the §10.1 type mapping. [S]

[OPEN] The exact byte offsets of the COV condition/priority block within the All_points per-type value alternatives (i.e. the precise on-wire layout of each lai_/ldo_/lenum_ CHOICE arm) are not pinned from the structure metadata alone — the metadata gives field order, not the codec's exact byte packing of each arm. Confirming these requires an alarmed/commanded capture or codec-level evidence. The field order and types are [S]; the per-arm byte offsets are [OPEN].

11. Point Model

The point is the atomic data object of a P2 system. Every value an operator reads or commands, every input a control program references, every quantity a trend logs, resolves to a point. This section specifies the logical point taxonomy, how a logical point decomposes into physical hardware terminations, how the FLN device layer self-describes its points, and the analog/enumerated scaling model. The runtime value carried for a point on the wire is specified in §12 (COV); the alarm attributes are specified in §13.

11.1 The three point layers

A point exists at one of three layers; an implementer must not conflate them. [I]

  • Logical point. The unit an operator commands and a control program references. Identified by a name (the routing key, §9), it has a single present value, a single condition, and a single command priority. A logical point is composed of one to four physical subpoints — the hardware terminations that implement it; the count and kind are fixed by the logical point's type (§11.2). [D]
  • Physical point (subpoint). A single hardware termination: one analog or digital input channel, or one analog or digital output channel. It has a raw digitized value (an ADC count for analog channels, a 0/1 state for digital channels) and a hardware/sensor type that fixes its signal range and linear calibration (§11.5). Physical subpoints are commanded only through the logical point that owns them, never directly. [D]
  • Virtual point. A logical point with no hardware termination — it occupies no physical channel. Virtual points hold computed values, setpoints, intermediate control-program results, and software flags. A virtual point carries the same attribute set and the same runtime value payload (§12) as a hardware-backed logical point. [D] In the object hierarchy the panel exposes, the containment is BLN → CEC (panel) → Point, and a point-team's default member is the logical point. [S]

11.2 Logical point types

P2 defines a fixed taxonomy of logical point types. Each type has a numeric type code (the panel's own Point_type enumeration) and fixes how many commandable outputs it drives, whether it monitors an input, and the total physical-subpoint count. [S]

Code Mnemonic Meaning Commandable outputs Monitored input Physical subpoints Tag
1 LDI Logical Digital Input 1 latched DI 1 [S/D]
2 LDO Logical Digital Output 1 latched or pulsed DO 1 [S/D]
3 LAI Logical Analog Input 1 AI 1 [S/D]
4 LAO Logical Analog Output 1 AO 1 [S/D]
6 L2SL Logical Two-State Latched 1 latched DO 1 latched DI 2 [S/D]
7 LOOAP Logical On/Off/Auto Pulsed 2 pulsed DO (ON/OFF) + 1 latched (AUTO) 1 latched DI (proof) 4 [S/D]
11 LPACI Logical Pulse Accumulator (Counter) Input 1 DI (pulse counter) 1 [S/D]
12 L2SP Logical Two-State Pulsed 2 pulsed DO 1 latched DI 3 [S/D]
13 LOOAL Logical On/Off/Auto Latched 2 latched DO 1 latched DI 3 [S/D]
14 LFSSL Logical Fast/Slow/Stop Latched 2 latched DO 1 latched DI 3 [S/D]
15 LFSSP Logical Fast/Slow/Stop Pulsed 3 pulsed DO 1 latched DI 4 [S/D]
20 LDAO Logical Digital-Actuated Analog Output analog driven by digital pulses optional varies [S]
21 LENUM Logical Enumerated (multistate) per state optional 1 (value → state-text set) [S/D]
22 LFMSSL Logical Fast/Medium/Slow/Stop (3-speed) Latched 3 latched DO 1 latched DI 4 [S/D]
23 LFMSSP Logical Fast/Medium/Slow/Stop (3-speed) Pulsed 4 pulsed DO 1 latched DI 5 [S/D]
24 PPCL_LAI PPCL-referenced analog input (control-program-visible analog) 1 AI 1 [S]

Type code 0 is point_type_undef (no point). The codes are the Point_type enumeration values that travel as a Point_type tag wherever a typed point body appears in an ASDU. [S] The decimal codes are not contiguous (5, 8, 9, 10, 16–19 are unused), so an implementer must dispatch on the explicit code, never on ordinal position. [S]

The on/off/auto and fast/slow/stop families (LOOAL, LOOAP, LFSSL, LFSSP, LFMSSL, LFMSSP) are digital-only: their commandable outputs are digital outputs, and they are not interchangeable with analog types. [D] The "latched" (...L) variants drive maintained digital outputs; the "pulsed" (...P) variants drive momentary outputs and require one additional subpoint to express the auto/stop state — so a pulsed variant always carries one more physical subpoint than its latched counterpart. [D]

11.3 Physical-subpoint composition

A logical point's address list contains exactly as many physical subpoints as its type prescribes (the "Physical subpoints" column above). A record claiming type LFSSL but carrying other than three addresses is malformed. [D] In a digital multi-address record the output addresses appear first, in command order, and the monitored/proof input address appears last. [D] This is why one logical point spans multiple subpoint indices: a single operator-facing on/off/auto command point (LOOAP) is implemented underneath as two pulsed command outputs, one latched auto output, and one proof input — four physical terminations — but the operator sees, commands, and alarms a single point. [D]

Each physical subpoint is one of four base hardware classes; analog classes are further divided by signal type. [D]

Physical class Subtypes Signal Tag
AI (analog input) AI-I, AI-V, AI-P, AI-T 4–20 mA, 0–10 Vdc, 3–15 psi, thermistor/RTD [D]
AO (analog output) AO-I, AO-V, AO-P 4–20 mA, 0–10 Vdc, 3–18 psig [D]
DI (digital input) energized / de-energized [D]
DO (digital output) energized / de-energized; latched (maintained) or pulsed (momentary) [D]

The wire/ASDU representation of a subpoint's address is one of the Physical_address_{AI,AO,DI,DO,PA} variants, each a tagged union of real_addr (a hardware-terminated channel) versus virtual_addr (NULL — no hardware), plus the Physical_address_Lenum variant (not_present versus present) carried alongside LENUM points. [S] The tag byte selects the variant; an implementer reads the tag first, then the variant body.

11.4 Point teams (.ptd) and the FLN subpoint model

11.4.1 Point-team descriptors

The panel side carries a declarative template called a point team: a named team keyed by a (family, type, revision) triple, containing one member per subpoint. [S/D] The team header is the Team_description_base record: team_family (an Application_family code — e.g. 7 = ppcl_program, 16 = tec_na, 17 = uc, 18 = tcu, 19 = lon, 20 = p1_pxc, 21 = bacnet_mstp, 257 = tec_eu), team_type (u16), team_revision (u16). [S] Each member is a Member_description_base: member_number (u16 — the subpoint slot index), a list of team_suffix strings, a free-text member_desc descriptor, point_type (the §11.2 L-code), and the boolean attributes virtual_pt, alarmable, reference_type (INPUT / OUTPUT / not_allowed), totalize, print_alarms, plus total_scale (seconds/minutes/hours/days). [S]

The on-disk point-team descriptor file (.ptd) is the serialized form of this model. It is XML, in two serialization variants, carrying the same logical model: [D]

  • WCIS variant: UTF-16 XML, element nesting PointTeamDefinition > Application > PointTeam > Point. [D]
  • DataMate variant: ASCII XML validating against ptd.xsd, root TEAM_DESCRIPTION, with member elements ANALOG_PHYSICAL_MEMBER / ANALOG_MEMBER / DIGITAL_MEMBER / ENUM_MEMBER, each keyed by SUBPOINT_NUMBER. [D]

An implementer parsing a .ptd must not assume a flat line-oriented text grammar; the descriptor is structured XML. [D] The logical member model is consistent across both variants: each member declares { SUBPOINT_NUMBER, REFERENCE (INPUT/OUTPUT), TYPE (L-code), SI and ENG slope, SI and ENG intercept, units, enumeration / state-text group }. [D] The SUBPOINT_NUMBER in a team member occupies the same index space as the FLN application roster (§11.4.2), tying the panel-side template to the field device. [D]

Member kind Use Carries Tag
analog-physical physical LAI/LAO subpoint descriptor, reference (direction), L-type, SI/ENG units, SI/ENG slope, SI/ENG intercept [S/D]
analog virtual/computed analog descriptor, reference, L-type, SI/ENG units, SI/ENG initial value [S/D]
digital LDI/LDO subpoint descriptor, reference, L-type, state-text set, initial value, [alarmable, print-alarm] [S/D]
enum enumerated/mode subpoint descriptor, reference, L-type, state-text set, initial value, [alarmable, print-alarm] [S/D]

11.4.2 FLN application roster and self-identification

A panel hosts one or more Field Level Networks (FLNs) — RS-485 fieldbus sub-buses (P1, §4.4) carrying field controllers (TEC, UC, TCU, PXM, P1DXR, VFD; FLN_Device_Type enumerates the families). [S/D] Each FLN device runs an application, identified by an application number, that fixes the device's roster of points. [D]

The subpoint index space is fixed by the controller class, and three slots have reserved meaning: [D]

Subpoint Meaning Tag
0 Bundled controller point — the device-level point representing the controller as a whole [D]
1 CTLR ADDRESS — the controller's FLN drop number (its position on the fieldbus); factory default 99 [D]
2 APPLICATION — the application number the device self-reports at runtime; reading it selects which point-team / roster map applies [D]

Resolution of a live FLN device's points is therefore: read subpoint 2 (APPLICATION) to learn the application number, look up that number's roster (the ordered (subpoint number, object type) rows), and for each subpoint the pair (application number, subpoint number) yields the object type. [D] A point team keyed by the device's (family, type) supplies the panel-side template for the same subpoint indices (§11.4.1). [D] A team whose type key is the "undefined/failed device" sentinel is the fallback template applied to a device that does not return a recognized application. [D]

11.4.3 Bundled vs unbundled subpoints

By default an FLN device's subpoints are bundled — they exist only inside the controller's own point and are not individually addressable as first-class BLN points. The {NN} bracket convention denotes a bundled subpoint NN. [D] Unbundling a subpoint registers it as a standalone point so it can be read and commanded over the BLN like any panel-resident point; whether a given subpoint may be unbundled is itself an attribute (the export CSV labels it "Unbundlable Status"). [D] Bundled subpoints reduce BLN point count and replication load; unbundling trades that for network visibility of an individual field value. [I]

11.5 Analog scaling, sensor types, and enumerations

11.5.1 Linear scaling

An analog subpoint converts a raw digitized count to an engineering value by a linear transform: [D]

EngValue = (DigitizedValue × Slope) + Intercept

Slope and Intercept are 32-bit big-endian floats fixed by the physical point's sensor/hardware type. [D] DigitizedValue is the raw ADC count. [D] A point carries two slope/intercept pairs — one for engineering (display) units and one for SI units — so the same digitized reading renders in either unit system; each pair is an independent linear transform from the same raw count. [S/D] The ASDU Analog_scale record is exactly { slope: f32, intercept: f32 }; the dual-unit set is two such records plus the eng/SI units strings. [S]

The FLN raw-count form is distinct from the BLN f32 value form: on the fieldbus an analog value is a raw integer whose register width is the point's P1MaxRange (most commonly 0–255, i.e. an 8-bit count), while the value seen at the BLN layer and in the COV payload is the scaled f32 (§12.3). [D] Worked example: a digitized count of 1954 with slope 0.03125 and intercept −5.0 yields 1954 × 0.03125 + (−5.0) = 56.0625, displayed as 56.06. [D]

11.5.2 Sensor types

An analog input carries a sensor type fixing its physical signal class; an analog output does not. The Sensor_type enumeration: [S]

Code Sensor Code Sensor
0 voltage 7 rtd1k
1 current 8 rtd1k_385
2 resistance 9 nickel1000
3 pneumatic 10 nickeljci
4 thermister10k 11 nickeldin
5 thermister100k 12 thermister10type3
6 ltype

An intercept_adjustment (f32) accompanies the sensor type for field trim of the linear transform (Analog_sensor = { sensor_type, intercept_adjustment }). [S]

11.5.3 Enumerated and digital state decoding

For digital and enumerated points the wire/value field carries a small integer state index, not a string. The index is resolved for display against a named text group / state-text set (the State_text_table), stored once per panel as a shared catalog and referenced by name from the point or team member. [S/D] Example sets: 2000 = {Off, On}; 2001 = {Normal, Alarm}; generic sets like {Clean, Dirty}. [D] A digital point's present value is encoded as the float 0.0 (OFF) or 1.0 (ON) in the value field (§12.3); an enumerated point's present value is the integer state index carried in that same float field. [D] An implementer renders the value by taking the index and looking it up in the referenced state-text group; the wire never carries the display string for a value. [D]

Implementation caution: the TEC-template "PTYPE" field seen in DataMate DBF/ISB data (1 = DI, 2 = DO, 3 = AI, 4 = AO, plus 10/11/12/34) is a template-local taxonomy, NOT the §11.2 Point_type L-codes and NOT priority values. The 1–4 alignment with LDI/LDO/LAI/LAO is coincidental for those four only (e.g. PTYPE 34 = RESET-FAULT, unrelated to command-priority 34 = smoke). Do not cross-map PTYPE to L-codes. [D]

11.5.4 Logical type → BACnet object type (optional cross-reference)

Where a P2 point is exposed on a BACnet/IP interface, its logical type maps to a BACnet object type; the physical/virtual flag selects the I/O-object versus value-object form. [D] (BACnet exposure is a separate stack; this map is provided only for implementers bridging the two.)

P2 L-type Physical Virtual Tag
LAI AI AV [D]
LAO AO AV [D]
LDI BI BV [D]
LDO BO BV [D]
L2SL / L2SP BO BV [D]
LOOAL / LOOAP BO (or MO/MV) BV/MV [D]
LFSSL / LFSSP / LFMSSL / LFMSSP MO MV [D]
LPACI AI (accumulator) AV [D]
LENUM MO MV [D]

12. Change-of-Value (COV)

COV is the mechanism by which a panel reports a point's value change to interested peers without being polled. It is a subscription service: a peer enables COV on points it cares about, the panel thereafter pushes a report (COV_ANNUNCIATE) whenever a subscribed point changes by at least its COV resolution, and the peer disables or deletes the subscription when done. COV is the highest-volume P2 operation in steady state. [W]

12.1 The subscription opcode set

The four COV opcodes are AP2 Function Code values (names and numbers from the function-code enumeration, cross-checked against the wire). [S/W]

AP2 function code Decimal Name Direction Role Tag
0x0271 625 AP2_COV_ENABLE subscriber → panel open a COV subscription on a point [S/W]
0x0272 626 AP2_COV_DELETE_STUB subscriber → panel delete a subscription stub [S/W]
0x0273 627 AP2_COV_DISABLE subscriber → panel close a COV subscription [S/W]
0x0274 628 AP2_COV_ANNUNCIATE panel → subscriber the actual change-of-value report (unsolicited push) [S/W]

Correction of an earlier reading: these opcodes were previously read as "ReadExtended / ReadDescriptorOnly / WriteNoValue-ExistenceProbe" against 0x0271/0x0272/0x0273. The function-code enumeration shows they are the COV subscription register/delete/cancel/report quartet. The 00 FF vs 00 00 mode trailer that earlier analysis treated as read-mode versus command-mode is the enable-vs-disable selector of the subscription. [S] This register/cancel-subscription model — rather than a one-shot read — is consistent with that enable/disable trailer. [S/I]

The AP2_COV_ENABLE request body (AP2_COV_Enable_Request) is { name_response, cov_mask } — the addressed point plus the subscription's class mask; the response (AP2_COV_Enable_Response) is { point: All_points, lenum_address, point_extension2 }, i.e. the panel returns the full current point object as the subscription's first report. [S] AP2_COV_DISABLE is { name_response, cov_mask }; AP2_COV_DELETE_STUB is { name_response }. [S] AP2_COV_ANNUNCIATE carries a count-prefixed array of Annunciate_request records (§12.3). [S] An annunciate push uses the request encoding (direction byte 0x00, §6.3) because it is unsolicited; the peer acknowledges with a direction-0x01 empty success. [W]

12.2 The COV class mask and subscription type

A subscription names a COV mask selecting which classes of change generate a report. The Cov_mask is a bitfield; the bit positions are the panel's own Cov_mask enumeration: [S]

Bit Class Meaning Tag
0 data present-value change (the ordinary COV) [S]
1 failure transition into/out of the failed condition [S]
2 alarm transition into/out of an alarm condition [S]
3 service out-of-service / in-service change [S]
4 priority command-priority (ownership) change [S]
5 TCU terminal-control-unit-related change [S]
6 temp_all a temporary all-class subscription [S]
7 proof_on proof-status change [S]

A complementary Point_cov_type enumeration selects the granularity of what is reported: 0 = all_types, 1 = point_values, 2 = point_priorities, 3 = point_status. [S] Together the mask (which transitions) and type (which attributes) define a subscription. A subscriber wanting only value movement subscribes with the data mask bit and point_values type; a supervisor mirroring full point state subscribes all_types. [I]

The panel also maintains a COV cross-reference (which peers are subscribed to which points), readable via AP2_Xref_COV_Display — its response is a count-prefixed array of Cov_data = { destination_panel: u16, cov_mask } rows, i.e. for each subscribing panel the mask it holds. [S]

12.3 The annunciate / value payload

12.3.1 Field model

The body the panel pushes for each changed point is the Annunciate_request record. Its fields are the complete dynamic state of a point (the static definition attributes — name, descriptor, limits, slope/intercept, units, proof delay — are not carried; they come from the point-definition record, §11.4/§11.5). [S]

# Field Type Meaning Tag
1 name_response {name_space, name, suffix} the point being reported [S]
2 value FLOAT_ (f32 BE) present value: eng units (analog/pulse), 0.0/1.0 (digital), state index (enum) [S/W]
3 point_priority Point_priority command-ownership holder (§12.4) [S]
4 control_status Control_status remote / tool-override / by-priority / config-only / input-only / manual-override / undefined [S]
5 out_of_service bool point taken out of service [S]
6 failed bool hardware-failed [S]
7 proof_on bool within proof window [S]
8 operator_disabled bool operator-disabled (frozen) [S]
9 program_disabled bool control-program-disabled [S]
10 commanded_to_alarm bool forced (alarm-by-command) [S]
11 alarm_state Alarm_state normal / alarm / high_alarm / low_alarm / trouble [S]
12 alarm_priority Alarm_priority priority_0 … priority_6 (§13.2) [S]

12.3.2 Operating-state taxonomy

The boolean and enum status fields (#4–#12 above) encode the point's operating state. The semantic taxonomy an implementer must render is: [S/D]

  • Normal — no status bit set, alarm_state = normal. [S/D]
  • Failedfailed true; the point cannot be commanded or examined. [S/D]
  • Out-of-Serviceout_of_service true; value and condition frozen against the field. [S/D]
  • Proofingproof_on true; transient state during the proof-delay window after a command, before proof confirms. [S/D]
  • Alarm-by-Commandcommanded_to_alarm true; a forced alarm asserted by operator or program, treated as an alarm. [S/D]
  • Program-Disabledprogram_disabled true; control program prevented from acting on the point. [S/D]
  • Operator-Disabledoperator_disabled true; value and condition frozen against operator and program (the point may still transition to failed). [S/D]

These are the semantic meaning of the per-point status the COV report conveys; alarm_state (normal/alarm/high/low/trouble) and alarm_priority ride alongside for points that are in alarm. [S]

12.3.3 Wire layout and the open offsets

The COV_ANNUNCIATE (0x0274) body is now wire-confirmed. The body opens with a count (u16) of points reported, then, per point, a name_response (its three sub-fields, §8.5) followed by the value and condition block: a 2-byte name_space (observed constant 00 00 = system), the name TLV 01 00 <len> <point-name>, an empty suffix TLV 01 00 00 (the §8.1 empty-string TLV — not a "value marker"; it is non-empty only for FLN subpoints, e.g. 01 00 09 "ROOM TEMP"), then the present-value f32 big-endian, then a fixed 10-byte condition/priority block carrying fields #3–#12 one byte each. The name TLV therefore begins at body offset +4 (after count + name_space), not +2. [W]

<count u16>                               -> number of points in this push
  per point (a name_response + value + condition):
    00 00                                 -> name_space (u16; 0 = system)   [+0 of record]
    01 00 <len> "<point-name>"            -> name TLV   (e.g. "OATEMP.BN")
    01 00 00                              -> suffix TLV (empty; non-empty only for FLN subpoints)
    <f32 BE>                              -> present value (e.g. 42 9b 62 3a ≈ 77.69)
    -- 10-byte condition/priority block = fields #3..#12, one byte each, in order:
    +0  point_priority        (§12.4 ladder; 0 = NONE)
    +1  control_status        (0x04 observed on normal analog points)
    +2  out_of_service  (bool)
    +3  failed          (bool)
    +4  proof_on        (bool)
    +5  operator_disabled    (bool)
    +6  program_disabled     (bool)
    +7  commanded_to_alarm   (bool)
    +8  alarm_state           (normal/alarm/high/low/trouble)
    +9  alarm_priority        (priority_0..6)

Worked examples (sanitized): a normal sensor OATEMP.BN, value 42 9b 62 3a (≈ 77.69), block all-zero at NONE priority; a normal analog point with control_status (+1) = 0x04 and everything else zero; a point commanded at OPER shows the block opening 23 02 …point_priority (+0) = 0x23 (OPER) and control_status (+1) = 0x02; and a failed sensor (…RET AIR TEMP, value c2 79 ff ff ≈ −62.5) shows non-zero bytes deeper in the block (a flag byte in the +7..+8 region asserted). A reader needing only the present value takes the f32 and may ignore the block. [W]

[W — values now partly pinned; alarm-band still OPEN] The 10-byte trailing block's position, size, and field order are established (the Annunciate_request ASDU defines fields #3–#12, exactly ten one-byte fields follow the value, and that fits the wire bit-for-bit). Newer command/abnormal-state captures confirm the first two bytes' asserted values: point_priority (+0) ∈ {0x00 NONE, 0x20 EMER, 0x23 OPER} tracking who holds the point (0x20/emer seen on BACnet-integration points commanded to 1.0), and control_status (+1) observed taking {0x00, 0x02, 0x03, 0x04, 0x06} (e.g. 0x02 when the point is under an active operator command, 0x04 on a normal analog input). A failed sensor asserts a flag byte in the OOS/failed region (+2..+8). [W] What remains [OPEN] is the alarm_state (+8) / alarm_priority (+9) encoding: no point in the captures was in a true hi/lo-limit alarm, so those two bytes never left zero. A single 0x0274 push from a point sitting in a limit alarm closes the last gap.

12.4 Command priority (carried in the COV payload)

point_priority is the command-ownership holder — which class of command source last successfully commanded the point. The Point_priority / User_command_priority ladder, lowest to highest acceptance, with the byte value that encodes it on the wire: [S]

Value Name Tag
0 none (default; most control-program output) [S]
1 tec_ovrd (TEC local override) [S]
5 pdl (peak-demand-limiting) [S]
10 host_2 [S]
15 host_3 [S]
20 host_4 [S]
25 host_5 [S]
30 host_6 [S]
32 emer (emergency) [S]
34 smoke (smoke control / life safety) [S]
35 oper (operator command, highest) [S]

The familiar operator-facing ladder is OPER (35) > SMOKE (34) > EMER (32) > PDL (5) > NONE (0); the host_2..host_6 band (10–30) and tec_ovrd (1) fill the intermediate rungs, and a later band (BACnet priorities 101–116) maps the 16 BACnet command-priority slots. [S] A command at a given priority overrides only equal-or-lower holders; a release lowers the holder to NONE so a control program can reacquire. [D] This same priority byte is the scope_byte of a scoped command request (§8.2) and the command-priority field of the point-definition header. [W] At the codec layer command priority is a single byte (GetPriority → BYTE). [S]

12.5 COV behavior and tuning

  • EU deadband (COV limit / resolution). An analog point reports a change only when the value moves by at least one COV limit — the engineering-units resolution of the subscription. The COV limit is floored at the point's slope: it cannot be finer than one raw count mapped through the slope (a smaller configured value is clamped up). For a pulse accumulator (LPACI) the floor is one pulse. [D] The COV limit is expressed in dual form — engineering value and equivalent raw count — because it maps through the slope to a raw-count resolution; "1.0 (32)" means one engineering unit = 32 raw counts. [D]
  • Digital / enum points. Any state transition is a change of value; there is no deadband. [D]
  • Dynamic COV. A panel may auto-tune a point's effective COV resolution to hold the report rate near a target — roughly one report per 100 seconds — widening the deadband for a noisy point so it does not flood the network. [D]
  • Alarm deadband linkage. The alarm-limit deadband (§13.3) is automatically set to one COV limit at each limit, so the COV resolution and the anti-nuisance alarm hysteresis are the same quantity. [D]

Implementation caution: a field/application document's "COV limit" or "DISPLAY RES" describes the local operator-display deadband of a TEC, which explicitly does NOT affect networked values. Do not treat that local display setting as the network COV subscription resolution specified here. [D]


13. Alarming

Alarming is the panel's autonomous evaluation of points against limits and proof, and the reporting of alarm transitions to operators and destinations. Alarm reporting reaches the network either as a dedicated unsolicited alarm report or as a COV with the alarm mask bit set (§12.2); a subscriber may learn of an alarm by either path. [D]

13.1 Standard vs Enhanced alarms

A point's alarming behavior is selected by its alarm object type. The Alarm_object is a tagged union over the Alarm_object_type enumeration: [S]

Code Alarm object Tag
0 no_alarming (point does not alarm) [S]
1 std_digital [S]
2 std_single_analog [S]
3 std_analog [S]
4 enhanced_digital [S]
5 enhanced_analog [S]
6 enhanced_lenum [S]
7 bacnet_alarm_analog [S]
8 bacnet_alarm_digital [S]

Standard alarms are the base form (limit-crossing or digital-state alarm, single destination set). Enhanced alarms add the richer alarm-mode machinery — multiple alarm modes (day/night/special), per-mode limits and setpoints, and enhanced-count behavior — and exist for digital, analog, and enumerated points. [S/D] The runtime alarm state of a point is the Alarm_object_data record: timestamps for current-state / first-alarm / acknowledgment, a state_changes counter, and the boolean condition set { ack_pending, return_to_normal_acks, inalarm, introuble, inalarm_by_command, operator_disabled, program_disabled, proofing, is_enhanced, print_alarms, enable_almcnt2 }. [S]

13.2 Alarm priority is distinct from command priority

An alarm carries an alarm priority that classifies the urgency of the event, which is a completely separate axis from the command priority (§12.4) that classifies the ownership of a command. An implementer must keep the two apart. [D] The Alarm_priority enumeration is priority_0 … priority_6; the operational severity bands these represent: [S/D]

Band Severity class Tag
highest Life Safety / Fire [D]
Critical [D]
Security [D]
Trouble [D]
lowest Maintenance [D]

The alarm report carries this priority as a 1-byte value (1–6) and may append a 4-character class label (e.g. URGT, MAIN, TROB). [W] The point's runtime alarm_state (§12.3.1) is one of normal / alarm / high_alarm / low_alarm / trouble. [S]

13.3 Analog limits, transitions, and deadband

An analog or pulse point carries a high limit and a low limit in engineering units. A value crossing a limit asserts the alarm condition (and, for analog, distinguishes high_alarm vs low_alarm). [S/D] An alarm deadband equal to one COV limit (§12.5) is automatically applied at each limit to suppress nuisance re-alarming as the value dithers across the threshold. [D] The point-definition record carries both English and SI limit pairs alongside the dual scaling (english_low_alarm / english_high_alarm / si_low_alarm / si_high_alarm). [S]

Alarm transitions follow the standard three-transition model — to-off-normal, to-fault, to-normal (the BAC_Transitions set: to_offnormal, to_fault, to_normal) — and a point may be configured to annunciate on each transition independently ("Annunciate to Normal / Off-Normal / Fault Transitions"). [S/D] A level_delay and a mode_delay (both u16, in the alarm-setup record) debounce the assertion so a brief excursion does not generate an alarm. [S]

13.4 Alarm destinations and routing

An alarm-setup record (AP2_Alarm_Setup_Request / _Modify / _Copy) configures, per alarm point and per alarm mode: mode_name + mode_suffix, normal_acks (require ack on return-to-normal), alarmcnt2 (second alarm count), level_delay, mode_delay, differential (f32 hysteresis), and four destination category bytes category0 … category3. [S] These four categories are the alarm's routing destinations; a system default destination (000) receives alarms not otherwise routed, so the effective routing is up to four configured destinations plus the default. [D] A destination is a category container that nodes append themselves to (AP2_Category_Nodes_Append / _Remove), so an alarm routed to a category reaches every node currently in that category. [S]

13.5 Controller alarms as digital points

A field controller's own health and discrete fault conditions are surfaced to the BLN as digital points carrying the alarm attributes above: an alarmable LDI whose state is the fault, with a proof/debounce window so a momentary glitch does not alarm, and network reporting via COV (alarm mask bit, §12.2) or by inclusion in an alarm poll. [D] This is how a controller-level event (a comm-fault, a hardware fault, a returning/failed device state) reaches an operator through the same alarm path as a process-point limit alarm: it is modeled as a digital point and rides the standard alarm and COV machinery rather than a separate channel. [D] The device-level liveness states themselves (Failed_status: normal / returning / unknown / failed) drive these point states. [S]

13.6 Alarm report and acknowledgment opcodes

AP2 function code Decimal Name Direction Role Tag
0x0508 1288 (class) AlarmReport / AP2_ALARM_PRINT family panel → operator (unsolicited) deliver an alarm notification [W]
0x0509 1289 AP2_ALARM_ACK / CommandRead operator → panel acknowledge an alarm and read the addressed command object (wire-observed, census count 11) [W]

The unsolicited alarm report (0x0508) body, as wire-decoded from captured reports, carries the "CC" scope, the point name_response, then three consecutive 8-byte timestamps (§8.3.4) — an event time, a reference time, and a created/configured time (located after the point name/descriptor TLVs and the value block; their absolute byte offset shifts with the name lengths, so a parser anchors on the preceding fields rather than a fixed offset; the created stamp's base recurs across captures and points) — followed by the point's runtime Alarm_object_data record: a 2-byte state_changes counter (UNSIGNED16), the boolean condition set (ack_pending, return_to_normal_acks, inalarm, introuble, inalarm_by_command, operator_disabled, program_disabled, proofing, is_enhanced, print_alarms, enable_almcnt2), the point's high/low alarm limits (two f32 BE — e.g. 85.0 / 50.0), and an engineering-units TLV (e.g. DEG F); the alarm condition is conveyed by these boolean flags (e.g. inalarm) together with the present value vs. the limits (e.g. a failed return-air sensor reading ≈ −62.5 below its low limit). [W] In the captured bodies there is no separate 1-byte alarm-state enum, no 1-byte priority field, and no 4-character class label — those (priority_0..6 and labels such as URGT/MAIN/TROB) are documented for the alarm model (§13.2) but were not present as discrete fields in these wire reports; treat them as [D]/[S] until a capture shows them. [W] An alarm may be duplicated across two transport channels or pushed inside a fresh 0x2E/0x2F carrier connection; the body bytes are identical regardless of envelope, so de-duplicate by sequence or by the (device, point, timestamp) tuple. [W]

The acknowledgment (0x0509) uses the "CC" scope and read-style framing; it acknowledges an alarm and reads the addressed command object, the response echoing the addressed name. [W] For an acknowledgment the body additionally carries acknowledgment markers, an operator-identity TLV, and an 8-byte acknowledgment timestamp. [W] A 0x0273 (COV disable) frame is sometimes observed immediately before a 0x0509 as a precursor. [W] Error path: 0x0003 (not found) when the named target does not exist or the alarm cleared between the operator action and the arrival of the acknowledgment. [W]

Enhanced-alarm configuration uses the broader alarm-mode and category opcode families (AP2_ALARM_MODE_* 1312–1328, AP2_CATEGORY_* 1344–1357, AP2_ALARM_MESSAGE_* 1376–1386) — the setup/management surface for the runtime behavior described above. [S]

14. PPCL over the Wire

PPCL — Powers Process Control Language — is the on-board control language a field panel (PXC/MEC/CEC) executes. A panel runs one or more PPCL programs locally; the supervisor never executes PPCL — it only edits, uploads, and reports it across P2. PPCL is therefore a language layer carried by P2, not part of the P2 framing itself: a program is a body payload inside the program-management opcodes of §14.5, in exactly the same TLV/frame envelope as every other operation (§6, §8). This section specifies the language as far as an implementer needs to read a program out of a panel, write one back, and understand how a program's runtime point commands interact with the priority ladder of §8. [D/S]

14.1 Program model

A PPCL program is an ordered set of line-numbered statements resident in one panel. [D]

Property Value Tag
Line number range 1–32767, integer; conventionally assigned in multiples of 10 to leave insertion room [D]
Execution order ascending line number from the lowest, unless redirected by GOTO/GOSUB/IF [D]
Line length short, ~80 characters [D]
Per-line state enabled/disabled, traced/untraced, resolved/unresolved, failed, looped — see the per-line record §14.4 [S]
Point references by dotted logical system name (§14.2); referenced point must exist in the panel point DB or the line is marked unresolved [D]
Restart behavior after any interruption (power failure, warmstart) execution resumes at the first line; the lowest line runs every pass [D]
Time-based statements LOOP, SAMPLE, TOD, WAIT must be evaluated every pass [D]

A program is identified on the wire by its program-name string (operator tools append a .PCL extension; the wire identifier is the bare name). The program name and program-family are scoped under Application_family = ppcl_program (value 7) in the controller application catalog (§16.4). [S]

14.2 Point references, names, and macros

A PPCL statement references points by dotted logical system name (the modern named form of §3/§11, e.g. AHU1.SAT), never by the legacy numeric TCCDSSPP address. Point names used inside PPCL must not contain parentheses (parentheses are reserved for the expression grammar). [D]

Two name-length limits apply, and they are distinct:

  • Legacy 6-character system-name reference limit. On old firmware revisions a PPCL statement could reference a system point only by a name of up to 6 characters; longer names were truncated for the reference. This is a firmware-keyed limit and is separate from — and stricter than — the BLN/node name limits. [D]
  • 15-character node-name limit / 30-character object-name limit. Current firmware references points by the full logical name (§3.4.2). The 6-character limit is a property of the legacy reference encoder, not of the point name itself. [D]

DEFINE (token WHOPDEFINE, §14.3) creates a macro — a symbolic alias substituted into later statements at compile time, used to give a readable handle to a point name or a constant. [S]

System (resident) points. A program has read access to panel-resident points that always exist and supply time, date, and system status. They are referenced by name like any other point: [S]

Resident point Meaning Tag
$LOC1$LOC15 15 local scratch variables (program-local storage) [S]
$ARG1$ARG15 15 subroutine argument slots (pass values into GOSUB) [S]
$PDL peak-demand-limiting interface point [S]
$BATT battery-status point [S]
TIME current time [S]
DAY day-of-week / day index [S]
ALMCNT current alarm count [S]
SECND1SECND7 per-second / scheduling helper points [S]
#ALMPRI alarm-priority indicator [S]
per-node liveness booleans, BLN link-status point one liveness boolean per node + a BLN link indicator [D]

14.3 Statement / keyword vocabulary

PPCL statements fall into functional categories; the panel stores each statement with a token byte identifying the statement type. The complete token set is the 71-entry statement-type table (vendor enum PPCL_statement_type, members prefixed WHOP* = "what-opcode"); it is reproduced in full in the appendix (cross-ref Appendix — PPCL statement-type token table). [S] The functional grouping: [D/S]

Category Statements (token name in parentheses where it differs from the keyword) Tag
Program control / flow LOOP, ACT/DEACT (activate/deactivate), ENABLE/DISABLE, GOTO, GOSUB, RETURN, IF/THEN/ELSE, SAMPLE, WAIT, ONPWRT (on-power-up), ONERR (on-error), DBSWITCH (database switch), TABLE, STATE, LSTSQR (least-squares), LOCAL, DIM, COMMENT, DEFINE [S]
Point control ON, OFF, AUTO, FAST, SLOW, SET, RELEASE, MIN, MAX, DCR (direct command/ratio) [S]
Operational / alarm control ENALM (enable alarming), DISALM (disable alarming), ALARM (force alarm), NORMAL (return to normal), LLIMIT (set low limit), HLIMIT (set high limit) [S]
Emergency control EMON/EMOFF (emergency on/off), EMSET, EMFAST, EMSLOW, EMAUTO, RELTCU (release TCU) [S]
Energy management DAY, NIGHT, DC (duty cycle), TOD / TODMOD / TODSET (time-of-day — §15), SSTO / SSTOCOEF (start-stop time optimization — §15.3), HOLIDAY, ENTHAL (enthalpy), PDL / PDLDAT / PDLMTR / PDLSET / PDLDPG (peak-demand limiting), TIMAVG, INITTOT (initialize totalizer) [S]
COV / trend ENCOV (enable COV), DISCOV (disable COV) — runtime COV-subscription control from within a program (cross-ref §12/§13 COV) [S]
Communications EPHONE/DPHONE (enable/disable phone — dial-up alarming), MMI (man-machine-interface control) [S]
Special function / math ASSIGN (expression assignment), LSTSQR, plus the arithmetic and logical operators of §14.4 [S]

14.4 Expressions, operator precedence, and the command parameter array

Operator precedence, highest to lowest (a conformant compiler/decompiler must honor this chain): [D]

( )                         parentheses                       (highest)
functions                   built-in functions (MIN, MAX, ROOT-class, etc.)
ROOT                        square-root operator
*  /                        multiply, divide
+  -                        add, subtract
<  <=  =  >=  >  <>          relational comparison
AND  NAND                   logical AND / NAND
OR  XOR                     logical OR / XOR             (lowest)

Built-in functions. The language provides a small fixed set of numeric functions, callable in any expression: the arithmetic/transcendental set ROOT (square root, also written SQRT), LN (natural log), and the trigonometric SIN / COS / TAN / ATN (arctangent); an accumulator function TOTAL; and a regression/adaptive group — LSTSQR (least-squares fit), its helpers LSQ2 / LSQDAT, and the adaptive-control functions ADAPTM / ADAPTS. The point-selection operators MIN / MAX (and the point-command statements MIN/MAX of §14.3) round out the math vocabulary. A function binds tighter than the arithmetic operators (it sits just below parentheses in the precedence chain above). [S/D]

The 16-slot command parameter array. A point-command statement (ON/OFF/SET/AUTO/etc.) carries an ordered parameter array of up to 16 slots. Each of the following consumes exactly one slot:

  • a target-point reference,
  • a SET value (numeric or expression result),
  • an @-priority indicator (see below).

An implementer building a command line must count parameters against the 16-slot ceiling; a 17th parameter is rejected. [D]

Priority @-indicators. A PPCL command may specify the command priority at which it acts using an @-prefixed indicator. These map one-to-one onto the command-priority ladder of §8: [S]

@-indicator Priority name scope_byte (§8.2) / User_command_priority value Tag
@OPER OPER (operator) 0x23 (35) [S]
@SMOKE SMOKE 0x22 (34) [S]
@EMER EMER (emergency) 0x20 (32) [S]
@PDL PDL (peak-demand limiting) 0x05 (5) [S]
@NONE NONE (release / lowest) 0x00 (0) [S]

A PPCL program that commands a point at, say, @EMER overrides any standard NONE-priority sequence and holds the point until a RELEASE (or @NONE) at an equal-or-higher priority frees it. Exactly these five @ levels exist; the intermediate host levels (host_2..host_6, values 10/15/20/25/30) and tec_ovrd (1) appear in the full User_command_priority enum but are not exposed as PPCL @-indicators. (Cross-ref §8 for the full priority ladder and the read-vs-write scope_byte dispatch.) [S]

14.5 PPCL program-management opcodes

All PPCL operations are AP2 function codes. The enumerate/upload family lives in the 0x09xx upload block; the editor family lives in the 0x41xx block. The editor opcodes carry the 12-byte SYST scope footer 01 00 04 "SYST" 23 3F FF FF FF at the end of the body — required, not padding; a body without it is rejected. (Cross-ref §7 for the directory-byte/error model and §6/§8 for the TLV envelope.) [S/W]

Opcode AP2 name Operation Status / class Tag
0x0985 AP2_UPL_ALL_PPCL upload all PPCL lines of a program (the bulk reader) upload, read-only [W]
0x0975 AP2_UPL_ADDED_PPCL upload only lines added since the last sync upload, read-only [S]
0x0965 AP2_UPL_DEL_PPCL report lines deleted since the last sync upload, read-only [S]
0x0955 AP2_DBCHANGE_PPCL DB-change notification for PPCL (replication trigger) replication push [S]
0x030A AP2_PPCL_SAVE persist program changes to non-volatile store state-changing [S]
0x4100 AP2_PPCL_ADD_LINE add/create one program line write (state-changing) [W]
0x4101 AP2_PPCL_EDIT_LINE edit one line write [S]
0x4103 AP2_PPCL_REMOVE_LINES remove a line range destructive [S]
0x4104 AP2_PPCL_ENABLE_LINES enable a line range write [S]
0x4105 AP2_PPCL_DISABLE_LINES disable a line range write [S]
0x4106 AP2_PPCL_CLEAR_TRACE clear all trace bits for a program state-changing [S]
0x4107 AP2_PPCL_PROGRAM_LOG report program execution log read [S]
0x4108 AP2_PPCL_SEARCH_NAME_TYPE find lines by statement type / referenced name read [S]
0x4109 AP2_PPCL_QUERY_PROGRAM query program metadata/state read [S]
0x410A AP2_PPCL_PROGRAM_DISPLAY display (decompiled) program source read [S]
0x410B AP2_PPCL_MODIFY_LINE modify one line in place write [S]
0x410C AP2_PPCL_COPY_LINE copy a line write [S]
0x410E AP2_PPCL_LOOK_LINES look at a line range (read interior) read [S]
0x412A AP2_PPCL_PROGRAM_DISPLAY_UNRESOLVED display only the unresolved lines read [S]
0x410F0x4111 AP2_PPCL_PDL_RESET/INIT/DISPLAY peak-demand-limiting control/init/display mixed [S]

Note on opcode polymorphism. Several opcodes select different operations by body shape, scope tag, and direction, so a robust dispatcher keys on (opcode, body shape) together, never on the opcode alone. The canonical example is the 00 FF read trailer versus the 00 00 command trailer carried on the same addressing grammar (see §6.4). [W]

14.5.1 Enumerating a program — 0x0985 (AP2_UPL_ALL_PPCL)

Request carries the system scope, the target program name, and a cursor; response carries program metadata, the source-text lines as TLVs, and a 1-byte has-more flag (0x01 more / 0x00 last). The client re-issues with the advanced cursor until has-more is 0x00, yielding the full line-numbered source. [W]

09 85  00 00  01 00 04 "SYST"  01 00 <len> "<program-name>"  00 <cursor>  00 00 00

The response body is a sequence of PPCL_data records (one per line). The PPCL_data structure, field order: [S]

Field Type Meaning Tag
name_space Name_space program name-space container [S]
name TEXT_ (TLV) program name [S]
line_status TEXT_ (TLV) rendered line-status text [S]
line_text TEXT_ (TLV) the source text of the line [S]
line_number UNSIGNED16 line number (1–32767) [S]
line_enabled BOOLEAN enabled flag [S]
line_traced BOOLEAN trace bit [S]
line_unresolved BOOLEAN references an undefined point [S]
line_failed BOOLEAN execution-failed flag [S]
line_looped BOOLEAN loop-detected flag [S]

14.5.2 Writing a program — 0x4100 (AP2_PPCL_ADD_LINE)

Body: the program-name TLV, a positional separator, the line-number/line-text payload, the line's encoded attributes, and the trailing 12-byte SYST footer. [W]

01 00 <len> "<program-name>"  00 00 00  01 00 <len> "<line-text>"  00 0A 00 00 00 00  01 00 04 "SYST"  23 3F FF FF FF

The request body decodes to PPCL_data + a User_profile block (user_logon TEXT_, point_priority, access_class); the User_profile carries the priority at which the writing user is authorized to act. A successful line write is acknowledged with an empty success (dir 0x01). [S/W]

Range operations (0x4103 remove, 0x4104 enable, 0x4105 disable, 0x4106 clear-trace) carry a PPCL_range body: [S]

Field Type Tag
name_space Name_space [S]
name TEXT_ (TLV) [S]
first_line UNSIGNED16 [S]
last_line UNSIGNED16 [S]

AP2_Ppcl_Search_Name_Type_Request carries a Statement_types : PPCL_statement_type[] array (the WHOP* token values of §14.3), so a client can ask "give me every line whose statement type is one of {…}". [S]

14.5.3 Safe program-transfer procedure (destructive-operation guidance)

0x4100/0x4101/0x410B/0x410C (writes), 0x4103 (remove), and 0x030A/0x4106 (save/clear-trace) modify panel runtime state and can restart program execution. A read-only client MUST NOT emit them. A client that does write programs MUST: [I]

  1. Snapshot the existing program first (walk 0x0985).
  2. Transfer all lines in the disabled state, then enable them once the full program is resident (a panel may begin executing a partially transferred program — line_enabled = false prevents premature execution).
  3. Persist with 0x030A (AP2_PPCL_SAVE) only after the complete program is present and validated.

A line that references a point not yet in the DB is retained as line_unresolved and does not execute correctly until the point exists. [D]


15. Scheduling (TOD / EQS)

P2 carries two related scheduling subsystems: TOD (time-of-day point/command scheduling, the simpler per-point schedule) and EQS (equipment scheduling — zone-based occupancy command tables with start-stop time optimization). Both are edited and uploaded over P2 in the 0x09xx/0x45xx/0x50xx opcode blocks, and both interact with PPCL (§14): a PPCL program reads/sets schedule state with the TOD/TODMOD/TODSET/SSTO/HOLIDAY statements. [D/S]

15.1 Time-of-day mode bitmask

A point's (or zone's) time-of-day mode is an additive bitmask of day-class bits, combined by the PPCL TODMOD statement. The standard bit weights are powers of two: [D]

Bit weight Day class Tag
1 (schedule class 1) [D]
2 (schedule class 2) [D]
4 (schedule class 3) [D]
8 (schedule class 4) [D]
16 HOLIDA (holiday) [D]

Mode semantics: DAY = occupied, NIGHT = the complement (unoccupied). TODSET re-evaluates the schedule after a power failure and carries a recommand flag controlling whether the schedule re-issues its current command on restart. [D]

The schedule day index that selects which day-class applies is the Schedule_days enum: Sunday=0 … Saturday=6, then Replacement1Replacement7 (values 7–13) for special/replacement (e.g. holiday-override) days. [S]

15.2 TOD opcodes (per-point time-of-day scheduling)

Opcode AP2 name Operation Tag
0x4500 AP2_TOD_POINT_ADD add a TOD-scheduled point [S]
0x4501 AP2_TOD_POINT_REMOVE remove [S]
0x4502 / 0x4503 AP2_TOD_POINT_ENABLE / DISABLE enable/disable schedule [S]
0x450E AP2_TOD_POINT_DISPLAY display the scheduled point [S]
0x4504 AP2_TOD_CMD_ADD add a TOD command (a timed action) [S]
0x4505 / 0x4506 AP2_TOD_CMD_REMOVE / DISABLE remove/disable [S]
0x450F AP2_TOD_CMD_DISPLAY display the command [S]
0x09B00x09B3 AP2_{DBCHANGE,UPL_DEL,UPL_ADDED,UPL_ALL}_TOD_POINT replication / upload of TOD point definitions [S]
0x09B40x09B7 AP2_{DBCHANGE,UPL_DEL,UPL_ADDED,UPL_ALL}_TOD_CMD replication / upload of TOD commands [S]

The supervisor↔panel TOD-define path (AP2_UC_Define_TOD_Request, AP2_UC_Define_TOD_Ovrd_Request) is structured as name : TEXT_, p1_command : UNSIGNED8, then an opaque tx_buffer : Data_byte[] (length-prefixed by nrOftx_buffer : UNSIGNED16); the upload form (AP2_UC_Upload_TOD_Response) returns nrOfrx_bytes : UNSIGNED16 + rx_bytes : Rx_byte[]. The tx_buffer/rx_bytes payload is a P1/FLN-device command stream tunneled through the panel — its interior byte layout is FLN-device-specific and is not decoded at the P2 layer. [S] [OPEN — interior of the TOD tx_buffer/rx_bytes blobs is opaque at the P2 layer; needs an FLN-scoped capture to map.]

15.3 EQS — equipment scheduling

EQS schedules a zone (a group of points) into occupancy modes, each mode driving a command table of point setpoints, with optional start-stop time optimization (SSTO) to pre-start equipment so the zone reaches setpoint by the occupied time. [D]

EQS opcode families:

Block Opcodes Purpose Tag
Zone edit 0x50000x5005 (AP2_EQS_ZONE_ADD/REMOVE/MODIFY/LOOK/ENABLE/DISABLE) create/modify/query an EQS zone [S]
Command-table edit 0x50180x501B (AP2_EQS_CMD_TABLE_ENTRY_ADD/MODIFY/REMOVE/LOOK) per-mode command entries [S]
Mode edit 0x50200x5025 (AP2_EQS_MODE_ENTRY_ADD/MODIFY/REMOVE/LOOK/ENABLE/DISABLE) occupancy-mode entries [S]
Override edit 0x50280x502B (AP2_EQS_OVERRIDE_ADD/MODIFY/REMOVE/LOOK) timed/manual overrides [S]
Display / log 0x50350x5039 (AP2_EQS_DISPLAY_ZONE/MODE_ENTRY/CMD_TABLE, ZONE_LOG, DISPLAY_OVERRIDES) read-only reports [S]
SSTO setup 0x503A0x503D (AP2_EQS_SSTO_SETUP_GENERAL/START/STOP/NIGHT) configure optimization [S]
SSTO look/control 0x503E0x5044 (AP2_EQS_SSTO_LOOK_*, SSTO_RESET/ENABLE/DISABLE) query/control optimization [S]
SSTO display 0x50500x5053 (AP2_EQS_SSTO_DISPLAY_GENERAL/START/STOP/NIGHT) display optimization state [S]
Member log 0x5054 (AP2_EQS_MEMBER_LOG) per-member status log [S]
Replication / upload 0x09570x0959, 0x09670x0969, 0x09770x0979, 0x09870x0989 (AP2_{DBCHANGE,UPL_DEL,UPL_ADDED,UPL_ALL}_EQS_{ZONE,CMD_TABLE,MODE_SCHED}); 0x09A40x09A7 (EQS_OVERRIDE); 0x095C0x095F, 0x097C0x097F, 0x098C0x098F (SSTO_GENERAL/START/STOP/NIGHT) DB-change + upload of all EQS sub-objects [W for 0x0988]

EQS zone definition (AP2_EQS_Zone_Add_Request = User_profile + Eqs_zone_definition): [S]

Field Type Meaning Tag
nrOfnames + names[] UNSIGNED16 + Team_response[] the member points of the zone [S]
eqs_zone_data Eqs_zone_data the zone's scheduling attributes (below) [S]
nrOfrecharacterization_values + recharacterization_values[] UNSIGNED16 + Recharacterization_value[] per-member command overrides [S]

Eqs_zone_data fields: zone_enabled (BOOLEAN), description (TEXT_), access_class, min_off_time (UNSIGNED16 — minimum off time, equipment-protection), recmd_after_warmstart (BOOLEAN — re-command after warmstart, mirrors TODSET recommand), warmstart_delay (UNSIGNED16), state_text_table, default_mode (SHORT_), english_units (BOOLEAN), optimization_osv (BOOLEAN — SSTO enable). [S]

Eqs_cmd_table_data (one command-table entry): mode (SHORT_) + command_value (FLOAT_, big-endian f32 on the wire per §8.3.2) + command_offset (UNSIGNED16). A command-table sequence (Eqs_cmd_table_sequence) is name_team + name_name + nrOfcmd_table_entries (UNSIGNED16) + the entry array. [S]

Recharacterization_value (per-member override): member_number (UNSIGNED16) + logical_value (FLOAT_) + point_priority (Point_priority — the §8 ladder) + control_status. So an EQS zone commands each member at an explicit priority, exactly like a PPCL @-command. [S]

15.3.1 SSTO — start-stop time optimization

SSTO is referenced both by PPCL (SSTO, SSTOCOEF statements, §14.3) and by the EQS SSTO opcode family above. SSTO computes an optimized equipment start time (and night-setback strategy) from learned coefficients so the zone reaches occupied setpoint by the scheduled occupancy time. The SSTO_GENERAL/START/STOP/NIGHT four-way split (visible across the setup/look/display opcode rows) corresponds to the four configuration blocks: general parameters, optimized-start, optimized-stop, and night-cycle. [D/S]


16. Database, Bulk Transfer, On-Disk (.P2) Format, Application Catalog & Firmware

16.1 Bulk database transfer

A panel database is moved across P2 record-by-record, not as a single monolithic blob. Each record is its own request that returns either a success (dir 0x01) or a 2-byte fault/error code (dir 0x05), corroborating the directory-byte/error model of §7. The transfer is organized into sections gated by firmware revision — a section unsupported by a panel's firmware is skipped or rejected per-record rather than aborting the whole transfer. [D/W]

The transfer surface is the UPL_* / DBCHANGE_* / *_DB_GET / *_DB_REPLACE opcode families: the 0x09xx UPL_ALL_*/UPL_ADDED_*/UPL_DEL_* block enumerates each object class (points, PPCL, TEC, trend, EQS, SSTO, TOD), and per-domain *_DB_GET/*_DB_REPLACE pairs move whole sub-databases (e.g. 0x0337/0x0338 USER_ACCT_DB_GET/REPLACE, 0x0357/0x0358 ACCESS_GROUPS_DB_*, 0x0362/0x0363 EMS_DB_*, 0x040A/0x040B ENUM_TYPE_DB_GET/REPLACE, the 0x06xx CAL_DB_*/DST_DB_* calendar blocks). 0x0950 AP2_DOWNLOAD_ME initiates a download to a panel. [S]

The replication-direction mechanics (notify/pull/changes push) live in the 0x46xx EBLN family and are specified in §5.3 (replication). [S]

16.2 On-disk (.P2) panel-database format

A panel database serialized to disk (file extension .P2, e.g. P2_Archive snapshots) uses TLV record framing persisted to a file — broadly the same style of encoding the panel exchanges on the wire, written to disk. This subsection is observed from a small sample of saved .P2 archive files (not from wire traffic) and is included only to help an operator read their own database exports; treat it as indicative rather than exhaustive. [I]

Record framing (observed in .P2 archive files across MBC-10 / SCU / PXCC snapshots — a limited sample, on the order of a few hundred records for a compact panel): [I]

Element Value Tag
Record delimiter SYN 0x16 at the start of each record [I]
Field encoding ASCII-hex fields [I]
Record terminator CRLF [I]
Record-type byte at offset ~6 within the record [I]
0x41 a PPCL line record [I]
0x020x06 point-definition records [I]
Format byte 0x02 = legacy generation / 0x03 = BACnet-era generation [I]
Subtype byte 1=LDI, 2=LAI, 4=analog, 6=LDO, 0x15=enum [I]

Note — distinct numbering. The .P2 on-disk record-subtype byte (1=LDI, 2=LAI, 4=analog, 6=LDO, 0x15=enum) is a separate numbering from the wire Point_type enum of §11.2 / Appendix A (where 2=LDO, 3=LAI, 4=LAO); the two encode the same point taxonomy but are not byte-for-byte equal and MUST NOT be cross-mapped (e.g. on-disk 2=LAI but wire 2=LDO). The format byte (0x02/0x03) is the on-disk reflection of the firmware-generation split described in §16.5. [I] [OPEN] The full record-type byte enumeration and the exact field order within each .P2 record type beyond the leading bytes are not pinned; this would need a per-record decode pass against a known panel DB.

16.3 Application catalog (controller applications)

A controller application is a pre-engineered point/control template loaded into a field controller (TEC/MEC/lab/fume-hood and similar). On the network it is identified by a compact key and resolves to an ordered subpoint list: [D/S]

Element Form Tag
Application number numeric (the catalog is ~1000+ applications) [D]
Application revision 4-character revision string [D]
Scope controller class (TEC / MSTP / LAB / FHOOD, etc.) [D]
Resolution application# + class → an ordered list of subpoints (each with type, reference INPUT/OUTPUT, slope/intercept/units) [D/S]
Family tag Application_family enum: eqs=5, ppcl_program=7, tec_na=16, uc=17, tcu=18, lon=19, p1_pxc=20, bacnet_mstp=21, tec_eu=257, plus pdl_area/pdl_load_group/decision_table/loop (1–4) [S]

A live device advertises its application number at subpoint 2 (subpoint 0 = bundled controller point, subpoint 1 = controller address, subpoint 2 = application). A client reading subpoint 2 learns which application a controller runs, and therefore which subpoint-list template applies (cross-ref §9 — FLN/sub-device browse, and §11 — point model). [D]

The master application/point-team library is the set of point-team descriptor files — one ASCII/XML descriptor per controller family in the Insight/WCIS .ptd library, or equivalently the Tecpnts.dbf + TECAppl.dbf tables in DataMate. Both encode the same logical model: a TEAM_DESCRIPTION / POINT_TEAM with ANALOG_MEMBER/DIGITAL_MEMBER/ENUM_MEMBER entries keyed by SUBPOINT_NUMBER (the FLN subpoint index / on-wire address), each carrying type (LAI/LAO/LDI/LDO/LENUM), reference (INPUT/OUTPUT), and SI+English slope/intercept/units. The application catalog is therefore the bridge between an advertised application number and the concrete subpoint semantics a client must apply to decode that controller's points. [D/S]

The FLN device classes an application can target are the FLN_Device_Type enum: DPU=0, MPU=1, TCU=2, TEC=3, UC=4, PXM=5, FSCS=6, GATEWAY=7, FLOAT_GATEWAY=8, P1BIM=9 (P1 Bus Interface Module), GLOBAL_IO=10. [S]

16.4 Reading a controller application from a live panel

The upload opcodes for the application/team model: 0x0986 AP2_UPL_ALL_TEC (upload all TEC application data), 0x400F AP2_TEAM_DESC_UPLOAD (upload a point-team descriptor), 0x4015/0x4016 AP2_TEAM_DESC_DB_CHANGE / TEAM_MEMBER_DB_CHANGE (team DB-change replication). Walking 0x0986 for a panel returns its loaded controller applications, which a client resolves against the master .ptd/DBF library by application number + revision. [S]

16.5 Firmware and revision identity

A panel reports its identity in the AP2_CABINET_DISPLAY response (opcode 0x010C, AP2_Cabinet_Display_Response). The leading fields: [W/S]

Field Type Meaning Tag
revstring TEXT_ (TLV) firmware-revision string (wire-observed e.g. PME1252 / PXME V2.8.10 APOGEE / Oct 28 2013) [W]
firmwaretype TEXT_ (TLV) hardware/platform string (separate from the firmware revision) [S]
linktime TEXT_ (TLV) firmware build/link timestamp [S]
firmware_checksum UNSIGNED16 firmware image checksum [S]
config_byte1..31 UNSIGNED8 / BOOLEAN per-cabinet configuration flags (alarm_config, report_config, …) [S]
config_checksum UNSIGNED16 config-block checksum [S]
battery_state UNSIGNED16 battery status [S]
node_name / site_name / bln_name TEXT_ (TLV) the cabinet's identity (cross-ref §3.4/§6) [W/S]
ip_addr_settings, MII/MAC, BACnet settings (sub-structs) network configuration [S]

The panel reports a firmware-revision string and a separate hardware string; together they form the firmware+hardware identity. That identity selects the wire STRING_TYPE — whether name/string fields are encoded in RAD-50 (pre-IP / early platforms) or ASCII (P2/IP platforms) — per the per-platform APOGEE_PANEL_REVISION_LIBRARY (firmware.rev.xml): each platform class (RCU_P2, MEC, MBC, SCU, MODULAR, COMPACT, FLNC, MXL, …) carries STRING_TYPE, REV_STRING, REV_LEVEL, CAB_TYPE, and CHECK_HW_STRING. A client decoding name fields must therefore key its string codec on the reported revision/hardware, not assume a single encoding (cross-ref §8 — the RAD-50 vs ASCII string codec and the 40-character RAD-50 alphabet). [D/W]

Firmware-keyed compatibility knobs. Two legacy compatibility settings are gated by firmware identity: [D]

  • Legacy P2 Host ID field — an older host-identity field retained for back-compatibility with pre-IP supervisors.
  • Extended Timeout flag0x003E AP2_CABINET_TIMEOUT_NORMAL / 0x003F AP2_CABINET_TIMEOUT_EXTENDED toggle a normal-vs-extended communications timeout; extended timeout accommodates slower links (modem/AEM-tunneled serial, cross-ref §4/§transport).

16.6 Cabinet lifecycle and destructive opcodes

The 0x00xx/0x01xx cabinet block contains the panel lifecycle and restart operations. Several are destructive and MUST be blocklisted in any read-only or scanning tool: [S/I]

Opcode AP2 name Effect Destructive? Tag
0x010C CABINET_DISPLAY read firmware/config/identity no (read) [W]
0x0108 CABINET_BOOT_MONITOR enter boot monitor YES [S]
0x010A CABINET_COLDSTART cold restart (clears state) YES [S]
0x010B CABINET_WARMSTART warm restart YES [S]
0x0128 CABINET_SET_BLN_ADDRESS change BLN address YES (reconfig) [S]
0x0041/0x0042 CABINET_ADD / CABINET_REMOVE add/remove a cabinet from the node table YES [S]
0x0046/0x0047 CABINET_ONLINE / CABINET_OFFLINE force a node online/offline YES [S]
0x01200x0126 CABINET_SET_*_BAUDRATE change MMI/FLN/BLN baud (can sever a link) YES (reconfig) [S]

Destructive-operation flag. A conformant read-only client or scanner MUST refuse to emit 0x0108, 0x010A, 0x010B, 0x0128, the CABINET_ADD/REMOVE/ONLINE/OFFLINE set, the baud-set block, and the state-change opcodes 0x0034/0x0035 (SET_NODE_STATE/SET_COMPLETE_NODE_STATE) — these reboot, reconfigure, or evict a panel. Document them for completeness; never invoke them against production hardware. [I]

17. Security Considerations

This section states plainly what P2 does and does not protect, characterizes the pre-authentication attack surface for defenders, and gives implementers and owner-operators concrete guidance. It is descriptive, not an attack toolkit: no exploit code or step-by-step intrusion procedure appears here. The intended reader is an owner-operator securing their own legacy P2 plant.

17.1 P2 has no cryptographic security

P2 provides no authentication, no integrity protection, and no encryption at the protocol layer. [W][I] There is no password exchange, no key, no challenge-response, no nonce, no message authentication code, and no session token that an attacker cannot also mint. Every byte travels in cleartext over TCP; anyone with packet capture on the path reads point values, point names, node names, firmware strings, control-program text, and routing tables directly. [W] Anyone who can send TCP to a node's P2 port can speak the protocol.

The protocol's framing predates IP and carries the trust model of an isolated, physically-secured field bus forward onto Ethernet unchanged. Treat a P2 segment exactly as you would treat an unswitched serial trunk in a locked mechanical room: every device on it is implicitly trusted, and the only real boundary is the wire itself.

17.2 The only gate is the BLN name

The single admission check that exists at the P2 layer is the BLN-name match in the routing slots and the IdentifyBlock body (see §3.4, §6.4). [W] A peer presenting the wrong BLN name is rejected at the transport layer — a field panel answers with a TCP RST, a supervisor listener with a graceful TCP FIN — before any application processing occurs, and no node-table side effect is written for a wrong-BLN attempt. [W] (Verified by controlled test: 24 wrong-BLN handshakes across 8 variants produced 24 RSTs and zero table entries.)

This makes the BLN name function as a read-only-reject access gate, not as a secret:

  • It is a shared, low-entropy, network-wide identifier, not a per-node credential. One value admits every node on the network. It appears in cleartext in every frame the network ever sends (§6.4), so a passive listener on the segment learns it from the first captured frame. [W]
  • It is case-significant and exact-match (§6.4), but it is not rotated, not salted, and not tied to any device identity. [D][I]
  • Knowing it is the only prerequisite for full peer participation. Once a peer presents the correct BLN, the node accepts the handshake and records the peer; acceptance of data service is a separate, later check (the slot-4 identity shape, §6.4), but registration itself is gated only by BLN-correctness. [W] A right-BLN handshake with a novel slot-4 identity writes a permanent node-table entry even when the panel then refuses to serve data to that identity. [W]

The practical consequence: the BLN name keeps out an attacker who has no foothold on the segment and no capture, and nothing more. An attacker who has sniffed one frame, or who guesses a short conventional name, is past the only gate.

Node-name acceptance behavior — what the node does with slot[1] (destination) and slot[3] (source identity) once the BLN matches — is detailed in §6.4 and is part of the same ungated surface; see the cross-reference there for the silent-drop-vs-service distinction.

17.3 Pre-authentication information disclosure

Because there is no authentication, every read-style operation is a pre-authentication read for any peer that presents the correct BLN. The following are directly observable on the wire with no credential beyond the BLN name. This list is for defenders sizing their exposure; each item is a normal protocol operation, not a bug to be triggered.

Disclosed item Mechanism Tag
Panel firmware revision, type, link/build date, firmware checksum 0x010C CABINET_DISPLAY returns a revstring / firmwaretype / linktime / firmware_checksum block (e.g. firmware string PME1252 / PXME V2.8.10 APOGEE / Oct 28 2013 observed on the wire); the full response layout is wire-confirmed (§10.5) [W]
Panel identity: node name, site name, BLN name Same 0x010C response carries node_name, site_name, bln_name fields (wire-confirmed, §10.5) [W]
Panel network config: IP settings, MAC (configured + hardware), link/MII state, BACnet settings Same 0x010C response carries ip_addr_settings, configured_mac_address, hardware_mac_address, link_status, BACnet sub-structures [S]
Supervisor / host name 0x0050 (DISK_LOG-family status query) returns the supervisor name to an unauthenticated peer [W]
Node roster (every peer's name, IP, and state) The full node-name table is obtainable via 0x4634 REPL_PULL with no authentication — the body is the replicated roster (8-byte header + per-node TLV(node-name) then u32 version entries, $paneldefault first; §5.3); 0x464C REPL_DIAG_NODELIST returns a node list [W]
Operator names / routing table contents [OPEN] single-shot responses observed once at 0x464E / 0x4650; these are not enum-confirmed function codes (they resolve to no AP2 enum member and are treated as probe noise — see §9.5), so they are not presented here as confirmed disclosure opcodes [OPEN]
Point existence, names, values, descriptors, units, alarm config Read/browse/enumerate families 0x0220 / 0x0271 / 0x0273 / 0x0981 and the 0x09xx enumerate family [W]
Complete node-state table 0x33 GET_COMPLETE_NODE_STATE returns node_table : Node_complete_state[] [S]
PPCL control-program source PPCL upload family (0x4133 and the program-report opcodes) returns program text [W][I]

The supervisor's TCP/5033 listener accepts arbitrary inbound connections and parses attacker-supplied frames; it does not return data to a non-peer but is a parser attack surface and an identity fingerprint (FIN-vs-RST close distinguishes supervisor from panel; per-opcode close timing exposes the supervisor's originated-handler set). [W] The supervisor's optional TCP/5034 push listener, by contrast, rejects at the accept stage (uniform fast close) and is comparatively hardened — an asymmetric design where the read channel is permissive and the push channel is locked down. [W] An owner should not assume the supervisor is uniformly exposed, nor assume it is uniformly defended.

No multicast discovery beacon exists (§5.2); there is nothing to passively "listen for" to enumerate a P2 network, and a captured frame plus a TCP/5033 connect are the realistic discovery primitives. [W]

17.4 Ungated destructive operations

The same absence of authentication means state-changing and destructive operations are ungated at the protocol layer. A peer that clears the BLN gate can issue them as readily as a read. These are documented here so defenders understand the blast radius; the operations themselves are flagged DESTRUCTIVE and must never be sent to production equipment.

Operation Opcode(s) / ASDU Effect Tag
DESTRUCTIVE — panel cold start 0x010A CABINET_COLDSTART (AP2 266) Reinitializes the panel; clears volatile state; observed in census [W][S]
DESTRUCTIVE — panel warm start CABINET_WARMSTART (AP2 267) Restarts panel firmware [S]
DESTRUCTIVE — drop to boot monitor CABINET_BOOT_MONITOR (AP2 264) Drops the panel into its boot monitor; takes it off the network [S]
DESTRUCTIVE — set node state SET_NODE_STATE (AP2 52) — body node_changed, node_table_event, node_complete_state Forces a peer's state (e.g. offline, failed, orderly_removed); node_table_event includes node_ostracized [S][I]
DESTRUCTIVE — set complete node state SET_COMPLETE_NODE_STATE (AP2 53) — body node_table : Node_complete_state[] Overwrites the entire node-state table [S]
DESTRUCTIVE — evict / remove node the node-eviction / remove-node / make-offline operations Force-evicts a peer from the BLN (node-eviction DoS) [S][I]
DESTRUCTIVE — point write / command 0x0240 AP2_POINT_CMD_VALUE and the 0x05xx command family Overrides physical outputs at a chosen command priority (§8.2) [W][S]
DESTRUCTIVE — flash database clear / restore CLEAR_FLASH_DBASE (AP2 21298), RESTORE_FLASH_DBASE (AP2 21297) Erases or overwrites the panel's saved database [S]
Denial of service (parser stall) 0x46xx replication-class (e.g. 0x4636, 0x4647) with a malformed/wildcard-SYST body Panel stops responding at the TCP layer for seconds [W]
Persistent node-table write right-BLN handshake with a novel source identity Registers that identity permanently in the node table (§17.5) [W]

The DoS surface is a class, not a single opcode: at least two 0x46xx replication-class opcodes have been observed to silently stall a panel, and the silent-discard behavior spans several specific request sizes. A defender should treat the whole replication-class opcode range as a potential availability risk from an untrusted peer, not just one value. [W][I]

17.5 Registration versus impersonation

There are two distinct ways for a peer to be "accepted," with very different forensic footprints. Both are possible at the protocol layer with only the correct BLN. [W]

  • Registration (more invasive, leaves a trail). Presenting a novel source identity with the correct BLN causes the node to write a persistent (Permanent) entry into its NODE NAME TABLE binding that identity to the sender's IP — the node-table-entry write. [W][S] This happens even if the panel subsequently refuses to serve data to that identity. [W] Every distinct identity tried accumulates its own permanent entry (and, at the observed site, orphan site-grouping containers as well), so a careless scanner or an attacker iterating identities bloats the node table and can leave stale entries that persist after IP/name changes. The footprint is real but bounded: one entry per distinct identity, and wrong-BLN attempts write nothing. [W]
  • Impersonation (less invasive, leaves no new trail). Presenting an existing table identity (e.g. the real supervisor's name) serves full data with no new table entry and no IP rebind — the permanent entry already exists and does not auto-update to the impostor's IP. [W] This is the stealthier path: an attacker who has learned a legitimate node name (trivial, given §17.3) reads as that node with zero forensic residue.

The asymmetry matters for both defenders and tool authors. Registration is detectable after the fact by auditing the node table for unexpected entries; impersonation is not. Adding a node table entry is the more invasive write (persistent state change); impersonating an existing one is the quieter read.

17.6 Guidance for implementers and owner-operators

The following is the read-only-by-default discipline appropriate to a protocol with no authentication. It applies to anyone writing P2 tooling for, or operating, their own hardware. [I]

For tool authors:

  1. Read-only by default. A client SHOULD implement only read, browse, and enumerate operations. Write, command, node-state, coldstart/warmstart, node add/remove/ostracize, and flash operations SHOULD NOT be implemented in general-purpose tooling, and SHOULD be refused in code (a hard blocklist, not a comment) even behind a flag. An attacker pays the same cost to add a write path whether or not read tooling exists; the asymmetry favors legitimate owners.
  2. Rate-limit. Inter-request delays on the order of seconds, back-off on consecutive errors, single persistent connection per peer (§7), no broadcast storms. The replication-class DoS surface (§17.4) means even read-shaped traffic can stall a panel; pace accordingly.
  3. Never send destructive or node-state opcodes to production. Test only against an isolated lab panel or a mock. Coldstart, warmstart, boot-monitor, set-node-state, ostracize/remove, and flash operations are out of scope for any tool that may touch a live building.
  4. Prefer passive discovery and impersonation-free identity. Passive capture inventories a network without writing anything. If active discovery is unavoidable, understand it writes a permanent node-table entry per novel identity (§17.5) and that the only footprint-free read path is impersonating an existing peer — which is precisely the path a tool should not normalize.
  5. Validate lengths before buffering. Reject frames whose declared total_len exceeds a sane ceiling (§6.1) and guard against malformed routing headers (slot-walk misalignment fabricates phantom opcodes, see §6.4).

For owners:

  1. P2 is not safe to expose to any untrusted network. It carries the trust model of an isolated field bus. Do not route it to the Internet, to tenant VLANs, to guest Wi-Fi, or to any segment an attacker might reach.
  2. Segment P2 networks. Place panels and supervisors on a dedicated, firewalled automation VLAN. Permit TCP/5033 (and 5034 where used) only between known supervisor and panel addresses. Block everything else at the segment boundary.
  3. Do not treat the BLN name as a secret. It is on every frame in cleartext. It is an access label, not a password. Choosing an obscure BLN name buys almost nothing against an attacker with capture access and is not a control.
  4. Audit the node table. Unexpected permanent entries indicate a peer (tool or attacker) registered against the BLN. Impersonation leaves no such trace, so a clean table is not proof of no access.
  5. Restrict the management plane. Telnet/serial firmware consoles, SNMP, and the supervisor host itself are higher-value targets than any single panel; lock them down independently of the P2 segment.

18. Appendices

Appendix A — Value-enum reference

These tables are the spec-relevant value enumerations of the protocol, reproduced in full. They are [S] struct/metadata-derived: every value and name comes from the AP2 function-code enumeration, which is definitional truth for the protocol's vocabulary. The underlying-storage type of each enum is Int32 unless noted. Where the on-wire representation of a field is narrower than Int32 (for example a single command- priority byte, §8.2), the enum value still gives the meaning of the byte. Source enum stem shown in backticks after each title.

These tables are generated programmatically from AP2_all_enums.txt by gen_enum_tables.py (co-located with this section), so they can be regenerated verbatim if the source enum dump is updated.

User_command_priority User_command_priority_enum

The command-priority ladder for operator/host commands. The same ladder is the scope_byte of the scope tag (§8.2): a command acts at this priority, a read uses 0 (NONE). The five named wire levels are none/pdl/emer/smoke/oper; oper = 0x23, the system-scope write selector.

Value Name
0 none
1 tec_ovrd
5 pdl
10 host_2
15 host_3
20 host_4
25 host_5
30 host_6
32 emer
34 smoke
35 oper

Point_priority Point_priority_enum

The full priority array a point arbitrates over (the command ladder above plus the 16 BACnet priority-array levels).

Value Name
0 none
1 tec_ovrd
5 pdl
10 host_2
15 host_3
20 host_4
25 host_5
30 host_6
32 emer
34 smoke
35 oper
101 bacnet_1
102 bacnet_2
103 bacnet_3
104 bacnet_4
105 bacnet_5
106 bacnet_6
107 bacnet_7
108 bacnet_8
109 bacnet_9
110 bacnet_10
111 bacnet_11
112 bacnet_12
113 bacnet_13
114 bacnet_14
115 bacnet_15
116 bacnet_16

Point_type Point_type_enum

The logical point types (§11.2). ldi/ldo/lai/lao are the four basic digital/ analog input/output kinds; looal/looap are On/Off/Auto latched/pulsed; lfssl/lfssp Fast-Slow-Speed; lpaci pulse-accumulator/counter input; lenum enumerated; l2sl/l2sp two-state latched/pulsed.

Value Name
0 point_type_undef
1 ldi
2 ldo
3 lai
4 lao
6 l2sl
7 looap
11 lpaci
12 l2sp
13 looal
14 lfssl
15 lfssp
20 ldao
21 lenum
22 lfmssl
23 lfmssp
24 ppcl_lai

Cov_mask Cov_mask_enum

Change-of-value subscription mask bits — which classes of change trigger a COV report (§12).

Value Name
0 data
1 failure
2 alarm
3 service
4 priority
5 TCU
6 temp_all
7 proof_on

Point_cov_type Point_cov_type_enum

The aspect of a point a COV subscription tracks.

Value Name
0 all_types
1 point_values
2 point_priorities
3 point_status

Native_type Native_type_enum

The native value-type tags used inside point/attribute encodings.

Value Name
0 NVT_TYPE_UNKNOWN
1 NVT_TYPE_SIGNED_CHAR
2 NVT_TYPE_UNSIGNED_CHAR
3 NVT_TYPE_SIGNED_SHORT
4 NVT_TYPE_UNSIGNED_SHORT
5 NVT_TYPE_SIGNED_LONG
6 NVT_TYPE_UNSIGNED_LONG
7 NVT_TYPE_ENUM
8 NVT_TYPE_ARRAY
9 NVT_TYPE_STRUCT
10 NVT_TYPE_UNION
11 NVT_TYPE_BITF
12 NVT_TYPE_FLOAT
13 NVT_TYPE_SIGNED_QUAD
14 NVT_TYPE_REFERENCE
15 NVT_TYPE_UNSIGNED_QUAD

Sensor_type Sensor_type_enum

Input-conditioning / sensor type for analog input points (selects the panel's raw- to-engineering-units curve, applied with the point's slope/intercept, §11.5).

Value Name
0 voltage
1 current
2 resistance
3 pneumatic
4 thermister10k
5 thermister100k
6 ltype
7 rtd1k
8 rtd1k_385
9 nickel1000
10 nickeljci
11 nickeldin
12 thermister10type3

Node_complete_state Node_complete_state_enum

The full node-state value set as reported/set in node-state tables (§10). This is the value carried by GET/SET_COMPLETE_NODE_STATE and SET_NODE_STATE (§17.4). The local and global sub-enums below partition the same value space.

Value Name
2 unknown_protocol
3 no_cov_links
7 p3_protocol_detected
8 TIU_cabinet
9 remote
10 failed
11 extended_timeout
12 offline
13 ready
14 defined
15 orderly_removed

Node_local_state Node_local_state_enum

Value Name
3 no_cov_links
8 TIU_cabinet
10 failed
12 offline
13 ready
15 orderly_removed

Node_global_state Node_global_state_enum

Value Name
9 remote
11 extended_timeout
14 defined

Alarm_priority Alarm_priority_enum

Value Name
0 priority_0
1 priority_1
2 priority_2
3 priority_3
4 priority_4
5 priority_5
6 priority_6

Alarm_state Alarm_state_enum

Value Name
0 normal
1 alarm
2 high_alarm
3 low_alarm
4 trouble

Alarm_object_type Alarm_object_type_enum

The alarm-object kind associated with a point — standard vs enhanced, digital vs analog vs enumerated, plus BACnet-mapped variants.

Value Name
0 no_alarming
1 std_digital
2 std_single_analog
3 std_analog
4 enhanced_digital
5 enhanced_analog
6 enhanced_lenum
7 bacnet_alarm_analog
8 bacnet_alarm_digital

Scope_Type Scope_Type_enum

The abstract scope-level enumeration. Distinct from the scope-name TLV strings ("SYST"/"NONE"/"CC") and the command-priority scope_byte of §8.2; this enum names the levels of the addressing hierarchy abstractly.

Value Name
0 SCOPE_UNDEFINED
1 SCOPE_1
2 SCOPE_2
3 SCOPE_3
4 SCOPE_4
5 SCOPE_5
6 SCOPE_6

Baud_rate Baud_rate_enum

Serial baud-rate codes for FLN/MMI/BLN trunk baud settings (relevant to the SET_*_BAUDRATE cabinet operations and to serial/MSTP-side configuration).

Value Name
0 baud150
1 baud300
2 baud600
3 baud1200
4 baud2400
5 baud4800
6 baud9600
7 baud19200
8 baud38400
9 baud57600
10 baud115p2k
11 baud230p4k
12 baud76800

Application_family Application_family_enum

The application/control-object family a definition belongs to (PPCL, EQS, loop, PDL, TEC, UC, LON, P1, BACnet-MSTP).

Value Name
0 any
1 pdl_area
2 pdl_load_group
3 decision_table
4 loop
5 eqs
7 ppcl_program
16 tec_na
17 uc
18 tcu
19 lon
20 p1_pxc
21 bacnet_mstp
256 rwi
257 tec_eu
258 rwp

FLN_Device_Type FLN_Device_Type_enum

The kinds of device on a Field Level Network beneath a panel (§11.4.2). TEC = terminal-equipment controller, UC = unitary controller, PXM = operator display, P1BIM = a P1 BACnet interface module, GLOBAL_IO = a TXM I/O module.

Value Name
0 FLN_DEVICE_DPU
1 FLN_DEVICE_MPU
2 FLN_DEVICE_TCU
3 FLN_DEVICE_TEC
4 FLN_DEVICE_UC
5 FLN_DEVICE_PXM
6 FLN_DEVICE_FSCS
7 FLN_DEVICE_GATEWAY
8 FLN_DEVICE_FLOAT_GATEWAY
9 FLN_DEVICE_P1BIM
10 FLN_DEVICE_GLOBAL_IO
65535 FLN_DEVICE_UNKNOWN

PPCL_statement_type PPCL_statement_type_enum

The statement/keyword tokens of PPCL (Powers Process Control Language) as carried in compiled program records over the wire (§14). The WHOP prefix is an artifact of the vendor's token-name convention; the meaningful token is the suffix (e.g. WHOPGOTO = the GOTO statement, WHOPIF/WHOPTHEN/WHOPELSE = the conditional, WHOPSET = setpoint assignment, WHOPALARM/WHOPNORMAL = alarm/ normal commands).

Value Name
1 WHOPLOOP
2 WHOPON
3 WHOPOFF
4 WHOPACT
5 WHOPDEACT
6 WHOPEMON
7 WHOPEMOFF
8 WHOPWAIT
9 WHOPEPHONE
10 WHOPDC
11 WHOPENCOV
12 WHOPDISCOV
13 WHOPMIN
14 WHOPMAX
15 WHOPDCR
16 WHOPSET
17 WHOPEMSET
18 WHOPPDL
19 WHOPPDLDAT
20 WHOPONPWRT
21 WHOPONERR
22 WHOPTABLE
23 WHOPDBSWITCH
24 WHOPRELEASE
25 WHOPENALM
26 WHOPDISALM
27 WHOPALARM
28 WHOPNORMAL
29 WHOPLLIMIT
30 WHOPHLIMIT
31 WHOPPDLMTR
32 WHOPPDLSET
33 WHOPPDLDPG
34 WHOPDPHONE
35 WHOPTIMAVG
36 WHOPINITTOT
37 WHOPFAST
38 WHOPSLOW
39 WHOPAUTO
40 WHOPDAY
41 WHOPNIGHT
42 WHOPTODMODE
43 WHOPTOD
44 WHOPTODSET
45 WHOPSSTO
46 WHOPSSTOCOEF
47 WHOPHOLIDAY
48 WHOPENTHAL
49 WHOPMMI
50 WHOPGOTO
51 WHOPGOSUB
52 WHOPRETURN
53 WHOPIF
54 WHOPSAMPLE
55 WHOPEMFAST
56 WHOPEMSLOW
57 WHOPEMAUTO
58 WHOPENABLE
59 WHOPDISABLE
60 WHOPRELTCU
61 WHOPUNKNOWN1
62 WHOPUNKNOWN2
63 WHOPTHEN
64 WHOPELSE
65 WHOPASSIGN
66 WHOPLSTSQR
67 WHOPLOCAL
68 WHOPDIM
69 WHOPCOMMENT
70 WHOPDEFINE
71 WHOPSTATE

Appendix B — Opcode-family index

The 2-byte AP2 function code (§9.1) groups by its high byte/range into operation families. This index gives the prefix and its meaning; the full per-opcode body grammar is in §9 (Function-Code / Opcode Catalog). Families are derived from the the AP2 function-code enumeration and corroborated by wire census. [S][W]

Prefix / range Family Representative opcodes Tag
0x00xx Cabinet / system control, node-state, license, revision 0x010C CABINET_DISPLAY, 0x010A COLDSTART, 0x0050 status/disk-log, SET_NODE_STATE, REMOTE_NODE_CHECK [S][W]
0x02xx Point data: read, write/command, add/define, log, COV 0x0220 POINT_LOG_VALUE (read), 0x0240 POINT_CMD_VALUE (write), 0x0271 COV_ENABLE, 0x0273 COV_DISABLE, 0x0274 COV_ANNUNCIATE (COV value push), 0x0263 PointRemove [W][S]
0x04xx Enum-type, alarm-setup, alarm-mode, category, calendar/DST, language alarm-mode add/modify, category add/remove, calendar DB ops [S]
0x05xx Command/control and alarm report/ack, alarm-message 0x0508 AlarmPrint, 0x0509 AlarmAck, 0x0540-0x054D category family, 0x0560-0x0568 alarm-message family [W][S]
0x09xx Bulk enumerate / upload (point, PPCL, TEC, trend, EQS, SSTO, port, partner, UC, LON, MSTP) and FLN browse 0x0981 UplAllPoint, 0x0985 UplAllPPCL, 0x0986 UplAllTEC, 0x0988/0x0989 EQS, 0x098C-0x098F SSTO [W][S]
0x40xx Team/application, member, report descriptors 0x400F TeamDescUpload, 0x4010 MemberDescUpload, 0x4011 ReportDescUpload [W][S]
0x41xx PPCL program lines and program ops 0x4100 PPCL AddLine, 0x4103 RemoveLines, 0x4104 EnableLines, 0x4133 UplAllProgram [W][S]
0x42xx Controller/TEC and init-value ops 0x4200 ControllerLog/TECLog, 0x4220/0x4221 TEC init-value log, 0x4222 SetInitValue [W][S]
0x45xx Time-of-day point/command (TOD) 0x4500 TODPointAdd [W][S]
0x46xx Session, EBLN, replication, discovery, panel/node enumeration 0x4640 IdentifyBlock (session establish + heartbeat), 0x4633 REPL_NOTIFY, 0x4634 REPL_PULL (roster), 0x4635 REPL_PULL_MORE, 0x4636 REPL_CHANGES, 0x4644 TELNET_ENABLE, 0x464C REPL_DIAG_NODELIST [W][S]
0x50xx EQS zone / mode / SSTO equipment scheduling 0x5003 EQSZoneLook, 0x5020/0x5022 mode-entry, 0x5038 ZoneLog [W][S]
0x53xx Status / HOA-map / string queries 0x5354 HOA_MAP_LOOK [W][S]

Note on encoding: a wire opcode's two bytes are the big-endian AP2 function code. Where a §9 entry and the AP2 enum name differ cosmetically, both denote the same function code. Some opcodes are polymorphic (the same code selects different operations by body shape, scope tag, and direction); dispatch on opcode plus body shape, never opcode alone (§6.4).

Appendix C — Glossary

Term Meaning
AEM APOGEE Ethernet Microserver — an Ethernet front-end / serial-to-IP front for legacy panels.
AP2 function code The 2-byte wire opcode (§9.1); Siemens' internal name for the operation selector. A separate CPI function code exists with an AP2↔CPI mapping in the stack.
ALN Automation Level Network — the current name for the tier P2 runs on; synonymous with BLN.
ASDU Application Service Data Unit — the typed operation body carried after the opcode (request/indication/response/confirm service model).
BBLN BACnet BLN — a BLN reached over the BACnet side of discovery (uses I-Am).
BIM (P1) BACnet Interface Module — an FLN-side device bridging P1 to BACnet.
BLN Building Level Network — the peer trunk P2 operates on; identified by the BLN System Name, the protocol's only admission gate (§3.4, §17.2). Newer name: ALN.
CEC Controller / panel exec — the panel's executive (the cabinet/node firmware).
COV Change of Value — an unsolicited report (opcode 0x0274) pushed when a subscribed point changes; subscriptions are register/cancel (§12).
CPI The internal function-code namespace the AP2 function code maps to/from inside the stack.
EBLN Ethernet BLN — a BLN running over Ethernet/IP (the P2/IP case this spec covers).
EPing Ethernet Ping — the discovery/liveness probe used on Ethernet BLNs (optional multicast; §5.1, §5.2).
EQS Equipment Scheduling — the panel's equipment-scheduling subsystem (0x50xx).
FLN Field Level Network — the sub-bus beneath a panel carrying field controllers (separate namespace from the BLN; §3.8, §5.5).
MEC Modular Equipment Controller — a field-panel platform.
MLN Management Level Network — the top tier, where supervisory workstations reside.
P1 Protocol I — the FLN/fieldbus protocol beneath a panel (master/slave).
P2 Protocol II — the BLN/backbone peer protocol this document specifies ("APOGEE PII protocol").
PPCL Powers Process Control Language — the panel's control-program language; carried over the wire as compiled statement records (§14, Appendix A PPCL_statement_type).
PXC A field-panel controller platform (e.g. PXC/PME family).
PXM An operator-display / man-machine-interface device (also an FLN device type).
RACS Remote Access / Communications Subsystem — partner/port/system remote-access ops (0x46xx/RACS family).
RAD-50 A 40-symbol character packing (3 chars per 16-bit word) used by pre-IP revisions for names; P2/IP revisions use plain ASCII (§8.4). Alphabet: space, A–Z, $, ., ?, 0–9.
SSTO Start/Stop Time Optimization — the optimal-start/stop scheduling subsystem within EQS.
TEC Terminal Equipment Controller — an FLN field controller.
TOD Time Of Day — scheduled point/command operations (0x45xx).
UC Unitary Controller — an FLN field controller class.

Appendix D — Open-questions register

The honest gap list. Every item here is [OPEN]: not yet confirmed to the byte level by capture or test. Each gives what is known, what is missing, and the specific test that would confirm or falsify it.

  1. COV condition/priority block — asserted values. Known: 0x0274 is the COV value push; its body is wire-confirmed (§12.3.3) — count, then per point a u16 name_space (observed 00 00), the name TLV, an (empty for top-level points) 01 00 00 suffix TLV, the f32 present value, and a fixed 10-byte trailing condition/priority block. The block's field order is now pinned: the Annunciate_request ASDU defines exactly ten status fields after the value (point_priority, control_status, out_of_service, failed, proof_on, operator_disabled, program_disabled, commanded_to_alarm, alarm_state, alarm_priority) and the wire block is exactly ten bytes, so it is one byte per field in schema order [W position/size; S field order] — wire-consistent (the only non-zero byte in the normal-state corpus is control_status at +1). Missing: the asserted values — exactly what each alarm/flag/priority byte reads when a point is actually in alarm / failed / out-of-service / held under command (all captured pushes were normal-state, so those bytes never asserted). Test: capture a 0x0274 push for a point in alarm and one commanded at a non-default priority; the bytes that go non-zero confirm the per-field values.

  2. Heartbeat-miss count for failed-node transition. Known: there is no application-layer ACK frame — the dir == 0x01 success (or dir == 0x05 error) response, matched by the echoed sequence, is the acknowledgment (§7.2) [W]; P2 relies on TCP for delivery/retransmit and has no app-layer retransmit (§7.2) [W]; the ~10 s heartbeat cadence is observed. The request→response latency distribution is now measured — median round-trips range ≈ 6 ms (ping/COV) to ≈ 53 ms (trend), p95 < ~2× median, up to 9 requests pipelined (§7.2, §6.5) [W]. Missing: the exact heartbeat-miss count / ACK-timeout that declares a peer failed is not pinned. Test: on a controlled peer, count the heartbeat misses that trigger a failed-node transition.

  3. Error-code value↔class meanings. Known: the wire error tails observed are 0x0003 (not found, dominant), 0x00AC (not supported), 0x0002 (out-of-scope), 0x0E11 (already exists), 0x0E15 (not commandable), 0x0009 (one occurrence, meaning unconfirmed); the AP2 error-class names exist (not-supported, bad-tag-value, bad-packet-length, etc.). Missing: the complete value→class map and the meaning of less-common codes (e.g. 0x0009); whether per-opcode error namespaces exist. Test: drive each error-class condition deliberately on a lab panel (malformed length, bad tag, wrong element count) and record the returned code, building the full map.

  4. FLN / P1 and serial-AEM frame bytes. Known: FLN points are a separate namespace reached via the 0x09xx browse family over an established node session (§5.5); P1 is the fieldbus beneath the panel; an AEM fronts serial panels. Missing: the on-wire byte layout of P1/FLN frames themselves and of the serial-AEM encapsulation — this spec covers the BLN/P2-over-IP frame, not the fieldbus frame. Test: capture FLN-scoped browse/enumerate traffic and, if accessible, the serial side of an AEM, to pin the P1/FLN and AEM frame formats.

  5. Sub-opcode byte structure. Known: some opcodes carry a 2-byte sub-field immediately after the opcode (e.g. 00 01 / 00 00) before the body, and the presence/value is opcode-specific; 0x4640 carries none (§6.4). Missing: a complete per-opcode catalog of which opcodes have a sub-field and what each sub-field value selects (it appears to be a request-variant/mode discriminator in some, a reserved constant in others). Test: for each opcode that shows a non-trivial sub-field in capture, vary it on a lab panel and observe the behavioral change to determine whether it is a mode selector or a constant.

  6. Polymorphic-opcode full dispatch map. Known: several opcodes select different operations by body shape, scope tag, and direction; the spec advises dispatching on opcode plus body. Missing: the exhaustive enumeration of body shapes per polymorphic opcode and which operation each selects. Test: for each known-polymorphic opcode, capture every distinct body shape against a lab panel and label the operation produced.

Appendix E — Evidence-tag legend and lineage pointer

Every non-trivial claim in this document carries an inline evidence tag so a reader can weigh provenance. The tags are:

Tag Meaning
[W] Wire-verified — observed directly in a packet capture or the opcode census. Ground truth for wire-format claims.
[S] Struct/metadata-derived — from the AP2 function-code enumeration or the ASDU structure definitions. Definitional truth for field names, types, and order, but not by itself proof of the on-wire byte offset.
[D] Doc-sourced — a behavioral, topology, or semantic statement from vendor documentation/help. Never presented as a byte-level wire fact.
[I] Inferred / synthesis — reasoned from [W], [S], and/or [D] above.
[OPEN] Not yet confirmed; needs a capture or test. Collected in Appendix D.

Precedence rule (consistent with §1): when a [W] wire observation and an [S]/[D] source disagree, the wire wins for what bytes actually appear; the [S]/[D] source still defines the intended meaning of those bytes. A field-layout table is tagged [S] when it derives from the ASDU structures and [W] when its offsets are confirmed on the wire; the two are distinct levels of confidence and this document keeps them separate rather than presenting a struct field order as a proven byte offset.

For the lineage of the protocol itself — Powers Protocol II → Landis & Gyr → Siemens APOGEE, and the System 600 / "PII" heritage that explains the point-type, priority, and PPCL vocabulary reproduced in Appendix A — see §1 (Overview and Architecture) and the informative lineage discussion there.