#include "ble_bobby.h" // esp-idf includes #include // 3rdparty lib includes #include #include // local includes #include "ledstrip.h" #include "globals.h" #include "modes/defaultmode.h" #include "modes/remotecontrolmode.h" #include "utils.h" #include "newsettings.h" namespace { constexpr const char * const TAG = "BOBBYBLE"; class RemoteControlCallbacks : public NimBLECharacteristicCallbacks { public: void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; }; class WirelessSettingsCallbacks : public NimBLECharacteristicCallbacks { public: void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; }; class WiFiListCallbacks : public NimBLECharacteristicCallbacks { public: void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; }; } // namespace BLEServer *pServer{}; BLEService *pService{}; BLECharacteristic *livestatsCharacteristic{}; BLECharacteristic *remotecontrolCharacteristic{}; BLECharacteristic *wirelessConfig{}; BLECharacteristic *getwifilist{}; namespace { RemoteControlCallbacks bleRemoteCallbacks; WirelessSettingsCallbacks bleWirelessSettingsCallbacks; WiFiListCallbacks bleWiFiListCallbacks; void createBle() { ESP_LOGI("BOBBY", "called"); NimBLEDevice::init(configs.bluetoothName.value()); const auto serviceUuid{"0335e46c-f355-4ce6-8076-017de08cee98"}; ESP_LOGI(TAG, "Creating BLE server"); pServer = NimBLEDevice::createServer(); ESP_LOGI(TAG, "Creating BLE service"); pService = pServer->createService(serviceUuid); ESP_LOGI(TAG, "Creating BLE characteristics"); livestatsCharacteristic = pService->createCharacteristic("a48321ea-329f-4eab-a401-30e247211524", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); remotecontrolCharacteristic = pService->createCharacteristic("4201def0-a264-43e6-946b-6b2d9612dfed", NIMBLE_PROPERTY::WRITE); remotecontrolCharacteristic->setCallbacks(&bleRemoteCallbacks); wirelessConfig = pService->createCharacteristic("4201def1-a264-43e6-946b-6b2d9612dfed", NIMBLE_PROPERTY::WRITE); wirelessConfig->setCallbacks(&bleWirelessSettingsCallbacks); getwifilist = pService->createCharacteristic("4201def2-a264-43e6-946b-6b2d9612dfed", NIMBLE_PROPERTY::READ); getwifilist->setCallbacks(&bleWiFiListCallbacks); ESP_LOGI(TAG, "Starting BLE service"); pService->start(); ESP_LOGI(TAG, "Starting BLE advertising"); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->addServiceUUID(serviceUuid); pAdvertising->setScanResponse(true); NimBLEDevice::startAdvertising(); } void destroyBle() { ESP_LOGI("BOBBY", "called"); NimBLEDevice::deinit(true); pServer = {}; pService = {}; livestatsCharacteristic = {}; remotecontrolCharacteristic = {}; wirelessConfig = {}; getwifilist = {}; } } // namespace void initBle() { if (configs.bleSettings.bleEnabled.value()) createBle(); } void handleBle() { if (!configs.feature.ble.isEnabled.value()) return; if (configs.bleSettings.bleEnabled.value()) { if (!pServer) createBle(); if (livestatsCharacteristic->getSubscribedCount()) { StaticJsonDocument<1024> doc; { auto arr = doc.createNestedArray("v"); if (controllers.front.feedbackValid) arr.add(controllers.front.getCalibratedVoltage()); else arr.add(nullptr); if (controllers.back.feedbackValid) arr.add(controllers.back.getCalibratedVoltage()); else arr.add(nullptr); } { auto arr = doc.createNestedArray("t"); if (controllers.front.feedbackValid) arr.add(fixBoardTemp(controllers.front.feedback.boardTemp)); else arr.add(nullptr); if (controllers.back.feedbackValid) arr.add(fixBoardTemp(controllers.back.feedback.boardTemp)); else arr.add(nullptr); } { auto arr = doc.createNestedArray("e"); if (controllers.front.feedbackValid) { arr.add(controllers.front.feedback.left.error); arr.add(controllers.front.feedback.right.error); } else { arr.add(nullptr); arr.add(nullptr); } if (controllers.back.feedbackValid) { arr.add(controllers.back.feedback.left.error); arr.add(controllers.back.feedback.right.error); } else { arr.add(nullptr); arr.add(nullptr); } } { auto arr = doc.createNestedArray("s"); if (controllers.front.feedbackValid) { arr.add(convertToKmh(controllers.front.feedback.left.speed * (profileSettings.controllerHardware.invertFrontLeft ? -1 : 1))); arr.add(convertToKmh(controllers.front.feedback.right.speed * (profileSettings.controllerHardware.invertFrontRight ? -1 : 1))); } else { arr.add(nullptr); arr.add(nullptr); } if (controllers.back.feedbackValid) { arr.add(convertToKmh(controllers.back.feedback.left.speed * (profileSettings.controllerHardware.invertBackLeft ? -1 : 1))); arr.add(convertToKmh(controllers.back.feedback.right.speed * (profileSettings.controllerHardware.invertBackRight ? -1 : 1))); } else { arr.add(nullptr); arr.add(nullptr); } } { auto arr = doc.createNestedArray("a"); if (controllers.front.feedbackValid) { arr.add(fixCurrent(controllers.front.feedback.left.dcLink) * 2); arr.add(fixCurrent(controllers.front.feedback.right.dcLink) * 2); } else { arr.add(nullptr); arr.add(nullptr); } if (controllers.back.feedbackValid) { arr.add(fixCurrent(controllers.back.feedback.left.dcLink) * 2); arr.add(fixCurrent(controllers.back.feedback.right.dcLink) * 2); } else { arr.add(nullptr); arr.add(nullptr); } } std::string json; serializeJson(doc, json); livestatsCharacteristic->setValue(json); livestatsCharacteristic->notify(); } } else if (pServer) { destroyBle(); } } namespace { void RemoteControlCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { const std::string& val = pCharacteristic->getValue(); StaticJsonDocument<256> doc; if (const auto error = deserializeJson(doc, val)) { ESP_LOGW(TAG, "ignoring cmd with invalid json: %.*s %s", val.size(), val.data(), error.c_str()); return; } if (configs.feature.ledstrip.isEnabled.value()) { const auto newBlinkAnimation = doc["anim"].as(); if (blinkAnimation != newBlinkAnimation) blinkAnimation = newBlinkAnimation; } const bool isInverted = (profileSettings.controllerHardware.invertFrontLeft && !profileSettings.controllerHardware.invertFrontRight); if (!simplified) { RemoteCommand cmd { .frontLeft = doc[isInverted ? "fr":"fl"].as(), .frontRight = doc[isInverted ? "fl":"fr"].as(), .backLeft = doc["bl"].as(), .backRight = doc["br"].as() }; modes::defaultMode.setRemoteCommand(cmd); modes::remoteControlMode.setRemoteCommand(cmd); } } void WirelessSettingsCallbacks::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { const std::string& val = pCharacteristic->getValue(); StaticJsonDocument<256> doc; if (const auto error = deserializeJson(doc, val)) { ESP_LOGW(TAG, "ignoring cmd with invalid json: %.*s %s", val.size(), val.data(), error.c_str()); return; } auto write_type = doc["type"].as(); if (write_type == "wifi") { const int index = doc["wifi_index"].as(); ESP_LOGI(TAG, "Set wifi%i: WiFi-SSID: %s, WiFi-Password: ***", doc["wifi_index"].as(), doc["wifi_ssid"].as()); configs.write_config(configs.wifi_configs[index].ssid, doc["wifi_ssid"].as()); configs.write_config(configs.wifi_configs[index].key, doc["wifi_pass"].as()); } else { const auto deserialized = deserializeJson(doc, val); ESP_LOGW(TAG, "Unkown type %s -> json: %.*s %s", doc["type"].as(), val.size(), val.data(), deserialized.c_str()); } } void WiFiListCallbacks::onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { StaticJsonDocument<768> responseDoc; auto wifiArray = responseDoc.createNestedArray("wifis"); ESP_LOGI(TAG, "Got request for listing wifi ssids."); for (const auto &wifi : configs.wifi_configs) { wifiArray.add(wifi.ssid.value()); } responseDoc["wifi_count"] = configs.wifi_configs.size(); std::string json; serializeJson(responseDoc, json); pCharacteristic->setValue(json); } } // namespace