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.
| Aspect | Global Table (Ch 19) | Listener Table (Ch 20) |
|---|---|---|
| Purpose | Maps our own nodes' aliases | Maps remote listener nodes' aliases |
| Population | Login process (CID/RID/AMD) | On-demand via AME/AMD exchange |
| Size | USER_DEFINED_NODE_BUFFER_DEPTH | MAX_LISTENERS_PER_TRAIN * TRAIN_NODE_COUNT |
| Alias source | Locally generated | Learned 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
| Function | Description |
|---|---|
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:
20.6 flush_aliases vs clear_alias_by_alias
Two operations invalidate resolved aliases, triggered by different CAN events:
| Operation | Trigger | Effect |
|---|---|---|
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. |
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:
- Get the listener Node ID from the train's listener list.
- Call
ListenerAliasTable_find_by_node_id(node_id). - If the returned entry has
alias != 0, use that alias as the destination alias in the CAN frame header. - 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
| File | Purpose |
|---|---|
drivers/canbus/alias_mapping_listener.h | Public API declarations |
drivers/canbus/alias_mapping_listener.c | Implementation -- single static table, linear scan |
drivers/canbus/can_types.h | Type definition for listener_alias_entry_t and LISTENER_ALIAS_TABLE_DEPTH |