Skip to navigation

Writing a Decision Maker

View as Markdown

A Safety Decision Maker (SDM) is a process that receives fused safety events from the NvPSD Gateway, decides what the machine may do, and sends that decision to a PLC or command receiver. This page states the contract every SDM must meet, whatever its use case. The reference SDMs implement it, and their use-case specifics are listed at the end:

  • atl_sdm: the forklift safety function (ATL).
  • proximity_sdm: Proximity Monitoring (PXC).
  • atl_proximity_sdm: the forklift-to-person proximity half of the combined ATL and PXC application.

The gateway’s own behaviour is described on NvPSD Gateway. The latch, its causes and the release protocol are described in the safe-state model. The numeric cause values are in Causes and Records. The structure of a reference SDM is shown on Safety Decision Maker (SDM).

The contract

An SDM must do all of the following. Each item has its own section below.

#Obligation
1Run with the same primary group as the NvPSD Gateway. See Shared process group.
2Connect to the gateway socket, and reconnect for as long as it runs. It must never exit because the gateway is missing.
3Register (REGR) on every connect, then again every 30 seconds. Handle a refusal (RGNK).
4Answer every gateway heartbeat (HBPG) with an acknowledgement (HBPC), and supervise the gateway heartbeat itself.
5Validate every decision packet: size, header, schema, CRC, fields and sequence. Drop anything that fails.
6Enter the safe state on a sequence gap, on loss of fresh decisions, and on every fault cause it receives.
7Never base a decision on an entry that is STALE, unhealthy, untrusted, or a health or trust report.
8Latch the safe output. Release it only through a validated request from the PLC, and only while no cause is active.
9Command the PLC with an integrity-checked packet, and track acknowledgements.

Build inputs

The development package installs the headers an SDM needs under /opt/nvidia/psf/include. See Install for the package contents and the build, and API Documentation for what each header declares.

HeaderWhat it defines
pss_protocol.hDecisionRequest, SensorData, FusedSafetyEvent, EventType, EventCause, the presence bits and PSS_SCHEMA_VERSION.
NvPSDGatewayProtocol.hThe wire constants: REGR, HBPG, HBPC, RGNK and its reason codes, and the NvPSDGatewayDecisionRequestPacket envelope with its validation helpers.
NvPSDGatewayContract.hppThe gateway’s registration rules: the 32-type limit, the mandatory types, and the refusal classifier (C++).
nvpsf_transport.hThe transport API used to open the gateway link, and the socket path NVPSF_SOCKET_PATH_PSD_GATEWAY.
pss_message_validate.hpssDecisionRequestVerifyCRC(), validateDecisionRequest() and the PSS_ERR_* flags. Its implementation, pss_message_validate.c, ships next to each reference SDM’s source.

The transport is the shared library libnvpsf_transport.so in /opt/nvidia/psf/lib.

Connect to the gateway

PropertyValue
Socket/run/nvpsf/nvpsd_gateway, AF_UNIX, SOCK_SEQPACKET. There is no UDP or TCP option.
Servernvpsd_gateway. The SDM is the client.
Socket file mode0660, set by the gateway after it binds.
Credential checkBoth ends compare the peer’s primary GID, read with SO_PEERCRED, with their own getgid(). A peer that has the group only as a supplementary group is refused.
Message sizeOne decision packet is sizeof(NvPSDGatewayDecisionRequestPacket): 1621 bytes.
Buffer floorThe client requests receive and send buffers for 516 decision packets and refuses a smaller grant. The kernel must allow it; see Kernel socket buffers.

Open the link with the transport library, the same way the reference SDMs do:

#include "nvpsf_transport.h"
#include "NvPSDGatewayProtocol.h"
#include <unistd.h>
NvPSFTransportCfg cfg = {};
cfg.kind = NVPSF_TRANSPORT_UNIX_SOCKET;
cfg.role = NVPSF_ROLE_CLIENT;
cfg.link = NVPSF_LINK_GATEWAY_TO_SDM;
cfg.maxPeers = 1U;
cfg.maxMessageBytes = sizeof(NvPSDGatewayDecisionRequestPacket);
cfg.minQueueDepth = NvPSFGatewaySdmMinQueueDepth(); /* 516 */
cfg.recvTimeoutMs = 50U;
cfg.sendTimeoutMs = 0U;
cfg.unixPath = NVPSF_SOCKET_PATH_PSD_GATEWAY; /* /run/nvpsf/nvpsd_gateway */
cfg.unixMode = 0; /* unused by a client */
cfg.allowedGid = getgid(); /* the gateway must share it */
NvPSFTransport* gw = nullptr;
NvPSFPeerId peer = NVPSF_PEER_INVALID;
if (NvPSFTransportOpen(&cfg, &gw) == NVPSF_TRANSPORT_OK &&
NvPSFTransportSolePeer(gw, &peer) == NVPSF_TRANSPORT_OK) {
/* connected: send REGR next */
}

What the open returns, and what the SDM must do:

ResultMeaningWhat to do
NVPSF_TRANSPORT_OKConnected. The gateway’s credential was accepted.Send REGR at once.
NVPSF_TRANSPORT_PEER_GONEThe socket does not exist yet, or nothing is listening.Retry. The reference SDMs retry every 500 ms with no limit.
NVPSF_TRANSPORT_WOULD_BLOCKThe gateway’s listen backlog is full.Retry.
NVPSF_TRANSPORT_DENIEDThe gateway runs with a different primary GID. The transport prints nvpsf_transport: server credential rejected; refusing to treat the connection as trusted.Retry, and fix the process group. See Shared process group.
NVPSF_TRANSPORT_FAILAny other error. A buffer shortfall prints nvpsf_transport: SO_RCVBUF granted <n> bytes but <m> are required for 516 messages of 1621 bytes; raise net.core.rmem_max (or SO_SNDBUF and wmem_max).Retry, and fix the host.

The gateway opens /run/nvpsf/nvpsd_gateway only after it has connected to nvpss_daemon and registered with it. Each step is retried up to 60 times at 500 ms intervals, so the socket can be missing for up to about a minute after the gateway starts. If nvpss_daemon never comes up, the gateway exits. An SDM started first must wait, not exit. The reference SDMs log SDM: psdGateway transport not available at launch (UNIX_SOCKET); waiting for the gateway and keep retrying.

When the link drops, NvPSFTransportPoll() reports NVPSF_EVENT_PEER_GONE or NvPSFTransportRecv() returns NVPSF_TRANSPORT_PEER_GONE. Close the handle, reconnect, and send REGR again before doing anything else. A reconnected link with no registration receives nothing, and the SDM then enters the safe state on lost freshness. The reference SDMs log SDM: gateway upstream link lost; reconnecting, then SDM: gateway upstream link re-established after <n> attempt(s).

Register

The REGR packet

BytesField
0–3Magic REGR (NVPSD_GATEWAY_REG_MAGIC), 4 ASCII bytes.
4Number of event types, uint8_t, 1 to 32.
5 onwardsOne uint32_t per event type, in network byte order.
#include <arpa/inet.h>
#include <cstring>
#include "NvPSDGatewayProtocol.h"
static const EventType kSubscribed[] = { EVENT_0, EVENT_1, EVENT_2, SW_FAIL };
static const uint8_t kCount = sizeof(kSubscribed) / sizeof(kSubscribed[0]);
char buf[4 + 1 + kCount * sizeof(uint32_t)];
std::memcpy(buf, NVPSD_GATEWAY_REG_MAGIC, 4);
buf[4] = static_cast<char>(kCount);
for (uint8_t i = 0; i < kCount; ++i) {
const uint32_t v = htonl(static_cast<uint32_t>(kSubscribed[i]));
std::memcpy(buf + 5 + i * sizeof(uint32_t), &v, sizeof(uint32_t));
}
NvPSFTransportSend(gw, peer, buf, sizeof(buf)); /* WOULD_BLOCK: retry shortly */

