mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-22 10:42:49 +00:00
move my bt experiment into its own repo (about to remove ttn)
This commit is contained in:
114
lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp
Normal file
114
lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
#include <CRC32.h>
|
||||
|
||||
static BLECharacteristic swUpdateTotalSizeCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ);
|
||||
static BLECharacteristic swUpdateDataCharacteristic("e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE);
|
||||
static BLECharacteristic swUpdateCRC32Characteristic("4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE);
|
||||
static BLECharacteristic swUpdateResultCharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
CRC32 crc;
|
||||
uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
|
||||
|
||||
class UpdateCallbacks : public BLECharacteristicCallbacks
|
||||
{
|
||||
void onRead(BLECharacteristic *pCharacteristic) {
|
||||
Serial.println("Got on read");
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *pCharacteristic)
|
||||
{
|
||||
// dumpCharacteristic(pCharacteristic);
|
||||
|
||||
if (pCharacteristic == &swUpdateTotalSizeCharacteristic)
|
||||
{
|
||||
// Check if there is enough to OTA Update
|
||||
uint32_t len = getValue32(pCharacteristic, 0);
|
||||
crc.reset();
|
||||
bool canBegin = Update.begin(len);
|
||||
Serial.printf("Setting update size %u, result %d\n", len, canBegin);
|
||||
if(!canBegin)
|
||||
// Indicate failure by forcing the size to 0
|
||||
pCharacteristic->setValue(0UL);
|
||||
}
|
||||
else if (pCharacteristic == &swUpdateDataCharacteristic)
|
||||
{
|
||||
std::string value = pCharacteristic->getValue();
|
||||
uint32_t len = value.length();
|
||||
uint8_t *data = pCharacteristic->getData();
|
||||
// Serial.printf("Writing %u\n", len);
|
||||
crc.update(data, len);
|
||||
Update.write(data, len);
|
||||
}
|
||||
else if (pCharacteristic == &swUpdateCRC32Characteristic)
|
||||
{
|
||||
uint32_t expectedCRC = getValue32(pCharacteristic, 0);
|
||||
Serial.printf("expected CRC %u\n", expectedCRC);
|
||||
|
||||
uint8_t result = 0xff;
|
||||
|
||||
// Check the CRC before asking the update to happen.
|
||||
if(crc.finalize() != expectedCRC) {
|
||||
Serial.println("Invalid CRC!");
|
||||
result = 0xe0; // FIXME, use real error codes
|
||||
}
|
||||
else {
|
||||
if (Update.end())
|
||||
{
|
||||
Serial.println("OTA done, rebooting in 5 seconds!");
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("Error Occurred. Error #: " + String(Update.getError()));
|
||||
}
|
||||
result = Update.getError();
|
||||
}
|
||||
swUpdateResultCharacteristic.setValue(&result, 1);
|
||||
}
|
||||
else {
|
||||
Serial.println("unexpected write");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void bluetoothRebootCheck() {
|
||||
if(rebootAtMsec && millis() > rebootAtMsec)
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
static UpdateCallbacks updateCb;
|
||||
|
||||
/*
|
||||
SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30
|
||||
|
||||
Characteristics
|
||||
|
||||
UUID properties description
|
||||
e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e write|read total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted)
|
||||
e272ebac-d463-4b98-bc84-5cc1a39ee517 write data, variable sized, recommended 512 bytes, write one for each block of file
|
||||
4826129c-c22a-43a3-b066-ce8f0d5bacc6 write crc32, write last - writing this will complete the OTA operation, now you can read result
|
||||
5e134862-7411-4424-ac4a-210937432c77 read|notify result code, readable but will notify when the OTA operation completes
|
||||
*/
|
||||
BLEService *createUpdateService(BLEServer* server) {
|
||||
// Create the BLE Service
|
||||
BLEService *service = server->createService("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30");
|
||||
|
||||
addWithDesc(service, &swUpdateTotalSizeCharacteristic, "total image size");
|
||||
addWithDesc(service, &swUpdateDataCharacteristic, "data");
|
||||
addWithDesc(service, &swUpdateCRC32Characteristic, "crc32");
|
||||
addWithDesc(service, &swUpdateResultCharacteristic, "result code");
|
||||
|
||||
swUpdateTotalSizeCharacteristic.setCallbacks(&updateCb);
|
||||
swUpdateDataCharacteristic.setCallbacks(&updateCb);
|
||||
swUpdateCRC32Characteristic.setCallbacks(&updateCb);
|
||||
|
||||
swUpdateResultCharacteristic.addDescriptor(new BLE2902()); // Needed so clients can request notification
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
7
lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h
Normal file
7
lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
BLEService *createUpdateService(BLEServer* server);
|
||||
|
||||
void bluetoothRebootCheck();
|
||||
152
lib/BluetoothOTA/src/BluetoothUtil.cpp
Normal file
152
lib/BluetoothOTA/src/BluetoothUtil.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "BluetoothSoftwareUpdate.h"
|
||||
#include "MeshBluetoothService.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <Update.h>
|
||||
|
||||
static BLECharacteristic SWVersionCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
static BLECharacteristic ManufacturerCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ);
|
||||
static BLECharacteristic HardwareVersionCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ);
|
||||
//static BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
/**
|
||||
* Create standard device info service
|
||||
**/
|
||||
BLEService *createDeviceInfomationService(BLEServer* server) {
|
||||
BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t) ESP_GATT_UUID_DEVICE_INFO_SVC));
|
||||
|
||||
/*
|
||||
* Mandatory characteristic for device info service?
|
||||
|
||||
BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ);
|
||||
|
||||
uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version;
|
||||
uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
|
||||
m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
|
||||
*/
|
||||
SWVersionCharacteristic.setValue("0.1");
|
||||
deviceInfoService->addCharacteristic(&SWVersionCharacteristic);
|
||||
ManufacturerCharacteristic.setValue("TTGO");
|
||||
deviceInfoService->addCharacteristic(&ManufacturerCharacteristic);
|
||||
HardwareVersionCharacteristic.setValue("1.0");
|
||||
deviceInfoService->addCharacteristic(&HardwareVersionCharacteristic);
|
||||
//SerialNumberCharacteristic.setValue("FIXME");
|
||||
//deviceInfoService->addCharacteristic(&SerialNumberCharacteristic);
|
||||
|
||||
// m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ);
|
||||
// m_manufacturerCharacteristic->setValue(name);
|
||||
|
||||
|
||||
/* add these later?
|
||||
ESP_GATT_UUID_SYSTEM_ID
|
||||
*/
|
||||
|
||||
// caller must call service->start();
|
||||
return deviceInfoService;
|
||||
}
|
||||
|
||||
bool _BLEClientConnected = false;
|
||||
|
||||
class MyServerCallbacks : public BLEServerCallbacks {
|
||||
void onConnect(BLEServer* pServer) {
|
||||
_BLEClientConnected = true;
|
||||
};
|
||||
|
||||
void onDisconnect(BLEServer* pServer) {
|
||||
_BLEClientConnected = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Help routine to add a description to any BLECharacteristic and add it to the service
|
||||
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) {
|
||||
BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t) ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1);
|
||||
desc->setValue(description);
|
||||
c->addDescriptor(desc);
|
||||
service->addCharacteristic(c);
|
||||
}
|
||||
|
||||
static BLECharacteristic BatteryLevelCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
/**
|
||||
* Create a battery level service
|
||||
*/
|
||||
BLEService *createBatteryService(BLEServer* server) {
|
||||
// Create the BLE Service
|
||||
BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F));
|
||||
|
||||
addWithDesc(pBattery, &BatteryLevelCharacteristic, "Percentage 0 - 100");
|
||||
BatteryLevelCharacteristic.addDescriptor(new BLE2902()); // Needed so clients can request notification
|
||||
|
||||
return pBattery;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void dumpCharacteristic(BLECharacteristic *c) {
|
||||
std::string value = c->getValue();
|
||||
|
||||
if (value.length() > 0) {
|
||||
Serial.print("New value: ");
|
||||
for (int i = 0; i < value.length(); i++)
|
||||
Serial.print(value[i]);
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
/** converting endianness pull out a 32 bit value */
|
||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) {
|
||||
std::string value = c->getValue();
|
||||
uint32_t r = defaultValue;
|
||||
|
||||
if(value.length() == 4)
|
||||
r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
BLEServer *initBLE(std::string deviceName) {
|
||||
BLEDevice::init(deviceName);
|
||||
// Create the BLE Server
|
||||
BLEServer *pServer = BLEDevice::createServer();
|
||||
pServer->setCallbacks(new MyServerCallbacks());
|
||||
|
||||
BLEService *pDevInfo = createDeviceInfomationService(pServer);
|
||||
|
||||
BLEService *pBattery = createBatteryService(pServer);
|
||||
pServer->getAdvertising()->addServiceUUID(pBattery->getUUID());
|
||||
|
||||
BLEService *pUpdate = createUpdateService(pServer); // We need to advertise this so our android ble scan operation can see it
|
||||
pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID());
|
||||
|
||||
BLEService *pMesh = createMeshBluetoothService(pServer); // We need to advertise this so our android ble scan operation can see it
|
||||
pServer->getAdvertising()->addServiceUUID(pMesh->getUUID());
|
||||
|
||||
// start all our services (do this after creating all of them)
|
||||
pDevInfo->start();
|
||||
pBattery->start();
|
||||
pUpdate->start();
|
||||
pMesh->start();
|
||||
|
||||
// Start advertising
|
||||
pServer->getAdvertising()->start();
|
||||
|
||||
return pServer;
|
||||
}
|
||||
|
||||
// Called from loop
|
||||
void loopBLE() {
|
||||
static uint8_t level = 31;
|
||||
|
||||
// Pretend to update battery levels - fixme do elsewhere
|
||||
BatteryLevelCharacteristic.setValue(&level, 1);
|
||||
BatteryLevelCharacteristic.notify();
|
||||
delay(5000);
|
||||
|
||||
level = (level + 1) % 100;
|
||||
|
||||
bluetoothRebootCheck();
|
||||
}
|
||||
21
lib/BluetoothOTA/src/BluetoothUtil.h
Normal file
21
lib/BluetoothOTA/src/BluetoothUtil.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <BLEDevice.h>
|
||||
#include <BLEServer.h>
|
||||
#include <BLEUtils.h>
|
||||
|
||||
|
||||
// Now handled by BluetoothUtil.cpp
|
||||
// BLEService *createDeviceInfomationService(BLEServer* server, uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
|
||||
|
||||
// Help routine to add a description to any BLECharacteristic and add it to the service
|
||||
void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description);
|
||||
|
||||
void dumpCharacteristic(BLECharacteristic *c);
|
||||
|
||||
/** converting endianness pull out a 32 bit value */
|
||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
|
||||
|
||||
void loopBLE();
|
||||
BLEServer *initBLE(std::string devName);
|
||||
163
lib/BluetoothOTA/src/MeshBluetoothService.cpp
Normal file
163
lib/BluetoothOTA/src/MeshBluetoothService.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "BluetoothUtil.h"
|
||||
#include "MeshBluetoothService.h"
|
||||
#include <esp_gatt_defs.h>
|
||||
#include <BLE2902.h>
|
||||
#include <Arduino.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
/*
|
||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
|
||||
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
|
||||
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
|
||||
|
||||
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
|
||||
a node number and keeping the current nodedb.
|
||||
|
||||
|
||||
typedef in32_t NodeNum;
|
||||
|
||||
class NodeInfo {
|
||||
position;
|
||||
last_seen
|
||||
user
|
||||
};
|
||||
|
||||
class NodeDB {
|
||||
NodeNum provisionalNodeNum; // if we are trying to find a node num this is our current attempt
|
||||
|
||||
NodeNum ourNodeNum; // -1 if not yet found
|
||||
|
||||
HashMap<NodeNum, NodeInfo> nodes;
|
||||
public:
|
||||
/// don't do mesh based algoritm for node id assignment (initially) - instead just store in flash - possibly even in the initial alpha release do this hack
|
||||
|
||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||
// bool handleWantNodeNum(NodeNum n);
|
||||
|
||||
void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
||||
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the
|
||||
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select
|
||||
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level
|
||||
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?)
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
|
||||
public:
|
||||
|
||||
/// handle an incoming message from the mesh
|
||||
void handleFromMesh(NodeNum fromNode, NodeNum toNode, std::string s);
|
||||
|
||||
/// handle a packet from the phone, send it on the mesh
|
||||
void handleFromRadio(MeshPacket p);
|
||||
};
|
||||
|
||||
/// Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
||||
class MeshRadio {
|
||||
public:
|
||||
};
|
||||
|
||||
*/
|
||||
|
||||
|
||||
static BLECharacteristic meshFromRadioCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ);
|
||||
static BLECharacteristic meshToRadioCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE);
|
||||
static BLECharacteristic meshFromNumCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
|
||||
|
||||
|
||||
/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh)
|
||||
static void handleToRadio(std::string s) {
|
||||
|
||||
}
|
||||
|
||||
class BluetoothMeshCallbacks : public BLECharacteristicCallbacks
|
||||
{
|
||||
void onRead(BLECharacteristic *c) {
|
||||
Serial.println("Got on read");
|
||||
|
||||
if(c == &meshFromRadioCharacteristic) {
|
||||
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
|
||||
// or make empty if the queue is empty
|
||||
// c->setValue(byteptr, len);
|
||||
}
|
||||
}
|
||||
|
||||
void onWrite(BLECharacteristic *c)
|
||||
{
|
||||
// dumpCharacteristic(pCharacteristic);
|
||||
Serial.println("Got on write");
|
||||
|
||||
if(c == &meshToRadioCharacteristic) {
|
||||
handleToRadio(c->getValue());
|
||||
}
|
||||
else {
|
||||
assert(0); // Not yet implemented
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static BluetoothMeshCallbacks btMeshCb;
|
||||
|
||||
/*
|
||||
MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd
|
||||
|
||||
FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best
|
||||
FIXME - in the esp32 mesh managment code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there.
|
||||
FIXME - make sure this protocol is guaranteed robust and won't drop packets
|
||||
|
||||
"According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
|
||||
In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes."
|
||||
|
||||
MAXPACKET is 256? look into what the lora lib uses. FIXME
|
||||
|
||||
Characteristics:
|
||||
UUID
|
||||
properties
|
||||
description
|
||||
|
||||
8ba2bcc2-ee02-4a55-a531-c525c5e454d5
|
||||
read
|
||||
fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet).
|
||||
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
|
||||
mailbox.
|
||||
|
||||
f75c76d2-129e-4dad-a1dd-7866124401e7
|
||||
write
|
||||
toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len)
|
||||
|
||||
ed9da18c-a800-4f66-a670-aa7547e34453
|
||||
read|notify|write
|
||||
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
|
||||
until it catches up with this number.
|
||||
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32
|
||||
callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
|
||||
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
|
||||
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.
|
||||
|
||||
Re: queue management
|
||||
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
|
||||
* only the most recent Position and User messages for a particular node are kept
|
||||
* all Data SubPackets are kept
|
||||
* No WantNodeNum / DenyNodeNum messages are kept
|
||||
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)
|
||||
|
||||
*/
|
||||
BLEService *createMeshBluetoothService(BLEServer* server) {
|
||||
// Create the BLE Service
|
||||
BLEService *service = server->createService("6ba1b218-15a8-461f-9fa8-5dcae273eafd");
|
||||
|
||||
addWithDesc(service, &meshFromRadioCharacteristic, "fromRadio");
|
||||
addWithDesc(service, &meshToRadioCharacteristic, "toRadio");
|
||||
addWithDesc(service, &meshFromNumCharacteristic, "fromNum");
|
||||
|
||||
meshFromRadioCharacteristic.setCallbacks(&btMeshCb);
|
||||
meshToRadioCharacteristic.setCallbacks(&btMeshCb);
|
||||
meshFromNumCharacteristic.setCallbacks(&btMeshCb);
|
||||
|
||||
meshFromNumCharacteristic.addDescriptor(new BLE2902()); // Needed so clients can request notification
|
||||
|
||||
return service;
|
||||
}
|
||||
|
||||
5
lib/BluetoothOTA/src/MeshBluetoothService.h
Normal file
5
lib/BluetoothOTA/src/MeshBluetoothService.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
BLEService *createMeshBluetoothService(BLEServer* server);
|
||||
Reference in New Issue
Block a user