Last active
December 3, 2022 10:32
-
-
Save markjlorenz/6386f6eca3e8bb3c370a6bf0d6cff235 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))); | |
}), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey thanks a lot for sharing!