PS4 dualshock 4 controller是一个很成熟的游戏控制器:除了最基本的类比摇杆,按钮,以及方向键之外,还多了六轴感应器(加速和陀螺仪)和触摸面板(触摸板)。虽然在大部分的游戏里面派不上用场,但六轴感应和触控面板功能在动作控制上有很大的自由度,也提供使用者不一样的操作体验,因此也激发了我想可以由dualshock 4 controller(DS4)来控制自动化xy平台的想法。不过,逐步需要做的,就是想办法让PC能够读取控制器中的数据。我所使用的作业系统是Windows 10,语言是Python 3.6,套件管理为Anaconda,控制器的连接方式则是USB。
要通过由Python来重新读取DS4的资料,主要有两种方法:
- 使用pygame套件。这是一个游戏控制器通用的套件,而且使用上非常方便,按钮,摇杆,方向键等都分属不同的类,可以直接引用。但实际测试后,发现它无法获得DS4中六轴感应器的资料,因此,陀螺仪和加速度计这两个最有趣的动作感应功能,无法通过pygame取得。所以我们必须使用第二种方法…
- 使用pyUSB套件,直接读取DS4中的原始数据。这个方法对我这种生手双手就麻烦很多,花了两天才把pyUSB和DS4的通讯搞定;再来就是如何解释读取到的数据,这些都得靠其他开发者替换的文件来摸索。不过至少目前而言,读取陀螺仪和加速度计的数据是没有问题的。
我所使用的作业系统是Win10,套件管理为Anaconda;先说明如何用pygame读取资料:
如果没有pygame package的话,第一步就是先安装它。
点安装pygame
程序的部分,当然就是直接导入,然后启动摇杆功能。
导入pygame
导入操作系统pygame.init()
pygame.joystick.init()控制器= pygame.joystick.Joystick(0)
controller.init()
在pygame的框架下,有三种控制模式,axis,button,hat:
#三种控件:轴,按钮和帽子
轴= {}
按钮= {}
帽子= {}
接着给他们一些初始值(通常设为0或False):
#分配初始数据值#轴初始化为0.0
对于我在范围内(controller.get_numaxes()):
axis [i] = 0.0#按钮初始化为False
对于我在范围内(controller.get_numbuttons()):
button [i] = False#帽子被初始化为0
对于我在范围内(controller.get_numhats()):
帽子[i] =(0,0)
再来就是将每个每个轴和按钮等的标签定义出来(要实际测试过,才比较清楚每个按键和读值的对应关系):
#DS4控制器轴的标签
AXIS_LEFT_STICK_X = 0
AXIS_LEFT_STICK_Y = 1
AXIS_RIGHT_STICK_X = 2
AXIS_RIGHT_STICK_Y = 3
AXIS_R2 = 4
AXIS_L2 = DS#控制器按钮的5#标签
#注意有14个按钮(pygame为0到13,Windows设置为1到14)
BUTTON_SQUARE = 0
BUTTON_CROSS = 1
BUTTON_CIRCLE = 2
BUTTON_TRIANGLE = 3BUTTON_L1 = 4
BUTTON_R1 = 5
BUTTON_L2 = 6
BUTTON_R2 = 7BUTTON_SHARE = 8
BUTTON_OPTIONS = 9BUTTON_LEFT_STICK = 10
BUTTON_RIGHT_STICK = 11BUTTON_PS = 12
BUTTON_PAD = 13#DS4控制器帽子的标签(仅一个帽子控件)
HAT_1 = 0
主要的回圈,将持续列印出目前从DS4读到的数据。按PS4键可以跳出回回圈:
#主循环,可以按下PS按钮来中断
退出=错误
而退出== False:#获取事件
对于pygame.event.get()中的事件:if event.type == pygame.JOYAXISMOTION:
axis [event.axis] = round(event.value,3)
elif event.type == pygame.JOYBUTTONDOWN:
button [event.button] = True
elif event.type == pygame.JOYBUTTONUP:
button [event.button] = False
elif event.type == pygame.JOYHATMOTION:
帽子[event.hat] = event.valuequit =按钮[BUTTON_PS]#打印结果
os.system('cls')#注意'cls'是Windows的命令
#轴
打印(“左摇杆X:”,轴[AXIS_LEFT_STICK_X])
打印(“左摇杆Y:”,轴[AXIS_LEFT_STICK_Y])
打印(“右摇杆X:”,轴[AXIS_RIGHT_STICK_X])
打印(“右摇杆Y:”,轴[AXIS_RIGHT_STICK_Y])
print(“ L2 strength:”,轴[AXIS_L2])
print(“ R2 strength:”,轴[AXIS_R2],“ \ n”)
# 纽扣
打印(“平方:”,按钮[BUTTON_SQUARE])
打印(“ Cross:”,按钮[BUTTON_CROSS])
打印(“圆:”,按钮[BUTTON_CIRCLE])
打印(“三角形:”,按钮[BUTTON_TRIANGLE])
打印(“ L1:”,按钮[BUTTON_L1])
打印(“ R1:”,按钮[BUTTON_R1])
打印(“ L2:”,按钮[BUTTON_L2])
打印(“ R2:”,按钮[BUTTON_R2])
打印(“共享:”,按钮[BUTTON_SHARE])
打印(“选项:”,按钮[BUTTON_OPTIONS])
打印(“按左摇杆:”,按钮[BUTTON_LEFT_STICK])
打印(“右摇杆按下:”,按钮[BUTTON_RIGHT_STICK])
打印(“ PS:”,按钮[BUTTON_PS])
打印(“触摸板:”,按钮[BUTTON_PAD],“ \ n”)
#帽子
打印(“帽子X:”,帽子[HAT_1] [0])
print(“ Hat Y:”,hat [HAT_1] [1],“ \ n”)print(“按PS按钮退出:”,退出)#限制为每秒30帧,以使显示效果不那么华丽
时钟= pygame.time.Clock()
clock.tick(30)
程式运作正常的情况下,你可以看到:

