Writing a Decision Maker
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.
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.
The transport is the shared library libnvpsf_transport.so in /opt/nvidia/psf/lib.
Connect to the gateway
Open the link with the transport library, the same way the reference SDMs do:
What the open returns, and what the SDM must do:
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
What the gateway does with it
- Subscribable types.
EVENT_0toEVENT_23andSW_FAIL, plus the four trust reportsSENSOR_INVALID,SENSOR_VALID,AI_PIPELINE_INVALIDandAI_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_INVALIDandAI_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_nboth receive it. - No positive reply. A successful registration is not answered. The first
HBPGheartbeat is the confirmation, because the gateway sends heartbeats only to registered SDMs. - Admission deadline. A connection that has not sent a valid
REGRwithin 5000 ms is dropped, and the gateway logsPSD-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). Thefsicom-agentbridge counts as one. - Re-registration. A
REGRon 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
REGRper 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().
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
Echo every HBPG at once, on the same link:
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:
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:
mis 0 while the elapsed time is at most--hb_stale_ms(default 5000 ms).- After that,
mis 1 plus the number of whole--hb_period_msperiods (default 5500 ms) beyond--hb_stale_ms, capped at--max_hb_failures(default 10). - Both timings accept 100 to 600000 ms.
--max_hb_failuresaccepts 1 to 255.
With W = --max_hb_failures / 2, rounded down:
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:
The envelope
The packet is packed, and its header fields are in host byte order.
Validation steps
Run these in order. Drop the packet at the first failure, and do not let a dropped packet refresh freshness.
-
Header.
NvPSDGatewayDecisionPacketHeaderIsValid()checks the magic, version, header size, epoch and sequence range. The reference SDMs logSDM: Gateway DecisionRequest packet header validation failed — dropping. -
Payload.
validateDecisionRequest()returnsPSS_VALID(0) or a bitmask ofPSS_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 logSDM: DecisionRequest validation failed (flags=0x<hex>) — dropping. -
Sequence. Track
gatewayEpochandgatewayTxSeqper link:
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
Rules for every request
Apply these before any use-case logic. They are the same in all reference SDMs.
- Trust and health reports first. Apply every
AI_PIPELINE_INVALID,AI_PIPELINE_VALID,SENSOR_INVALIDandSENSOR_VALIDin 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. - Mode
ERROR. Enter the safe state. Apply everySW_FAILin the request too, then stop. - Status no-op only. A request whose only entry is
PSS_STATUS_NOOPrefreshes freshness and carries no physical event. Apply what it reports, then stop. SW_FAIL. Enter the safe state for everySW_FAILin the request, whatever its position and whatever the mode.- Per-entry filter. Skip an entry that is
STALE, that is a health or trust report, whoseisHealthyisfalse, or whoseisTrustedSourceisfalse. The gateway forwardsSTALEentries; the SDM must drop them. - 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:
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.
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
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.

Opcodes
Timing in the reference SDMs
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.
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)
Proximity Monitoring (proximity_sdm)
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.
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.