10constexpr uint16_t kGapServiceUuid{0x1800};
11constexpr uint16_t kGapDeviceNameUuid{0x2A00};
13constexpr uint16_t kInputsServiceUuid{0xFFA0};
14constexpr uint16_t kInputsCharacteristicUuid{0xFFA1};
16constexpr uint16_t kBatteryServiceUuid{0x180F};
17constexpr uint16_t kBatteryLevelCharacteristicUuid{0x2A19};
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};
31 if (!NimBLEDevice::isInitialized()) {
32 NimBLEDevice::init(
"CodexPadClient");
36bool CodexPad::Connect(
const std::string& bluetooth_device_address,
const uint32_t timeout_ms)
noexcept {
38 if (bluetooth_device_address.length() != 17 || bluetooth_device_address[2] !=
':' || bluetooth_device_address[5] !=
':' ||
39 bluetooth_device_address[8] !=
':' || bluetooth_device_address[11] !=
':' || bluetooth_device_address[14] !=
':') {
44 return Connect(NimBLEAddress(bluetooth_device_address, 0),
false, timeout_ms);
48 auto scanner = NimBLEDevice::getScan();
49 scanner->setActiveScan(
true);
50 scanner->setInterval(1000);
51 scanner->setWindow(999);
53 if (!scanner->start(1000)) {
55 scanner->clearResults();
60 while (scanner->isScanning()) {
66 int8_t rssi = INT8_MIN;
67 NimBLEAddress address;
70 struct ManufacturerSpecificData {
71 uint16_t company_id = 0xFFFF;
72 uint8_t header[8] = {
'C',
'o',
'd',
'e',
'x',
'P',
'a',
'd'};
73 uint8_t version_major = 0;
74 uint8_t version_minor = 0;
75 uint8_t version_patch = 0;
76 uint32_t button_state = 0;
77 uint8_t button_states_duration_seconds = 0;
81 for (
const auto device : scanner->getResults()) {
82 if (device->haveName() && String(device->getName().c_str()).startsWith(
"CodexPad-") && device->haveManufacturerData()) {
84 const auto manufacturer_data = device->getManufacturerData();
85 if (manufacturer_data.length() >=
sizeof(ManufacturerSpecificData)) {
86 const auto data =
reinterpret_cast<const ManufacturerSpecificData*
>(manufacturer_data.c_str());
87 if (data->company_id == 0xFFFF
88 && memcmp(data->header,
"CodexPad", 8) == 0
89 && data->button_state ==
static_cast<uint32_t
>(buttons)
90 && device->getRSSI() > rssi
91 && (data->version_major < 2 || data->button_states_duration_seconds >= 1)
93 rssi = device->getRSSI();
94 address = device->getAddress();
101 scanner->clearResults();
103 return address.isNull() ? false :
Connect(address, 2000);
107 if (ble_client_ ==
nullptr) {
108 return input_tracker_;
111 if (!ble_client_->isConnected()) {
113 return input_tracker_;
116 input_tracker_.Tick();
119 std::lock_guard<std::mutex> l(mutex_);
120 if (inputs_queue_.empty()) {
124 input_tracker_.Update(inputs_queue_.front());
128 return input_tracker_;
134 if (ble_client_ ==
nullptr) {
138 if (!ble_client_->isConnected()) {
142 auto remote_service = ble_client_->getService(uint16_t{0x1804});
143 if (remote_service ==
nullptr) {
147 auto remote_characteristic = remote_service->getCharacteristic(uint16_t{0x2A07});
148 if (remote_characteristic ==
nullptr) {
152 return remote_characteristic->writeValue(
static_cast<uint8_t
>(tx_power));
155bool CodexPad::Connect(
const NimBLEAddress& address,
bool async_connect,
const uint32_t timeout_ms) {
157 assert(ble_client_ ==
nullptr);
158 ble_client_ = NimBLEDevice::createClient(address);
159 ble_client_->setConnectTimeout(timeout_ms);
160 auto ret = ble_client_->connect(
true, async_connect,
true);
162 if (!ret || !ble_client_->isConnected()) {
166 remote_device_name_ = ble_client_->getValue(kGapServiceUuid, kGapDeviceNameUuid);
167 remote_model_number_ = ble_client_->getValue(kDeviceInfoServiceUuid, kModelNumberCharacteristicUuid);
169 auto firmware_revision = ble_client_->getValue(kDeviceInfoServiceUuid, kFirmwareRevisionCharacteristicUuid);
170 if (firmware_revision.length() == remote_firmware_version_.size()) {
171 memcpy(remote_firmware_version_.data(), firmware_revision.data(), firmware_revision.length());
176 auto remote_service = ble_client_->getService(kInputsServiceUuid);
177 if (remote_service ==
nullptr) {
181 auto remote_characteristic = remote_service->getCharacteristic(kInputsCharacteristicUuid);
182 if (remote_characteristic ==
nullptr) {
186 if (!remote_characteristic->canNotify()) {
190 if (!remote_characteristic->subscribe(
191 true, std::bind(&CodexPad::OnNotify,
this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,
192 std::placeholders::_4))) {
204void CodexPad::OnNotify(
const NimBLERemoteCharacteristic* remote_characteristic,
const uint8_t* data,
const size_t length,
205 const bool is_notify) {
206 if (remote_characteristic !=
nullptr && remote_characteristic->getUUID().equals(kInputsCharacteristicUuid)) {
207 if (length !=
sizeof(gamepad::input::State)) {
208 printf(
"WARNING: length != sizeof(Inputs)\n");
212 std::lock_guard<std::mutex> l(mutex_);
213 if (inputs_queue_.size() > kInputsQueueMax) {
216 inputs_queue_.emplace(gamepad::input::State::FromBytes(data));
220void CodexPad::Reset() {
221 if (ble_client_ !=
nullptr) {
222 ble_client_->cancelConnect();
223 ble_client_->disconnect();
224 NimBLEDevice::deleteClient(ble_client_);
225 ble_client_ =
nullptr;
228 remote_device_name_.clear();
229 remote_model_number_.clear();
230 remote_firmware_version_.fill(0);
231 input_tracker_.Reset();
232 std::lock_guard<std::mutex> l(mutex_);
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中不断调用
bool set_remote_tx_power(TxPower power) noexcept
设置发射功率,连接状态下调用,立即生效于当前连接,下次连接生效
bool ScanAndConnect(gamepad::input::Button buttons) noexcept
扫描附近的 CodexPad 设备,并自动连接到一个按键状态与指定掩码匹配的设备。