What the gateway does with it

  • Subscribable types. EVENT_0 to EVENT_23 and SW_FAIL, plus the four trust reports SENSOR_INVALID, SENSOR_VALID, AI_PIPELINE_INVALID and AI_PIPELINE_VALID. Any other value is dropped without notice.
  • Mandatory types. Once at least one requested type is valid, the gateway adds six types to the subscription: SW_FAIL, PSS_STATUS_NOOP, SENSOR_INVALID, SENSOR_VALID, AI_PIPELINE_INVALID and AI_PIPELINE_VALID. Every SDM therefore receives all six and must handle them, whatever it asked for.
  • Routing. Each SDM receives only the entries whose type it subscribed to, in the order the SEI daemon produced them. A request with no entry for this SDM is not sent to it. Two SDMs on one gateway that subscribe to the same EVENT_n both receive it.
  • No positive reply. A successful registration is not answered. The first HBPG heartbeat is the confirmation, because the gateway sends heartbeats only to registered SDMs.
  • Admission deadline. A connection that has not sent a valid REGR within 5000 ms is dropped, and the gateway logs PSD-Gateway: dropped a connection that never registered.
  • Capacity. At most 10 SDMs can be registered at once (nvpsd_gateway --num_clients, 1 to 10, default 10). The fsicom-agent bridge counts as one.
  • Re-registration. A REGR on an already registered connection replaces its subscription set and resets its heartbeat miss count. It keeps the connection’s decision epoch and sequence number, so it causes no sequence gap.
  • Rate limit. By default the gateway accepts 4 REGR per connection in a 10-second window. See NvPSD Gateway for the two flags that change this.

Send REGR on every connect, then again every 30 seconds. The periodic REGR lets a restarted gateway rediscover the SDM. The reference SDMs keep sending it while latched.

Refusal (RGNK)

The gateway answers a refused REGR with a 5-byte message: RGNK followed by one reason byte. Parse it with NvPSDGatewayParseRegReject() and name it with NvPSDGatewayRegRejectReasonName().

Reason byteNameCauseWhat happens next
1malformedShorter than 5 bytes, a count of 0 or more than 32, or fewer type values than the count says.Not registered. Fix the packet.
2no_valid_event_typesNo requested type is subscribable.Not registered. Fix the type list.
3table_fullEvery SDM slot is taken.The connection stays unregistered and is dropped after 5000 ms.
4rate_limitedToo many REGR from this connection in the window. Only the first refusal in each window is answered; later ones are ignored without a reply.The REGR was not read. Wait for the window to pass.
otherunknownA reason this build does not know.Treat it as a refusal.

A refused SDM receives no heartbeats and no decisions, so its gateway-heartbeat supervision and its freshness timeout put it in the safe state. Log the reason once per distinct value. The reference SDMs log SDM: the gateway refused this SDM's registration; reason=<name> -- not registered, no decisions will arrive and keep sending the periodic REGR.

Heartbeats

DirectionMessage
Gateway to SDM8 bytes: HBPG, then a uint32_t sequence number in network byte order.
SDM to gateway8 bytes: HBPC, then the same sequence number, echoed.

Echo every HBPG at once, on the same link:

if (n == NVPSD_GATEWAY_HB_MSG_SIZE &&
std::memcmp(rx, NVPSD_GATEWAY_HB_MAGIC_GATEWAY, 4) == 0) {
char ack[NVPSD_GATEWAY_HB_MSG_SIZE];
std::memcpy(ack, NVPSD_GATEWAY_HB_MAGIC_CLIENT, 4);
std::memcpy(ack + 4, rx + 4, 4); /* echo the sequence number */
NvPSFTransportSend(gw, peer, ack, sizeof(ack));
lastGatewayHeartbeat = std::chrono::steady_clock::now();
}

How the gateway supervises the SDM

The gateway sends one HBPG per cycle: it waits 2000 ms, sends, waits 1300 ms for the acknowledgement, then checks. A cycle therefore takes about 3.3 seconds. A cycle without an acknowledgement newer than the last one counts as a miss; an older or repeated acknowledgement does not reset the count. A new SDM counts as missed until its first acknowledgement.

With M = nvpsd_gateway --max_hb_failures (1 to 255, default 10) and W = M / 2, rounded down:

MissesTierGateway behaviour
1 to WWarnLogs no HB ACK from client <c> seq=<s> miss=<m>/<M> [warn tier]. Nothing else changes.
W + 1 to M - 1DegradedStops forwarding decision packets to this SDM, including SW_FAIL and recovery records. Heartbeats continue. Withheld packets use no sequence numbers, so they cause no gap.
MReclaimFrees the slot, drops the connection and forgets the SDM’s epoch and sequence. The SDM must reconnect and register again.

