Skip to content

Joy-Con 2 / Pro 2 / NSO GC2

Status: hardware-validated (Joy-Con 2 L; the other variants share the protocol).

The second-generation controllers - Joy-Con 2 L/R, Pro Controller 2, and the NSO GameCube 2 - use Bluetooth Low Energy, not Bluetooth Classic HID. They are the only Nintendo controllers here with a magnetometer, enabling 9-DOF fusion.

Component Part Range / scale
IMU (6-axis) TDK InvenSense ICM-42670-P gyro ≈ ±1994 dps (0.001062 rad/s per raw), accel ±8 g (4096 = 1 g)
Magnetometer Asahi Kasei AK09919 3-axis, ±4900 µT, 16-bit (~0.15 µT resolution)
Battery charger TI bq25619
Audio amp MAX98388/89

The right Joy-Con also carries an NFC controller, which is irrelevant to tracking.

The “special handshake” that blocks the Windows pairing dialog is a UX quirk, not cryptography. Connect to the unpaired, advertising controller directly - do not pair it through the OS Bluetooth settings. Rapid reconnects lock the radio for several minutes, so the driver honors a backoff.

  • Nintendo manufacturer ID 0x0553.
  • Manufacturer-data prefix 01 00 03 7E (first 4 bytes).
  • Discovery must not gate on advert byte 4 (an unverified per-variant guess that leaves controllers undiscoverable). Any 0553 + 01 00 03 7E advert is treated as a Switch 2 device; Left/Right is resolved from the USB PID scanned in the advert.

Service ab7de9be-89fe-49ad-828f-118f09df7fd0 (handle 0x0008):

Role UUID Handle
Common input report (0x05) ab7de9be-89fe-49ad-828f-118f09df7fd2 0x000A
Joy-Con L input (0x07) cc1bbbb5-7354-4d32-a716-a81cb241a32a 0x000E
Joy-Con R input (0x08) d5a9e01e-2ffc-4cca-b20c-8b67142bf442 0x000E
Pro Controller 2 input (0x09) 7492866c-ec3e-4619-8258-32755ffcc0f8 0x000E
NSO GC2 input (0x0A) 8261cba1-9435-420c-84d6-f0c75a2c8e4d 0x000E
Write commands 649d4ac9-8eb7-4e6c-af44-1ea54fe5f005 -
Output / rumble 289326cb-a471-485d-a8f4-240c14f18241 0x0012
Command response (notify) c765a961-d9d8-4d36-a20a-5315b111836a -
PID Device
2066 Joy-Con 2 Right
2067 Joy-Con 2 Left
2068 Joy-Con 2 Pair (grip mode)
2069 Switch 2 Pro Controller
2073 Switch 2 GameCube Controller
Offset Size Field
0x00 4 Counter (uint32 LE, +1 per packet - use for drop detection)
0x04 4 Buttons bitfield
0x0A 3 Left stick (12-bit packed)
0x0D 3 Right stick
0x10 8 Mouse data (Joy-Con only)
0x19 6 Magnetometer X/Y/Z (int16 LE, feature bit 7)
0x1F 2 Battery voltage mV (uint16 LE)
0x21 1 Charging state (0x34 charging, 0x20 full)
0x22 2 Battery current (feature bit 5)
0x2A 18 Motion block (feature bit 2)
0x3C 1 Left trigger (NSO GC2 only)
0x3D 1 Right trigger (NSO GC2 only)
Sub-offset Abs Size Field
+0 0x2A 4 Motion timestamp
+4 0x2E 2 Temperature (25 + raw/127 °C)
+6 0x30 2 Accel X (int16 LE, 4096 = 1 g)
+8 0x32 2 Accel Y
+10 0x34 2 Accel Z
+12 0x36 2 Gyro X (int16 LE)
+14 0x38 2 Gyro Y
+16 0x3A 2 Gyro Z

Effective IMU rate is ~62 Hz (16 ms packets) on the common report.

These controllers never issue a Connection Parameter Update Request - the host must request a short interval. A default host interval of ~60 ms caps reporting at ~16.7 Hz; requesting a throughput-optimized interval (~5 ms) restores the full rate. On Windows 11 the driver requests it right after connect; on Linux the interval is negotiated by the system, tuned via the Bluetooth config. The path degrades gracefully - it streams at whatever interval the OS grants rather than failing.

Written to the command characteristic (WriteWithoutResponse, 8-byte minimum pad). Header: CMD | 0x91 | 0x01 | SUBCMD | 0x00 | LEN | 0x00 | 0x00 | DATA…

CMD Purpose
0x02 Flash read/write (factory / user calibration)
0x03 Init (input-report select)
0x06 Shutdown / reboot
0x09 Player LED
0x0A Vibration / sound preset
0x0B Battery query
0x0C Feature select (enable IMU / mag)
0x15 DO NOT USE - pairing-persistence write; the wrong call can brick pairing
Bit Mask Feature
0 0x01 Button state
1 0x02 Analog sticks
2 0x04 IMU (accel + gyro)
4 0x10 Mouse (Joy-Con only)
5 0x20 Rumble + battery current
7 0x80 Magnetometer (9-DOF)

Canonical enable sequence:

  1. 0C 91 01 02 00 04 00 00 <mask> 00 00 00 - set feature mask
  2. wait 500 ms
  3. 0C 91 01 04 00 04 00 00 <mask> 00 00 00 - activate features

Masks: 0x04 IMU, 0x80 mag, 0xFF all.

Address Content
0x13000-0x14FFF Factory data block
0x13002 Serial number
0x13012 USB product ID (authoritative variant id)
0x13040 + 4/8/12 3 × float32 gyro bias
0x13080 / 0x130C0 left / right stick calibration
0x13100 + 12/16/20 3 × float32 accel bias
0x1FA000 BLE pairing info (host + link key, up to 2 hosts)
0x1FD000 shipment flag

The chip applies factory IMU calibration internally, so there is no host-consumable cal to read for tracking - fusion’s rest-bias estimator covers it.

Body frame (x, z, -y), gravity +Z face-up. Standalone (non-grip) variants add:

Variant Remap
Joy-Con 2 L (x, y, z) → ( z, y, -x) (+90° about Y)
Joy-Con 2 R (x, y, z) → (-z, y, x) (−90° about Y)

The magnetometer shares the IMU’s PCB, so it uses the same base remap.

Gesture Bit Action
Home (short) byte 0x05, bit 0x10 Reset Yaw
Home (≥1 s hold) byte 0x05, bit 0x10 (timed) Reset Full
Capture byte 0x05, bit 0x20 Reset Yaw
  • A separate high-rate motion block (reports 0x07-0x0A, 40 bytes) is encrypted and unusable without a pairing-derived key, so the plaintext common report 0x05 is used instead.
  • Motion-based auto-calibration re-estimates gyro bias when the controller is held still (never still-based subtraction, which would break fusion).
  • The peripheral disappears from the BLE scan after pairing; restart the adapter if it isn’t seen within 30 s.