罗技方向盘SDK开发笔记

这段时间因为项目需求,接触到了罗技G29方向盘的SDK开发,能够参考的资料比较有限,一路磕磕碰碰遇见不少问题,硬着头走了下去,不过最后还是成功了,写下这篇笔记来记录下我的开发过程,也给有需要的人参考,少走点弯路

一.开发环境和开发工具
开发环境:win10
开发工具:vs2017
方向盘型号:罗技G29

开发前的准备
(1).去罗技官网上下载罗技方向盘SDK
https://www.logitechg.com.cn/zh-cn/innovation/developer-lab.html
文件中有相关的.h和.lib文件,以及三个mfc例程以及相关的说明文档

(2).下载罗技游戏软件
这里要说一下,在SDK文档里提出了方向盘得在罗技游戏软件运行的情况下才能进行相关的SDK开发,所以这个软件在开发中需要全程运行,注意下载后它会提醒你下载新的罗技 G support,别理它就是了,罗技 G support根本就识别不出来罗技G29方向盘.
下载链接:https://support.logi.com/hc/zh-cn/articles/360025298053

(3).检测方向盘是否正常工作
正常情况下,在方向盘上电并接入电脑后,方向盘会自动旋转几圈然后拨正,打开罗技游戏软件的时候也会有这个现象,接好方向盘并且打开罗技游戏软件,我们来通过官方提供的demo来检测一下方向盘是否能够正常工作.
先介绍一下这几个demo
罗技方向盘SDK开发笔记_第1张图片第一个是个比较完善的demo,打开后的界面大概如下
罗技方向盘SDK开发笔记_第2张图片正常情况下,在点击INIT按钮后,界面上就会出现方向盘的相关数据,这里我没有接入方向盘,所以就没有显示,我们需要记录下一些数据,首先是确认下数据是出现在哪块区域的,就是divice 0 还是divice 1,这关系到后面相关api的调用参数,还有就是按下方向盘上的十字按键后,是在POV 0 还是POV 1显示数据的理由同上。

第二个demo我也没搞懂究竟是干什么用的,不过我这边的开发也用不到这个demo,感兴趣的可以去自己研究

第三个demo比较有意思,这个demo是罗技官方提供给开发者用来测试SDK中提供的相关API是否能够正常工作的MFC程序

罗技方向盘SDK开发笔记_第3张图片具体的代码逻辑就是你点击相关的API按钮,它就会调用相关的API,然后将返回结果显示在下方的信息栏里面,不过这个demo没有写完整,很多按钮的功能都还没有实现,你点击的话它会提示这个功能还没有写完(???,官方拖更),不过并不影响我们正常获取方向盘数据

二.阅读SDK文档,弄清相关的API调用顺序

(1).这里我就简单介绍一下几个关键的函数,首先是两个初始化函数
罗技方向盘SDK开发笔记_第4张图片bool LogiSteeringInitialize(CONST bool ignoreXInputControllers)
这个初始化函数会自动寻找当前处于最前端的窗口句柄并传入给这个函数进行方向盘的初始化,不过我在实际使用中这个函数经常抽风,返回值一直不稳定,所以我在后面的程序开发中舍弃了使用这个初始化的打算

bool LogiSteeringInitializeWithWindow(CONST bool ignoreXInputControllers, HWND hwnd)

这个函数的效果和上一个函数是一样的,第一个参数是是否忽略X-IPU的参数,第二个参数是当前程序的窗口句柄,这个函数的不同之处在于我们需要手动将你写的程序界面的句柄传入到这个函数里面去,那么什么是句柄呢?
我也看了不少资料,我是把它理解为界面程序的类似pid号的一个东西,让windows系统能够找到你的界面程序,这是我遇见的第一个坑,当初我想如果用windows编程创建一个窗口的话太麻烦了,底层的代码又臭又长,想直接通过获取win32控制台程序的句柄传入到这个函数里面去,但初始化结果一直都是失败的,折腾了很久一直都没有搞定,最好还是老老实实写了一个空界面来初始化,值得一提的是,这个初始化函数要求当前界面处于所有应用的最顶端,意思就是如果你把这个界面缩小,获取点击其他界面后,这个程序就拉跨了,所以在使用的时候要确保这一点

