#include "badge/ble.h" #include #include "badge/config.h" #include "badge/log.h" #include using de::uvok::badge::DisplayIndicator; static NimBLEServer *server; static NimBLEAdvertising *pAdvertising; static NimBLECharacteristic *selectImageCharacteristic; static NimBLECharacteristic *selectTextCharacteristic; template struct CharacteristicPoll { volatile bool changed; T value; }; static CharacteristicPoll select_image_value_changed; static CharacteristicPoll select_text_changed; static volatile DisplayIndicator ble_indicator = DisplayIndicator::Uninit; #define IMAGE_DATA(_, display) display const char *templates[] = { #include "./images.cfg" }; class BadgeServerCallbacks : public NimBLEServerCallbacks { void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override { LOG_F("Connected!\n"); ble_indicator = DisplayIndicator::Connected; NimBLEServerCallbacks::onConnect(pServer, connInfo); } void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override { LOG_F("Disconnected!\n"); ble_indicator = DisplayIndicator::None; NimBLEServerCallbacks::onDisconnect(pServer, connInfo, reason); } } badgeServerCallbacks; class BadgeSelectorCallbacks : public NimBLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override { NimBLECharacteristicCallbacks::onWrite(pCharacteristic, connInfo); if (pCharacteristic == selectImageCharacteristic) { LOG_F("Write!"); const char *val = pCharacteristic->getValue().c_str(); char *end = nullptr; long newVal = strtol(val, &end, 10); String defVal("0"); if (end == nullptr || end == val) { LOG_F("Error parsing value\n"); pCharacteristic->setValue(defVal); (void)pCharacteristic->notify(defVal, BLE_HS_CONN_HANDLE_NONE); } else if (newVal >= ARRAY_SIZE(templates)) { LOG_F("Value out of range: %ld\n", newVal); pCharacteristic->setValue(defVal); (void)pCharacteristic->notify(defVal, BLE_HS_CONN_HANDLE_NONE); } else { LOG_F("Value set to %ld\n", newVal); select_image_value_changed.value = newVal; select_image_value_changed.changed = true; } } else if (pCharacteristic == selectTextCharacteristic) { // auto val = pCharacteristic->getValue().c_str(); if (val == nullptr) { return; } std::string newText = val; select_text_changed.value = newText; select_text_changed.changed = true; LOG_F("Received new text: %s\n", newText.c_str()); } } } badgeSelectorCallbacks; void de::uvok::badge::ble_init() { NimBLEDevice::init("Espadge"); server = NimBLEDevice::createServer(); server->setCallbacks(&badgeServerCallbacks); NimBLEService *service = new NimBLEService("ca260000-b4bb-46b2-bd06-b7b7a61ea990"); // read/write current selectImageCharacteristic = service->createCharacteristic("ca260001-b4bb-46b2-bd06-b7b7a61ea990", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY); selectImageCharacteristic->setValue("0"); selectImageCharacteristic->setCallbacks(&badgeSelectorCallbacks); // get pictures auto call = service->createCharacteristic("ca260002-b4bb-46b2-bd06-b7b7a61ea990", NIMBLE_PROPERTY::READ); String s{}; for (int i = 0; i < ARRAY_SIZE(templates); i++) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%d-%s;", i, templates[i]); s.concat(tmp); } call->setValue(s.c_str()); // write QR code selectTextCharacteristic = service->createCharacteristic("ca260003-b4bb-46b2-bd06-b7b7a61ea990", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY); selectTextCharacteristic->setValue(""); selectTextCharacteristic->setCallbacks(&badgeSelectorCallbacks); const uint16_t mtuLen = max(s.length() + 16, 256); NimBLEDevice::setMTU(mtuLen); server->addService(service); pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->setName("NimBLE Badge"); pAdvertising->setManufacturerData("\xff\xffuvok"); pAdvertising->setAdvertisingCompleteCallback([](NimBLEAdvertising *) { LOG_F("Finished advertising\n"); ble_indicator = DisplayIndicator::None; }); server->start(); } bool de::uvok::badge::ble_is_active() { return pAdvertising->isAdvertising() || server->getConnectedCount() > 0; // ???? NimBLEDevice::getConnectedClients().size() > 0; } void de::uvok::badge::ble_advertise() { if (!pAdvertising->isAdvertising()) { Serial.println("Long press detected. Starting advertising..."); pAdvertising->start(10000); ble_indicator = DisplayIndicator::Advertising; } } de::uvok::badge::BlePollResult de::uvok::badge::ble_poll() { if (select_image_value_changed.changed) { const uint8_t val = select_image_value_changed.value; select_image_value_changed.changed = false; return BlePollResult::MakeTemplate(val); } if (select_text_changed.changed) { const std::string val = select_text_changed.value; select_text_changed.changed = false; return BlePollResult::MakeText(val); } if (ble_indicator != DisplayIndicator::Uninit) { DisplayIndicator ind = ble_indicator; ble_indicator = DisplayIndicator::Uninit; return BlePollResult::MakeIndicator(ind); } return BlePollResult::MakeEmpty(); } void de::uvok::badge::ble_set_image(uint8_t image) { LOG_F("Notify BLE: set image to %d\n", image); String s(image); selectImageCharacteristic->setValue(s); (void)selectImageCharacteristic->notify(s, BLE_HS_CONN_HANDLE_NONE); }