QtでJoypad取得

# はじめに
今までの投稿はOpenCVばかりでしたので、JoypadをQtで取得しようと思います。
次に投稿を予定しているOpenCVのEdgeの処理でもJoypadを使用したいと思います。

# 仕様
1.SubThreadでJoypadの処理を行う
2.MainThreadでJoypadの表示を行う
3.JoypadのAnalog Axisを使用する

# 使用Joypad
ELECOMさんのJoypadを使用しました。
Axis:3(Analog/Digital切り替えが内2つ) 
Button:12

# 参考先
Joypadの実装に関しては
[こちら](https://aki-yam.hatenablog.com/entry/20130729/1375097014)
を参考にさせていただきました。

# JoypadのCalibration
初めてJoypadを使用するときは、Calibrationを実施したほうが良いと思います。
「jstest-gtk」でCalibrationします。(他にCalibrationするやり方があるのでしたら、ご教示していただけると嬉しいです)
## インストール
ターミナルから下記を実行します。
```install command.
sudo apt install -y jstest-gtk
```
## Calibration
「アプリケーションを表示する」から「jstest-gtk」をクリックし、「プロパティ」をクリックします。
ここでAxis(表示はAxes)を見ると、「6」となっております。
左右で1、上下で1 = 2で、3Axis(軸)あるので、6と表示されるようです。
![Screenshot from 2022-12-19 10-34-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/f3a56e0a-9a03-a7e1-fe27-b1890aaf2485.png)
「Calibration」をクリックします。
![Screenshot from 2022-12-19 10-39-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/e169bb59-a3f5-0cbe-c191-987e09959361.png)
「StartCaribration」をクリックします。
![Screenshot from 2022-12-19 10-41-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/0d2a288b-e0c5-5d9b-e6ed-abaa8ecaa33a.png)
「OK」をクリックします。
![Screenshot from 2022-12-19 10-42-29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/b46ad6df-70c3-46a9-1c3c-5ddee9fbb57b.png)
「OK」クリック後、下記画面になりますので「StartCaribration」をクリックしてからJoyPadのすべての軸をすべての方向に傾けたり、すべてのボタンを押したりしてください。
すべての軸を傾けたり、すべてのボタンを押したら、「閉じる」をクリックします。
![Screenshot from 2022-12-19 10-44-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/27ca4bf0-9290-ea64-d7f3-ddf6f49fe025.png)
すべての軸を傾けたり、すべてのボタンを押して問題がないか確認してください。
終了するときは、「閉じる」をクリックしてください。
![Screenshot from 2022-12-19 10-48-33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/2d2cbd71-01b1-17b2-f826-a6b359a57ff5.png)
# プロジェクトの新規作成
新規に「joypad」というプロジェクトを作成します。
新規にプロジェクトを作成する方法は、
[QtでHello world!](https://qiita.com/tom_tom_tom/items/5cc7b3334b9fda9c7dc7)
を参照してください。
# UI
Joypad名称用Labelを1(geometryの幅を200、背景色はピンクに設定してくさい)
表題用のlabelを18個(Axis:6、Button:12)
表示用のlabelを18個(Axis:6、Button:12)
配置します。
labelの配置方法は、
[QtでHello world!](https://qiita.com/tom_tom_tom/items/5cc7b3334b9fda9c7dc7)
を参照してください。
表題用のLabelの名称を下記の通り設定します。
Axis1(L/R)、Axis1(U/D)、Axis2(L/R)、Axis2(U/D)、Axis3(L/R)、Axis3(U/D)
Button01、Button02、Button03、Button04、Button05、Button06
Button07、Button08、Button09、Button10、Button11、Button12
表示用のlabelのobjectName、text、背景色を下記の通り設定します。
|表題用Label|objectName|text|背景色|
|:---|:---|:---|:---|
|Axis1(L/R)|label_Axis1_LR|なし|黄色|
|Axis1(U/D)|label_Axis1_UD|なし|黄色|
|Axis2(L/R)|label_Axis2_LR|なし|黄色|
|Axis2(U/D)|label_Axis2_UD|なし|黄色|
|Axis3(L/R)|label_Axis3_LR|なし|黄色|
|Axis3(U/D)|label_Axis3_UD|なし|黄色|
|Button01|label_Button01|なし|水色|
|Button02|label_Button02|なし|水色|
|Button03|label_Button03|なし|水色|
|Button04|label_Button04|なし|水色|
|Button05|label_Button05|なし|水色|
|Button06|label_Button06|なし|水色|
|Button07|label_Button07|なし|水色|
|Button08|label_Button08|なし|水色|
|Button09|label_Button09|なし|水色|
|Button10|label_Button10|なし|水色|
|Button11|label_Button11|なし|水色|
|Button12|label_Button12|なし|水色|

上記を設定すると、下記のようになると思います。
![Screenshot from 2022-12-20 10-31-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/4d7fbee4-8459-0f36-5e03-735cd5b10d21.png)
# 実装
## クラスの追加
実装はJoypadを別スレッドで取得するようにします。
Joypad用のクラスは「getJoypad」とします。
クラスの作成方法は、
[QtでOpenCV(Threadによる取得)](https://qiita.com/tom_tom_tom/items/49c0229671f0e55d22e3)
を参照してください。
## 実装詳細
```mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include "getJoypad.h"								// 追加

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    Ui::MainWindow *ui;
// 追加(ここから)
public:
	getJoypad *getJoyThread;
private:
	int axisNum, buttonNum;
	QString joypadName;
	std::vector<int> joyAxisStatus;
	std::vector<char> joyButtonStatus;
	void reSizeAxisButtonMain(int, int);
public slots:
	void onValueChangedJoy(void);
};
// 追加(ここまで)
#endif // MAINWINDOW_H
```
```mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>								// 追加

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

	// 追加(ここから)
	getJoyThread = new getJoypad(this);
	if(getJoyThread->initJoypad()){
		getJoyThread->getJoypadData(&axisNum, &buttonNum, &joypadName);
		getJoyThread->reSizeAxisButton(axisNum, buttonNum);
		reSizeAxisButtonMain(axisNum, buttonNum);
		ui->label_joypadName->setText(joypadName);

		connect(getJoyThread, SIGNAL(valueChangedJoy(void)), this, SLOT(onValueChangedJoy(void)));
		getJoyThread->start();
	} else {
		QMessageBox msgBox(this);
		msgBox.setText(tr("Joypadがオープンできませんでした。"));
		msgBox.setWindowTitle(tr("エラー"));
		msgBox.setStandardButtons(QMessageBox::Yes);
		msgBox.setDefaultButton(QMessageBox::Yes);
		msgBox.setIcon(QMessageBox::Warning);
		msgBox.exec();
		exit(0);
	}
	// 追加(ここまで)
}

MainWindow::~MainWindow()
{
	getJoyThread->Stop = true;						// 追加
	delete ui;
}

// 追加(ここから)
void MainWindow::reSizeAxisButtonMain(int axisNum, int buttonNum)
{
	joyAxisStatus.resize(axisNum, 0);
	joyButtonStatus.resize(buttonNum, 0);
}

void MainWindow::onValueChangedJoy(void)
{
	getJoyThread->getJoypadStatus(joyAxisStatus, joyButtonStatus);

	ui->label_Axis1_LR->setNum(joyAxisStatus[0]);
	ui->label_Axis1_UD->setNum(joyAxisStatus[1]);
	ui->label_Axis2_LR->setNum(joyAxisStatus[2]);
	ui->label_Axis2_UD->setNum(joyAxisStatus[3]);
	ui->label_Axis3_LR->setNum(joyAxisStatus[4]);
	ui->label_Axis3_UD->setNum(joyAxisStatus[5]);

	ui->label_Button01->setNum(joyButtonStatus[0]);
	ui->label_Button02->setNum(joyButtonStatus[1]);
	ui->label_Button03->setNum(joyButtonStatus[2]);
	ui->label_Button04->setNum(joyButtonStatus[3]);
	ui->label_Button05->setNum(joyButtonStatus[4]);
	ui->label_Button06->setNum(joyButtonStatus[5]);
	ui->label_Button07->setNum(joyButtonStatus[6]);
	ui->label_Button08->setNum(joyButtonStatus[7]);
	ui->label_Button09->setNum(joyButtonStatus[8]);
	ui->label_Button10->setNum(joyButtonStatus[9]);
	ui->label_Button11->setNum(joyButtonStatus[10]);
	ui->label_Button12->setNum(joyButtonStatus[11]);
}
// 追加(ここまで)
```
```getJoypad.h
#ifndef GETJOYPAD_H
#define GETJOYPAD_H

#include <QThread>
#include <QObject>
#include <vector>									// 追加
#include <linux/joystick.h>							// 追加

#define JOYPAD_NAME_SIZE	80						// 追加

class getJoypad : public QThread
{
    Q_OBJECT
public:
//	getJoypad();									// 削除
	getJoypad(QObject *parent = 0, bool b = false);	// 修正
	// 追加(ここから)
	~getJoypad();
	bool initJoypad(void);
	void getJoypadData(int*, int*, QString*);
	void reSizeAxisButton(int, int);
	void run(void);
	void getJoypadStatus(std::vector<int>&, std::vector<char>&);
	bool Stop;
private:
	int joyFd, numOfAxis, numOfButtons;
	std::vector<int> joyAxis;
	std::vector<char> joyButton;
	char nameOfJoypad[JOYPAD_NAME_SIZE];
	js_event js;
signals:
	void valueChangedJoy(void);
// 追加(ここまで)
};

#endif // GETJOYPAD_H
```
```getJoypad.cpp
#include <QMutex>									// 追加
#include <QTextCodec>								// 追加
#include <sys/ioctl.h>								// 追加
#include <unistd.h>									// 追加
#include <fcntl.h>									// 追加
#include "getJoypad.h"

#define JOY_DEV "/dev/input/js0"					// 追加

//getJoypad::getJoypad()							// 削除
getJoypad::getJoypad(QObject *parent, bool b) : QThread(parent), Stop(b)	// 修正
{

}

// 追加(ここから)
getJoypad::~getJoypad()
{
	Stop = true;
	close(joyFd);
}

bool getJoypad::initJoypad(void)
{
	bool ret = true;

	numOfAxis = numOfButtons = 0;
	memset(nameOfJoypad, 0x00, sizeof(nameOfJoypad));

	if((joyFd = open(JOY_DEV, O_RDONLY)) < 0){
		ret = false;
	} else {
		ioctl(joyFd, JSIOCGAXES, &numOfAxis);
		ioctl(joyFd, JSIOCGBUTTONS, &numOfButtons);
		ioctl(joyFd, JSIOCGNAME(JOYPAD_NAME_SIZE), nameOfJoypad);

		fcntl(joyFd, F_SETFL, O_NONBLOCK);
	}

	return ret;
}

void getJoypad::getJoypadData(int *axisNum, int *buttonNum, QString *joypadName)
{
	*axisNum = numOfAxis;
	*buttonNum = numOfButtons;
	QTextCodec *tc = QTextCodec::codecForLocale();
	*joypadName = QString(tc->toUnicode(nameOfJoypad));
}

void getJoypad::reSizeAxisButton(int axisNum, int buttonNum)
{
	joyAxis.resize(axisNum, 0);
	joyButton.resize(buttonNum, 0);
}

void getJoypad::run(void)
{
	QMutex mutex;

	while(1){
		mutex.lock();
		if(this->Stop){
			 break;
		}
		mutex.unlock();

		read(joyFd, &js, sizeof(js_event));
		switch(js.type & ~JS_EVENT_INIT){
			case JS_EVENT_AXIS:
				if((int)js.number>=(int)joyAxis.size()){
					continue;
				}
				joyAxis[(int)js.number] = js.value;
				break;
			case JS_EVENT_BUTTON:
                if((int)js.number>=(int)joyButton.size()){
					continue;
				}
				joyButton[(int)js.number] = js.value;
				break;
		}

		emit valueChangedJoy();

		this->msleep(10);
	}
}

void getJoypad::getJoypadStatus(std::vector<int>& axisStatus, std::vector<char>& buttonStatus)
{
	for(int loopCount = 0; loopCount < (int)joyAxis.size(); loopCount++){
		axisStatus[loopCount] = joyAxis[loopCount];
	}

	for(int loopCount = 0; loopCount < (int)joyButton.size(); loopCount++){
		buttonStatus[loopCount] = joyButton[loopCount];
	}
}
```
# build&run
実装が完了しましたら、▶をクリックして、build & run します。
Calibrationが済んでいるのでしたら、下記の表示になると思います。
![Screenshot from 2022-12-20 10-40-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/d65a80af-4b17-954b-7da3-b2a0d410ddee.png)
## Axis1を動かしてみる
Axis1を左に倒した状態です。
![Screenshot from 2022-12-20 10-43-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/c781a991-49fd-0312-9cc0-d09a4f25cac5.png)
## Axis2を動かしてみる
Axis2を下に倒した状態です。
![Screenshot from 2022-12-20 10-45-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/e17e853c-f67a-b63c-3104-ab24e3228472.png)
## Buttonを押してみる
Button04を押した状態です。
![Screenshot from 2022-12-20 10-46-35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1473918/8bbc4e67-67ff-49b1-d78a-c2808969de84.png)
皆さんも上手く動いたでしょうか？
# まとめ
今回のJoypadもSubThreadで取得し、MainThreadでGUIの処理を行いました。
OpenCVのThreadの投稿では、USBカメラの取得をSubThreadで取得し、MainThreadでGUIの処理を行いました。
考えた方(実装の仕方)は同じにしております。
JoypadのCalibrationも一緒に説明しましたので、記事が長くなりました。申し訳ございません。