Skip to content

Instantly share code, notes, and snippets.

@markjlorenz
Last active December 3, 2022 10:32
Show Gist options
  • Save markjlorenz/6386f6eca3e8bb3c370a6bf0d6cff235 to your computer and use it in GitHub Desktop.
Save markjlorenz/6386f6eca3e8bb3c370a6bf0d6cff235 to your computer and use it in GitHub Desktop.
# Starts a BLE service, that allows central devices to register for notifications.
# When a button attached to GPIO-32 is pressed, the counter is incremented, and
# registerd central devices are notified.
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include "driver/rtc_io.h"
BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
BLECharacteristic* battCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t napTime = 0;
uint32_t wakeTime = 20000; // miliseconds
RTC_DATA_ATTR uint32_t value = 0; // RTC so that we don't loose the value
RTC_DATA_ATTR bool didBoot = false;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "345caca9-d598-4874-8e86-7d049872cab3"
#define CHARACTERISTIC_UUID "983448d2-9f66-4f58-a53d-fc7440e32ece"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
gpio_num_t buttonPin = GPIO_NUM_32;
int buttonState = 0;
void setup() {
Serial.begin(9600);
if(!didBoot) {
bootSplash();
didBoot = true;
}
pinMode(buttonPin, INPUT_PULLDOWN);
// Create the BLE Device
BLEDevice::init("YOUR DEVICE NAME");
// Create the BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
// https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
// Create a BLE Descriptor
pCharacteristic->addDescriptor(new BLE2902());
// Start the service
pService->start();
// Start advertising
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
// pAdvertising->setScanResponse(false);
// pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
BLEDevice::startAdvertising();
Serial.println("WAITING FOR A CLIENT CONNECTION TO NOTIFY...");
print_wakeup_reason();
esp_sleep_enable_ext0_wakeup(buttonPin, 1); //1 for high
napTime = clock() + wakeTime; // seconds
}
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 :
Serial.println("Wakeup caused by external signal using RTC_IO");
value++;
break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
}
}
bool wasDown = false;
void loop() {
buttonState = digitalRead(buttonPin);
if (buttonState == 1) {
wasDown = true;
}
// notify changed value
if (wasDown && buttonState == 0) {
wasDown = false;
value++;
Serial.print("Value: "); Serial.print(value);
}
if (deviceConnected) {
notifyBLE();
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("START ADVERTISING");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
if (clock() >= napTime) {
Serial.println("ENTERING DEEP SLEEP");
Serial.flush();
esp_deep_sleep_start();
}
}
void notifyBLE() {
if (deviceConnected) {
pCharacteristic->setValue((uint8_t *)&value, 4);
pCharacteristic->notify();
delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';
import 'dart:typed_data';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'YOUR APP TITLE',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'YOUR PAGE TITLE'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamSubscription? _subscription;
StreamSubscription<ConnectionStateUpdate>? _connection;
var serviceId = Uuid.parse('345CACA9-D598-4874-8E86-7D049872CAB3'); // REPLACE WITH YOUR SERVICE_ID
var characteristicId = Uuid.parse('983448D2-9F66-4F58-A53D-FC7440E32ECE'); // REPLACE WITH YOUR CHARACTERISTIC_ID
QualifiedCharacteristic? characteristic;
final bleManager = FlutterReactiveBle();
var lastRead = 0;
@override
void initState() {
super.initState();
bleManager.statusStream.listen((status) {
print("STATUS: $status");
if (status == BleStatus.ready) initBle();
});
}
@override
void dispose() {
_subscription?.cancel();
_connection?.cancel();
super.dispose();
}
Future<void> stopScan() async {
print('HF: stopping BLE scan');
await _subscription?.cancel();
_subscription = null;
}
void initBle() {
_subscription?.cancel();
_subscription = bleManager.scanForDevices(
withServices: [serviceId],
scanMode: ScanMode.lowLatency).listen((device) {
print("SCAN FOUND: ${device.name}");
stopScan();
_connection = bleManager
.connectToDevice(
id: device.id,
servicesWithCharacteristicsToDiscover: {
serviceId: [characteristicId]
},
connectionTimeout: const Duration(seconds: 2),
)
.listen((connectionState) {
print("CONNECTING: $connectionState");
if (connectionState.connectionState ==
DeviceConnectionState.connected) {
setState(() {
characteristic = QualifiedCharacteristic(
serviceId: serviceId,
characteristicId: characteristicId,
deviceId: device.id);
});
someStuff();
} else {
print("NOT CONNECTED");
initBle(); // try to connect to the device until it's in range.
}
}, onError: (Object error) {
print("error on connect: $error");
});
}, onError: (obj, stack) {
print('AN ERROR WHILE SCANNING:\r$obj\r$stack');
});
}
void someStuff() async {
final stream = bleManager.subscribeToCharacteristic(characteristic!);
await for (final data in stream) {
var dataInt = ByteData.view(Uint8List.fromList(data).buffer)
.getUint32(0, Endian.little);
print("GOT DATA: $dataInt");
setState(() {
lastRead = dataInt;
});
}
}
@override
Widget build(BuildContext context) {
if (characteristic == null) {
print("IS NULLL");
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [CircularProgressIndicator()],
);
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder<List<int>>(
stream: bleManager.subscribeToCharacteristic(characteristic!),
builder: (BuildContext context, AsyncSnapshot<List<int>> snapshot) {
if (snapshot.hasError) {
print(snapshot.error);
return Center(child: Text('${snapshot.error}'));
}
if (snapshot.connectionState == ConnectionState.waiting &&
lastRead == 0)
return Center(child: CircularProgressIndicator());
// List<int> data = snapshot.requireData;
// var dataInt = ByteData.view(Uint8List.fromList(data).buffer)
// .getUint32(0, Endian.little);
return Center(
child: Text('$lastRead', style: TextStyle(fontSize: 56)));
}),
);
}
}
@abrenoch
Copy link

Hey thanks a lot for sharing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment