開発用PCに接続されたゲームパッドの信号を、JetsonNanoに送る方法

ゲームパッドの入力を取得するためにWindowsでのデバイス操作と通信方法を組み合わせる。

MinGW,SDL2,VScodeで出来ないかと試行錯誤したが、何故かPassが通らなかったり、ヘッダーファイルが検索できなかったりとあった為、断念した。

代わりに、Visual Studioにインストールされている**Windows API**を使用して試した。

## 接続方法

ゲームパッド(USB) → 開発用PC(windows)(LAN) → JetsonNano

## 完成品
![スクリーンショット 2024-10-28 210533.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3840203/ba515d59-29d6-ed27-af27-afa811f7095a.png)

## **Windows側（送信側）プログラム**

```c
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <xinput.h>
#include <stdio.h>
#include <stdint.h>  

#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "xinput.lib")

#define SERVER_IP "192.168.1.100"  // Jetson NanoのIPアドレスに変更
#define SERVER_PORT 12345
#define TERMINATION_SIGNAL 0xFFFF  // 終了シグナル

// ゲームパッドのデータ構造
typedef struct {
    int16_t lx;  // 左スティック X
    int16_t ly;  // 左スティック Y
    int16_t rx;  // 右スティック X
    int16_t ry;  // 右スティック Y
    uint8_t lt;  // 左トリガー
    uint8_t rt;  // 右トリガー
    uint16_t buttons;  // ボタン状態
} GamepadData;

int main() {
    // Winsockの初期化
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        printf("WSAStartupに失敗しました\n");
        return 1;
    }

    // ソケット作成
    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == INVALID_SOCKET) {
        printf("ソケット作成に失敗しました\n");
        WSACleanup();
        return 1;
    }

    // サーバーアドレスの設定
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    InetPtonA(AF_INET, SERVER_IP, &serverAddr.sin_addr);

    // XInputでゲームパッドの状態を取得
    XINPUT_STATE state;
    GamepadData data;
    while (1) {
        if (XInputGetState(0, &state) == ERROR_SUCCESS) {
            // 左スティック、右スティック、トリガー、ボタン状態を取得
            data.lx = state.Gamepad.sThumbLX;
            data.ly = state.Gamepad.sThumbLY;
            data.rx = state.Gamepad.sThumbRX;
            data.ry = state.Gamepad.sThumbRY;
            data.lt = state.Gamepad.bLeftTrigger;
            data.rt = state.Gamepad.bRightTrigger;
            data.buttons = state.Gamepad.wButtons;

            // データ送信
            sendto(sock, (char*)&data, sizeof(data), 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr));

            // 送信の出力
            printf("送信: LX = %d, LY = %d, RX = %d, RY = %d, LT = %d, RT = %d, Buttons = %X\n",
                   data.lx, data.ly, data.rx, data.ry, data.lt, data.rt, data.buttons);

            // Selectボタン（BACKボタン）が押された場合、終了シグナルを送信してプログラム終了
            if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) {  // Selectボタン = BACKボタン
                printf("Selectボタンが押されました。終了シグナルを送信します。\n");
                data.buttons = TERMINATION_SIGNAL;
                sendto(sock, (char*)&data, sizeof(data), 0, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
                break;
            }
        }
        Sleep(100);  // データ送信間隔（100ms）
    }

    // クリーンアップ
    closesocket(sock);
    WSACleanup();
    return 0;
}
```
## **Jetson Nano側（受信側）プログラム**

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdint.h> 

#define LOCAL_PORT 12345  // 受信ポート番号
#define TERMINATION_SIGNAL 0xFFFF  // 終了シグナル

// ゲームパッドのデータ構造
typedef struct {
    int16_t lx;  // 左スティック X
    int16_t ly;  // 左スティック Y
    int16_t rx;  // 右スティック X
    int16_t ry;  // 右スティック Y
    uint8_t lt;  // 左トリガー
    uint8_t rt;  // 右トリガー
    uint16_t buttons;  // ボタン状態
} GamepadData;

