CodexPad Arduino Lib 3.0.0
Loading...
Searching...
No Matches
scan_and_connect.ino
Go to the documentation of this file.
1/**
2 * @~English
3 * @file scan_and_connect.ino
4 * @example scan_and_connect.ino
5 * @brief Demonstrates how to scan for and connect to a CodexPad device by matching specific button presses.
6 * @details This example shows the usage of the `ScanAndConnect()` function. The device will scan for nearby CodexPad devices
7 * and automatically connect to one where the operator is holding down the predefined combination of buttons (the
8 * *button mask*). The button mask is defined by the `kExpectedButtonMask` constant. You must physically press and hold
9 * the corresponding button(s) on the target CodexPad for the connection to succeed.
10 * @warning **Important:** The button mask must NOT include `Button::kHome`. Pressing and holding the Home button causes the
11 * device to reboot, which will interrupt or prevent connection.
12 * @see CodexPad::ScanAndConnect
13 */
14/**
15 * @~Chinese
16 * @file scan_and_connect.ino
17 * @example scan_and_connect.ino
18 * @brief 演示如何通过匹配特定按键按压来扫描并连接 CodexPad 设备。
19 * @details 本示例展示了 `ScanAndConnect()` 函数的使用方法。设备将扫描附近的 CodexPad
20 * 设备,并自动连接到操作者正按住预定义按键组合(*按钮掩码*)的那一个。 按钮掩码由常量 `kExpectedButtonMask`
21 * 定义。您必须在目标 CodexPad 手柄上物理按住对应的按键,连接才能成功。
22 * @warning **重要:** 按钮掩码中 **不得** 包含 `Button::kHome`。按住 Home 键会导致设备重启,从而中断或阻止连接。
23 * @see CodexPad::ScanAndConnect
24 */
25
26#include "codex_pad.h"
27
28/**
29 * IMPORTANT:
30 * This using directive is REQUIRED to directly access `Button` and `Axis`.
31 * Without it, you must write the fully qualified names:
32 * gamepad::input::Button::kUp
33 * gamepad::input::Axis::kLeftStickX
34 * Forgetting this line will cause compile errors when using Button or Axis.
35 */
36/**
37 * 重要:
38 * 必须使用该命名空间,否则无法直接访问 `Button` 和 `Axis`。
39 * 如果没有这一行,就必须写成完整限定名:
40 * gamepad::input::Button::kUp
41 * gamepad::input::Axis::kLeftStickX
42 * 忘记引入命名空间会导致编译失败。
43 */
44using namespace gamepad::input;
45
46namespace {
47// You can set a button mask to automatically connect when a target device is scanned and its button state matches this mask.
48// For example, you can set it to connect only when a specific button is pressed, or when multiple specified buttons are pressed
49// simultaneously on the device. 你可以设置一个按钮掩码,当扫描到目标设备并检测到其按键状态与该掩码匹配时,自动进行连接。
50// 例如,可以设置为当设备上某个特定按键被按下,或多个指定按键被同时按下时才建立连接。
51
52// 【Important Warning】DO NOT use `Button::kHome` (Home button) alone to set the button mask. Pressing and holding the Home
53// button will trigger a device shutdown. If you need to use the Home button, it is recommended to use it in combination with
54// other buttons (e.g., Home + Cross). 【重要警告】请勿单独使用 `Button::kHome` (Home键)
55// 来设置按钮掩码。因为按住Home键会触发设备关机。如需使用Home键,建议采用组合按键方式(例如 Home + Cross)。
56
57// Example 1: The button mask to match - Only the Start button
58// 示例 1:需要匹配的按钮掩码 - 仅Start按钮
59// constexpr auto kExpectedButtonMask = Button::kStart;
60
61// Example 2: The button mask to match - Start and CrossA buttons
62// 示例 2:需要匹配的按钮掩码 - Start 和 CrossA 按钮
63constexpr auto kExpectedButtonMask = Button::kStart | Button::kCrossA;
64
65// Example 3: The button mask to match - Start, CrossA, and SquareX buttons
66// 示例 3:需要匹配的按钮掩码 - Start、CrossA 和 SquareX 按钮
67// constexpr auto kExpectedButtonMask = Button::kStart | Button::kCrossA | Button::kSquareX;
68
69CodexPad g_codex_pad;
70
71/**
72 * Convert button constant to readable string name
73 * 将按钮枚举转换为可读的字符串名称
74 */
75std::string ButtonToString(Button button) {
76 switch (button) {
77 case Button::kUp: {
78 return "Up"; // 上按钮 | UP button
79 }
80 case Button::kDown: {
81 return "Down"; // 下按钮 | DOWN button
82 }
83 case Button::kLeft: {
84 return "Left"; // 左按钮 | LEFT button
85 }
86 case Button::kRight: {
87 return "Right"; // 右按钮 | RIGHT button
88 }
89 case Button::kSquareX: {
90 return "Square(X)"; // 方形 或者 X 按钮 | SQUARE or X button
91 }
92 case Button::kTriangleY: {
93 return "Triangle(Y)"; // 三角 或者 Y 按钮 | TRIANGLE or Y button
94 }
95 case Button::kCrossA: {
96 return "Cross(A)"; // 叉型 或者 A 按钮 | CROSS or A button
97 }
98 case Button::kCircleB: {
99 return "Circle(B)"; // 圆形 或者 B 按钮 | CIRCLE or B button
100 }
101 case Button::kL1: {
102 return "L1"; // L1按钮 | L1 button
103 }
104 case Button::kL2: {
105 return "L2"; // L2按钮 | L2 button
106 }
107 case Button::kL3: {
108 return "L3"; // L3按钮 | L3 button
109 }
110 case Button::kR1: {
111 return "R1"; // R1按钮 | R1 button
112 }
113 case Button::kR2: {
114 return "R2"; // R2按钮 | R2 button
115 }
116 case Button::kR3: {
117 return "R3"; // R3按钮 | R3 button
118 }
119 case Button::kSelect: {
120 return "Select"; // 选择按钮 | SELECT button
121 }
122 case Button::kStart: {
123 return "Start"; // 开始按钮 | START button
124 }
125 case Button::kHome: {
126 return "Home"; // 首页按钮 | HOME button
127 }
128 default: {
129 return {}; // 未知按钮返回空字符串 | Unknown button returns empty string
130 }
131 }
132}
133
134void Connect() {
135 printf("Start to scan and connect, button mask: 0x%08X\n", kExpectedButtonMask);
136
137 while (!g_codex_pad.ScanAndConnect(kExpectedButtonMask)) {
138 printf("Retry to scan and connect, button mask: 0x%08X\n", kExpectedButtonMask);
139 }
140
141 printf("Remote device name: %s\n", g_codex_pad.remote_device_name().c_str());
142 printf("Remote model number: %s\n", g_codex_pad.remote_model_number().c_str());
143 printf("Remote firmware revision: %u.%u.%u\n", g_codex_pad.remote_firmware_version()[0],
144 g_codex_pad.remote_firmware_version()[1], g_codex_pad.remote_firmware_version()[2]);
145
146 if (const auto ble_client = g_codex_pad.ble_client(); ble_client != nullptr) {
147 printf("Remote Bluetooth Device Address: %s\n", ble_client->getPeerAddress().toString().c_str());
148 } else {
149 printf("Remote Bluetooth Device Address: unknown\n");
150 }
151
152 // Set transmission power to 0dBm
153 // Transmission power affects communication range and power consumption:
154 // Higher power provides longer range but consumes more battery
155 // Choose appropriate power level based on your application to balance range and battery life
156 // 设置发射功率为0dBm
157 // 发射功率影响通信距离和功耗:功率越高,通信距离越远,但功耗也越大
158 // 建议根据实际应用场景选择合适的功率等级以平衡距离和电池寿命
160 printf("Set remote tx power to 0dBm successfully\n");
161 }
162
163 printf("Connected\n");
164}
165} // namespace
166
167void setup() {
168 Serial.begin(115200);
169
170 printf("Init\n");
171 g_codex_pad.Init();
172
173 Connect();
174}
175
176void loop() {
177 // ==========================================================================
178 // 🔴 CRITICAL: Call Update() as frequently as possible in loop()
179 // ==========================================================================
180 // • Update() processes incoming Bluetooth packets from the CodexPad
181 // • Any delay(...) or long blocking code WILL cause:
182 // - Packet loss
183 // - Input lag
184 // - Unstable connection
185 //
186 // • For real-time control, call Update() every loop iteration
187 // without any blocking operations
188 //
189 // 🔴【重要】Update() 必须在 loop() 中尽可能高频调用
190 // • Update() 负责处理来自 CodexPad 的蓝牙数据包
191 // • 任何形式的 delay 或阻塞代码都会导致:
192 // - 数据丢失
193 // - 响应延迟
194 // - 连接不稳定
195 //
196 // • 实时控制应用中,必须每轮循环都调用 Update(),不可阻塞
197 // ==========================================================================
198 const gamepad::input::Tracker& it = g_codex_pad.Update();
199 // ==========================================================================
200 // Tracker: Gamepad Input Snapshot & Change Engine
201 // ==========================================================================
202 // • Returned by Update()
203 // • Maintains previous and current input snapshots
204 // • Enables edge detection and delta detection
205 //
206 // Tracker 由 Update() 返回
207 // 内部保存上一帧和当前帧的输入数据
208 // 支持边沿检测和差值检测
209 //
210 // 📚 https://codexpad.github.io/gamepad_input_arduino_lib/
211 // ==========================================================================
212
213 if (!g_codex_pad.is_connected()) {
214 printf("Disconnected, start to reconnect\n");
215 Connect();
216 return;
217 }
218
219 // ==========================================================================
220 // 🟢 Button State Change Detection (Edge-Based)
221 // ==========================================================================
222 // • pressed() → button was just pressed (released → pressed)
223 // • released() → button was just released (pressed → released)
224 // • holding() → button remains pressed across frames
225 //
226 // These APIs are *frame-differential* and rely on Update() frequency.
227 // Ideal for UI navigation, action triggers, and avoiding repeat firing.
228 //
229 // • pressed() → 按钮刚被按下(弹起 → 按下)
230 // • released() → 按钮刚被释放(按下 → 弹起)
231 // • holding() → 按钮在两帧之间持续按下
232 //
233 // 这些接口是“帧间差分”的,依赖于 Update() 的高频调用
234 // 非常适合 UI 导航、动作触发和防止长按连发
235 // ==========================================================================
236 for (auto button : {Button::kUp, Button::kDown, Button::kLeft, Button::kRight, Button::kSquareX, Button::kTriangleY,
237 Button::kCrossA, Button::kCircleB, Button::kL1, Button::kL2, Button::kL3, Button::kR1, Button::kR2,
238 Button::kR3, Button::kSelect, Button::kStart, Button::kHome}) {
239 if (it.pressed(button)) {
240 printf("Button %s: pressed\n", ButtonToString(button).c_str());
241 } else if (it.released(button)) {
242 printf("Button %s: released\n", ButtonToString(button).c_str());
243 } else if (it.holding(button)) {
244 printf("Button %s: holding\n", ButtonToString(button).c_str());
245 }
246 }
247
248 // ==========================================================================
249 // 🟢 Joystick Axis Change Detection (Threshold-Based Filtering)
250 // ==========================================================================
251 // • AxisChanged() detects significant changes between frames
252 // • Uses a threshold to filter out noise and minor jitter
253 // • Only reports movement when change ≥ threshold
254 //
255 // • AxisChanged() 用于检测摇杆轴值的有效变化
256 // • 使用阈值过滤微小抖动和噪声
257 // • 只有当变化幅度 ≥ 阈值时才视为有效移动
258 // ==========================================================================
259 constexpr uint8_t kAxisValueChangeThreshold = 2;
260
261 if (it.AxisChanged(Axis::kLeftStickX, kAxisValueChangeThreshold) || it.AxisChanged(Axis::kLeftStickY, kAxisValueChangeThreshold) ||
262 it.AxisChanged(Axis::kRightStickX, kAxisValueChangeThreshold) ||
263 it.AxisChanged(Axis::kRightStickY, kAxisValueChangeThreshold)) {
264 printf("L(X: %3" PRIu8 ", Y:%3" PRIu8 "), R(X: %3" PRIu8 ", Y: %3" PRIu8 ")\n", it[Axis::kLeftStickX],
265 it[Axis::kLeftStickY], it[Axis::kRightStickX], it[Axis::kRightStickY]);
266 }
267}
CodexPad main class.
Definition codex_pad.h:23
bool is_connected() const noexcept
Is connected.
const std::array< uint8_t, 3 > & remote_firmware_version() const noexcept
Get firmware version of the CodexPad.
Definition codex_pad.h:354
const gamepad::input::Tracker & Update() noexcept
Update, need to be called in Loop.
const std::string & remote_model_number() const noexcept
Get model number of the CodexPad.
Definition codex_pad.h:342
void Init() noexcept
Initialize.
Definition codex_pad.cpp:30
NimBLEClient * ble_client() const noexcept
Get the BLE client object.
Definition codex_pad.h:383
const std::string & remote_device_name() const noexcept
Get model number of the CodexPad.
Definition codex_pad.h:330
bool set_remote_tx_power(TxPower power) noexcept
Set transmission power, only effective when connected, immediately effective for current connection,...
bool ScanAndConnect(gamepad::input::Button buttons) noexcept
Scans for nearby CodexPad devices and automatically connects to a device whose button state matches t...
Definition codex_pad.cpp:47