v1.3增加一个简单的越障模式
This commit is contained in:
1 parent
9d56ba85f1
commit
7a1931cb72
1835 files changed
+475654
-1
No files matched your search
@@ -0,0 +1,118 @@
|
||||
/* ---------------------------------
|
||||
* LED 控制模块实现
|
||||
|
||||
// 开始红色闪烁,每次持续200毫秒,闪烁3次
|
||||
startLEDBlink(CRGB::Red, 200, 3);
|
||||
|
||||
// 开始蓝色闪烁,每次持续500毫秒,闪烁5次
|
||||
startLEDBlink(CRGB::Blue, 500, 5);
|
||||
|
||||
// 停止闪烁
|
||||
stopLEDBlink();
|
||||
|
||||
// 设置LED为绿色常亮
|
||||
setLEDColor(CRGB::Green);
|
||||
------------------------------------- */
|
||||
#include "RGBController.h"
|
||||
|
||||
// LED 全局变量定义(与主文件解耦)
|
||||
CRGB leds[NUM_LEDS];
|
||||
unsigned long previousBlinkMillis = 0;
|
||||
int blinkState = 0;
|
||||
int blinkCount = 0;
|
||||
bool isBlinking = false;
|
||||
CRGB blinkColors[6] = {CRGB::Red, CRGB::Red, CRGB::Yellow, CRGB::Yellow, CRGB::Green, CRGB::Green};
|
||||
|
||||
// 新增全局变量定义
|
||||
unsigned long blinkDuration = 200; // 默认闪烁持续时间200毫秒
|
||||
unsigned long blinkInterval = 200; // 默认闪烁间隔时间200毫秒
|
||||
CRGB currentBlinkColor = CRGB::Red; // 默认红色
|
||||
int totalBlinkCount = 3; // 默认闪烁3次
|
||||
bool useColorSequence = false; // 默认不使用颜色序列模式
|
||||
|
||||
// 初始化LED(替代原setup中的FastLED初始化代码)
|
||||
void initLEDs() {
|
||||
FastLED.addLeds<WS2812, RGB_PIN, GRB>(leds, NUM_LEDS);
|
||||
FastLED.setBrightness(50);
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black); // 初始熄灭
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
// 开始LED闪烁
|
||||
void startLEDBlink(CRGB color, unsigned long duration, int count) {
|
||||
currentBlinkColor = color; // 设置闪烁颜色
|
||||
blinkDuration = duration; // 设置闪烁持续时间
|
||||
blinkInterval = duration; // 设置闪烁间隔时间(与持续时间相同)
|
||||
totalBlinkCount = count * 2; // 设置总闪烁次数(亮灭算一次)
|
||||
useColorSequence = false; // 使用单色模式
|
||||
|
||||
blinkCount = 0; // 重置当前闪烁计数
|
||||
blinkState = 0; // 重置闪烁状态
|
||||
isBlinking = true; // 开始闪烁
|
||||
previousBlinkMillis = millis(); // 重置时间戳
|
||||
}
|
||||
|
||||
// 开始颜色序列闪烁
|
||||
void startColorSequenceBlink() {
|
||||
blinkDuration = 50; // 设置闪烁持续时间
|
||||
blinkInterval = 50; // 设置闪烁间隔时间
|
||||
totalBlinkCount = 6; // 设置总闪烁次数(6种颜色)
|
||||
useColorSequence = true; // 使用颜色序列模式
|
||||
|
||||
blinkCount = 0; // 重置当前闪烁计数
|
||||
blinkState = 0; // 重置闪烁状态
|
||||
isBlinking = true; // 开始闪烁
|
||||
previousBlinkMillis = millis(); // 重置时间戳
|
||||
}
|
||||
|
||||
// 停止LED闪烁
|
||||
void stopLEDBlink() {
|
||||
isBlinking = false;
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
// 设置LED颜色
|
||||
void setLEDColor(CRGB color) {
|
||||
fill_solid(leds, NUM_LEDS, color);
|
||||
FastLED.show();
|
||||
}
|
||||
|
||||
// 处理LED闪烁逻辑(非阻塞实现)
|
||||
void handleLEDBlink() {
|
||||
if (!isBlinking) return;
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
// 检查是否达到切换时间
|
||||
if (currentMillis - previousBlinkMillis >= (blinkState == 0 ? blinkDuration : blinkInterval)) {
|
||||
previousBlinkMillis = currentMillis;
|
||||
|
||||
if (blinkState == 0) {
|
||||
// LED亮起
|
||||
if (useColorSequence) {
|
||||
// 使用颜色序列模式
|
||||
fill_solid(leds, NUM_LEDS, blinkColors[blinkCount % 6]);
|
||||
} else {
|
||||
// 使用单色模式
|
||||
fill_solid(leds, NUM_LEDS, currentBlinkColor);
|
||||
}
|
||||
FastLED.show();
|
||||
blinkState = 1;
|
||||
} else {
|
||||
// LED熄灭
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
FastLED.show();
|
||||
blinkState = 0;
|
||||
blinkCount++;
|
||||
|
||||
// 检查是否完成所有闪烁
|
||||
if (blinkCount >= totalBlinkCount) {
|
||||
isBlinking = false;
|
||||
blinkCount = 0;
|
||||
fill_solid(leds, NUM_LEDS, CRGB::Black);
|
||||
FastLED.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* ---------------------------------
|
||||
* RGB 控制模块
|
||||
* 包含:RGB初始化、闪烁逻辑、状态管理
|
||||
------------------------------------- */
|
||||
#ifndef RGB_CONTROLLER_H
|
||||
#define RGB_CONTROLLER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FastLED.h>
|
||||
|
||||
// RGB 硬件配置(原代码中的宏定义)
|
||||
#define RGB_PIN 21
|
||||
#define NUM_LEDS 2
|
||||
|
||||
// LED 全局变量(原代码中的状态变量)
|
||||
extern CRGB leds[NUM_LEDS]; // 外部声明,在主文件中定义
|
||||
extern unsigned long previousBlinkMillis;
|
||||
extern int blinkState;
|
||||
extern int blinkCount;
|
||||
extern bool isBlinking;
|
||||
extern CRGB blinkColors[6];
|
||||
|
||||
// 新增全局变量
|
||||
extern unsigned long blinkDuration; // 闪烁持续时间(毫秒)
|
||||
extern unsigned long blinkInterval; // 闪烁间隔时间(毫秒)
|
||||
extern CRGB currentBlinkColor; // 当前闪烁颜色
|
||||
extern int totalBlinkCount; // 总闪烁次数
|
||||
extern bool useColorSequence; // 是否使用颜色序列闪烁模式
|
||||
|
||||
// LED 函数声明
|
||||
void initLEDs(); // 初始化LED
|
||||
void handleLEDBlink(); // 处理LED闪烁逻辑
|
||||
void startLEDBlink(CRGB color, unsigned long duration, int count = 3); // 开始LED闪烁
|
||||
void stopLEDBlink(); // 停止LED闪烁
|
||||
void setLEDColor(CRGB color); // 设置LED颜色
|
||||
void startColorSequenceBlink(); // 开始颜色序列闪烁
|
||||
|
||||
#endif // RGB_CONTROLLER_H
|
||||
@@ -0,0 +1,99 @@
|
||||
#ifndef RobotActions_h
|
||||
#define RobotActions_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// 单个动作步骤结构体
|
||||
typedef struct {
|
||||
int joyx; // 左右转向(-100~100),正面看,正数:向左转,负数,向右转
|
||||
int joyy; // 前后移动(-100~100)
|
||||
int jump; // 跳跃标记(0=不跳,1=跳)
|
||||
float roll_adjust; // 左右平衡调整(-5~5),基数0,正面看,正数:向右摆动;负数:向左摆动
|
||||
float leg_height_base; // 腿部基准高度(20~60,核心高度控制参数),基数20,变小升高,变大降低
|
||||
unsigned long stepDuration; // 步骤持续时间(毫秒)
|
||||
unsigned long pauseDuration; // 步骤执行后的停顿时间(毫秒)
|
||||
// 舵机参数:无需默认值,动作集定义时按需填写,不填则用结构体默认值(或省略)
|
||||
int acc0; // 舵机1加速度 0-250
|
||||
int acc1; // 舵机2加速度 0-250
|
||||
int speed0; // 舵机1速度 0-500
|
||||
int speed1; // 舵机2速度 0-500
|
||||
} ActionStep;
|
||||
|
||||
// 结束标记 后4个舵机参数,使用默认值-1
|
||||
const ActionStep END_MARKER = {0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1};
|
||||
|
||||
// 连续动作指令集结构体
|
||||
typedef struct {
|
||||
String actionName; // 动作名称
|
||||
ActionStep* steps; // 步骤数组(末尾必须包含END_MARKER)
|
||||
} RobotAction;
|
||||
|
||||
// 宏:初始化RobotAction
|
||||
#define CREATE_ACTION(actionName, stepsArray) \
|
||||
{ actionName, stepsArray }
|
||||
|
||||
// -------------------------- 自定义连续动作指令集 --------------------------
|
||||
|
||||
ActionStep step_say_hello[] = {
|
||||
// {左+右-,前+后-,跳跃,平衡20左-右+,
|
||||
// 高度20升-降+,持续时间, 停顿时间0,
|
||||
// 舵机1加速度,舵机2加速度,舵机1速度,舵机2速度}
|
||||
{0, 0, 0, 25, 10, 500, 0, -1, -1, -1, -1}, // 右摆
|
||||
{0, 0, 0, 0, -10, 200, 0, -1, -1, -1, -1}, // 回到中间最高位
|
||||
{0, 0, 0, -25, 10, 500, 0, -1, -1, -1, -1}, // 左摆
|
||||
{0, 0, 0, 0, -10, 200, 0, -1, -1, -1, -1}, // 回到中间最高位
|
||||
|
||||
{0, 0, 0, 25, 10, 500, 0, -1, -1, -1, -1}, // 右摆
|
||||
{0, 0, 0, 0, -10, 200, 0, -1, -1, -1, -1}, // 回到中间最高位
|
||||
{0, 0, 0, -25, 10, 500, 0, -1, -1, -1, -1}, // 左摆
|
||||
{0, 0, 0, 0, 20, 500, 0, -1, -1, -1, -1}, // 回到中间最高位
|
||||
|
||||
{0, 0, 0, 0, 40, 150, 0, 250, 250, 800, 800}, // 下降
|
||||
{0, 0, 0, 0, 20, 150, 300, 150, 150, 400, 400}, // 回到高度20
|
||||
{0, 0, 0, 0, 40, 150, 0, 250, 250, 800, 800}, // 下降
|
||||
{0, 0, 0, 0, 20, 150, 0, 150, 150, 400, 400}, // 回到高度20
|
||||
|
||||
{0, 0, 0, 0, 20, 200, 0, -1, -1, -1, -1}, // 回到中间中位
|
||||
END_MARKER // 结束标记
|
||||
};
|
||||
RobotAction action_say_hello = CREATE_ACTION("action_say_hello", step_say_hello);
|
||||
|
||||
ActionStep step_down[] = {
|
||||
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
|
||||
// 舵机1加速度,舵机2加速度,舵机1速度,舵机2速度}
|
||||
{0, 0, 0, 0, 40, 150, 0, 250, 250, 1500, 1500}, // 下降
|
||||
{0, 0, 0, 0, 20, 150, 0, 250, 250, 800, 800}, // 回到高度20
|
||||
END_MARKER // 结束标记
|
||||
};
|
||||
RobotAction action_step_down = CREATE_ACTION("action_step_down", step_down);
|
||||
|
||||
|
||||
ActionStep step_up[] = {
|
||||
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
|
||||
// 舵机1加速度,舵机2加速度,舵机1速度,舵机2速度}
|
||||
{0, 0, 0, 0, -20, 150, 0, 250, 250, 1500, 1500}, // 上升
|
||||
{0, 0, 0, 0, 20, 200, 0, 250, 250, 800, 800}, // 回到高度20
|
||||
END_MARKER // 结束标记
|
||||
};
|
||||
RobotAction action_step_up = CREATE_ACTION("action_step_up", step_up);
|
||||
|
||||
ActionStep step_right[] = {
|
||||
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
|
||||
// 舵机1加速度,舵机2加速度,舵机1速度,舵机2速度}
|
||||
{0, 0, 0, 50, 10, 150, 0, 250, 250, 1500, 1500}, // 右摆
|
||||
{0, 0, 0, 0, -10, 150, 0, 250, 250, 800, 800}, // 回到中间最高位
|
||||
END_MARKER // 结束标记
|
||||
};
|
||||
RobotAction action_step_right = CREATE_ACTION("action_step_right", step_right);
|
||||
|
||||
ActionStep step_left[] = {
|
||||
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
|
||||
// 舵机1加速度,舵机2加速度,舵机1速度,舵机2速度}
|
||||
{0, 0, 0, -50, 10, 150, 0, 250, 250, 1500, 1500}, // 左摆
|
||||
{0, 0, 0, 0, -10, 150, 0,250, 250, 800, 800}, // 回到中间最高位
|
||||
END_MARKER // 结束标记
|
||||
};
|
||||
RobotAction action_step_left = CREATE_ACTION("action_step_left", step_left);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "VoltageMonitor.h"
|
||||
|
||||
// 初始化电压检测相关全局变量(与头文件声明对应)
|
||||
uint16_t bat_check_num = 0;
|
||||
esp_adc_cal_characteristics_t adc_chars;
|
||||
|
||||
/**
|
||||
* @brief 电压检测初始化(原代码逻辑完全保留)
|
||||
*/
|
||||
void adc_calibration_init()
|
||||
{
|
||||
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
|
||||
printf("eFuse Two Point: Supported\n");
|
||||
} else {
|
||||
printf("eFuse Two Point: NOT supported\n");
|
||||
}
|
||||
// Check Vref is burned into eFuse
|
||||
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
|
||||
printf("eFuse Vref: Supported\n");
|
||||
} else {
|
||||
printf("eFuse Vref: NOT supported\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 电压检测逻辑(原代码逻辑完全保留)
|
||||
*/
|
||||
void bat_check()
|
||||
{
|
||||
if (bat_check_num > 1000)
|
||||
{
|
||||
// 电压读取
|
||||
uint32_t sum = 0;
|
||||
sum = analogRead(BAT_PIN);
|
||||
uint32_t voltage = esp_adc_cal_raw_to_voltage(sum, &adc_chars);
|
||||
double battery = (voltage * 3.97) / 1000.0;
|
||||
|
||||
// 电量显示(LED_BAT引脚控制)
|
||||
if (battery >= 7.8)
|
||||
digitalWrite(LED_BAT, HIGH);
|
||||
else
|
||||
digitalWrite(LED_BAT, LOW);
|
||||
bat_check_num = 0;
|
||||
}
|
||||
else
|
||||
bat_check_num++;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef VOLTAGE_MONITOR_H
|
||||
#define VOLTAGE_MONITOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp_adc_cal.h>
|
||||
|
||||
// 电压检测相关宏定义(原代码中电压模块的宏)
|
||||
#define LED_BAT 13 // 电量显示LED引脚
|
||||
#define BAT_PIN 35 // 电池电压检测ADC引脚
|
||||
static const adc1_channel_t channel = ADC1_CHANNEL_7; // ADC通道
|
||||
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;// ADC采样宽度
|
||||
static const adc_atten_t atten = ADC_ATTEN_DB_12; // ADC衰减系数
|
||||
static const adc_unit_t unit = ADC_UNIT_1; // ADC单元
|
||||
|
||||
// 电压检测相关全局变量(原代码中电压模块的全局变量)
|
||||
extern uint16_t bat_check_num; // 电压检测计数
|
||||
extern esp_adc_cal_characteristics_t adc_chars; // ADC校准参数
|
||||
|
||||
// 电压检测相关函数声明(原代码中电压模块的函数)
|
||||
void adc_calibration_init(); // 电压检测初始化
|
||||
void bat_check(); // 电压检测逻辑
|
||||
|
||||
#endif // VOLTAGE_MONITOR_H
|
||||
@@ -0,0 +1,138 @@
|
||||
/* ---------------------------------
|
||||
* 蓝牙手柄(Xbox)模块
|
||||
* 包含:蓝牙实体、状态变量、连接处理函数
|
||||
------------------------------------- */
|
||||
#ifndef XBOX_BLUETOOTH_H
|
||||
#define XBOX_BLUETOOTH_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <XboxSeriesXControllerESP32_asukiaaa.hpp>
|
||||
|
||||
/************ 全局实体与状态变量 *************/
|
||||
XboxSeriesXControllerESP32_asukiaaa::Core xboxController("0a:27:12:5c:71:79");
|
||||
XboxSeriesXHIDReportBuilder_asukiaaa::ReportBase repo;
|
||||
|
||||
// 跟踪手柄连接状态(用于检测首次连接和断开)
|
||||
bool wasConnected = 0; // 是否链接
|
||||
|
||||
// 震动状态机(关键:记录当前处于震动的哪个阶段)
|
||||
enum VibrateState {
|
||||
VIBRATE_OFF, // 未震动
|
||||
VIBRATE_STARTED, // 已发送开启指令,等待关闭
|
||||
VIBRATE_TRIGGER // 扳机键震动状态(单独管理)
|
||||
};
|
||||
VibrateState vibrateState = VIBRATE_OFF;
|
||||
unsigned long vibrateStartMs = 0; // 震动开始的时间戳
|
||||
unsigned long long xbox_vibrate_duration = 200; // 按键震动持续时间 (可被函数动态更新)
|
||||
|
||||
/************ 外部函数声明 *************/
|
||||
extern void processControllerData(const XboxControllerNotificationParser& data);
|
||||
|
||||
/************ 核心函数 *************/
|
||||
|
||||
void xboxControllerInit() {
|
||||
xboxController.begin();
|
||||
vibrateState = VIBRATE_OFF; // 初始化状态
|
||||
}
|
||||
|
||||
// 非阻塞震动状态更新(必须在主循环中调用)
|
||||
void updateXboxVibration() {
|
||||
unsigned long currentMs = millis();
|
||||
|
||||
// 处理按键震动的"开启→等待→关闭"流程
|
||||
if (vibrateState == VIBRATE_STARTED || vibrateState == VIBRATE_TRIGGER) {
|
||||
// 检查是否达到震动持续时间
|
||||
if (currentMs - vibrateStartMs >= xbox_vibrate_duration) {
|
||||
// 执行原函数中delay后的关闭逻辑
|
||||
repo.v.power.center = 0;
|
||||
repo.v.power.shake = 0;
|
||||
xboxController.writeHIDReport(repo);
|
||||
vibrateState = VIBRATE_OFF; // 回到未震动状态
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按键震动(完整复现原逻辑的非阻塞版本)
|
||||
void XboxKeyVibration(uint8_t power = 20, unsigned long duration = 200) {
|
||||
|
||||
power = constrain(power, 0, 100); // 约束功率范围(防止无效值,确保在0-100之间)
|
||||
duration = constrain(duration, 50, 2000); // 时长限制50-2000ms(合理范围)
|
||||
|
||||
// 核心:将传入的时长(默认200或自定义)赋值给全局变量
|
||||
xbox_vibrate_duration = duration;
|
||||
|
||||
// 若正在震动,先重置状态(避免重复触发导致混乱)
|
||||
if (vibrateState != VIBRATE_OFF) {
|
||||
repo.v.power.center = 0;
|
||||
repo.v.power.shake = 0;
|
||||
xboxController.writeHIDReport(repo);
|
||||
}
|
||||
|
||||
// 执行原函数中delay前的开启逻辑
|
||||
repo.setAllOff();
|
||||
repo.v.select.center = true;
|
||||
repo.v.select.left = true;
|
||||
repo.v.select.right = true;
|
||||
repo.v.select.shake = true;
|
||||
repo.v.power.center = power; // 20% power
|
||||
repo.v.power.shake = power;
|
||||
repo.v.timeActive = 10; // x/100 second
|
||||
xboxController.writeHIDReport(repo);
|
||||
|
||||
// 记录开始时间,进入"等待关闭"状态(替代delay(250))
|
||||
vibrateState = VIBRATE_STARTED;
|
||||
vibrateStartMs = millis();
|
||||
}
|
||||
|
||||
// 扳机键震动(同样保留完整时序)
|
||||
void XboxtrigRTVibration( unsigned long duration = 300) {
|
||||
static uint16_t TrigMax = XboxControllerNotificationParser::maxTrig;
|
||||
uint8_t power = (uint8_t)((float)xboxController.xboxNotif.trigRT / TrigMax * 100) * 0.5;
|
||||
|
||||
// 核心:将传入的时长(默认300或自定义)赋值给全局变量
|
||||
xbox_vibrate_duration = duration;
|
||||
|
||||
// 若扳机键松开,直接关闭震动
|
||||
if (power == 0) {
|
||||
repo.v.power.center = 0;
|
||||
repo.v.power.shake = 0;
|
||||
xboxController.writeHIDReport(repo);
|
||||
vibrateState = VIBRATE_OFF;
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行开启震动逻辑
|
||||
repo.setAllOff();
|
||||
repo.v.select.center = true;
|
||||
repo.v.select.left = true;
|
||||
repo.v.select.right = true;
|
||||
repo.v.select.shake = true;
|
||||
repo.v.power.center = power;
|
||||
repo.v.power.shake = power;
|
||||
repo.v.timeActive = 10;
|
||||
xboxController.writeHIDReport(repo);
|
||||
|
||||
// 进入扳机震动状态,等待关闭
|
||||
vibrateState = VIBRATE_TRIGGER;
|
||||
vibrateStartMs = millis();
|
||||
}
|
||||
|
||||
void process_xbox_notif() {
|
||||
xboxController.onLoop();
|
||||
if (xboxController.isConnected() && !xboxController.isWaitingForFirstNotification()) {
|
||||
if(!wasConnected){ // 原来未链接,现在链接成功
|
||||
XboxKeyVibration(100,1000);
|
||||
startLEDBlink(CRGB::Green, 50, 3);
|
||||
wasConnected = 1;
|
||||
}
|
||||
processControllerData(xboxController.xboxNotif);
|
||||
}else{
|
||||
if(wasConnected){ // 原来是链接成功的,说明断了
|
||||
XboxKeyVibration(80);
|
||||
wasConnected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // XBOX_BLUETOOTH_H
|
||||
@@ -0,0 +1,158 @@
|
||||
#include "XboxControllerNotificationParser.h"
|
||||
|
||||
#define XBOX_CONTROLLER_INDEX_BUTTONS_DIR 12
|
||||
#define XBOX_CONTROLLER_INDEX_BUTTONS_MAIN 13
|
||||
#define XBOX_CONTROLLER_INDEX_BUTTONS_CENTER 14
|
||||
#define XBOX_CONTROLLER_INDEX_BUTTONS_SHARE 15
|
||||
|
||||
XboxControllerNotificationParser::XboxControllerNotificationParser() {
|
||||
btnA = btnB = btnX = btnY = false;
|
||||
btnShare = btnStart = btnSelect = btnXbox = false;
|
||||
btnLB = btnRB = btnLS = btnRS = false;
|
||||
btnDirUp = btnDirLeft = btnDirRight = btnDirDown = false;
|
||||
joyLHori = joyLVert = joyRHori = joyRVert = 0xffff / 2;
|
||||
trigLT = trigRT = 0;
|
||||
}
|
||||
|
||||
uint8_t XboxControllerNotificationParser::update(uint8_t* data, size_t length) {
|
||||
if (length != expectedDataLen) {
|
||||
return XBOX_CONTROLLER_ERROR_INVALID_LENGTH;
|
||||
}
|
||||
for (uint8_t i = 0; i < expectedDataLen; i++)
|
||||
{
|
||||
rx_buf[i]=data[i];
|
||||
}
|
||||
uint8_t btnBits;
|
||||
btnBits = data[XBOX_CONTROLLER_INDEX_BUTTONS_MAIN];
|
||||
btnA = btnBits & 0b00000001;
|
||||
btnB = btnBits & 0b00000010;
|
||||
btnX = btnBits & 0b00001000;
|
||||
btnY = btnBits & 0b00010000;
|
||||
btnLB = btnBits & 0b01000000;
|
||||
btnRB = btnBits & 0b10000000;
|
||||
|
||||
btnBits = data[XBOX_CONTROLLER_INDEX_BUTTONS_CENTER];
|
||||
btnSelect = btnBits & 0b00000100;
|
||||
btnStart = btnBits & 0b00001000;
|
||||
btnXbox = btnBits & 0b00010000;
|
||||
btnLS = btnBits & 0b00100000;
|
||||
btnRS = btnBits & 0b01000000;
|
||||
|
||||
btnBits = data[XBOX_CONTROLLER_INDEX_BUTTONS_SHARE];
|
||||
btnShare = btnBits & 0b00000001;
|
||||
|
||||
btnBits = data[XBOX_CONTROLLER_INDEX_BUTTONS_DIR];
|
||||
btnDirUp = btnBits == 1 || btnBits == 2 || btnBits == 8;
|
||||
btnDirRight = 2 <= btnBits && btnBits <= 4;
|
||||
btnDirDown = 4 <= btnBits && btnBits <= 6;
|
||||
btnDirLeft = 6 <= btnBits && btnBits <= 8;
|
||||
|
||||
joyLHori = (uint16_t)data[0] | ((uint16_t)data[1] << 8);
|
||||
joyLVert = (uint16_t)data[2] | ((uint16_t)data[3] << 8);
|
||||
joyRHori = (uint16_t)data[4] | ((uint16_t)data[5] << 8);
|
||||
joyRVert = (uint16_t)data[6] | ((uint16_t)data[7] << 8);
|
||||
trigLT = (uint16_t)data[8] | ((uint16_t)data[9] << 8);
|
||||
trigRT = (uint16_t)data[10] | ((uint16_t)data[11] << 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void XboxControllerNotificationParser::convertU16TU8Arr(uint16_t vU16,
|
||||
uint8_t* data) {
|
||||
data[0] = vU16 & 0xff;
|
||||
data[1] = vU16 >> 8;
|
||||
}
|
||||
|
||||
uint8_t XboxControllerNotificationParser::toArr(uint8_t* data, size_t length) {
|
||||
if (length < expectedDataLen) {
|
||||
return XBOX_CONTROLLER_ERROR_INVALID_LENGTH;
|
||||
}
|
||||
convertU16TU8Arr(joyLHori, &data[0]);
|
||||
convertU16TU8Arr(joyLVert, &data[2]);
|
||||
convertU16TU8Arr(joyRHori, &data[4]);
|
||||
convertU16TU8Arr(joyRVert, &data[6]);
|
||||
convertU16TU8Arr(trigLT, &data[8]);
|
||||
convertU16TU8Arr(trigRT, &data[10]);
|
||||
{
|
||||
uint8_t btnBits = 0;
|
||||
if (btnA) btnBits |= 0b00000001;
|
||||
if (btnB) btnBits |= 0b00000010;
|
||||
if (btnX) btnBits |= 0b00001000;
|
||||
if (btnY) btnBits |= 0b00010000;
|
||||
if (btnLB) btnBits |= 0b01000000;
|
||||
if (btnRB) btnBits |= 0b10000000;
|
||||
data[XBOX_CONTROLLER_INDEX_BUTTONS_MAIN] = btnBits;
|
||||
}
|
||||
{
|
||||
uint8_t btnBits = 0;
|
||||
if (btnSelect) btnBits |= 0b00000100;
|
||||
if (btnStart) btnBits |= 0b00001000;
|
||||
if (btnXbox) btnBits |= 0b00010000;
|
||||
if (btnLS) btnBits |= 0b00100000;
|
||||
if (btnRS) btnBits |= 0b01000000;
|
||||
data[XBOX_CONTROLLER_INDEX_BUTTONS_CENTER] = btnBits;
|
||||
}
|
||||
{
|
||||
uint8_t btnBits = 0;
|
||||
if (btnShare) btnBits |= 0b00000001;
|
||||
data[XBOX_CONTROLLER_INDEX_BUTTONS_SHARE] = btnBits;
|
||||
}
|
||||
{
|
||||
uint8_t btnBits = 0;
|
||||
if (btnDirUp) {
|
||||
if (btnDirRight) {
|
||||
btnBits = 2;
|
||||
} else if (btnDirLeft) {
|
||||
btnBits = 8;
|
||||
} else {
|
||||
btnBits = 1;
|
||||
}
|
||||
} else if (btnDirDown) {
|
||||
if (btnDirRight) {
|
||||
btnBits = 4;
|
||||
} else if (btnDirLeft) {
|
||||
btnBits = 6;
|
||||
} else {
|
||||
btnBits = 5;
|
||||
}
|
||||
} else {
|
||||
if (btnDirRight) {
|
||||
btnBits = 3;
|
||||
} else if (btnDirLeft) {
|
||||
btnBits = 7;
|
||||
} else {
|
||||
btnBits = 0;
|
||||
}
|
||||
}
|
||||
data[XBOX_CONTROLLER_INDEX_BUTTONS_DIR] = btnBits;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
String XboxControllerNotificationParser::toString() {
|
||||
// clang-format off
|
||||
String str = String("") +
|
||||
"btnY: " + String(btnY) + " " +
|
||||
"btnX: " + String(btnX) + " " +
|
||||
"btnB: " + String(btnB) + " " +
|
||||
"btnA: " + String(btnA) + " " +
|
||||
"btnLB: " + String(btnLB) + " " +
|
||||
"btnRB: " + String(btnRB) + "\n" +
|
||||
"btnSelect: " + String(btnSelect) + " " +
|
||||
"btnStart: " + String(btnStart) + " " +
|
||||
"btnXbox: " + String(btnXbox) + " " +
|
||||
"btnShare: " + String(btnShare) + " " +
|
||||
"btnLS: " + String(btnLS) + " " +
|
||||
"btnRS: " + String(btnRS) + "\n" +
|
||||
"btnDirUp: " + String(btnDirUp) + " " +
|
||||
"btnDirRight: " + String(btnDirRight) + " " +
|
||||
"btnDirDown: " + String(btnDirDown) + " " +
|
||||
"btnDirLeft: " + String(btnDirLeft) + "\n"
|
||||
"joyLHori: " + String(joyLHori) + "\n" +
|
||||
"joyLVert: " + String(joyLVert) + "\n" +
|
||||
"joyRHori: " + String(joyRHori) + "\n" +
|
||||
"joyRVert: " + String(joyRVert) + "\n" +
|
||||
"trigLT: " + String(trigLT) + "\n" +
|
||||
"trigRT: " + String(trigRT) + "\n";
|
||||
// clang-format on
|
||||
return str;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef __XBOX_CONTROLLER_NOTIFICATION_PARSER_H__
|
||||
#define __XBOX_CONTROLLER_NOTIFICATION_PARSER_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define XBOX_CONTROLLER_ERROR_INVALID_LENGTH 1
|
||||
|
||||
class XboxControllerNotificationParser {
|
||||
public:
|
||||
XboxControllerNotificationParser();
|
||||
|
||||
bool btnA, btnB, btnX, btnY;
|
||||
bool btnShare, btnStart, btnSelect, btnXbox;
|
||||
// side top button
|
||||
bool btnLB, btnRB;
|
||||
// button on joy stick
|
||||
bool btnLS, btnRS;
|
||||
bool btnDirUp, btnDirLeft, btnDirRight, btnDirDown;
|
||||
uint16_t joyLHori = maxJoy / 2;
|
||||
uint16_t joyLVert = maxJoy / 2;
|
||||
uint16_t joyRHori = maxJoy / 2;
|
||||
uint16_t joyRVert = maxJoy / 2;
|
||||
uint16_t trigLT, trigRT;
|
||||
uint8_t update(uint8_t* data, size_t length);
|
||||
uint8_t toArr(uint8_t* data, size_t length);
|
||||
String toString();
|
||||
|
||||
static const size_t expectedDataLen = 16;
|
||||
static const uint16_t maxJoy = 0xffff;
|
||||
static const uint16_t maxTrig = 0x3ff;
|
||||
uint8_t rx_buf[expectedDataLen];
|
||||
private:
|
||||
static void convertU16TU8Arr(uint16_t vU16, uint8_t* data);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,553 @@
|
||||
#pragma once
|
||||
|
||||
#include <NimBLEDevice.h>
|
||||
#include <XboxControllerNotificationParser.h>
|
||||
|
||||
#include <XboxSeriesXHIDReportBuilder_asukiaaa.hpp>
|
||||
|
||||
// #define XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL Serial
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
const unsigned long printInterval = 100UL;
|
||||
#endif
|
||||
|
||||
namespace XboxSeriesXControllerESP32_asukiaaa {
|
||||
|
||||
static NimBLEUUID uuidServiceGeneral("1801");
|
||||
static NimBLEUUID uuidServiceBattery("180f");
|
||||
static NimBLEUUID uuidServiceHid("1812");
|
||||
static NimBLEUUID uuidCharaReport("2a4d");
|
||||
static NimBLEUUID uuidCharaPnp("2a50");
|
||||
static NimBLEUUID uuidCharaHidInformation("2a4a");
|
||||
static NimBLEUUID uuidCharaPeripheralAppearance("2a01");
|
||||
static NimBLEUUID uuidCharaPeripheralControlParameters("2a04");
|
||||
|
||||
static NimBLEAdvertisedDevice* advDevice;
|
||||
static NimBLEClient* pConnectedClient = nullptr;
|
||||
|
||||
static const uint16_t controllerAppearance = 964;
|
||||
static const String controllerManufacturerDataNormal = "060000";
|
||||
static const String controllerManufacturerDataSearching = "0600030080";
|
||||
|
||||
enum class ConnectionState : uint8_t {
|
||||
Connected = 0,
|
||||
WaitingForFirstNotification = 1,
|
||||
Found = 2,
|
||||
Scanning = 3,
|
||||
};
|
||||
|
||||
class ClientCallbacks : public NimBLEClientCallbacks {
|
||||
public:
|
||||
ConnectionState* pConnectionState;
|
||||
ClientCallbacks(ConnectionState* pConnectionState) {
|
||||
this->pConnectionState = pConnectionState;
|
||||
}
|
||||
|
||||
void onConnect(NimBLEClient* pClient) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Connected");
|
||||
#endif
|
||||
*pConnectionState = ConnectionState::WaitingForFirstNotification;
|
||||
// pClient->updateConnParams(120,120,0,60);
|
||||
};
|
||||
|
||||
void onDisconnect(NimBLEClient* pClient) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print(
|
||||
pClient->getPeerAddress().toString().c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(" Disconnected");
|
||||
#endif
|
||||
*pConnectionState = ConnectionState::Scanning;
|
||||
pConnectedClient = nullptr;
|
||||
};
|
||||
|
||||
/********************* Security handled here **********************
|
||||
****** Note: these are the same return values as defaults ********/
|
||||
uint32_t onPassKeyRequest() {
|
||||
// Serial.println("Client Passkey Request");
|
||||
/** return the passkey to send to the server */
|
||||
return 0;
|
||||
};
|
||||
|
||||
bool onConfirmPIN(uint32_t pass_key) {
|
||||
// Serial.print("The passkey YES/NO number: ");
|
||||
// Serial.println(pass_key);
|
||||
/** Return false if passkeys don't match. */
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Pairing process complete, we can check the results in ble_gap_conn_desc */
|
||||
void onAuthenticationComplete(ble_gap_conn_desc* desc) {
|
||||
// Serial.println("onAuthenticationComplete");
|
||||
if (!desc->sec_state.encrypted) {
|
||||
// Serial.println("Encrypt connection failed - disconnecting");
|
||||
/** Find the client with the connection handle provided in desc */
|
||||
NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
|
||||
return;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/** Define a class to handle the callbacks when advertisments are received */
|
||||
class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
|
||||
public:
|
||||
AdvertisedDeviceCallbacks(String strTargetDeviceAddress,
|
||||
ConnectionState* pConnectionState) {
|
||||
if (strTargetDeviceAddress != "") {
|
||||
this->targetDeviceAddress =
|
||||
new NimBLEAddress(strTargetDeviceAddress.c_str());
|
||||
}
|
||||
this->pConnectionState = pConnectionState;
|
||||
}
|
||||
|
||||
private:
|
||||
NimBLEAddress* targetDeviceAddress = nullptr;
|
||||
ConnectionState* pConnectionState;
|
||||
void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
|
||||
int rssi = advertisedDevice->getRSSI();
|
||||
if(rssi > -45)
|
||||
{
|
||||
Serial.printf("Strong device found: address=%s, RSSI=%d\n",
|
||||
advertisedDevice->getAddress().toString().c_str(), rssi);
|
||||
}
|
||||
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print("Advertised Device found: ");
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
advertisedDevice->toString().c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(
|
||||
"name:%s, address:%s\n", advertisedDevice->getName().c_str(),
|
||||
advertisedDevice->getAddress().toString().c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(
|
||||
"uuidService:%s\n",
|
||||
advertisedDevice->haveServiceUUID()
|
||||
? advertisedDevice->getServiceUUID().toString().c_str()
|
||||
: "none");
|
||||
#endif
|
||||
char* pHex = NimBLEUtils::buildHexData(
|
||||
nullptr, (uint8_t*)advertisedDevice->getManufacturerData().data(),
|
||||
advertisedDevice->getManufacturerData().length());
|
||||
if ((targetDeviceAddress != nullptr &&
|
||||
advertisedDevice->getAddress().equals(*targetDeviceAddress)) ||
|
||||
(targetDeviceAddress == nullptr &&
|
||||
advertisedDevice->getAppearance() == controllerAppearance &&
|
||||
(strcmp(pHex, controllerManufacturerDataNormal.c_str()) == 0 ||
|
||||
strcmp(pHex, controllerManufacturerDataSearching.c_str()) == 0) &&
|
||||
advertisedDevice->getServiceUUID().equals(uuidServiceHid)))
|
||||
// if (advertisedDevice->isAdvertisingService(uuidServiceHid))
|
||||
{
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Found target");
|
||||
#endif
|
||||
/** stop scan before connecting */
|
||||
// NimBLEDevice::getScan()->stop();
|
||||
/** Save the device reference in a global for the client to use*/
|
||||
*pConnectionState = ConnectionState::Found;
|
||||
advDevice = advertisedDevice;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class Core {
|
||||
public:
|
||||
Core(String targetDeviceAddress = "") {
|
||||
this->advDeviceCBs =
|
||||
new AdvertisedDeviceCallbacks(targetDeviceAddress, &connectionState);
|
||||
this->clientCBs = new ClientCallbacks(&connectionState);
|
||||
}
|
||||
|
||||
AdvertisedDeviceCallbacks* advDeviceCBs;
|
||||
ClientCallbacks* clientCBs;
|
||||
uint8_t battery = 0;
|
||||
static const int deviceAddressLen = 6;
|
||||
uint8_t deviceAddressArr[deviceAddressLen];
|
||||
|
||||
void begin() {
|
||||
NimBLEDevice::setScanFilterMode(CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE);
|
||||
// NimBLEDevice::setScanDuplicateCacheSize(200);
|
||||
NimBLEDevice::init("");
|
||||
NimBLEDevice::setOwnAddrType(BLE_OWN_ADDR_PUBLIC);
|
||||
NimBLEDevice::setSecurityAuth(true, false, false);
|
||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9); /* +9db */
|
||||
}
|
||||
|
||||
void writeHIDReport(uint8_t* dataArr, size_t dataLen) {
|
||||
if (pConnectedClient == nullptr) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("no connnected client");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
NimBLEClient* pClient = pConnectedClient;
|
||||
auto pService = pClient->getService(uuidServiceHid);
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(pService->toString().c_str());
|
||||
#endif
|
||||
for (auto pChara : *pService->getCharacteristics()) {
|
||||
if (pChara->canWrite()) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
"canWrite " + String(pChara->canWrite()));
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
pChara->toString().c_str());
|
||||
writeWithComment(pChara, dataArr, dataLen);
|
||||
#else
|
||||
pChara->writeValue(dataArr, dataLen, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeHIDReport(
|
||||
const XboxSeriesXHIDReportBuilder_asukiaaa::ReportBase& repo) {
|
||||
writeHIDReport((uint8_t*)repo.arr8t, repo.arr8tLen);
|
||||
}
|
||||
|
||||
void writeHIDReport(
|
||||
const XboxSeriesXHIDReportBuilder_asukiaaa::ReportBeforeUnion&
|
||||
repoBeforeUnion) {
|
||||
XboxSeriesXHIDReportBuilder_asukiaaa::ReportBase repo;
|
||||
repo.v = repoBeforeUnion;
|
||||
writeHIDReport((uint8_t*)repo.arr8t, repo.arr8tLen);
|
||||
}
|
||||
|
||||
void onLoop() {
|
||||
if (!isConnected()) {
|
||||
if (advDevice != nullptr) {
|
||||
auto connectionResult = connectToServer(advDevice);
|
||||
if (!connectionResult || !isConnected()) {
|
||||
NimBLEDevice::deleteBond(advDevice->getAddress());
|
||||
++countFailedConnection;
|
||||
// reset();
|
||||
connectionState = ConnectionState::Scanning;
|
||||
} else {
|
||||
countFailedConnection = 0;
|
||||
}
|
||||
advDevice = nullptr;
|
||||
} else if (!isScanning()) {
|
||||
// reset();
|
||||
startScan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String buildDeviceAddressStr() {
|
||||
char buffer[18];
|
||||
auto addr = deviceAddressArr;
|
||||
snprintf(buffer, sizeof(buffer), "%02x:%02x:%02x:%02x:%02x:%02x", addr[5],
|
||||
addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
void startScan() {
|
||||
connectionState = ConnectionState::Scanning;
|
||||
auto pScan = NimBLEDevice::getScan();
|
||||
// pScan->clearResults();
|
||||
// pScan->clearDuplicateCache();
|
||||
pScan->setDuplicateFilter(false);
|
||||
pScan->setAdvertisedDeviceCallbacks(advDeviceCBs);
|
||||
// pScan->setActiveScan(true);
|
||||
pScan->setInterval(97);
|
||||
pScan->setWindow(97);
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Start scan");
|
||||
#endif
|
||||
// assign scanCompleteCB to scan on other thread
|
||||
pScan->start(scanTime, &Core::scanCompleteCB, false);
|
||||
}
|
||||
|
||||
XboxControllerNotificationParser xboxNotif;
|
||||
|
||||
bool isWaitingForFirstNotification() {
|
||||
return connectionState == ConnectionState::WaitingForFirstNotification;
|
||||
}
|
||||
bool isConnected() {
|
||||
return connectionState == ConnectionState::WaitingForFirstNotification ||
|
||||
connectionState == ConnectionState::Connected;
|
||||
}
|
||||
unsigned long getReceiveNotificationAt() { return receivedNotificationAt; }
|
||||
uint8_t getCountFailedConnection() { return countFailedConnection; }
|
||||
|
||||
private:
|
||||
ConnectionState connectionState = ConnectionState::Scanning;
|
||||
unsigned long receivedNotificationAt = 0;
|
||||
uint32_t scanTime = 4; /** 0 = scan forever */
|
||||
uint8_t countFailedConnection = 0;
|
||||
uint8_t retryCountInOneConnection = 3;
|
||||
unsigned long retryIntervalMs = 100;
|
||||
NimBLEClient* pClient = nullptr;
|
||||
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
static void writeWithComment(NimBLERemoteCharacteristic* pChara,
|
||||
uint8_t* data, size_t len) {
|
||||
Serial.println("send(print from addr 0) ");
|
||||
for (int i = 0; i < len; ++i) {
|
||||
Serial.print(data[i]);
|
||||
Serial.print(" ");
|
||||
}
|
||||
if (pChara->writeValue(data, len, true)) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("suceeded in writing");
|
||||
} else {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("failed writing");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void readAndPrint(NimBLERemoteCharacteristic* pChara) {
|
||||
auto str = pChara->readValue();
|
||||
if (str.size() == 0) {
|
||||
str = pChara->readValue();
|
||||
}
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
printValue(str);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isScanning() { return NimBLEDevice::getScan()->isScanning(); }
|
||||
|
||||
// void reset() {
|
||||
// NimBLEDevice::deinit(true);
|
||||
// delay(500);
|
||||
// begin();
|
||||
// delay(500);
|
||||
// }
|
||||
|
||||
/** Handles the provisioning of clients and connects / interfaces with the
|
||||
* server */
|
||||
bool connectToServer(NimBLEAdvertisedDevice* advDevice) {
|
||||
NimBLEClient* pClient = nullptr;
|
||||
|
||||
/** Check if we have a client we should reuse first **/
|
||||
if (NimBLEDevice::getClientListSize()) {
|
||||
pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
|
||||
if (pClient) {
|
||||
pClient->connect();
|
||||
}
|
||||
}
|
||||
|
||||
/** No client to reuse? Create a new one. */
|
||||
if (!pClient) {
|
||||
if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
"Max clients reached - no more connections available");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
pClient = NimBLEDevice::createClient();
|
||||
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("New client created");
|
||||
#endif
|
||||
|
||||
// default values
|
||||
// pClient->setConnectionParams(
|
||||
// BLE_GAP_INITIAL_CONN_ITVL_MIN, BLE_GAP_INITIAL_CONN_ITVL_MAX,
|
||||
// BLE_GAP_INITIAL_CONN_LATENCY, BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
|
||||
// 16, 16);
|
||||
// pClient->setConnectionParams(
|
||||
// BLE_GAP_INITIAL_CONN_ITVL_MIN, BLE_GAP_INITIAL_CONN_ITVL_MAX,
|
||||
// BLE_GAP_INITIAL_CONN_LATENCY, BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
|
||||
// 100, 100);
|
||||
pClient->setClientCallbacks(clientCBs, true);
|
||||
pClient->connect(advDevice, true);
|
||||
}
|
||||
|
||||
int retryCount = retryCountInOneConnection;
|
||||
while (!pClient->isConnected()) {
|
||||
if (retryCount <= 0) {
|
||||
return false;
|
||||
} else {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Try connection. left: " +
|
||||
String(retryCount));
|
||||
#endif
|
||||
}
|
||||
|
||||
// NimBLEDevice::getScan()->stop();
|
||||
// pClient->disconnect();
|
||||
delay(retryIntervalMs);
|
||||
// Serial.println(pClient->toString().c_str());
|
||||
pClient->connect(true);
|
||||
--retryCount;
|
||||
}
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print("Connected to: ");
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
pClient->getPeerAddress().toString().c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print("RSSI: ");
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(pClient->getRssi());
|
||||
#endif
|
||||
|
||||
// pClient->discoverAttributes();
|
||||
|
||||
bool result = afterConnect(pClient);
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Done with this device!");
|
||||
#endif
|
||||
pConnectedClient = pClient;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool afterConnect(NimBLEClient* pClient) {
|
||||
memcpy(deviceAddressArr, pClient->getPeerAddress().getNative(),
|
||||
deviceAddressLen);
|
||||
for (auto pService : *pClient->getServices(true)) {
|
||||
auto sUuid = pService->getUUID();
|
||||
if (!sUuid.equals(uuidServiceHid) && !sUuid.equals(uuidServiceBattery)) {
|
||||
continue; // skip
|
||||
}
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
pService->toString().c_str());
|
||||
#endif
|
||||
for (auto pChara : *pService->getCharacteristics(true)) {
|
||||
charaHandle(pChara);
|
||||
charaSubscribeNotification(pChara);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
void charaPrintId(NimBLERemoteCharacteristic* pChara) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(
|
||||
"s:%s c:%s h:%d",
|
||||
pChara->getRemoteService()->getUUID().toString().c_str(),
|
||||
pChara->getUUID().toString().c_str(), pChara->getHandle());
|
||||
}
|
||||
|
||||
static void printValue(std::__cxx11::string str) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf("str: %s\n", str.c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf("hex:");
|
||||
for (auto v : str) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(" %02x", v);
|
||||
}
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("");
|
||||
}
|
||||
#endif
|
||||
|
||||
void charaHandle(NimBLERemoteCharacteristic* pChara) {
|
||||
if (pChara->canWrite()) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
charaPrintId(pChara);
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(" canWrite");
|
||||
#endif
|
||||
}
|
||||
if (pChara->canRead()) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
charaPrintId(pChara);
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(" canRead");
|
||||
#endif
|
||||
// Reading value is required for subscribe
|
||||
auto str = pChara->readValue();
|
||||
if (str.size() == 0) {
|
||||
str = pChara->readValue();
|
||||
}
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
printValue(str);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void charaSubscribeNotification(NimBLERemoteCharacteristic* pChara) {
|
||||
if (pChara->canNotify()) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
charaPrintId(pChara);
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(" canNotify ");
|
||||
#endif
|
||||
if (pChara->subscribe(
|
||||
true,
|
||||
std::bind(&Core::notifyCB, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4),
|
||||
true)) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
"succeeded in subscribing");
|
||||
#endif
|
||||
} else {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("failed subscribing");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic,
|
||||
uint8_t* pData, size_t length, bool isNotify) {
|
||||
auto sUuid = pRemoteCharacteristic->getRemoteService()->getUUID();
|
||||
if (connectionState != ConnectionState::Connected) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
"Received first notification");
|
||||
#endif
|
||||
connectionState = ConnectionState::Connected;
|
||||
}
|
||||
if (sUuid.equals(uuidServiceHid)) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
static bool isPrinting = false;
|
||||
static unsigned long printedAt = 0;
|
||||
if (isPrinting || millis() - printedAt < printInterval) return;
|
||||
isPrinting = true;
|
||||
std::string str = (isNotify == true) ? "Notification" : "Indication";
|
||||
str += " from ";
|
||||
/** NimBLEAddress and NimBLEUUID have std::string operators */
|
||||
str += std::string(pRemoteCharacteristic->getRemoteService()
|
||||
->getClient()
|
||||
->getPeerAddress());
|
||||
str += ": Service = " +
|
||||
std::string(pRemoteCharacteristic->getRemoteService()->getUUID());
|
||||
str +=
|
||||
", Characteristic = " + std::string(pRemoteCharacteristic->getUUID());
|
||||
// str += ", Value = " + std::string((char*)pData, length);
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(str.c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print("value: ");
|
||||
for (int i = 0; i < length; ++i) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(" %02x", pData[i]);
|
||||
}
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("");
|
||||
#endif
|
||||
xboxNotif.update(pData, length);
|
||||
receivedNotificationAt = millis();
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
// XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.print(xboxNotif.toString());
|
||||
printedAt = millis();
|
||||
isPrinting = false;
|
||||
#endif
|
||||
} else {
|
||||
if (sUuid.equals(uuidServiceBattery)) {
|
||||
battery = pData[0];
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("battery notification");
|
||||
#endif
|
||||
} else {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf("s:%s",
|
||||
sUuid.toString().c_str());
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println(
|
||||
"not handled notification");
|
||||
#endif
|
||||
}
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
for (int i = 0; i < length; ++i) {
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.printf(" %02x", pData[i]);
|
||||
}
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void scanCompleteCB(NimBLEScanResults results) {
|
||||
#ifdef XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL
|
||||
XBOX_SERIES_X_CONTROLLER_DEBUG_SERIAL.println("Scan Ended");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace XboxSeriesXControllerESP32_asukiaaa
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
namespace XboxSeriesXHIDReportBuilder_asukiaaa {
|
||||
|
||||
struct InfoSelect {
|
||||
uint8_t center : 1;
|
||||
uint8_t shake : 1;
|
||||
uint8_t right : 1;
|
||||
uint8_t left : 1;
|
||||
uint8_t _unkown_bit_4 : 1;
|
||||
uint8_t _unkown_bit_5 : 1;
|
||||
uint8_t _unkown_bit_6 : 1;
|
||||
uint8_t _unkown_bit_7 : 1;
|
||||
};
|
||||
|
||||
// 0 to 0x64 (100), over 0x64 becomes 0x64
|
||||
struct InfoPower {
|
||||
uint8_t left;
|
||||
uint8_t right;
|
||||
uint8_t shake;
|
||||
uint8_t center;
|
||||
};
|
||||
|
||||
struct ReportBeforeUnion {
|
||||
InfoSelect select;
|
||||
InfoPower power;
|
||||
// value * 0.01 seconds, max 2.55 seconds
|
||||
uint8_t timeActive;
|
||||
uint8_t timeSilent;
|
||||
// max 0xff
|
||||
uint8_t countRepeat;
|
||||
};
|
||||
|
||||
class ReportBase {
|
||||
public:
|
||||
static const size_t arr8tLen = 8;
|
||||
union {
|
||||
ReportBeforeUnion v;
|
||||
uint8_t arr8t[arr8tLen];
|
||||
};
|
||||
|
||||
ReportBase() { setFullPowerFor1Sec(); }
|
||||
|
||||
void setFullPowerFor1Sec() {
|
||||
v.select.center = true;
|
||||
v.select.left = true;
|
||||
v.select.right = true;
|
||||
v.select.shake = false;
|
||||
v.power.center = 30;
|
||||
// v.power.left = 30;
|
||||
// v.power.right = 30;
|
||||
// v.power.shake = 30;
|
||||
// v.timeActive = 30;
|
||||
v.timeSilent = 0;
|
||||
v.countRepeat = 0;
|
||||
}
|
||||
|
||||
void setAllOff() {
|
||||
v.select.center = false;
|
||||
v.select.left = false;
|
||||
v.select.right = false;
|
||||
v.select.shake = false;
|
||||
v.power.center = 0;
|
||||
v.power.left = 0;
|
||||
v.power.right = 0;
|
||||
v.power.shake = 0;
|
||||
v.timeActive = 0;
|
||||
v.timeSilent = 0;
|
||||
v.countRepeat = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace XboxSeriesXHIDReportBuilder_asukiaaa
|
||||
+1318
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user