With the default of 10, misses 1 to 5 warn, 6 to 9 are degraded, and the tenth miss reclaims the SDM, about 33 seconds after its last acknowledgement. With M set to 1 the first miss reclaims; with M set to 2 there is no degraded tier.

How the SDM supervises the gateway

The SDM must also watch the gateway, because a silent gateway sends nothing that could carry a fault. The reference SDMs derive a miss count m from the time since the last HBPG:

  • m is 0 while the elapsed time is at most --hb_stale_ms (default 5000 ms).
  • After that, m is 1 plus the number of whole --hb_period_ms periods (default 5500 ms) beyond --hb_stale_ms, capped at --max_hb_failures (default 10).
  • Both timings accept 100 to 600000 ms. --max_hb_failures accepts 1 to 255.

With W = --max_hb_failures / 2, rounded down:

TierConditionReference behaviourTime from last HBPG, defaults
1, warnm from 1 to WLogs HB-PSD: gateway HB warn tier miss_count=<m>/<M> elapsed_ms=<e>.after 5 s
2, active faultm above W and below MSends the safe hold and keeps it. Not latched: it lifts when m falls back to W or below. A release request is denied while it holds. REGR retries are limited to M - W - 1.from 32.5 s
3, latchedm reaches MLatches the safe state: HB-PSD: gateway HB fault latched (tier 3) — local fail-safe, no PSS connection.at 54.5 s

The watchdog is armed at start-up, so a gateway that never appears still trips it. When heartbeats return, the reference SDMs log HB-PSD: gateway HB recovered; safe-state latch awaits PLC release. The tier lifts, but a tier-3 latch stays until a validated release.

Receive and validate decisions

Each message on the link is exactly one of these, told apart by size and magic:

SizeContentHandling
8 bytes, magic HBPGHeartbeatEcho it (see above).
5 bytes, magic RGNKRegistration refusalLog it (see above).
1621 bytesNvPSDGatewayDecisionRequestPacketValidate, then process.
Any other sizeNot a valid messageDrop it. The reference SDMs log SDM: received partial packet (<n> bytes, expected 1621). A message larger than the buffer returns NVPSF_TRANSPORT_TRUNCATED and is already discarded.

The envelope

The packet is packed, and its header fields are in host byte order.

FieldTypeValid values
magicchar[4]DRPG
versionuint16_t1 (NVPSD_GATEWAY_DECISION_PACKET_VERSION)
headerSizeuint16_t20, the offset of request
gatewayEpochuint64_tNot 0 and not UINT64_MAX. A new value for each new connection.
gatewayTxSequint32_t1 to UINT32_MAX - 1, one higher for each packet to this SDM. After UINT32_MAX - 1 it wraps to 1.
requestDecisionRequestValidated in the next step.

Validation steps

Run these in order. Drop the packet at the first failure, and do not let a dropped packet refresh freshness.

  1. Header. NvPSDGatewayDecisionPacketHeaderIsValid() checks the magic, version, header size, epoch and sequence range. The reference SDMs log SDM: Gateway DecisionRequest packet header validation failed — dropping.

  2. Payload. validateDecisionRequest() returns PSS_VALID (0) or a bitmask of PSS_ERR_* flags. It checks the schema version (PSS_SCHEMA_VERSION, 5), the CRC-32, the entry count (at most 8; 0 only for the stop sentinel described below), the mode, and every entry’s type, cause, severity, confidence, timestamp, status, sensor name and object types. The reference SDMs log SDM: DecisionRequest validation failed (flags=0x<hex>) — dropping.

  3. Sequence. Track gatewayEpoch and gatewayTxSeq per link:

    ObservationResultReference action
    First packet, or a new epochAccepted. Becomes the new baseline.Process.
    gatewayTxSeq equal to or below the last oneDuplicate or staleDrop it.
    Out of the valid rangeInvalidDrop it.
    Higher than last + 1Gap: packets were lostEnter the safe state and latch, then process this packet.
    Last + 1AcceptedProcess. If a gap fault was active, it clears; the latch stays.