(2).相关API的调用顺序
这里贴一张图,是来自于其他博主的,使用的C#的sdk,不过流程都是一致的
罗技方向盘SDK开发笔记_第5张图片在C/C++使用的时候直接把前面的类名去掉即可,然后将初始化的函数换成
LogiSteeringInitializeWithWindow,值得一提的是,如果方向盘初始化成功的话,方向盘的旋转阻力会变成0,所以可以通过这个方法来判断方向盘是否初始化成功.

三.搭建开发环境

创建一个空的windows桌面应用程序,并且将SDK包中提供的头文件和库文件复制到项目目录下面,并且配置好,具体的配置过程可以参考下面
罗技方向盘SDK开发笔记_第6张图片然后在你的main.cpp里面添加

#pragma comment(lib, "LogitechSteeringWheelLib.lib")

先贴一下完整的代码,我这个程序主要是获取方向盘的基础数据,转向,油门,刹车等信息后上传到局域网内的服务器上

#include 
#include 
#include 
#include 
#include

#include "LogitechSteeringWheelLib.h"

#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "LogitechSteeringWheelLib.lib")
#pragma comment( linker, "/subsystem:\"console\" /entry:\"WinMainCRTStartup\"")


using namespace std;



void initialization() {
	//初始化套接字库
	WORD w_req = MAKEWORD(2, 2);//版本号
	WSADATA wsadata;
	int err;
	err = WSAStartup(w_req, &wsadata);
	if (err != 0) {
		cout << "初始化套接字库失败!" << endl;
	}
	else {
		//cout << "初始化套接字库成功!" << endl;
	}
	//检测版本号
	if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
		cout << "套接字库版本号不符!" << endl;
		WSACleanup();
	}
	else {
		//cout << "套接字库版本正确!" << endl;
	}
	//填充服务端地址信息

}

void SendMessage(int x, int y, int z)
{
	//定义长度变量
	int send_len = 0;
	int recv_len = 0;
	//定义发送缓冲区和接受缓冲区
	char send_buf[100];
	char recv_buf[100];
	//定义服务端套接字,接受请求套接字
	SOCKET s_server;
	//服务端地址客户端地址
	SOCKADDR_IN server_addr;
	initialization();
	//填充服务端信息
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.103");
	//server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	//server_addr.sin_port = htons(2020);
	server_addr.sin_port = htons(1888);
	//创建套接字
	s_server = socket(AF_INET, SOCK_STREAM, 0);
	if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
		//cout << "服务器连接失败!" << endl;
		WSACleanup();
	}
	else {
		//cout << "服务器连接成功!" << endl;
	}

	//发送,接收数据
	
		//cout << "请输入发送信息:";
		//cin >> send_buf;
		sprintf_s(send_buf, "%d,%d,%d", x, y, z);
		send_len = send(s_server, send_buf, strlen(send_buf), 0);
		if (send_len < 0) {

			cout << "发送失败!" << endl;
			
		}
		printf("x = %d,y = %d,z = %d\n", x, y, z);
		Sleep(500);
		
		/*recv_len = recv(s_server, recv_buf, 100, 0);
		if (recv_len < 0) {
			cout << "接受失败!" << endl;
			break;
		}
		else {
			cout << "服务端信息:" << recv_buf << endl;
		}*/

	
	//关闭套接字
	closesocket(s_server);
	//释放DLL资源
	WSACleanup();

}


void WheelInit(HWND hwnd)
{

	//LogiSteeringInitialize(true);
	
		if (LogiSteeringInitializeWithWindow(true, hwnd))
		{
			
				printf("init secuss\n");
				//printf("%d\n", hwnd);
				while(LogiUpdate() && LogiIsConnected(1))
				{
						//printf("connect secuss\n");
						Sleep(100);
						DIJOYSTATE2 * wheel = LogiGetState(1);
						//输出角度,油门,刹车信息
						
						//printf("Angle = %d  Accelerator = %d  Brake = %d\n", wheel->lX, wheel->lY,wheel->lRz);
						SendMessage(wheel->lX, wheel->lY, wheel->lRz);
						//printf(wheel->rgdwPOV)
						//std::cout << wheel->rgdwPOV[0] << endl;
						switch (wheel->rgdwPOV[0])
						{
						case(0):
							cout << "D-pad Up Button Pressed" << endl; break;
						case(18000):
							cout << "D-pad Down Button Pressed" << endl; break;
						case(27000):
							cout << "D-pad Left Button Pressed" << endl; break;
						case(9000):
							cout << "D-pad Right Button Pressed" << endl; break;
						case(31500):
							cout << "D-pad Left-up Button Pressed" << endl; break;
						case(4500):
							cout << "D-pad Right-up Button Pressed" << endl; break;
						case(22500):
							cout << "D-pad Left-down Button Pressed" << endl; break;
						case(13500):
							cout << "D-pad Right-down Button Pressed" << endl; break;

						
						}
						
						if (LogiButtonTriggered(1, 19)) 
						{
							printf("-----------------------\n");
								printf("Button 19 Pressed\n");
							printf("-----------------------\n");
						}

						if (LogiButtonTriggered(1, 20))
						{
							printf("-----------------------\n");
							printf("Button 20 Pressed\n");
							printf("-----------------------\n");
						}

						if (LogiButtonTriggered(1, 23))
						{
							printf("-----------------------\n");
							printf("Button 23 Pressed\n");
							printf("-----------------------\n");
						}
						

				}
			
		}
		else
		{
			printf("init faild");
		}
	
}


