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