The gateway allocates a sequence number before it sends, and a failed send does not return it. A gap therefore always means a packet meant for this SDM was not delivered, so it must latch. The shared header sdm_gateway_decision_sequence.hpp in the reference sources implements this table.

The requestId inside DecisionRequest is the SEI daemon’s audit identifier. It is not the delivery sequence; use gatewayTxSeq for that.

Freshness

Every accepted packet refreshes freshness, including a status no-op. The SEI daemon sends a status no-op every status_noop_interval_ms (5000 ms in the shipped nvpss.conf), and the gateway delivers it to every SDM, so a healthy link never goes quiet for long.

The reference SDMs check freshness every 500 ms. If no accepted packet has arrived for longer than --decision_freshness_timeout_ms (default 7000 ms, range 50 to 300000), they latch and log <app>: DecisionRequest freshness timeout; entering safe hold, elapsed_ms=<e>. The timer is armed at start-up, so an SDM that never receives a packet latches after about 7 seconds. Keep the timeout above the status no-op interval.

Freshness is also how an SDM learns that the SEI daemon has crashed: when the daemon’s link to the gateway drops, the gateway sends the SDMs nothing until it has reconnected.

Decide

The DecisionRequest

FieldMeaning
requestIdSEI audit identifier. UINT32_MAX with mode ERROR and no entries is the gateway’s stop sentinel (see below).
sensorDataSummarySizeNumber of valid entries, 0 to 8.
sensorDataSummary[i].eventA FusedSafetyEvent: type, cause, severity (OPERATIONAL 0 or CRITICAL 1, set by the SEI daemon), timestamp, confidenceLevel, status (FUSED, PASSTHROUGH, STALE, UNKNOWN), sensorIdentifier and fusionMetadata (pipeline, client, object ids, types and trajectory).
sensorDataSummary[i].isHealthyfalse while a SENSOR_INVALID verdict stands for this entry’s pipeline.
sensorDataSummary[i].isTrustedSourcefalse while an AI_PIPELINE_INVALID verdict stands. The verdict covers the whole perception pipeline.
pssStatus.modeNORMAL, DEGRADED or ERROR.
integritySchema version and CRC-32, checked during validation.

Rules for every request

Apply these before any use-case logic. They are the same in all reference SDMs.

  1. Trust and health reports first. Apply every AI_PIPELINE_INVALID, AI_PIPELINE_VALID, SENSOR_INVALID and SENSOR_VALID in the request, and the presence and input-state report carried by a status no-op, before any early return. These are reported on change, so one that is skipped may not be sent again until the SEI daemon’s next re-assertion.
  2. Mode ERROR. Enter the safe state. Apply every SW_FAIL in the request too, then stop.
  3. Status no-op only. A request whose only entry is PSS_STATUS_NOOP refreshes freshness and carries no physical event. Apply what it reports, then stop.
  4. SW_FAIL. Enter the safe state for every SW_FAIL in the request, whatever its position and whatever the mode.
  5. Per-entry filter. Skip an entry that is STALE, that is a health or trust report, whose isHealthy is false, or whose isTrustedSource is false. The gateway forwards STALE entries; the SDM must drop them.
  6. One command per request. A request carries up to 8 entries. Resolve them all to one decision and send it once, after the loop. A command per entry lets a later benign entry overwrite an earlier critical one on the wire.

The safe-state model says which of these causes latch, and what clears each one.

The stop sentinel

When the gateway loses its own heartbeat to the SEI daemon, it sends every SDM it is still forwarding to a request with requestId UINT32_MAX, mode ERROR and no entries. validateDecisionRequest() accepts zero entries only in this form. Treat it like any other ERROR request.

Enter the safe state and latch

Every cause is held in a fault-safe lockout latch. While the latch is set, the SDM sends its safe output instead of its decision, and keeps re-sending it on each request and on its periodic repeat. The causes a reference SDM latches on are:

