完善V1.4踢乒乓球版本

This commit is contained in:
myperworld committed 2026-01-20 22:15:03 +08:00
1 parent d14fe6c37b
commit 72455a5e76
1864 files changed
+19294 -218

No files matched your search

@@ -0,0 +1,36 @@
// 引入头文件
#include "PreferencesManager.h"
// 1. 关键:定义全局唯一的 prefManager 对象(对应头文件的 extern 声明)
Preferences prefManager;
// 2. 实现初始化函数(初始化全局对象)
void initPreferencesManager() {
// 打开命名空间(读写模式),初始化全局 prefManager
bool initSuccess = prefManager.begin(PREFERENCES_NAMESPACE, false);
if (initSuccess) {
// Serial.println("Preferences 初始化成功(命名空间:" + String(PREFERENCES_NAMESPACE) + ")");
} else {
Serial.println("Preferences 初始化失败!");
}
}
// 3. 实现读取函数(使用全局 prefManager)
String getPrefBluetoothMacAddress() {
// 必须确保 prefManager 已 begin(通过 initPreferencesManager())
String macAddr = prefManager.getString(PREF_MAC_ADDRESS_KEY, DEFAULT_BLUETOOTH_MAC);
return macAddr;
}
// 4. 实现写入函数(使用全局 prefManager)
void setPrefBluetoothMacAddress(String macAddr) {
// 写入前检查是否已 begin(可选,增加鲁棒性)
prefManager.putString(PREF_MAC_ADDRESS_KEY, macAddr);
}
// 5. 实现重启函数
void restartDevice() {
Serial.println("设备将在 100ms 后重启...");
delay(100);
ESP.restart();
}
@@ -0,0 +1,22 @@
#ifndef PREFERENCES_MANAGER_H
#define PREFERENCES_MANAGER_H
#include <Arduino.h>
#include <Preferences.h>
// 1. 宏定义保持不变
#define PREF_MAC_ADDRESS_KEY "mac_address"
#define PREFERENCES_NAMESPACE "wrobot_esp32"
#define DEFAULT_BLUETOOTH_MAC "0a:27:12:5c:71:79"
// 2. 关键修改:extern 声明全局对象(不定义,只声明)
// 作用:告诉所有包含该头文件的文件,"prefManager" 在其他地方定义,全局唯一
extern Preferences prefManager;
// 3. 函数声明(去掉 inline,后续移到 .cpp 中实现,避免头文件冲突)
void initPreferencesManager();
String getPrefBluetoothMacAddress();
void setPrefBluetoothMacAddress(String macAddr);
void restartDevice();
#endif // PREFERENCES_MANAGER_H
@@ -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,122 @@
#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);
ActionStep step_turn_right[] = {
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
{ -100, 0, 0, 0, -10, 50, 0, -1, -1, -1, -1},
END_MARKER // 结束标记
};
RobotAction action_step_turn_right = CREATE_ACTION("action_step_turn_right", step_turn_right);
ActionStep step_turn_left[] = {
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
{ 100, 0, 0, 0, -10, 50, 0, -1, -1, -1, -1},
END_MARKER // 结束标记
};
RobotAction action_step_turn_left = CREATE_ACTION("action_step_turn_left", step_turn_left);
ActionStep step_forward[] = {
// {左+右-,前+后-,跳跃,平衡20左-右+,高度20升-降+,持续时间, 停顿时间0,
{ 0, 50, 0, 0, 10, 500, 0, -1, -1, -1, -1},
END_MARKER // 结束标记
};
RobotAction action_step_forward = CREATE_ACTION("action_step_forward", step_forward);
#endif
@@ -0,0 +1,204 @@
#include "SerialParser.h" // 仅包含自己的头文件,无需 main.h
#include "PreferencesManager.h" // 包含设备配置相关功能
// -------------------------- 全局变量定义--------------------------
char serial_buf[32]; // 存储串口接收数据
uint8_t buf_idx = 0; // 缓冲区索引
// -------------------------- 串口接收处理函数--------------------------
// 修改 serialReceiveProcess 函数以支持两种结束符
void serialReceiveProcess() {
while (Serial.available()) {
char c = Serial.read();
// 支持两种结束符:回车/换行 或 分号
if (c == '\n' || c == '\r' || c == ';') {
serial_buf[buf_idx] = '\0'; // 字符串终止
// 判断是哪种指令格式
if (strchr(serial_buf, '=') != NULL) {
parseSingleParam(serial_buf); // 处理原格式指令
} else if (strstr(serial_buf, "S1:") != NULL && strstr(serial_buf, "S2:") != NULL) {
parseServoAngleCommand(serial_buf); // 处理舵机角度控制指令 S1:90,S2:90;
} else if (strstr(serial_buf, "DX:") != NULL && strstr(serial_buf, "DY:") != NULL) {
parseCameraDeviationCommand(serial_buf); // 处理摄像头偏差控制指令 DX:90,DY:45;
} else if (buf_idx > 0) {
// Serial.println("指令格式错误!请使用正确格式");
}
buf_idx = 0; // 重置缓冲区索引
} else if (buf_idx < 31) { // 防止缓冲区溢出
serial_buf[buf_idx++] = c; // 存入缓冲区
}
}
}
// 添加舵机指令解析函数
void parseServoAngleCommand(char* cmd) {
char* s1Ptr = strstr(cmd, "S1:");
char* s2Ptr = strstr(cmd, "S2:");
// 检查指令格式是否正确
if (s1Ptr == NULL || s2Ptr == NULL) {
Serial.println("舵机指令格式错误!请使用\"S1:角度,S2:角度;\"格式(例如\"S1:90,S2:45;\")");
return;
}
// 转换为角度数值(跳过"S1:"和"S2:"字符)
int angle1 = atoi(s1Ptr + 3); // "S1:"占3个字符,跳过它们取数值
int angle2 = atoi(s2Ptr + 3); // "S2:"占3个字符,跳过它们取数值
// 舵机控制函数
setServoAngles(angle1, angle2);
}
void parseCameraDeviationCommand(char* cmd) {
char* dxPtr = strstr(cmd, "DX:");
char* dyPtr = strstr(cmd, "DY:");
// 检查指令格式是否正确
if (dxPtr == NULL || dyPtr == NULL) {
Serial.println("摄像头指令格式错误!请使用\"DX:角度,DY:角度;\"格式(例如\"DX:90,DY:45;\")");
return;
}
// 转换为角度数值(跳过"S1:"和"S2:"字符)
int dx = atoi(dxPtr + 3); // "DX:"占3个字符,跳过它们取数值
int dy = atoi(dyPtr + 3); // "DY:"占3个字符,跳过它们取数值
// 控制摄像头上下左右
kick_ball_main(dx, dy);
}
// -------------------------- 参数解析函数(原逻辑不变)--------------------------
void parseSingleParam(char* cmd) {
char* eq_pos = strchr(cmd, '='); // 找到等号位置
if (eq_pos == NULL) { // 无等号则忽略
Serial.println("格式错误!需含\"=\"");
return;
}
*eq_pos = '\0'; // 分割参数名与数值(cmd=参数名,eq_pos+1=数值)
char* param_name = cmd;
float param_val = atof(eq_pos + 1); // 转为浮点值
// 匹配参数名并赋值(访问全局对象,已在 SerialParser.h 中声明)
if (strcmp(param_name, "kp") == 0) {
pid_speed.P = param_val;
} else if (strcmp(param_name, "ki") == 0) {
pid_speed.I = param_val;
} else if (strcmp(param_name, "kd") == 0) {
pid_speed.D = param_val;
} else if (strcmp(param_name, "zaccel_threshold") == 0) {
zaccel_threshold = param_val;
} else if (strcmp(param_name, "dragon_joyx_factor") == 0) {
dragon_joyx_factor = param_val;
// } else if (strcmp(param_name, "go_shrink_v") == 0) {
// go_shrink_v = param_val;
// } else if (strcmp(param_name, "servo_up_height") == 0) {
// servo_up_height = param_val;
// } else if (strcmp(param_name, "servo_down_height") == 0) {
// servo_down_height = param_val;
// } else if (strcmp(param_name, "servo_down_count_max") == 0) {
// servo_down_count_max = param_val;
} else if (strcmp(param_name, "mid_obstacle_pitch") == 0) {
mid_obstacle_pitch = param_val;
} else if (strcmp(param_name, "mid_servo_up_height") == 0) {
mid_servo_up_height = param_val;
} else if (strcmp(param_name, "mid_servo_down_height") == 0) {
mid_servo_down_height = param_val;
} else if (strcmp(param_name, "mid_motor_target") == 0) {
mid_motor_target = param_val;
} else if (strcmp(param_name, "high_obstacle_pitch") == 0) {
high_obstacle_pitch = param_val;
} else if (strcmp(param_name, "high_servo_up_height") == 0) {
high_servo_up_height = param_val;
} else if (strcmp(param_name, "high_servo_down_height") == 0) {
high_servo_down_height = param_val;
} else if (strcmp(param_name, "high_motor_target") == 0) {
high_motor_target = param_val;
} else if (strcmp(param_name, "pid_trace_joyx_p") == 0) {
pid_trace_joyx.P = param_val;
} else if (strcmp(param_name, "pid_trace_joyx_d") == 0) {
pid_trace_joyx.D = param_val;
} else if (strcmp(param_name, "pid_trace_joyy_p") == 0) {
pid_trace_joyy.P = param_val;
} else if (strcmp(param_name, "pid_trace_joyy_d") == 0) {
pid_trace_joyy.D = param_val;
} else if (strcmp(param_name, "pid_cam_p") == 0) {
pid_cam_p = param_val;
} else if (strcmp(param_name, "pid_cam_d") == 0) {
pid_cam_d = param_val;
} else if (strcmp(param_name, "place_ball_s2") == 0) {
place_ball_s2 = param_val;
} else if (strcmp(param_name, "place_kick_ball_dy") == 0) {
place_kick_ball_dy = param_val;
} else if (strcmp(param_name, "chase_ball_s2") == 0) {
chase_ball_s2 = param_val;
} else if (strcmp(param_name, "run_kick_ball_dy") == 0) {
run_kick_ball_dy = param_val;
} else if (strcmp(param_name, "ob_ball_dy") == 0) {
ob_ball_dy = param_val;
} else if (strcmp(param_name, "kick_over_count_max") == 0) {
kick_over_count_max = param_val;
} else if (strcmp(param_name, "leg_height_base") == 0) {
leg_height_base = param_val;
} else if (strcmp(param_name, "pitch_adjust") == 0) {
pitch_adjust = param_val;
// } else if (strcmp(param_name, "step_joyx") == 0) {
// step_joyx = param_val;
// } else if (strcmp(param_name, "step_duration") == 0) {
// step_duration = param_val;
} else if (strcmp(param_name, "mac") == 0) {
// 1. 获取传入的 MAC 字符串(注意:eq_pos+1 是字符串,不能用 atof 转浮点)
String newMac = String(eq_pos + 1);
// 2. 调用全局函数写入(而非直接操作 prefManager)
setPrefBluetoothMacAddress(newMac);
// 3. 立即读取验证(调用全局函数)
String readMac = getPrefBluetoothMacAddress();
// 4. 串口输出结果(判断是否真的写入成功)
Serial.println("尝试写入 MAC:" + newMac);
if (readMac.equals(newMac)) {
Serial.println("MAC 写入成功!当前配置:" + readMac);
} else {
Serial.println(" MAC 写入失败!当前配置:" + readMac);
}
restartDevice();
return; // 跳过后面的浮点参数反馈
}
// -------------------------- 处理 show_mac 指令 --------------------------
else if (strcmp(param_name, "show_mac") == 0) {
String macAddr = getPrefBluetoothMacAddress();
Serial.println("🔍 读取到蓝牙 MAC:" + macAddr);
} else {
Serial.println("参数名错误!");
return;
}
// 反馈更新结果
Serial.printf("更新成功:%s=%.2f\n", param_name, param_val);
}
@@ -0,0 +1,89 @@
#ifndef SERIAL_PARSER_H
#define SERIAL_PARSER_H
#include <Arduino.h>
#include <SimpleFOC.h> // 包含 PIDController 类的定义
#include <FastLED.h> // 包含 CRGB 类(如果用到 LED 相关)
// -------------------------- 全局对象声明(与 main.cpp 中定义一致)--------------------------
// 这些对象在 main.cpp 中定义,此处用 extern 声明供 SerialParser 访问
extern PIDController pid_speed;
extern float zaccel_threshold;
extern float dragon_joyx_factor;
extern PIDController pid_trace_joyx;
extern PIDController pid_trace_joyy;
extern float pid_cam_p;
extern float pid_cam_d;
extern int chase_ball_s2;
extern int ob_ball_dy;
extern int place_ball_s2;
extern int place_kick_ball_dy;
extern int run_kick_ball_dy;
extern int kick_over_count_max;
extern float leg_height_base;
extern float pitch_adjust;
// extern float obstacle_pitch;
// extern int servo_up_height;
// extern int servo_down_height;
// extern float motor_target;
extern float mid_obstacle_pitch ;
extern int mid_servo_up_height;
extern int mid_servo_down_height;
extern float mid_motor_target;
extern float high_obstacle_pitch;
extern int high_servo_up_height;
extern int high_servo_down_height;
extern float high_motor_target;
// extern int step_joyx;
// extern int step_duration;
extern char serial_buf[32];
extern uint8_t buf_idx;
// -------------------------- 函数声明 --------------------------
void serialReceiveProcess();
void parseSingleParam(char* cmd);
// 舵机控制函数声明
void parseServoAngleCommand(char* cmd);
void parseCameraDeviationCommand(char* cmd);
// 踢球相关函数声明
void kick_ball_loop();
void SetKickMode(String mode);
void kick_ball_main(int dx, int dy);
// void chase_ball(int dy);
void chase_and_kick_ball(int dy);
void place_kick_ball(int dy);
void kick_ball_core();
void ready_kick_ball();
void aim_ob ( int dx, int dy, String ob);
void aim_ball(int dx, int dy);
void aim_face ( int dx, int dy);
void setCarmeraFixServoAngles(int servo_angle);
void setFrontierServoAngles(int servo_angle);
void setServoAngles(int s1, int s2);
void cancel_trace_mode();
void SerialToMaixcam(String cat, String str);
#endif // SERIAL_PARSER_H
@@ -0,0 +1,51 @@
#include "VoltageMonitor.h"
#include "RGBController.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);
// startLEDBlink(CRGB::Red, 200, 5); // rgb 红灯闪烁
}
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,147 @@
/* ---------------------------------*
* 蓝牙手柄(Xbox)模块(保留.语法+修复MAC读取)
* 包含:蓝牙实体、状态变量、连接处理函数(合并版)
------------------------------------- */
#ifndef XBOX_BLUETOOTH_H
#define XBOX_BLUETOOTH_H
#include <Arduino.h>
#include <XboxSeriesXControllerESP32_asukiaaa.hpp>
#include "PreferencesManager.h"
#include "RGBController.h" // 依赖RGB控制模块
/************ 全局实体与状态变量 *************/
// 震动状态机
enum VibrateState {
VIBRATE_OFF, // 未震动
VIBRATE_STARTED, // 已发送开启指令,等待关闭
VIBRATE_TRIGGER // 扳机键震动状态(单独管理)
};
// 核心:通过函数返回延迟初始化的全局对象引用(保留.语法)
inline XboxSeriesXControllerESP32_asukiaaa::Core& getXboxController() {
// 静态局部对象:第一次调用时才初始化(此时已执行initPreferencesManager,能读最新MAC)
static XboxSeriesXControllerESP32_asukiaaa::Core instance(getPrefBluetoothMacAddress().c_str());
return instance;
}
// 全局别名(让main中仍可直接用xboxController.xxx,无需改代码)
#define xboxController getXboxController()
// 其他全局变量
static XboxSeriesXHIDReportBuilder_asukiaaa::ReportBase repo;
static bool wasConnected = false;
static VibrateState vibrateState = VIBRATE_OFF;
static unsigned long vibrateStartMs = 0;
static unsigned long long xbox_vibrate_duration = 0;
/************ 外部函数声明 *************/
extern void processControllerData(const XboxControllerNotificationParser& data);
/************ 核心函数实现 *************/
/**
* 初始化Xbox手柄控制器(关键:触发对象初始化,读取最新MAC)
*/
inline void xboxControllerInit() {
// 调用getXboxController()触发对象初始化(此时已执行initPreferencesManager)
getXboxController().begin();
vibrateState = VIBRATE_OFF;
}
/**
* 非阻塞震动状态更新
*/
inline void updateXboxVibration() {
unsigned long currentMs = millis();
if (vibrateState == VIBRATE_STARTED || vibrateState == VIBRATE_TRIGGER) {
if (currentMs - vibrateStartMs >= xbox_vibrate_duration) {
repo.v.power.center = 0;
repo.v.power.shake = 0;
xboxController.writeHIDReport(repo); // 仍用.语法
vibrateState = VIBRATE_OFF;
}
}
}
/**
* 按键震动(非阻塞版本)
*/
inline void XboxKeyVibration(uint8_t power = 20, unsigned long duration = 200) {
power = constrain(power, 0, 100);
duration = constrain(duration, 50, 2000);
xbox_vibrate_duration = duration;
if (vibrateState != VIBRATE_OFF) {
repo.v.power.center = 0;
repo.v.power.shake = 0;
xboxController.writeHIDReport(repo); // 仍用.语法
}
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_STARTED;
vibrateStartMs = millis();
}
/**
* 扳机键震动
*/
inline void XboxtrigRTVibration(unsigned long duration = 300) {
static uint16_t TrigMax = XboxControllerNotificationParser::maxTrig;
// 核心:仍用.语法访问xboxNotif.trigRT,无需改main中代码
uint8_t power = (uint8_t)((float)xboxController.xboxNotif.trigRT / TrigMax * 100) * 0.5;
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();
}
/**
* 处理Xbox手柄通知
*/
inline void process_xbox_notif() {
xboxController.onLoop(); // 仍用.语法
if (xboxController.isConnected() && !xboxController.isWaitingForFirstNotification()) { // 仍用.语法
if (!wasConnected) {
XboxKeyVibration(100,1000);
startLEDBlink(CRGB::Green, 50, 3);
wasConnected = true;
}
processControllerData(xboxController.xboxNotif); // 仍用.语法访问xboxNotif
} else {
if (wasConnected) {
XboxKeyVibration(80);
wasConnected = false;
}
}
}
#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
File diff suppressed because it is too large. Load diff