PS4手柄控制无人驾驶小车车
系列文章目录
提示:
第一章 基于索尼PS4手柄的遥控器双向控制
文章目录
前言
随着人工智能的不断发展,做无人驾驶调试肯定需要一个遥控器来调试,对于价值比较大的无人驾驶车辆会有专业的遥控器控制,但是对于刚入门的小白可能没有那么多机会接触高端无人驾驶设备,这里另辟蹊径用打游戏的手柄进行控制。
提示:以下是本篇文章正文内容,下面案例可供参考
一、PS手柄是什么?
示例:PS系列手柄 是索尼提供的游戏机的手柄,该工具是为了解决游戏玩家设计的。
二、一般如何用PS手柄?
1.案例一
之前我用一个PS3做小车双向控制,优点是通过修改硬件把蓝牙换成了Lora,实现远距离双向互传。我的应用场景之一:可以通过ROS发送相关反馈给手柄,比如震动几下等等,这样在机器人调试过程中可以模拟车辆在ROS或者真实小车发生碰撞的情况下让手柄震动给开发者人员。
同时,手柄会把按键的模拟量和键值数据发送给通过无线传输发送给ROS,进而控制小车。
但是也有美中不足的事情:
1.PS3实在太丑了,,相比PS4
2.PS4多了RGB灯,所以可以反向反馈灯光,PS3则只能反馈震动
3.PS4内部有陀螺仪,但是由于我买的国产的,内部IMU芯片没有焊接,ε=(´ο`*)))唉
4.重要的事情再说一遍,PS4还是比PS3帅啊
2.常见的使用方法
常见使用蓝牙与工控机这里我用的是NUC11,配准是11代i71185G7,16G内存,系统安装的是Ubuntu16.04:
sudo apt-get install jstest-gtk
sudo jstest /dev/input/js0
然后在PS4手柄与工控机连接前后对比/dev/input/下面你会看到多出5个文件,分别是一个js0、event*/event*+1、event*+2和一个mouse,这三个分别对应jstest通过JavaScript解析后的键值,和三个未解析的键值,好像有一个是IMU数据,因为一直在发送数据,但是我的PS4是国产的没有IMU。。。另外一个是鼠标数据,pyPS4Controller 库,然后就是再安装这个库在Ubuntu上开发。
pip install pyPS4Controller
优点:不需要额外接收器,成本低直接可以在Linux系统上开发
缺点:1.不灵活 2.很难做到反向控制手柄的灯效和震动等等,很难做ROS与手柄的双向互动,目前只能拿到手柄的键值数据单向控制小车车。
该处使用的url网络请求的数据。
三、!!!我的解决方案
1.首先要了解PS4与工控机的通信机制–蓝牙
蓝牙通讯具有两种工作模式:命令响应工作模式和自动
连接工作模式,在自动连接工作模式下模块又可分为主(Master)、从(Slave)和回环(Loopback)三种工作角色。当模块处于自动连接工作模式时,将自动根据事先设定的方式连接的数据传输。
通过两天的摸索:
1.SPP 规范库(Initialisethesppprofilelib)在第一次配对前需要初始化(这个是蓝牙规范,不需要看懂)
2.—查询设备类为 0x1f1f的蓝牙设备,也是蓝牙规范,为此我拆解了PS4,通过一些设备和方法得到一些信息,PS4的蓝牙芯片是用的是RDA5850的芯片,通过查询数据手册看到这是一个多媒体蓝牙芯片,感觉集成化程度很高啊,不管了我们首先要做的是自己做个蓝牙接收器能够连接上这个PS4,我给出两种方案:
1.单片机+蓝牙模块(双模蓝牙才OK)
4.集成单片机的蓝牙芯片,esp8266或者esp32
2.让PS4连接上蓝牙
在连接蓝牙模块之前我们先连接一次手机,看一下这个PS4的MAC地址是多少我通过一个HC蓝牙助手的安卓app看到的
下面是PS4的反馈代码,用VSCODE就可以开发PS4接受模组了
#include "PS4Controller.h"
#include <esp_bt_defs.h>
#include <esp_bt_main.h>
extern "C" {
#include "ps4.h"
}
#define ESP_BD_ADDR_HEX_PTR(addr)
(uint8_t*)addr + 0, (uint8_t*)addr + 1, (uint8_t*)addr + 2,
(uint8_t*)addr + 3, (uint8_t*)addr + 4, (uint8_t*)addr + 5
PS4Controller::PS4Controller() {}
bool PS4Controller::begin() {
ps4SetEventObjectCallback(this, &PS4Controller::_event_callback);
ps4SetConnectionObjectCallback(this, &PS4Controller::_connection_callback);
if (!btStarted() && !btStart()) {
log_e("btStart failed");
return false;
}
esp_bluedroid_status_t btState = esp_bluedroid_get_status();
if (btState == ESP_BLUEDROID_STATUS_UNINITIALIZED) {
if (esp_bluedroid_init()) {
log_e("esp_bluedroid_init failed");
return false;
}
}
if (btState != ESP_BLUEDROID_STATUS_ENABLED) {
if (esp_bluedroid_enable()) {
log_e("esp_bluedroid_enable failed");
return false;
}
}
ps4Init();
return true;
}
#define ESP_BD_ADDR_STR "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"
bool PS4Controller::begin(const char* mac) {
esp_bd_addr_t addr;
if (sscanf(mac, ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX_PTR(addr)) != ESP_BD_ADDR_LEN) {
log_e("Could not convert %sn to a MAC address", mac);
return false;
}
ps4SetBluetoothMacAddress(addr);
return begin();
}
void PS4Controller::end() {}
bool PS4Controller::isConnected() { return ps4IsConnected(); }
//下面这个函数是通过上位机发送指令控制PS4手柄的RGB灯颜色的
void PS4Controller::setLed(uint8_t r, uint8_t g, uint8_t b) {
output.r = r;
output.g = g;
output.b = b;
}
//下面这个函数是通过上位机发送指令控制PS4手柄的震动的基础库
void PS4Controller::setRumble(uint8_t small, uint8_t large) {
output.smallRumble = small;
output.largeRumble = large;
}
void PS4Controller::setFlashRate(uint8_t onTime, uint8_t offTime) {
output.flashOn = onTime / 10;
output.flashOff = offTime / 10;
}
void PS4Controller::sendToController() { ps4SetOutput(output); }
void PS4Controller::attach(callback_t callback) { _callback_event = callback; }
void PS4Controller::attachOnConnect(callback_t callback) {
_callback_connect = callback;
}
void PS4Controller::attachOnDisconnect(callback_t callback) {
_callback_disconnect = callback;
}
void PS4Controller::_event_callback(
void* object, ps4_t data, ps4_event_t event) {
PS4Controller* This = (PS4Controller*)object;
memcpy(&This->data, &data, sizeof(ps4_t));
memcpy(&This->event, &event, sizeof(ps4_event_t));
if (This->_callback_event) {
This->_callback_event();
}
}
void PS4Controller::_connection_callback(void* object, uint8_t isConnected) {
PS4Controller* This = (PS4Controller*)object;
if (isConnected) {
delay(250); // ToDo: figure out how to know when the channel is free again
// so this delay can be removed
if (This->_callback_connect) {
This->_callback_connect();
}
}
else {
if (This->_callback_disconnect) {
This->_callback_disconnect();
}
}
}
#if !defined(NO_GLOBAL_INSTANCES)
PS4Controller PS4;
#endif
然后在PS4手柄与工控机连接前后对比/dev/input/下面你会看到多出5个文件,分别是一个js0、event*/event*+1、event*+2和一个mouse,这三个分别对应jstest通过JavaScript解析后的键值,和三个未解析的键值,好像有一个是IMU数据,因为一直在发送数据,但是我的PS4是国产的没有IMU。。。另外一个是鼠标数据,pyPS4Controller 库,然后就是再安装这个库在Ubuntu上开发。
#include "ps4.h"
#include <esp_system.h>
#include <string.h>
#include "ps4_int.h"
/********************************************************************************/
/* PS4接受数据的常量部分 */
/********************************************************************************/
static const uint8_t hid_cmd_payload_ps4_enable[] = {0x43, 0x02};
/********************************************************************************/
/* 本地变量 */
/********************************************************************************/
static ps4_connection_callback_t ps4_connection_cb = NULL;
static ps4_connection_object_callback_t ps4_connection_object_cb = NULL;
static void* ps4_connection_object = NULL;
static ps4_event_callback_t ps4_event_cb = NULL;
static ps4_event_object_callback_t ps4_event_object_cb = NULL;
static void* ps4_event_object = NULL;
static bool is_active = false;
/********************************************************************************/
/* 下面这些是基础功能 */
/********************************************************************************/
/*******************************************************************************
**
** Function ps4Init
**
** Description 初始化蓝牙接受服务,然后连接手柄
**
**
** Returns void
**
*******************************************************************************/
void ps4Init() {
sppInit();
ps4_l2cap_init_services();
}
/*******************************************************************************
**
** Function ps4IsConnected
**
** Description 当两个设备之间成功发生握手后返回一个值
**
**
**
** Returns bool
**
*******************************************************************************/
bool ps4IsConnected() { return is_active; }
/*******************************************************************************
**
** Function ps4Enable
**
** Description PS4使能后持续发送数据
**
**
**
** Returns void
**
*******************************************************************************/
void ps4Enable() {
uint16_t length = sizeof(hid_cmd_payload_ps4_enable);
hid_cmd_t hidCommand;
hidCommand.code = hid_cmd_code_set_report | hid_cmd_code_type_feature;
hidCommand.identifier = hid_cmd_identifier_ps4_enable;
memcpy(hidCommand.data, hid_cmd_payload_ps4_enable, length);
ps4_l2cap_send_hid(&hidCommand, length);
ps4SetLed(32, 32, 200);
}
/*******************************************************************************
**
** Function ps4Cmd
**
** Description 发送给PS4让其震动或者灯光的控制的命令
**
**
** Returns void
**
*******************************************************************************/
void ps4Cmd(ps4_cmd_t cmd) {
hid_cmd_t hidCommand = {.data = {0x80, 0x00, 0xFF}};
uint16_t length = sizeof(hidCommand.data);
hidCommand.code = hid_cmd_code_set_report | hid_cmd_code_type_output;
hidCommand.identifier = hid_cmd_identifier_ps4_control;
hidCommand.data[ps4_control_packet_index_small_rumble] = cmd.smallRumble; // Small Rumble
hidCommand.data[ps4_control_packet_index_large_rumble] = cmd.largeRumble; // Big rumble
hidCommand.data[ps4_control_packet_index_red] = cmd.r; // Red
hidCommand.data[ps4_control_packet_index_green] = cmd.g; // Green
hidCommand.data[ps4_control_packet_index_blue] = cmd.b; // Blue
// Time to flash bright (255 = 2.5 seconds)
hidCommand.data[ps4_control_packet_index_flash_on_time] = cmd.flashOn;
// Time to flash dark (255 = 2.5 seconds)
hidCommand.data[ps4_control_packet_index_flash_off_time] = cmd.flashOff;
ps4_l2cap_send_hid(&hidCommand, length);
}
/*******************************************************************************
**
** Function ps4SetLedOnly
**
** Description 设置灯光颜色RGB三原色控制的数据
**
**
** Returns void
**
*******************************************************************************/
void ps4SetLed(uint8_t r, uint8_t g, uint8_t b) {
ps4_cmd_t cmd = {0};
cmd.r = r;
cmd.g = g;
cmd.b = b;
ps4Cmd(cmd);
}
/*******************************************************************************
**
** Function ps4SetOutput
**
** Description 设置回调
**
**
** Returns void
**
*******************************************************************************/
void ps4SetOutput(ps4_cmd_t prevCommand) { ps4Cmd(prevCommand); }
/*******************************************************************************
**
** Function ps4SetConnectionCallback
**
** Description 当PS4发送一次手柄键值给接收机时候,进行一次中断处理的回调
**
**
**
** Returns void
**
*******************************************************************************/
void ps4SetConnectionCallback(ps4_connection_callback_t cb) {
ps4_connection_cb = cb;
}
/*******************************************************************************
**
** Function ps4SetConnectionObjectCallback
**
** Description 当PS4连接的时候进行连接通知回调
**
**
**
** Returns void
**
*******************************************************************************/
void ps4SetConnectionObjectCallback(void* object, ps4_connection_object_callback_t cb) {
ps4_connection_object_cb = cb;
ps4_connection_object = object;
}
/*******************************************************************************
**
** Function ps4SetEventCallback
**
** Description 接收PS4的回调事件
**
**
** Returns void
**
*******************************************************************************/
void ps4SetEventCallback(ps4_event_callback_t cb) { ps4_event_cb = cb; }
/*******************************************************************************
**
** Function ps4SetEventObjectCallback
**
** Description PS4接受控制事件
**
**
** Returns void
**
*******************************************************************************/
void ps4SetEventObjectCallback(void* object, ps4_event_object_callback_t cb) {
ps4_event_object_cb = cb;
ps4_event_object = object;
}
/*******************************************************************************
**
** Function ps4SetBluetoothMacAddress
**
** Description 设置接收机需要连接的PS4手柄位移MAC地址
**
**
** Returns void
**
*******************************************************************************/
void ps4SetBluetoothMacAddress(const uint8_t* mac) {
// The bluetooth MAC address is derived from the base MAC address
// https://docs.espressif.com/projects/esp-idf/en/stable/api-reference/system/system.html#mac-address
uint8_t baseMac[6];
memcpy(baseMac, mac, 6);
baseMac[5] -= 2;
esp_base_mac_addr_set(baseMac);
}
/********************************************************************************/
/* 本地化的一些功能 */
/********************************************************************************/
void ps4ConnectEvent(uint8_t is_connected) {
if (is_connected) {
ps4Enable();
} else {
is_active = false;
}
}
void ps4PacketEvent(ps4_t ps4, ps4_event_t event) {
// Trigger packet event, but if this is the very first packet
// after connecting, trigger a connection event instead
if (is_active) {
if(ps4_event_cb != NULL) {
ps4_event_cb(ps4, event);
}
if (ps4_event_object_cb != NULL && ps4_event_object != NULL) {
ps4_event_object_cb(ps4_event_object, ps4, event);
}
} else {
is_active = true;
if(ps4_connection_cb != NULL) {
ps4_connection_cb(is_active);
}
if (ps4_connection_object_cb != NULL && ps4_connection_object != NULL) {
ps4_connection_object_cb(ps4_connection_object, is_active);
}
}
}
等我完成PS4的回环流程后再重新组织架构文章。
总结
提示:这里对文章进行总结:
目的:使用PS4连接小车,可以遥控控制一些开关或者其他,当小车在ROS里发生碰撞会反馈给手柄发生碰撞震动,ROS小车的电量或者一些状态通过PS4的RGB灯光进行反馈,实时可以看得到。
完成度:50%,后面开发完成了再整理一下发出来。
祝大家元旦快乐,2022年元旦。