node-hid を使って Joy-Con のボタン押下を Node.js で検出（とりあえず A・Bボタン）

## はじめに

過去に、以下の記事で書いた内容を少しだけ試していた node-hid の話です。この node-hid は、Joy-Con を HIDデバイスとして扱う際に便利なパッケージです。

●【完走賞ゲット-14】node-hid の hid-showdevicesコマンドで HIDデバイスに関する情報を出力（Node.js、JavaScript） - Qiita
　https://qiita.com/youtoy/items/eb7f8418fa926266946f

この時は、コードを書かずに利用できる、コマンドとして扱える仕組みを使っていました。

今回は Node.js のコードを書いて、それで Joy-Con の A・Bボタン押下を検出してみます。


ちなみに、以下のコントローラーを PC で扱って、個人のモノ作り活動に活用しようと思っていて、その準備運動的な感じで手をつけました（なお、過去に WebHID／Gamepad API で Joy-Con を扱ったことは、既にあったりします）。

https://x.com/youtoy/status/1936591098362871891

上記は、おそらくは Joy-Con を HIDデバイスとして扱って、値をとれるだろうと想定してのことです（[それをやっていた記事](https://dailyportalz.jp/kiji/jidou-de-marksheet)を見かけていて、問題なく実現できそう）。


## Joy-Con を HIDデバイスとして扱う

Joy-Con を HIDデバイスとして扱う場合、以下の記事の中で紹介されているような、やりとりされるデータのフォーマットを考慮した処理が必要になります。

●Amusement Creators | SwitchのJoyConをプログラムから使う
　https://www.amusement-creators.info/articles/advent_calendar/2019/23_0/

通信時のデータフォーマットの情報が必要だったり、内容によっては機能を有効化する手順をふむ必要があったりしてその手順に関する情報を得るなどという、そういったデバイス固有の情報を得る必要があったりします。


### ブラウザの WebHID の場合

ちなみに Node.js ではなく、ブラウザ上での JavaScript で扱う場合（ブラウザの WebHID の API を使う場合）は、以下の記事 1つ目のようにデータフォーマットの仕様に合わせた処理を実装したことがありました。また、以下の 2つ目の記事にあるようなライブラリ（データフォーマットを考慮した処理をラップしてくれているもの）を活用したりする方法があります。

●ブラウザの WebHID を p5.js Web Editor上で利用する：デバイスは DualShock 4 - Qiita
　https://qiita.com/youtoy/items/90c1c98829f400969657

●【小ネタ】ブラウザで PS4用コントローラー「DUALSHOCK 4」を扱う下準備： WebHID API と WebHID-DS4 - Qiita
　https://qiita.com/youtoy/items/deabcc7a126186933092

メジャーなコントローラーは、ライブラリがでているので、それを探して活用するのがオススメです。


## node-hid を使った処理

この後は、サクッと実装の例 ＋ 処理を実行した結果の話を書いて終わります。

node-hid を使った処理は以下の通りです。以下を実行する前には、PC と Joy-Con を Bluetooth でペアリングしておいてください。

```javascript
const HID = require("node-hid");

const VID = 0x057e; // Nintendo
const PID = 0x2007; // Joy-Con R

const devInfo = HID.devices().find(
  (d) => d.vendorId === VID && d.productId === PID
);
if (!devInfo) {
  console.error(
    "Joy-Con R が見つかりません。Bluetooth ペアリングを確認してください。"
  );
  process.exit(1);
}

/* --- 出力レポート 0x01（Sub-command）生成ユーティリティ --- */
let pkt = 0;
const RUMBLE_OFF = Buffer.from([0, 1, 0x40, 0x40, 0, 1, 0x40, 0x40]); // 8 byte 固定

function makeSubCmd(id, data = Buffer.alloc(0)) {
  const buf = Buffer.alloc(10 + data.length);
  buf[0] = 0x01; // Report ID
  buf[1] = pkt++ & 0x0f; // Packet counter (0-15)
  RUMBLE_OFF.copy(buf, 2);
  buf[10] = id; // Sub-command ID
  data.copy(buf, 11);
  return buf;
}

/* --- 本体 --- */
const joycon = new HID.HID(devInfo.path);

/* 0x03: Set input-report mode → 0x30 (標準フルレポート 60 Hz) */
joycon.write([...makeSubCmd(0x03, Buffer.from([0x30]))]);
console.log("🎮 Joy-Con R 接続完了。A/B 押下を監視中…");

/* 直前の A/B 状態を保持 */
let prev = 0;

joycon.on("data", (buf) => {
  if (buf[0] !== 0x30) return;
  const buttons = buf[3];

  const now = buttons & 0x0c; // 0x08 = A, 0x04 = B
  const rising = ~prev & now; // 押下関連のフラグ
  if (rising & 0x08) console.log("A ボタン押下");
  if (rising & 0x04) console.log("B ボタン押下");
  prev = now;
});

joycon.on("error", (err) => console.error("HID エラー:", err));
```

処理の冒頭部分で ID をハードコーディングしていますが、HIDデバイスをもれなく探すのではなく、Switch の Joy-Con R を探す処理になるよう、とりあえず適当にフィルターをかけています。それ以降は、所定のフォーマットに合わせたバイナリの読み書きの処理などを入れています。

### 実行結果

上記を実行した結果は以下で、ボタン押下時にコンソールにメッセージが出ており、そのメッセージで A・Bボタンのどちらが押されたかを確認できます。問題なく動作したようです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/aei96Tz2N_Q?si=E8bP6uUQW4Ou0r_a" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

無事、A・Bボタンの押下を検出できました。


#### 他のボタンやスティックを扱う検証

他のボタン・スティックを扱う処理も、とりあえず版を実装してみて、動作確認はやってみました（コードの内容は整理中）。その結果は以下のとおりです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/scHrGTGrXns?si=m4wijk9wiR5G9p8o" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


## おわりに

今回、node-hid を使って Joy-Con のボタン押下を Node.js で検出するというのを、とりあえず A・Bボタンを対象にやってみました。

この先は、冒頭で少し掲載していた以下のコントローラーを扱う部分へ入っていければと思います。

https://x.com/youtoy/status/1936433290984247537


### 余談

「人生ゲーム for Nintendo Switch専用ルーレットコントローラー」をポチるか長らく保留してたものの、それをポチる引き金になった記事について、[実装が GitHubリポジトリで公開されている](https://github.com/stamefusa/joycon-roulette-reader/tree/main)という情報をいただきました（GitHubリポジトリのほうの情報公開は気がつけてなかったので、知れてありがたかったです）。

https://x.com/mascii_k/status/1936584031740801358

Node.js での実装らしいとのことで、サクッと試せそうです。また、個人的には WebHID で扱いたいと思ったものなので、それもやっていければと思います。


#### 【追記】余談に関するその後

とりあえず、上記の GitHubリポジトリに公開されていたものを使った動作確認ができました。

https://x.com/youtoy/status/1936613381391499659


また、描画まわりを独自の内容に置きかえてみる（まずは p5.js によるシンプルな Canvas描画のものに置きかえ）というのも試せました。

<iframe width="560" height="315" src="https://www.youtube.com/embed/1kovta1WCgo?si=46viNPjcz1x1tUVk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>