1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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

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

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

接続方法

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

完成品

スクリーンショット 2024-10-28 210533.png

Windows側(送信側)プログラム

#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側(受信側)プログラム

#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になるため、プログラム内で確認し、終了シグナルを送信している

送信データの構造体

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を使用している
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?