// 必须要进行前导声明  
LRESULT CALLBACK WindowProc(
	_In_  HWND hwnd,
	_In_  UINT uMsg,
	_In_  WPARAM wParam,
	_In_  LPARAM lParam
);

int CALLBACK WinMain(
	_In_  HINSTANCE hInstance,
	_In_  HINSTANCE hPrevInstance,
	_In_  LPSTR lpCmdLine,
	_In_  int nCmdShow
)
{
	// 类名  
	TCHAR cls_Name[] = L"My Class";
	// 设计窗口类  
	WNDCLASS wc = { sizeof(WNDCLASS) };
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wc.lpfnWndProc = WindowProc;
	wc.lpszClassName = cls_Name;
	wc.hInstance = hInstance;
	wc.style = CS_HREDRAW | CS_VREDRAW;

	// 注册窗口类  
	RegisterClass(&wc);

	// 创建窗口
	HWND hwnd = CreateWindow(
		cls_Name,           //类名,要和刚才注册的一致  
		L"方向盘Demo",          //窗口标题文字  
		WS_OVERLAPPEDWINDOW,        //窗口外观样式  
		38,             //窗口相对于父级的X坐标  
		20,             //窗口相对于父级的Y坐标  
		500,                //窗口的宽度  
		500,                //窗口的高度  
		NULL,               //没有父窗口,为NULL  
		NULL,               //没有菜单,为NULL  
		hInstance,          //当前应用程序的实例句柄  
		NULL);              //没有附加数据,为NULL  
	if (hwnd == NULL)                //检查窗口是否创建成功  
		return 0;

	// 显示窗口  
	ShowWindow(hwnd, SW_SHOW);

	// 更新窗口  
	UpdateWindow(hwnd);

	SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); //设置窗口最前端
	thread GetWheelData(WheelInit, hwnd);
	GetWheelData.detach();
	
	




	//MessageBox(0, "调用了WinMain函数", "测试:", 0);


	// 消息循环  
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 0;
}

// 在WinMain后实现  
LRESULT CALLBACK WindowProc(
	_In_  HWND hwnd,
	_In_  UINT uMsg,
	_In_  WPARAM wParam,
	_In_  LPARAM lParam
)
{

	switch (uMsg)
	{
	case WM_DESTROY:
	{
		PostQuitMessage(0);
		return 0;
	}
	case WM_PAINT:
	{
		/*PAINTSTRUCT     ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		int length;
		TCHAR buff[1024];
		length = wsprintf(buff, TEXT("the angle is : %d"), x);
		TextOut(hdc, 20, 20, buff, length);
		EndPaint(hwnd, &ps);
		break;*/

	}
	default:
		break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

方向盘的基础信息是整合在一个 DIJOYSTATE2的结构体里面,然后通过

DIJOYSTATE2* LogiGetState(const int index);

方法来返回该结构体变量,然后输出变量成员来获取数据,这个函数的传入参数是设备的ID号,就是我在前面提到的divice 0 还是divice 1

提一下方向盘按键数据的获取,主要是通过

bool LogiButtonTriggered(const int index, const int buttonNbr);

获取的,第一个参数的设备ID,第二个参数是按键编号,具体的编号可以打开罗技游戏软件,然后看方向盘上的编号,不过要注意一下,实际上调用的编号是软件上显示的编号-1。

最后推荐一篇其他人的笔记,写的比我详细多了,想要深入研究的可以参考一下这篇文档

G29开发笔记

你可能感兴趣的:(罗技方向盘SDK开发笔记)