9e58e72bf6
clang-format had no config here, so adopting the submodule's puts both repos on the same pointer alignment. .editorconfig is what lua-language-server reads on save, which is why the Lua tree had drifted between tabs and two widths.
237 lines
6.7 KiB
C++
237 lines
6.7 KiB
C++
#include "providers.h"
|
|
|
|
#include <Arduino.h>
|
|
#include <NimBLEDevice.h>
|
|
#include <WiFi.h>
|
|
|
|
#include "../heaplog.h"
|
|
|
|
#include <cstdio>
|
|
|
|
namespace slate {
|
|
namespace {
|
|
|
|
using esp32lua::Status;
|
|
|
|
constexpr size_t MAX_SCAN_RESULTS = 6;
|
|
|
|
struct ScanResult {
|
|
char name[32];
|
|
char address[18];
|
|
int32_t rssi;
|
|
uint8_t addressType;
|
|
};
|
|
|
|
class ScanCallbacks : public NimBLEScanCallbacks {
|
|
public:
|
|
void reset() { count = 0; }
|
|
size_t size() const { return count; }
|
|
const ScanResult& result(size_t index) const { return results[index]; }
|
|
|
|
void onResult(const NimBLEAdvertisedDevice* device) override {
|
|
ScanResult result{};
|
|
const std::string name = device->haveName() ? device->getName() : "";
|
|
const std::string address = device->getAddress().toString();
|
|
std::snprintf(result.name, sizeof(result.name), "%s", name.c_str());
|
|
std::snprintf(result.address, sizeof(result.address), "%s",
|
|
address.c_str());
|
|
result.rssi = device->getRSSI();
|
|
result.addressType = device->getAddressType();
|
|
|
|
size_t at;
|
|
if (count < MAX_SCAN_RESULTS) {
|
|
at = count++;
|
|
} else {
|
|
if (result.rssi <= results[count - 1].rssi)
|
|
return;
|
|
at = count - 1;
|
|
}
|
|
while (at > 0 && result.rssi > results[at - 1].rssi) {
|
|
results[at] = results[at - 1];
|
|
at--;
|
|
}
|
|
results[at] = result;
|
|
}
|
|
|
|
private:
|
|
ScanResult results[MAX_SCAN_RESULTS]{};
|
|
size_t count = 0;
|
|
};
|
|
|
|
ScanCallbacks scanCallbacks;
|
|
|
|
bool validAddress(const std::string& address) {
|
|
if (address.length() != 17)
|
|
return false;
|
|
for (size_t at = 0; at < address.length(); at++) {
|
|
if (at % 3 == 2) {
|
|
if (address[at] != ':')
|
|
return false;
|
|
} else if (!((address[at] >= '0' && address[at] <= '9') ||
|
|
(address[at] >= 'a' && address[at] <= 'f') ||
|
|
(address[at] >= 'A' && address[at] <= 'F'))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Status Ble::init(const std::string* name) {
|
|
if (isInitialized())
|
|
return Status::success();
|
|
WiFi.mode(WIFI_OFF);
|
|
logHeap("pre-ble");
|
|
if (!NimBLEDevice::init(name ? *name : "Slate32"))
|
|
return Status::failure("cannot initialize BLE");
|
|
logHeap("ble-on");
|
|
return Status::success();
|
|
}
|
|
|
|
void Ble::deinit() {
|
|
if (!isInitialized())
|
|
return;
|
|
disconnect();
|
|
stopAdvertising();
|
|
NimBLEDevice::deinit(true);
|
|
addressTypes.clear();
|
|
logHeap("ble-off");
|
|
}
|
|
|
|
bool Ble::isInitialized() const { return NimBLEDevice::isInitialized(); }
|
|
|
|
void bleShutdown() {
|
|
if (!NimBLEDevice::isInitialized())
|
|
return;
|
|
NimBLEDevice::deinit(true);
|
|
Serial.println("[ble] released for wifi");
|
|
}
|
|
|
|
Status Ble::scan(int32_t durationMs,
|
|
std::vector<esp32lua::BleDevice>& devices) {
|
|
if (!isInitialized())
|
|
return Status::failure("BLE is not initialized");
|
|
|
|
NimBLEScan* scan = NimBLEDevice::getScan();
|
|
scanCallbacks.reset();
|
|
scan->setActiveScan(false);
|
|
scan->setMaxResults(0);
|
|
scan->setScanCallbacks(&scanCallbacks);
|
|
scan->getResults(static_cast<uint32_t>(durationMs), false);
|
|
scan->setScanCallbacks(nullptr);
|
|
scan->clearResults();
|
|
|
|
devices.clear();
|
|
addressTypes.clear();
|
|
for (size_t at = 0; at < scanCallbacks.size(); at++) {
|
|
const ScanResult& result = scanCallbacks.result(at);
|
|
devices.push_back({result.name, result.address, result.rssi});
|
|
addressTypes[result.address] = result.addressType;
|
|
}
|
|
return Status::success();
|
|
}
|
|
|
|
Status Ble::connect(const std::string& address) {
|
|
if (!isInitialized())
|
|
return Status::failure("BLE is not initialized");
|
|
if (!validAddress(address))
|
|
return Status::failure("invalid BLE address");
|
|
|
|
disconnect();
|
|
client = NimBLEDevice::createClient();
|
|
if (!client)
|
|
return Status::failure("cannot create BLE client");
|
|
|
|
uint8_t addressType = BLE_ADDR_PUBLIC;
|
|
const auto found = addressTypes.find(address);
|
|
if (found != addressTypes.end())
|
|
addressType = found->second;
|
|
if (!client->connect(NimBLEAddress(address, addressType))) {
|
|
NimBLEDevice::deleteClient(client);
|
|
client = nullptr;
|
|
return Status::failure("cannot connect to BLE device");
|
|
}
|
|
return Status::success();
|
|
}
|
|
|
|
void Ble::disconnect() {
|
|
if (!client)
|
|
return;
|
|
if (client->isConnected())
|
|
client->disconnect();
|
|
NimBLEDevice::deleteClient(client);
|
|
client = nullptr;
|
|
}
|
|
|
|
bool Ble::isConnected() const {
|
|
return isInitialized() && client && client->isConnected();
|
|
}
|
|
|
|
Status Ble::read(const std::string& service, const std::string& characteristic,
|
|
std::string& value) {
|
|
if (!isConnected())
|
|
return Status::failure("BLE device is not connected");
|
|
|
|
NimBLERemoteService* remoteService = client->getService(service.c_str());
|
|
if (!remoteService)
|
|
return Status::failure("BLE service not found");
|
|
NimBLERemoteCharacteristic* remoteCharacteristic =
|
|
remoteService->getCharacteristic(characteristic.c_str());
|
|
if (!remoteCharacteristic)
|
|
return Status::failure("BLE characteristic not found");
|
|
if (!remoteCharacteristic->canRead())
|
|
return Status::failure("BLE characteristic is not readable");
|
|
|
|
value = static_cast<std::string>(remoteCharacteristic->readValue());
|
|
return Status::success();
|
|
}
|
|
|
|
Status Ble::write(const std::string& service, const std::string& characteristic,
|
|
const std::string& value) {
|
|
if (!isConnected())
|
|
return Status::failure("BLE device is not connected");
|
|
|
|
NimBLERemoteService* remoteService = client->getService(service.c_str());
|
|
if (!remoteService)
|
|
return Status::failure("BLE service not found");
|
|
NimBLERemoteCharacteristic* remoteCharacteristic =
|
|
remoteService->getCharacteristic(characteristic.c_str());
|
|
if (!remoteCharacteristic)
|
|
return Status::failure("BLE characteristic not found");
|
|
if (!remoteCharacteristic->canWrite() &&
|
|
!remoteCharacteristic->canWriteNoResponse())
|
|
return Status::failure("BLE characteristic is not writable");
|
|
|
|
if (!remoteCharacteristic->writeValue(
|
|
reinterpret_cast<const uint8_t*>(value.data()), value.size(),
|
|
remoteCharacteristic->canWrite()))
|
|
return Status::failure("BLE write failed");
|
|
return Status::success();
|
|
}
|
|
|
|
Status Ble::startAdvertising(const std::string* name) {
|
|
if (!isInitialized())
|
|
return Status::failure("BLE is not initialized");
|
|
|
|
NimBLEAdvertising* bleAdvertising = NimBLEDevice::getAdvertising();
|
|
if (advertising)
|
|
bleAdvertising->stop();
|
|
bleAdvertising->clearData();
|
|
if (name && !bleAdvertising->setName(*name))
|
|
return Status::failure("BLE advertising name is too long");
|
|
if (!bleAdvertising->start())
|
|
return Status::failure("cannot start BLE advertising");
|
|
advertising = true;
|
|
return Status::success();
|
|
}
|
|
|
|
void Ble::stopAdvertising() {
|
|
if (!isInitialized() || !advertising)
|
|
return;
|
|
NimBLEDevice::getAdvertising()->stop();
|
|
advertising = false;
|
|
}
|
|
|
|
} // namespace slate
|