CauseRaised byCleared (the cause, not the latch) by
Gateway heartbeatTier 3 of the SDM’s own gateway-heartbeat supervision. Tier 2 holds the output without latching.Heartbeats resuming.
SEI faultMode ERROR, any SW_FAIL, a source reported lost or missing, or a source whose reports are rejected. Each has its own sub-cause.Its own evidence only. See the safe-state model.
Decision freshnessNo accepted packet for longer than the timeout.The next accepted packet.
Gateway sequenceA gap in gatewayTxSeq.The next in-order packet.
All reviewed sensors failedEvery pipeline in the reviewed sensor set is failed.Any one of them recovering.
AI pipeline invalidA non-STALE AI_PIPELINE_INVALID.AI_PIPELINE_VALID.

The reviewed sensor set comes from sensor_pipelines_config.conf (--sensor_pipelines_config, default /opt/nvidia/psf/bin/sensor_pipelines_config.conf). A missing or malformed file stops the reference SDMs at start-up. Its format is on Sensor files.

A cause clearing does not clear the latch. Only an accepted release does. For the full table of SW_FAIL causes and record numbers, see Causes and Records.

Release the latch

The PLC or command receiver asks for a release with a SAFE_RELEASE_REQUEST packet. The SDM must validate it and decide it under the same lock it holds while processing a decision packet. Otherwise a release could land between one packet’s clear and the same packet’s new fault, and be accepted in a state that never existed upstream.

Validate the request

The reference SDMs drop a packet from the PLC unless all of these hold, in this order. The log lines are atl_sdm’s; the other SDMs word some of them differently.

CheckRuleatl_sdm log on failure
Size64 bytes. A shorter datagram is ignored. A longer one is cut to its first 64 bytes, which must then pass the checks below.none
SourceThe configured receiver address and port (--cmd_rx_ip, --cmd_rx_port).SDM: dropping PLC packet from unexpected endpoint
IdentifierThe application’s identifier byte.SDM: dropping PLC packet with invalid identifier
CRCThe CRC-32 over bytes 0–19 and 24–63 matches.SDM: dropping PLC packet with invalid CRC
OpcodeOne the SDM accepts from the PLC: HEARTBEAT, the application’s decision opcodes, HW_ERROR, SW_ERROR or SAFE_RELEASE_REQUEST.SDM: dropping PLC packet with unsupported command

No sequence-number or timestamp check is applied to a release request. A packet that passes is either a release request or an acknowledgement, matched to the pending command by its sequence number.

Decide the request

StateSDM response
Any cause active, including a tier-2 heartbeat holdSend SAFE_RELEASE_DENIED (0x0A), then the safe output. Log <app>: PLC safe-release denied; active safe-state fault source remains, active_causes=<bitmask>.
Latched, no cause activeSend SAFE_RELEASE_ACK (0x09), then the application’s normal command. Clear the latch only if both sends succeeded and no new cause arrived meanwhile. Then reset the decision state.
Not latchedSend SAFE_RELEASE_ACK and the normal command anyway, and reset the decision state. Within 1000 ms of the last successful release only the log line changes; the response is still sent.

If a cause arrives while the response is in flight, the reference SDMs log <app>: safe-release response sent but latch retained due to concurrent fault and send the safe output again. If a send fails, they log <app>: safe-release response TX failed; latch retained and state not reset.

Command the PLC

The reference SDMs and receivers exchange 64-byte packets over UDP. The SDM binds an ephemeral port on all interfaces and sends to --cmd_rx_ip:--cmd_rx_port. Acknowledgements and release requests arrive on the same socket. A custom SDM may use another PLC protocol, but it must provide the same properties: an integrity check, an identifier, a sequence number, and acknowledgement tracking.

Packet layout

The packet is packed. Multi-byte fields are written in host byte order.

BytesFieldTypeContent
0Identifieruint8_t0xA2 for ATL, 0xA5 for PXC and the combined proximity SDM.
1–2Sequence numberuint16_tOne counter per process, starting at 0 and wrapping. Every packet, heartbeats included, takes the next value.
3Commanduint8_tOpcode (see below).
4–11Timestamp secondsuint64_tUTC seconds since the epoch, from the wall clock.
12–19Timestamp microsecondsuint64_tMicrosecond part.
20–23CRC-32uint32_tReflected polynomial 0xEDB88320, initial value and final XOR 0xFFFFFFFF, computed over bytes 0–19 and 24–63.
24–43Object record 020 bytesuint32_t object_id, float x, float y, float z, uint32_t metadata.
44–63Object record 120 bytesSame layout.

