CodexPad Arduino 库 2.1.3
载入中...
搜索中...
未找到
codex_pad.cpp
1#include "codex_pad.h"
2
3#include <memory>
4
5#include "WString.h"
6
7namespace {
8constexpr uint16_t kGapServiceUuid{0x1800};
9constexpr uint16_t kGapDeviceNameUuid{0x2A00};
10
11constexpr uint16_t kInputsServiceUuid{0xFFA0};
12constexpr uint16_t kInputsCharacteristicUuid{0xFFA1};
13
14constexpr uint16_t kBatteryServiceUuid{0x180F};
15constexpr uint16_t kBatteryLevelCharacteristicUuid{0x2A19};
16
17constexpr uint16_t kDeviceInfoServiceUuid{0x180A};
18constexpr uint16_t kModelNumberCharacteristicUuid{0x2A24};
19constexpr uint16_t kSerialNumberCharacteristicUuid{0x2A25};
20constexpr uint16_t kFirmwareRevisionCharacteristicUuid{0x2A26};
21constexpr uint16_t kManufacturerNameCharacteristicUuid{0x2A29};
22
23bool HasAxisValueChangedSignificantly(const int16_t prev_value, const int16_t current_value, const uint8_t threshold) {
24 return prev_value != current_value && (current_value == 0 || current_value == 255 || std::abs(current_value - prev_value) >= threshold);
25}
26} // namespace
27
29
30CodexPad::~CodexPad() { Reset(); }
31
33 if (!NimBLEDevice::isInitialized()) {
34 NimBLEDevice::init("CodexPadClient");
35 }
36}
37
38bool CodexPad::Connect(const std::string& bluetooth_device_address, const uint32_t timeout_ms) {
39 // check mac address is valid
40 if (bluetooth_device_address.length() != 17 || bluetooth_device_address[2] != ':' || bluetooth_device_address[5] != ':' ||
41 bluetooth_device_address[8] != ':' || bluetooth_device_address[11] != ':' || bluetooth_device_address[14] != ':') {
42 abort();
43 return false;
44 }
45
46 return Connect(NimBLEAddress(bluetooth_device_address, 0), false, timeout_ms);
47}
48
49bool CodexPad::ScanAndConnect(const uint32_t button_mask) {
50 auto scanner = NimBLEDevice::getScan();
51 scanner->setActiveScan(true); // active scan uses more power, but get results faster
52 scanner->setInterval(1000);
53 scanner->setWindow(999); // less or equal setInterval value
54
55 if (!scanner->start(1000)) {
56 scanner->stop();
57 scanner->clearResults();
58 // printf("Scan failed\n");
59 return false;
60 }
61
62 while (scanner->isScanning()) {
63 delay(100);
64 }
65
66 // printf("Scan done, device count: %d\n", scanner->getResults().getCount());
67
68 int8_t rssi = INT8_MIN;
69 NimBLEAddress address;
70
71#pragma pack(push, 1)
72 struct ManufacturerData {
73 uint16_t company_id = 0xFFFF;
74 uint8_t header[8] = {'C', 'o', 'd', 'e', 'x', 'P', 'a', 'd'};
75 uint8_t version_major = 0;
76 uint8_t version_minor = 0;
77 uint8_t version_patch = 0;
78 uint32_t button_state = 0;
79 };
80#pragma pack(pop)
81
82 for (const auto device : scanner->getResults()) {
83 if (device->haveName() && String(device->getName().c_str()).startsWith("CodexPad-") && device->haveManufacturerData()) {
84 // printf("Name: %s\n", device->getName().c_str());
85 const auto manufacturer_data = device->getManufacturerData();
86 if (manufacturer_data.length() >= sizeof(ManufacturerData)) {
87 const auto data = reinterpret_cast<const ManufacturerData*>(manufacturer_data.c_str());
88 if (data->company_id == 0xFFFF && memcmp(data->header, "CodexPad", 8) == 0 && data->button_state == button_mask && device->getRSSI() > rssi) {
89 rssi = device->getRSSI();
90 address = device->getAddress();
91 // printf("Found device, rssi: %d, address: %s\n", rssi, address.toString().c_str());
92 }
93 }
94 }
95 }
96
97 scanner->clearResults();
98
99 return address.isNull() ? false : Connect(address, 2000);
100}
101
103 if (ble_client_ == nullptr) {
104 return;
105 }
106
107 if (!ble_client_->isConnected()) {
108 Reset();
109 return;
110 }
111
112 prev_inputs_ = current_inputs_;
113 do {
114 std::lock_guard<std::mutex> l(mutex_);
115 if (inputs_queue_.empty()) {
116 break;
117 }
118 current_inputs_ = std::move(inputs_queue_.front());
119 inputs_queue_.pop();
120 } while (false);
121}
122
123bool CodexPad::is_connected() const { return ble_client_ != nullptr && ble_client_->isConnected(); }
124
126 if (ble_client_ == nullptr) {
127 return false;
128 }
129
130 if (!ble_client_->isConnected()) {
131 return false;
132 }
133
134 auto remote_service = ble_client_->getService(uint16_t{0x1804});
135 if (remote_service == nullptr) {
136 return false;
137 }
138
139 auto remote_characteristic = remote_service->getCharacteristic(uint16_t{0x2A07});
140 if (remote_characteristic == nullptr) {
141 return false;
142 }
143
144 return remote_characteristic->writeValue(static_cast<uint8_t>(tx_power));
145}
146
147bool CodexPad::pressed(const Button button) const {
148 return (prev_inputs_.button_states & static_cast<uint32_t>(button)) == 0 && (current_inputs_.button_states & static_cast<uint32_t>(button)) != 0;
149}
150
151bool CodexPad::released(const Button button) const {
152 return (prev_inputs_.button_states & static_cast<uint32_t>(button)) != 0 && (current_inputs_.button_states & static_cast<uint32_t>(button)) == 0;
153}
154
155bool CodexPad::holding(const Button button) const {
156 return (prev_inputs_.button_states & static_cast<uint32_t>(button)) != 0 && (current_inputs_.button_states & static_cast<uint32_t>(button)) != 0;
157}
158
159bool CodexPad::button_state(const Button button) const { return (current_inputs_.button_states & static_cast<uint32_t>(button)) != 0; }
160
161uint32_t CodexPad::button_states() const { return current_inputs_.button_states; }
162
163uint8_t CodexPad::axis_value(const Axis axis) const { return current_inputs_.axis_values[static_cast<size_t>(axis)]; }
164
165std::array<uint8_t, CodexPad::kAxisValueNum> CodexPad::axis_values() const {
166 std::array<uint8_t, kAxisValueNum> axis_values = {kAxisCenter, kAxisCenter, kAxisCenter, kAxisCenter};
167 for (size_t i = 0; i < kAxisValueNum; i++) {
168 axis_values[i] = current_inputs_.axis_values[i];
169 }
170 return axis_values;
171}
172
173bool CodexPad::HasAxisValueChanged(const Axis axis, const uint8_t threshold) const {
174 return HasAxisValueChangedSignificantly(
175 current_inputs_.axis_values[static_cast<size_t>(axis)], prev_inputs_.axis_values[static_cast<size_t>(axis)], threshold);
176}
177
178bool CodexPad::Connect(const NimBLEAddress& address, bool async_connect, const uint32_t timeout_ms) {
179 Reset();
180 assert(ble_client_ == nullptr);
181 ble_client_ = NimBLEDevice::createClient(address);
182 ble_client_->setConnectTimeout(timeout_ms);
183 auto ret = ble_client_->connect(true, async_connect, true);
184
185 if (!ret || !ble_client_->isConnected()) {
186 goto FAILED;
187 }
188
189 remote_device_name_ = ble_client_->getValue(kGapServiceUuid, kGapDeviceNameUuid);
190 remote_model_number_ = ble_client_->getValue(kDeviceInfoServiceUuid, kModelNumberCharacteristicUuid);
191 {
192 auto firmware_revision = ble_client_->getValue(kDeviceInfoServiceUuid, kFirmwareRevisionCharacteristicUuid);
193 if (firmware_revision.length() == sizeof(remote_firmware_version_)) {
194 memcpy(remote_firmware_version_.data(), firmware_revision.data(), firmware_revision.length());
195 }
196 }
197
198 {
199 auto remote_service = ble_client_->getService(kInputsServiceUuid);
200 if (remote_service == nullptr) {
201 goto FAILED;
202 }
203
204 auto remote_characteristic = remote_service->getCharacteristic(kInputsCharacteristicUuid);
205 if (remote_characteristic == nullptr) {
206 goto FAILED;
207 }
208
209 if (!remote_characteristic->canNotify()) {
210 goto FAILED;
211 }
212
213 if (!remote_characteristic->subscribe(
214 true, std::bind(&CodexPad::OnNotify, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))) {
215 goto FAILED;
216 }
217 }
218
219 return ret;
220
221FAILED:
222 Reset();
223 return false;
224}
225
226void CodexPad::OnNotify(const NimBLERemoteCharacteristic* remote_characteristic, const uint8_t* data, const size_t length, const bool is_notify) {
227 if (remote_characteristic != nullptr && remote_characteristic->getUUID().equals(kInputsCharacteristicUuid)) {
228 if (length != sizeof(Inputs)) {
229 printf("WARNING: length != sizeof(Inputs)\n");
230 return;
231 }
232
233 std::lock_guard<std::mutex> l(mutex_);
234 if (inputs_queue_.size() > kInputsQueueMax) {
235 inputs_queue_.pop();
236 }
237 Inputs inputs;
238 memcpy(&inputs, data, sizeof(inputs));
239 inputs_queue_.emplace(std::move(inputs));
240 }
241}
242
243void CodexPad::Reset() {
244 if (ble_client_ != nullptr) {
245 ble_client_->cancelConnect();
246 ble_client_->disconnect();
247 NimBLEDevice::deleteClient(ble_client_);
248 ble_client_ = nullptr;
249 }
250
251 remote_device_name_.clear();
252 remote_model_number_.clear();
253 remote_firmware_version_.fill(0);
254 prev_inputs_ = {};
255 current_inputs_ = {};
256 std::lock_guard<std::mutex> l(mutex_);
257 inputs_queue_ = {};
258}
uint32_t button_states() const
以位掩码形式获取所有按键的当前状态
bool ScanAndConnect(const uint32_t button_mask)
扫描附近的 CodexPad 设备,并自动连接到一个按键状态与指定掩码匹配的设备。
bool pressed(const Button button) const
查询按键是否被按下
static constexpr size_t kAxisValueNum
轴值数量
bool Connect(const std::string &bluetooth_device_address, const uint32_t timeout_ms=5000)
连接
uint8_t axis_value(const Axis axis) const
获取轴值
~CodexPad()
析构函数
bool HasAxisValueChanged(const Axis axis, const uint8_t threshold) const
查询轴值是否发生变化
static constexpr uint8_t kAxisCenter
轴中心值
CodexPad()
构造函数
bool set_remote_tx_power(const TxPower power)
设置发射功率,连接状态下调用,立即生效于当前连接,下次连接生效
bool is_connected() const
是否连接
bool released(const Button button) const
查询按键是否被释放
std::array< uint8_t, kAxisValueNum > axis_values() const
获取所有模拟轴的当前值
void Init()
初始化
void Update()
更新,需要在Loop中不断调用
bool button_state(const Button button) const
查询按键是否被按下或持续按下
bool holding(const Button button) const
查询按键是否被持续按下