HID Controller Setup¶
ATLAS supports HID controllers (gamepads, joysticks) for hands-on map control in the DEF and BMS functional areas. The controller gateway publishes raw input over DDS, and the HMI maps axes and buttons to screen actions via JSON configuration.
Architecture¶
(Xbox, PlayStation, etc.)"] Gateway["gva-gateway-joystick
SDL2 → DDS"] HMI["gva-hmi
HidInputManager"] DEF["DEF Radar
Zoom · Threats"] BMS["BMS Map
Zoom · Pan"] Controller --> Gateway Gateway -->|"DDS
Displays_And_Controls"| HMI HMI --> DEF HMI --> BMS
The system uses two processes:
| Process | Role |
|---|---|
gva-gateway-joystick |
Reads the physical controller via SDL2 and publishes raw axes/buttons on DDS Displays_And_Controls topics. No application logic — just raw input. |
gva-hmi |
Subscribes to the DDS topics, resolves each component's role descriptor, and maps inputs to screen-specific actions based on the JSON config. |
This separation means any SDL2-compatible controller works — Xbox, PlayStation, Switch Pro, flight sticks, or third-party pads — without changing the HMI.
Supported Controllers¶
Any controller recognised by SDL2's SDL_GameController API works out of the box:
- Xbox Elite / Xbox Series — recommended, all features mapped
- Xbox 360 — wired and wireless (with adapter)
- PlayStation DualSense / DualShock 4 — via SDL2's mapping database
- Nintendo Switch Pro — via SDL2's mapping database
- Generic USB gamepads — if listed in SDL2's controller database
Tip
Run gva-gateway-joystick --verbose to see a live ASCII readout of all axes and buttons — useful for verifying your controller is recognised.
Quick Start¶
1. Connect the controller¶
Plug in a USB controller or pair a Bluetooth controller. Verify Linux sees it:
# List game controllers
ls /dev/input/js*
# Or check SDL2 directly
gva-gateway-joystick --verbose
# Output: SDL sees 1 joystick(s)
# [0] gamepad Xbox Elite Wireless Controller
2. Start the joystick gateway¶
The gateway publishes three types of DDS samples:
| DDS Topic | Controller Input | Published When |
|---|---|---|
C_Input_Control_2D |
Left stick, right stick, D-Pad | Axis value changes |
C_Input_Control_1D |
Left trigger, right trigger | Trigger value changes |
C_Button_Control |
A/B/X/Y, LB/RB, Back/Start, stick clicks | Button press/release |
Idle input generates zero DDS traffic — samples are only published on change.
3. Start the HMI¶
Navigate to the DEF screen (key 3). Trigger zoom and bumper threat-cycling work immediately with default bindings.
4. Verify with DDS discovery¶
Look for the gateway's participants and matched writer/reader pairs on the Displays_And_Controls__* topics.
Gateway Command-Line Options¶
| Option | Default | Description |
|---|---|---|
-d, --domain <N> |
0 |
DDS domain ID (must match the HMI) |
-i, --id <N> |
0 |
Gateway LDM resource ID |
-j, --joystick <N> |
0 |
SDL2 controller index (0 = first detected) |
-p, --publish-rate <ms> |
20 |
Poll interval in milliseconds (50 Hz default) |
-z, --deadzone <0..1> |
0.05 |
Normalised stick/trigger deadzone at the gateway level |
-V, --verbose |
off | Print live ASCII gamepad state to stdout |
JSON Configuration¶
Controller bindings are defined in the "controller" section of gva-hmi-config.json. The HMI reads this at startup — no recompilation needed.
Configuration File Location¶
The HMI searches for gva-hmi-config.json in order:
./gva-hmi-config.json(current directory)./etc/gva-hmi-config.json<app-dir>/etc/gva-hmi-config.json/etc/gva/hmi/gva-hmi-config.json
Controller Section¶
{
"controller": {
"enabled": true,
"resourceId": -1,
"instanceId": -1,
"deadzone": 0.15,
"triggerZoomSpeed": 1.5,
"tickIntervalMs": 30,
"axisBindings": {
"LEFT_STICK": { "action": "pan", "invertY": false },
"RIGHT_STICK": { "action": "rotateTilt", "invertY": false },
"DPAD": { "action": "pan", "invertY": false },
"LEFT_TRIGGER": { "action": "zoomOut" },
"RIGHT_TRIGGER": { "action": "zoomIn" }
},
"buttonBindings": {
"BUTTON_A": "select",
"BUTTON_B": "back",
"BUTTON_X": "cycleMap",
"BUTTON_Y": "toggleZoom",
"LEFT_BUMPER": "prevThreat",
"RIGHT_BUMPER": "nextThreat",
"BACK": "resetView",
"LEFT_STICK_BUTTON": "toggleTrackAll",
"RIGHT_STICK_BUTTON": "resetNorth"
}
}
}
Global Settings¶
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | true |
Enable/disable controller input entirely |
resourceId |
int | -1 |
LDM resource ID of the controller gateway to accept. Set to -1 to accept input from any controller. When multiple controllers are connected, set this to the --id value of the specific gateway instance. |
instanceId |
int | -1 |
LDM instance ID to accept. Set to -1 to accept any instance. Use together with resourceId to match a specific (resourceId, instanceId) pair when multiple gateway instances share the same resource ID. |
deadzone |
float | 0.15 |
Trigger/stick dead zone at the HMI level (0.0–1.0). Values below this threshold are ignored. |
triggerZoomSpeed |
float | 1.5 |
Zoom factor per tick at full trigger deflection. Higher = faster zoom. |
tickIntervalMs |
int | 30 |
Continuous axis evaluation interval in milliseconds. Lower = smoother but more CPU. |
Note
There are two deadzones in the pipeline. The gateway has its own (--deadzone, default 0.05) which suppresses jitter at the DDS level. The HMI's deadzone (default 0.15) sets the threshold for triggering an action — values between 0.05 and 0.15 are published but ignored by the HMI.
Axis Bindings¶
Axis bindings map analogue inputs (sticks, triggers) to continuous actions. The key is the role descriptor published by the gateway.
| Role Descriptor | Controller Input | Available Actions |
|---|---|---|
LEFT_STICK |
Left thumbstick (2D) | pan |
RIGHT_STICK |
Right thumbstick (2D) | rotateTilt |
DPAD |
D-Pad (2D, digital) | pan |
LEFT_TRIGGER |
Left trigger (1D, 0–1) | zoomOut |
RIGHT_TRIGGER |
Right trigger (1D, 0–1) | zoomIn |
2D axis options:
| Option | Type | Default | Description |
|---|---|---|---|
action |
string | — | Action to perform: pan or rotateTilt |
invertY |
bool | false |
Invert the Y axis (push-up = pan-down) |
Button Bindings¶
Button bindings map momentary presses to discrete actions. Only the press event is used — release is ignored.
| Role Descriptor | Controller Input | Default Action |
|---|---|---|
BUTTON_A |
A (Xbox) / Cross (PS) | select |
BUTTON_B |
B (Xbox) / Circle (PS) | back |
BUTTON_X |
X (Xbox) / Square (PS) | cycleMap |
BUTTON_Y |
Y (Xbox) / Triangle (PS) | toggleZoom |
LEFT_BUMPER |
LB (Xbox) / L1 (PS) | prevThreat |
RIGHT_BUMPER |
RB (Xbox) / R1 (PS) | nextThreat |
BACK |
Back/View (Xbox) / Share (PS) | resetView |
START |
Start/Menu (Xbox) / Options (PS) | (unbound) |
GUIDE |
Xbox/PS button | (unbound) |
LEFT_STICK_BUTTON |
Left stick click | toggleTrackAll |
RIGHT_STICK_BUTTON |
Right stick click | resetNorth |
Available Actions¶
Map / Radar Actions (DEF Screen)¶
| Action | Description |
|---|---|
zoomIn |
Zoom into the radar map (shrink displayed range). Proportional to trigger deflection — light press = slow zoom, full pull = fast zoom. |
zoomOut |
Zoom out of the radar map (increase displayed range). |
pan |
Pan the map in the direction of stick deflection. |
nextThreat |
Select the next threat (further away by range) and centre the radar view on it. |
prevThreat |
Select the previous threat (closer by range) and centre the radar view on it. |
select |
Select/confirm the current threat or map feature. |
back |
Deselect the current selection. |
cycleMap |
Cycle through map background types (Off → Satellite → Topographic → Street → Off). |
toggleZoom |
Toggle between minimum and maximum zoom range. |
resetView |
Reset pan offset and centre the view on the vehicle position. |
toggleTrackAll |
Toggle trail display for all tracked threats. |
resetNorth |
Reset map rotation/tilt to north-up orientation. |
Screen Navigation Actions¶
| Action | Description |
|---|---|
rotateTilt |
Rotate and tilt the map (right stick X = rotation, Y = tilt). Only active on BMS. |
DEF Screen Controls¶
On the DEF (Defence) screen, the controller provides hands-on interaction with the radar view:
| Input | Action |
|---|---|
| Right Trigger | Zoom in (proportional — light press = slow, full pull = fast) |
| Left Trigger | Zoom out (proportional) |
| RB | Select next threat (further) and centre view |
| LB | Select previous threat (closer) and centre view |
| X | Cycle map type (Off → Satellite → Topo → Street) |
| Y | Toggle min/max zoom |
| Back | Reset view to vehicle position |
| Left Stick Click | Toggle track-all trails |
BMS Screen Controls¶
On the BMS screen, controller inputs are forwarded to the external BMS application (e.g. TALOS) via the Display Extension protocol:
| Input | Action |
|---|---|
| Right Trigger | Zoom in (forwarded as wheel event) |
| Left Trigger | Zoom out (forwarded as wheel event) |
| Left Stick | Pan the map |
| Right Stick | Rotate / tilt the map |
DDS Topics¶
The joystick gateway publishes on three Displays_And_Controls_PSM topics, using the 2-arg constructor so QoS is resolved via PatternForTopic():
| Topic | Type | Content |
|---|---|---|
Displays_And_Controls__Input_Control_2D |
C_Input_Control_2D |
Stick X/Y in [-1, 1], D-Pad as {-1, 0, +1} per axis |
Displays_And_Controls__Input_Control_1D |
C_Input_Control_1D |
Trigger value in [0, 1] |
Displays_And_Controls__Button_Control |
C_Button_Control |
Press/release state per button |
Each component is identified by a (resourceId, instanceId) pair, with a role descriptor string (e.g. "LEFT_TRIGGER", "RIGHT_BUMPER") published once at startup via C_Human_Interaction_Component_Specification.
Troubleshooting¶
Controller not detected¶
# Check if SDL2 sees the controller
gva-gateway-joystick --verbose
# If "no SDL_GameController-compatible device found":
# - Check /dev/input/js* exists
# - Ensure current user is in the 'input' group:
sudo usermod -aG input $USER
# - Log out and back in for group change to take effect
Controller works in gateway but HMI doesn't respond¶
-
Verify both processes are on the same DDS domain:
-
Check DDS discovery:
Look for matched writer/reader pairs on theDisplays_And_Controls__*topics. -
Verify the
"controller"section ingva-hmi-config.jsonhas"enabled": true. -
Check HMI logs for controller startup:
You should see:
Trigger zoom is too fast or too slow¶
Adjust triggerZoomSpeed in the JSON config:
- Slower:
0.5–1.0 - Default:
1.5 - Faster:
2.0–3.0
Stick/trigger jitter at rest¶
Increase the deadzone value in the JSON config (default 0.15). A value of 0.2–0.25 eliminates most controller jitter.
Multiple controllers¶
When multiple controllers are connected, each needs its own gva-gateway-joystick instance with a unique --id:
# Controller 1 — map control (joystick index 0)
gva-gateway-joystick --domain=0 --id=100 --joystick=0
# Controller 2 — weapon system (joystick index 1)
gva-gateway-joystick --domain=0 --id=101 --joystick=1
Then set resourceId (and optionally instanceId) in the JSON config to the --id / --instance of the controller you want the HMI to use for map control:
With resourceId set to 100, the HMI ignores all controller input from resource 101. Set instanceId to further narrow the match when multiple gateways share the same resource ID. MFD bezel input (from gva-gateway-hid) is always accepted regardless of these filters.