Command and acknowledgement packet: identifier, sequence number, command, timestamp seconds, timestamp microseconds, CRC-32 and two 20-byte object records

Opcodes

CodeATL (atl_cmd_pkt.h)PXC (proximity_cmd_pkt.h)
0x00CMD_HEARTBEATCMD_HEARTBEAT
0x01CMD_HW_ERRORCMD_HW_ERROR
0x02CMD_MUTE: allow operationCMD_STOP: emergency stop
0x03CMD_SW_ERROR: fault safe state, alarmCMD_SW_ERROR: fault safe state, alarm
0x05—CMD_REDUCE: safe speed
0x07CMD_UNMUTE: prevent operationCMD_NORMAL: normal operation
0x08CMD_SAFE_RELEASE_REQUEST, PLC to SDMCMD_SAFE_RELEASE_REQUEST, PLC to SDM
0x09CMD_SAFE_RELEASE_ACK, SDM to PLCCMD_SAFE_RELEASE_ACK, SDM to PLC
0x0ACMD_SAFE_RELEASE_DENIED, SDM to PLCCMD_SAFE_RELEASE_DENIED, SDM to PLC

Timing in the reference SDMs

BehaviourValue
CMD_HEARTBEAT to the PLCEvery 5 s, also while latched. Not acknowledged.
Acknowledgement timeout3 s. The SDM logs WARNING: No acknowledgment received for command: <name> (SeqNo: <n>) after 3 seconds. No safe-state action follows; the PLC side must supervise the SDM.
Periodic repeat of the current decision--decision_interval_ms: 0 turns it off; otherwise 100 to 36000 ms. The binaries default to 5000 ms; both launchers pass 0, so a decision is sent when a request produces one and is not repeated. While latched, the repeat sends the safe output.

The reference command receivers

atl_sdm_cmd_receiver and proximity_sdm_cmd_receiver stand in for the PLC. They validate each packet (exactly 64 bytes, the expected sender, identifier and CRC), acknowledge it with the same sequence number and command, and supervise the SDM’s CMD_HEARTBEAT.

OptionDefaultMeaning
-p <port>12345Listen port, 1 to 65535.
--sdm_ip <IPv4>127.0.0.1Expected SDM address. Packets from elsewhere are dropped.
--sdm_port <port>anyExpected SDM source port. The SDM sends from an ephemeral port, so leave it unset.
--max_hb_failures <N>10Upstream heartbeat misses, 1 to 255. The first miss is counted 5 s after the last CMD_HEARTBEAT and each further miss 5.5 s later. At N the receiver prints cmd_rx: upstream CMD_HEARTBEAT fault latched (tier 3) — local fail-safe, no PSS — stopping and exits: about 54.5 s at the default.
--vst_ip <IPv4> and --vst_port <port>offATL receiver only: relay each validated decision command to a VST display destination. Give both or neither; an invalid value stops the receiver at start-up. A relay send failure affects only the display. The PXC receiver rejects both options with error: unknown option (see --help).
--log-file <path>/tmp/cmdrx.logPer-packet log. The console shows only latch, clear and release events.
--no-log-fileoffKeep the per-packet log on the console.

How a receiver drives the release, and what the operator sees, is in the safe-state model.

Use-case specifics

Forklift safety function (atl_sdm)

ItemValue
Configuration--config <file> is required. It binds six situations to six distinct codes from EVENT_0 to EVENT_23: forklift_entered, forklift_exited, person_entered, person_exited, roi_violation, roi_cleared. Every key is mandatory. The shipped atl_sdm_zone1.conf binds EVENT_0 to EVENT_5.
RegistrationThe six codes plus SW_FAIL: 7 types.
DecisionCMD_MUTE (allow) only while the forklift is in the trailer, no person is in the trailer and no restricted-area violation is set. Otherwise CMD_UNMUTE (prevent).
Safe outputCMD_UNMUTE then CMD_SW_ERROR.
Release responseCMD_SAFE_RELEASE_ACK then CMD_UNMUTE. The trailer state resets to empty: no forklift, no persons, no violation.
Extra latchA person leaving while the count is 0, or entering while it is at its cap of 10000, latches the safe state. This cause has no external source, so it does not block a release.
Other options--log_file (default atl_sdm.log in the working directory), --cmd_rx_ip (default 127.0.0.1), --cmd_rx_port (default 12345). Two atl_sdm on one gateway must not share an event code.

