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};
25bool HasAxisValueChangedSignificantly(
const int16_t prev_value,
const int16_t current_value,
const uint8_t threshold) {
26 return prev_value != current_value && (current_value == 0 || current_value == 255 || std::abs(current_value - prev_value) >= threshold);
35 if (!NimBLEDevice::isInitialized()) {
36 NimBLEDevice::init(
"CodexPadClient");
40bool CodexPad::Connect(
const std::string& bluetooth_device_address,
const uint32_t timeout_ms) {
42 if (bluetooth_device_address.length() != 17 || bluetooth_device_address[2] !=
':' || bluetooth_device_address[5] !=
':' ||
43 bluetooth_device_address[8] !=
':' || bluetooth_device_address[11] !=
':' || bluetooth_device_address[14] !=
':') {
48 return Connect(NimBLEAddress(bluetooth_device_address, 0),
false, timeout_ms);
52 auto scanner = NimBLEDevice::getScan();
53 scanner->setActiveScan(
true);
54 scanner->setInterval(1000);
55 scanner->setWindow(999);
57 if (!scanner->start(1000)) {
59 scanner->clearResults();
64 while (scanner->isScanning()) {
70 int8_t rssi = INT8_MIN;
71 NimBLEAddress address;
74 struct ManufacturerSpecificData {
75 uint16_t company_id = 0xFFFF;
76 uint8_t header[8] = {
'C',
'o',
'd',
'e',
'x',
'P',
'a',
'd'};
77 uint8_t version_major = 0;
78 uint8_t version_minor = 0;
79 uint8_t version_patch = 0;
81 uint8_t button_states_duration_seconds = 0;
85 for (
const auto device : scanner->getResults()) {
86 if (device->haveName() && String(device->getName().c_str()).startsWith(
"CodexPad-") && device->haveManufacturerData()) {
88 const auto manufacturer_data = device->getManufacturerData();
89 if (manufacturer_data.length() >=
sizeof(ManufacturerSpecificData)) {
90 const auto data =
reinterpret_cast<const ManufacturerSpecificData*
>(manufacturer_data.c_str());
91 if (data->company_id == 0xFFFF
92 && memcmp(data->header,
"CodexPad", 8) == 0
93 && data->button_state == button_mask
94 && device->getRSSI() > rssi
95 && (data->version_major < 2 || data->button_states_duration_seconds >= 1)
97 rssi = device->getRSSI();
98 address = device->getAddress();
105 scanner->clearResults();
107 return address.isNull() ? false :
Connect(address, 2000);
111 if (ble_client_ ==
nullptr) {
115 if (!ble_client_->isConnected()) {
120 prev_inputs_ = current_inputs_;
122 std::lock_guard<std::mutex> l(mutex_);
123 if (inputs_queue_.empty()) {
126 current_inputs_ = std::move(inputs_queue_.front());
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));
156 return (prev_inputs_.button_states &
static_cast<uint32_t
>(button)) == 0 && (current_inputs_.button_states &
static_cast<uint32_t
>(button)) != 0;
160 return (prev_inputs_.button_states &
static_cast<uint32_t
>(button)) != 0 && (current_inputs_.button_states &
static_cast<uint32_t
>(button)) == 0;
164 return (prev_inputs_.button_states &
static_cast<uint32_t
>(button)) != 0 && (current_inputs_.button_states &
static_cast<uint32_t
>(button)) != 0;
182 return HasAxisValueChangedSignificantly(prev_inputs_.axis_values[
static_cast<size_t>(axis)], current_inputs_.axis_values[
static_cast<size_t>(axis)],
186bool CodexPad::Connect(
const NimBLEAddress& address,
bool async_connect,
const uint32_t timeout_ms) {
188 assert(ble_client_ ==
nullptr);
189 ble_client_ = NimBLEDevice::createClient(address);
190 ble_client_->setConnectTimeout(timeout_ms);
191 auto ret = ble_client_->connect(
true, async_connect,
true);
193 if (!ret || !ble_client_->isConnected()) {
197 remote_device_name_ = ble_client_->getValue(kGapServiceUuid, kGapDeviceNameUuid);
198 remote_model_number_ = ble_client_->getValue(kDeviceInfoServiceUuid, kModelNumberCharacteristicUuid);
200 auto firmware_revision = ble_client_->getValue(kDeviceInfoServiceUuid, kFirmwareRevisionCharacteristicUuid);
201 if (firmware_revision.length() ==
sizeof(remote_firmware_version_)) {
202 memcpy(remote_firmware_version_.data(), firmware_revision.data(), firmware_revision.length());
207 auto remote_service = ble_client_->getService(kInputsServiceUuid);
208 if (remote_service ==
nullptr) {
212 auto remote_characteristic = remote_service->getCharacteristic(kInputsCharacteristicUuid);
213 if (remote_characteristic ==
nullptr) {
217 if (!remote_characteristic->canNotify()) {
221 if (!remote_characteristic->subscribe(
222 true, std::bind(&CodexPad::OnNotify,
this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4))) {
234void CodexPad::OnNotify(
const NimBLERemoteCharacteristic* remote_characteristic,
const uint8_t* data,
const size_t length,
const bool is_notify) {
235 if (remote_characteristic !=
nullptr && remote_characteristic->getUUID().equals(kInputsCharacteristicUuid)) {
236 if (length !=
sizeof(Inputs)) {
237 printf(
"WARNING: length != sizeof(Inputs)\n");
241 std::lock_guard<std::mutex> l(mutex_);
242 if (inputs_queue_.size() > kInputsQueueMax) {
246 memcpy(&inputs, data,
sizeof(inputs));
247 inputs_queue_.emplace(std::move(inputs));
251void CodexPad::Reset() {
252 if (ble_client_ !=
nullptr) {
253 ble_client_->cancelConnect();
254 ble_client_->disconnect();
255 NimBLEDevice::deleteClient(ble_client_);
256 ble_client_ =
nullptr;
259 remote_device_name_.clear();
260 remote_model_number_.clear();
261 remote_firmware_version_.fill(0);
263 current_inputs_ = {};
264 std::lock_guard<std::mutex> l(mutex_);
uint32_t button_states() const
Get all button states, return a 32-bit unsigned integer where each bit represents the state of a spec...
bool ScanAndConnect(const uint32_t button_mask)
Scans for nearby CodexPad devices and automatically connects to a device whose button state matches t...
bool pressed(const Button button) const
check if a button is pressed
static constexpr size_t kAxisValueNum
Number of axis values.
bool Connect(const std::string &bluetooth_device_address, const uint32_t timeout_ms=5000)
Connect.
uint8_t axis_value(const Axis axis) const
Get axis value.
bool HasAxisValueChanged(const Axis axis, const uint8_t threshold) const
check if an axis value has changed
static constexpr uint8_t kAxisCenter
Axis center value.
bool set_remote_tx_power(const TxPower power)
Set transmission power, only effective when connected, immediately effective for current connection,...
bool is_connected() const
Is connected.
bool released(const Button button) const
check if a button is released
std::array< uint8_t, kAxisValueNum > axis_values() const
Get current values of all analog axes.
void Update()
Update, need to be called in Loop.
bool button_state(const Button button) const
check if a button is pressed or held
bool holding(const Button button) const
check if a button is held