Chapter 20 — Alias Mapping: Listener Table

When a train node has consist listeners (other train nodes that receive forwarded commands), the CAN TX path needs to resolve each listener's 48-bit Node ID to its 12-bit CAN alias. The listener alias table provides this on-demand resolution, implemented in alias_mapping_listener.h and alias_mapping_listener.c.

20.1 Purpose: Why a Separate Table?

The global alias mapping table (Chapter 19) tracks aliases for nodes known to this device. The listener alias table serves a different purpose: it maps remote listener Node IDs to their CAN aliases so consist commands can be forwarded to them.

AspectGlobal Table (Ch 19)Listener Table (Ch 20)
PurposeMaps our own nodes' aliasesMaps remote listener nodes' aliases
PopulationLogin process (CID/RID/AMD)On-demand via AME/AMD exchange
SizeUSER_DEFINED_NODE_BUFFER_DEPTHMAX_LISTENERS_PER_TRAIN * TRAIN_NODE_COUNT
Alias sourceLocally generatedLearned from remote AMD frames

20.2 The listener_alias_entry_t Structure

typedef struct listener_alias_entry_struct {
    node_id_t node_id; // Listener Node ID (from protocol layer attach). 0 = unused.
    uint16_t alias;    // Resolved CAN alias (0 = not yet resolved).
} listener_alias_entry_t;

An entry with node_id != 0 and alias == 0 means the listener is registered but its CAN alias has not yet been resolved. The TX path must wait (or trigger resolution) before it can send a CAN frame to that listener.

20.3 Table Size

The table is sized statically at compile time:

#define LISTENER_ALIAS_TABLE_DEPTH \
    (USER_DEFINED_MAX_LISTENERS_PER_TRAIN * USER_DEFINED_TRAIN_NODE_COUNT)

For example, if each train supports 4 listeners and there are 2 train nodes, the table has 8 slots. All slots are shared across all train nodes -- the table is a flat array, not partitioned per train.

20.4 API Functions

FunctionDescription
ListenerAliasTable_initialize() Zeros all entries. Call once at startup.
ListenerAliasTable_register(node_id) Registers a listener Node ID with alias = 0. If already registered, returns the existing entry without modification. Returns NULL if the table is full or node_id is invalid.
ListenerAliasTable_unregister(node_id) Removes the entry matching node_id. Clears both node_id and alias fields. Safe to call with a node_id that is not registered.
ListenerAliasTable_set_alias(node_id, alias) Stores a resolved alias for a registered listener Node ID. Called when an AMD frame arrives. If node_id is not in the table, this is a no-op.
ListenerAliasTable_find_by_node_id(node_id) Primary TX-path query. Returns the entry for the given Node ID. Caller checks entry->alias != 0 to confirm the alias has been resolved.
ListenerAliasTable_flush_aliases() Zeros all alias fields but preserves registered node_ids. Called on global AME (empty payload) receipt.
ListenerAliasTable_clear_alias_by_alias(alias) Zeros the alias field for the entry matching a specific alias value. Called when an AMR frame is received.

20.5 Population Flow

The listener alias resolution follows this sequence when a listener is attached to a train consist:

sequenceDiagram participant App as Application participant Train as Train Protocol Handler participant LT as Listener Alias Table participant CAN as CAN Bus participant Remote as Remote Listener Node App->>Train: Attach listener (Node ID = 0x050101012345) Train->>LT: ListenerAliasTable_register(0x050101012345) Note over LT: Entry created: node_id=0x050101012345, alias=0 Train->>CAN: Send AME frame with Node ID 0x050101012345 CAN->>Remote: AME received Remote->>CAN: Reply with AMD (alias=0x2AB, Node ID=0x050101012345) CAN->>LT: ListenerAliasTable_set_alias(0x050101012345, 0x2AB) Note over LT: Entry updated: node_id=0x050101012345, alias=0x2AB Note over Train: TX path can now resolve alias for listener Train->>LT: ListenerAliasTable_find_by_node_id(0x050101012345) Note over Train: alias=0x2AB, send forwarded command

20.6 flush_aliases vs clear_alias_by_alias

Two operations invalidate resolved aliases, triggered by different CAN events:

OperationTriggerEffect
flush_aliases() Global AME with empty payload received Zeros all alias fields, preserves all node_ids. The AMD replies triggered by the global AME will re-populate aliases via set_alias().
clear_alias_by_alias(alias) AMR frame received for a specific alias Zeros the alias field of the one matching entry, preserves the node_id. When the remote node re-claims an alias and sends AMD, set_alias() will re-populate.
Node IDs Are Never Lost

Both flush operations preserve the registered node_id values. Only the alias field is cleared. This means the table remembers which listeners are attached even when their CAN aliases are temporarily unknown. Once the remote nodes re-announce via AMD, the aliases are automatically re-learned.

20.7 How the TX Path Uses This Table

When the CAN TX state machine needs to forward a consist command to a listener, it follows this pattern:

  1. Get the listener Node ID from the train's listener list.
  2. Call ListenerAliasTable_find_by_node_id(node_id).
  3. If the returned entry has alias != 0, use that alias as the destination alias in the CAN frame header.
  4. If alias == 0, the alias is not yet resolved -- the message must be held until an AMD resolves it.

The TX state machine interface includes an optional listener_find_by_node_id function pointer, wired to ListenerAliasTable_find_by_node_id() by can_config.c.

20.8 Thread Safety

The listener alias table is written by the CAN RX interrupt (when AMD arrives and set_alias() is called) and read by the CAN TX main thread (during alias resolution). The lock_shared_resources / unlock_shared_resources mechanism must protect concurrent accesses, as described in Chapter 6.

20.9 Source Files

FilePurpose
drivers/canbus/alias_mapping_listener.hPublic API declarations
drivers/canbus/alias_mapping_listener.cImplementation -- single static table, linear scan
drivers/canbus/can_types.hType definition for listener_alias_entry_t and LISTENER_ALIAS_TABLE_DEPTH
← Prev: Ch 19 — Alias Mapping Next: Ch 21 — Multi-Node Routing →