Proximity Monitoring (proximity_sdm)

ItemValue
Configuration--config <file> is required. pair_groups selects machine-to-person pair groups; each selected group binds three distinct codes for its normal, reduce and stop tiers. The shipped pxc_sdm_forklift.conf binds forklift-to-person to EVENT_12, EVENT_13 and EVENT_14; pxc_sdm_robots.conf binds three robot groups to EVENT_15 to EVENT_23.
DecisionThe worst tier across the request wins: CMD_STOP, then CMD_REDUCE, then CMD_NORMAL. One command per request.
Safe outputCMD_STOP then CMD_SW_ERROR.
Release responseCMD_SAFE_RELEASE_ACK then CMD_NORMAL.
Other options--log_file (default pxc_sdm.log), --cmd_rx_ip (default 127.0.0.1), --cmd_rx_port (default 12345). --halo_ip <IPv4> also sends every decision command to a VST halo overlay listener at that address and --halo_port (default 12346), best effort; it is off unless --halo_ip is given.

The PXC command receiver evaluates the commands it received every 100 ms and applies the most conservative: any SW_ERROR gives the fault safe state, then any STOP or HW_ERROR gives an emergency stop, then any REDUCE gives a slow-down, and only all-NORMAL gives normal operation. An empty window keeps the previous action.

Combined forklift and proximity (atl_proximity_sdm)

The combined application runs atl_sdm and atl_proximity_sdm against one gateway. atl_proximity_sdm has no --config: it registers EVENT_8, EVENT_9, EVENT_10 and SW_FAIL, keeps the worst live forklift-to-person pair (pairs expire after --pair_ttl_ms, default 2000 ms, range 200 to 60000), and speaks the PXC packet with identifier 0xA5. Its default --cmd_rx_port is 12346, so it does not collide with atl_sdm on 12345.

SDM on the FSI

On IGX Thor the SDM can run on the Functional Safety Island (FSI) instead of the CCPLEX. The fsicom-agent bridge then takes the SDM’s place on the gateway link.

ItemBehaviour
Gateway linkfsicom-agent is an ordinary SDM client: same socket, same group rule, same heartbeats. It registers EVENT_0 to EVENT_23 and SW_FAIL (25 types), re-sends REGR every 30 s, and retries the connect every 500 ms with no limit.
ValidationIt validates the envelope and sequence as above, then forwards the raw DecisionRequest, without the envelope, to the FSI over NvFsiCom channel 6.
Sequence gapIt forwards a SW_FAIL with cause CAUSE_GATEWAY_SEQUENCE_GAP (9) ahead of the packet that showed the gap, so the FSI SDM latches.
Full FSI queueThe queue holds 100 messages. A dropped request is followed by the same cause-9 SW_FAIL.
Return pathThe FSI SDM returns its command packet. With --relay-fsi-resp the agent sends it over UDP to --ip:--port, and sends CMD_HEARTBEAT there every 5 s.
ReleaseThe agent forwards a release request to the FSI only if it comes from exactly that address and port, carries the --cmd-identifier value and has a valid CRC. At most one per 1000 ms.
FSI SDM causesThe same causes as the CCPLEX SDMs, with two differences: the CCPLEX heartbeat on channel 6 replaces the gateway heartbeat, and an FSI task watchdog fault latches and cannot be cleared without restarting the FSI. Freshness is fixed at 7000 ms. Until the first decision and the first CCPLEX heartbeat arrive after a cold boot, the safe output is held without latching.
LimitsATL and PXC only. 2D deployments only: the FSI SDMs carry a compiled reviewed sensor set (pipelines 1 Camera, 2 Camera_01, 3 Camera_02) instead of reading sensor_pipelines_config.conf.

The agent’s options are on Launcher. The firmware and the agent ship in the psf-tegra-fsi package; see Install. Running it is covered in Custom Deployment.