ゲームパッドの入力を取得するためにWindowsでのデバイス操作と通信方法を組み合わせる。
MinGW,SDL2,VScodeで出来ないかと試行錯誤したが、何故かPassが通らなかったり、ヘッダーファイルが検索できなかったりとあった為、断念した。
代わりに、Visual StudioにインストールされているWindows APIを使用して試した。
接続方法
ゲームパッド(USB) → 開発用PC(windows)(LAN) → JetsonNano
完成品
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)を指定している
- ソケットを作成する関数。UDPソケットを作成するため、
-
InetPtonA()
- IPアドレスを「文字列」から「バイナリ形式」に変換するための関数
-
SERVER_IP
として設定されたJetson NanoのIPアドレス("192.168.1.100"
)をネットワークバイナリ形式に変換し、serverAddr.sin_addr
に格納する
-
XInputGetState()
- Xboxコントローラーの状態を取得する関数です。コントローラーのボタンやスティック、トリガーの状態を
XINPUT_STATE
構造体に格納する - プログラムでは、0番目のゲームパッドを対象にしている(複数のゲームパッドが接続されている場合に区別するため)
- Xboxコントローラーの状態を取得する関数です。コントローラーのボタンやスティック、トリガーの状態を
-
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側は無限ループでこの関数を呼び出し続け、送信されたデータを常に受信する
- UDPでデータを受信するための関数です。Windows側から送信されたゲームパッドのデータを
重要な設定
-
TERMINATION_SIGNAL
- 受信データ内のボタン情報(
buttons
フィールド)が0xFFFF
であれば、終了シグナルとして認識し、プログラムのループを終了する
- 受信データ内のボタン情報(
-
close()
- ソケットを閉じるための関数。プログラムの終了時にリソースを適切に解放する
3. 通信の仕組み(全体の流れ)
-
ゲームパッドの状態取得
- Windows側では、
XInputGetState()
関数を使ってゲームパッドの状態を定期的に取得する
- Windows側では、
-
データの送信
- 取得したゲームパッドの状態(スティックの位置、トリガーの押し具合、ボタンの状態)は、
GamepadData
構造体に格納され、UDPソケットを通してJetson Nano側に送信される
- 取得したゲームパッドの状態(スティックの位置、トリガーの押し具合、ボタンの状態)は、
-
データの受信
- Jetson Nano側で、
recvfrom()
でゲームパッドデータを受信する。データが受信されるたびに、スティックやトリガー、ボタンの状態を出力する
- Jetson Nano側で、
-
終了シグナルの処理
- Windows側で
Selectボタン
が押された場合、終了シグナル(TERMINATION_SIGNAL
=0xFFFF
)を設定し、Jetson Nanoに送信する - Jetson Nanoはこのシグナルを受信すると、ループを終了してプログラムを終了。Windows側も同様に終了
- Windows側で
補足:UDP通信の特性
- UDP通信は、軽量で高速だが、データが必ずしも届くとは限らない非信頼性がある。したがって、特にリアルタイム性やデータの喪失が許されないシステムでは、通常TCPなどの信頼性のあるプロトコルが使用される
- 今回のプログラムでは、リアルタイム性を優先したためUDPを使用している