int main() {
    // ソケットの初期化
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        printf("Socket creation failed!\n");
        return 1;
    }

    // 受信側アドレスの設定
    struct sockaddr_in local_addr;
    memset(&local_addr, 0, sizeof(local_addr));
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(LOCAL_PORT);
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // バインド処理
    if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
        printf("Bind failed!\n");
        return 1;
    }

    // メインループ
    GamepadData data;
    while (1) {
        // データ受信
        recvfrom(sockfd, &data, sizeof(data), 0, NULL, NULL);

        // 受信したデータの出力
        printf("受信: LX = %d, LY = %d, RX = %d, RY = %d, LT = %d, RT = %d, Buttons = %X\n",
               data.lx, data.ly, data.rx, data.ry, data.lt, data.rt, data.buttons);

        // 終了シグナルが送られてきた場合、ループを終了
        if (data.buttons == TERMINATION_SIGNAL) {
            printf("終了シグナルを受信しました。プログラムを終了します。\n");
            break;
        }
    }

    // クリーンアップ
    close(sockfd);
    return 0;
}
```



## 解説

### 1. **Windows側（送信側）**

#### **構造**
- **`WSAStartup()`**
  - WindowsのネットワークAPIであるWinsockを初期化する関数
  - この関数を呼び出して、ネットワーク通信ができるように環境をセットアップしている
  - `MAKEWORD(2, 2)` はWinsockのバージョンを指定している（バージョン2.2）

- **`socket()`**
  - ソケットを作成する関数。UDPソケットを作成するため、`AF_INET`（IPv4）と`SOCK_DGRAM`（UDP）を指定している

- **`InetPtonA()`**
  - IPアドレスを「文字列」から「バイナリ形式」に変換するための関数
  - `SERVER_IP`として設定されたJetson NanoのIPアドレス（`"192.168.1.100"`）をネットワークバイナリ形式に変換し、`serverAddr.sin_addr`に格納する

- **`XInputGetState()`**
  - Xboxコントローラーの状態を取得する関数です。コントローラーのボタンやスティック、トリガーの状態を`XINPUT_STATE`構造体に格納する
  - プログラムでは、0番目のゲームパッドを対象にしている（複数のゲームパッドが接続されている場合に区別するため）

- **`sendto()`**
  - 作成したUDPソケットを使って、Jetson Nano側にゲームパッドのデータを送信する関数
  - `GamepadData`という構造体にゲームパッドのデータをまとめて送り、`serverAddr`に設定されたJetson Nanoのアドレスに向けてデータを送信する

#### **重要な設定**
- **`TERMINATION_SIGNAL`**
  - プログラムが終了する際に送信する特別なシグナル。値は`0xFFFF`と設定し、Jetson Nano側にこの値を送信することでプログラムが終了するようにしている
  
- **`XINPUT_GAMEPAD_BACK`**
  - ゲームパッドの**Selectボタン**に対応する定数。このボタンが押されると、この定数がビットマスクでONになるため、プログラム内で確認し、終了シグナルを送信している

#### **送信データの構造体**
```c
struct GamepadData {
    int16_t lx, ly;  // 左スティック X, Y軸
    int16_t rx, ry;  // 右スティック X, Y軸
    uint8_t lt, rt;  // 左トリガー、右トリガー
    uint16_t buttons;  // ボタンの状態
};
```
- ゲームパッドの状態（スティックの位置やトリガーの押し具合、ボタンの状態）をまとめて管理するための構造体
  - **`int16_t lx, ly, rx, ry`**: 左右のスティックのX軸とY軸の値（-32768から32767の範囲）
  - **`uint8_t lt, rt`**: 左右のトリガーの押し具合（0から255の範囲）
  - **`uint16_t buttons`**: ボタンの状態（各ビットが特定のボタンの状態を示す）

---

### 2. **Jetson Nano側（受信側）**

#### **構造**
- **`socket()`**
  - Windows側と同様に、Jetson Nano側でもUDPソケットを作成する

- **`bind()`**
  - ソケットを特定のポート（`LOCAL_PORT` = 12345）にバインドする。これで、Jetson Nanoは特定のポートでデータを受信できるようになる
  - **`INADDR_ANY`** を使うことで、どのIPアドレスからでもデータを受信できるよう設定している（Jetson Nanoが複数のネットワークインターフェースを持っている場合も含む）

- **`recvfrom()`**
  - UDPでデータを受信するための関数です。Windows側から送信されたゲームパッドのデータを`GamepadData`構造体に格納する
  - Jetson Nano側は無限ループでこの関数を呼び出し続け、送信されたデータを常に受信する

#### **重要な設定**
- **`TERMINATION_SIGNAL`**
  - 受信データ内のボタン情報（`buttons` フィールド）が`0xFFFF`であれば、終了シグナルとして認識し、プログラムのループを終了する

- **`close()`**
  - ソケットを閉じるための関数。プログラムの終了時にリソースを適切に解放する

---

### 3. **通信の仕組み（全体の流れ）**

1. **ゲームパッドの状態取得**
   - Windows側では、`XInputGetState()`関数を使ってゲームパッドの状態を定期的に取得する

2. **データの送信**
   - 取得したゲームパッドの状態（スティックの位置、トリガーの押し具合、ボタンの状態）は、`GamepadData`構造体に格納され、UDPソケットを通してJetson Nano側に送信される
   
3. **データの受信**
   - Jetson Nano側で、`recvfrom()`でゲームパッドデータを受信する。データが受信されるたびに、スティックやトリガー、ボタンの状態を出力する

4. **終了シグナルの処理**
   - Windows側で`Selectボタン`が押された場合、終了シグナル（`TERMINATION_SIGNAL` = `0xFFFF`）を設定し、Jetson Nanoに送信する
   - Jetson Nanoはこのシグナルを受信すると、ループを終了してプログラムを終了。Windows側も同様に終了


### **補足：UDP通信の特性**
- **UDP通信**は、軽量で高速だが、データが必ずしも届くとは限らない**非信頼性**がある。したがって、特にリアルタイム性やデータの喪失が許されないシステムでは、通常**TCP**などの信頼性のあるプロトコルが使用される
- 今回のプログラムでは、リアルタイム性を優先したため**UDP**を使用している