因此,如果需要利用DS4的输出来控制机器的话,只要把数值对应的范围抓出来就可以了(例如摇杆的部分,L2和R2等类比输出,全押全放的数值范围是-1到+ 1)。所以如果不需要用到六轴感应的数据的话,pygame套件可以很快速地把想要的控制数据从DS4取出来。完整的代码请参考GitHub(https://github.com/anubisankh/ dualshock4-python)。
再来就是六轴感应的部分了。
因为这边使用的DS4是用USB连接到PC,因此我们将使用pyUSB:
pip安装pyusb
pip安装libusb
这边须注意的是,只有安装pyUSB还不够,相关的库也是必要的,因此如果之前没有libusb的话,须同时安装。再来就是浪费我许久,但是最关键的部分了。例子来写:
进口 usb.coredev = usb.core.find()
随后就会出现usb.core.NoBackendError:无后端可用的错误消息。为了要让backend(* .dll)能够被找到,我们必须特别把它的位置找来。如果pip install libusb成功的话,通常可以在Anaconda3 \ Lib \ site-packages \ libusb \ _platform \ _windows \ x64 \ libusb-1.0.dll中找到。如果用pip安装了libusb还是libusb-1.0.dll,可以在网上找到适合该作业系统的libusb-1.0.dll下载下来,并把路径复制到程序码中:
导入usb.core
导入usb.util
import usb.backend.libusb1 #您必须将下面的路径更改为libusb-1.0.dll
BACKEND = usb.backend.libusb1.get_backend(find_library = lambda x:“ C:\\ Users \\ YourUserName \\ Anaconda3 \\ Lib \\ site-packages \\ libusb \\ _ platform \\ _ windows \\ x64 \\ libusb -1.0.dll”)
再来我们就可以列出所有USB装置的相关资讯:
如果dev为None,则dev = usb.core.find(find_all = True):
打印(“找不到设备”)
其他:
对于d in dev:
print('Decimal Vendor ID:'+ str(d.idVendor)+'&ProductID:'+ str(d.idProduct))#以十进制打印所有连接的USB设备
print('Hexadecimal Vendor ID:'+ hex(d.idVendor)+'&ProductID:'+ hex(d.idProduct)+'\ n')#以十六进制打印所有已连接的USB设备
这边会列出所有连接上的USB装置的厂商ID(供应商ID)和产品ID(产品ID),示例输出如下:

把DS4拔出后跑上述程式码,再重新装回,可进一步发现DS4的供应商ID:0x54c /列印出的装置资讯可以用十进位(十进制)或标准的十六进位(十六进制)来表示。产品ID:0x9cc。把这个消息记录下来后,我们开始只针对DS4来深入研究:
导入usb.core
导入usb.backend.libusb1
导入操作系统
导入时间#DS4控制器ID
VENDOR_ID = 0x54c
PRODUCT_ID = 0x9ccBACKEND = usb.backend.libusb1.get_backend(find_library = lambda x:“ C:\\ Users \\ YourUserName \\ Anaconda3 \\ Lib \\ site-packages \\ libusb \\ _ platform \\ _ windows \\ x64 \ \ libusb-1.0.dll“)dev = usb.core.find(idVendor = VENDOR_ID,idProduct = PRODUCT_ID,backend = BACKEND)
这一个边定义的USB装置(device,dev),就只有DS4 controller。每一个USB设备可能会有不同的配置,在之下又会有几个不同的接口,而interface下面又会有不同的端点,如下图所示:

因此,当我们获得DS的配置时:
cfg = dev.get_active_configuration()print('配置数据:\ n \ n',cfg,'\ n')
会得到一长串的资料:

其中可以看到某些interface是给音频,再往下拉的话,可以看到其中一个接口是Human Interface Device(HID),这就是我们DS4控制器主要输入的介面。在该介面下,还包含了端点0x84和端点0x3两个端点。端点0x84:中断IN则是我们想要获得access的端点。

设置端点资讯如下:
INTERFACE_DS4 = 3#HID接口
SETTING_DS4 = 0
interface = cfg [(INTERFACE_DS4,SETTING_DS4)] ENDPOINT_DS4_OUT = 0#输入端点
端点=接口[ENDPOINT_DS4_OUT]打印( endpoint.read(0x40) )#wMaxPacketSize 0x40
执行后,列印出端点读取资料的结果为一个数组:’(’B’,[1,131,128,129,132,8,0,16,0,0,189,178,11,4 ,0,3,0,255,255,103,1,177,31,113,6,0,0,0,0,0,27,0,0,1,135,134,187,180,32 ,132、29、55、12、0、128、0、0、0、128、0、0、0、0、128、0、0、0、128、0、0、0、0、128、0 ])。[]中的数字便是我们想要的DS4各个按钮,摇杆,还有六轴感应器的读值了。
这些部分可以参考PS4开发者已经试出来的资讯。这些输出当中,读出的是[1]〜[9]是和摇杆及按钮相关的资讯,而我们有兴趣的陀螺仪和加速度计则是在[14]〜[25]。
所以摇杆及按钮的部分(同时列出hex和binary的结果):
#Axesprint('Readout L stick X:',format(endpoint.read(0x40)[1],'#04X'),format(endpoint.read(0x40)[1],'08b'),endpoint.read( 0x40)[1])print('读数L棒Y:',format(endpoint.read(0x40)[2],'#04X'),format(endpoint.read(0x40)[2],'08b') ,endpoint.read(0x40)[2])print('读数R棒X:',format(endpoint.read(0x40)[3],'#04X'),format(endpoint.read(0x40)[3] ,'08b'),endpoint.read(0x40)[3])print('读数R棒Y:',format(endpoint.read(0x40)[4],'#04X'),format(endpoint.read( 0x40)[4],'08b'),endpoint.read(0x40)[4])print('读出L2触发器:',format(endpoint.read(0x40)[8],'#04X'),format( endpoint.read(0x40)[8],'08b'),endpoint.read(0x40)[8])print('Readout R2 trigger:',format(endpoint.read(0x40)[9],'#04X' ),格式(endpoint.read(0x40)[9],'08b'),endpoint.read(0x40)[9],'\ n')
陀螺仪和加速度计的六轴部分,一个轴的输出需要两个字节的数据,所以:
#加速度计(2字节为有符号整数)
数据= endpoint.read(0x40)
print('Accelerometer X:',format(256 * data [18] + data [19],'016b'),256 * data [18] + data [19]-(65536 if data [18]> 127 else 0 ))print('Accelerometer Y:',format(256 * data [16] + data [17],'016b'),256 * data [16] + data [17]-(65536 if data [16]> 127 else 0)print('Accelerometer Z:',format(256 * data [14] + data [15],'016b'),256 * data [14] + data [15]-(65536 if data [14]> 127 else 0),'\ n')#陀螺仪
数据= endpoint.read(0x40)
print('Gyroscope X(Roll):',format(256 * data [20] + data [21],'016b'),256 * data [20] + data [21]-(65536 if data [20]> 127 else 0))print('陀螺仪Y(Yaw):',format(256 * data [22] + data [23],'016b'),256 * data [22] + data [23]-(65536 data [22]> 127 else 0))print('Gyroscope Z(Pitch):',format(256 * data [24] + data [25],'016b'),256 * data [24] + data [25 ]-(65536,如果data [24]> 127否则为0),'\ n')
加上其他按钮和功能的数据,示例输出如下:

如此一来,便可以通过由pyUSB读取DS 4六轴感测器的数据来对准想控制的机器了。另外,DS4还有触摸板,甚至支持两点触控,如果想使用触摸板功能还可以用Readout [34]〜[51]的输出来设计自己想要的干预模式。相关原始码及参考资料可参考:https://github.com/anubisankh/dualshock4-python