Communicating between a Flutter app on Android and an ESP32 under ESP-IDF
Bluetooth Low Energy overview
If you're interested in working with BLE first read this oreilly online chapter
Yea, it's long, you'll be tempted to skip it. I sure did. Once you get stuck go back and actually read it
At a high level BLE is composed of several layers:
- Device - A piece of hardware you connect to
- Service - A logicical grouping of behavior or information on the device
- Characteristic - A specific part of the service
- Attribute - The actual concrete value
There exists a structured description of various characteristics that allow your device to behave predictably, for example if your device has a battery level you can expose it in a way that cause phones to display it next to the device.
Flutter BLE
The first library under consideration is flutter blue
Running the "Packages get" in android studio doesn't work because flutter tries to use its sdk directory as a tmp.
pubspec.yaml
...
dependencies:
...
flutter_blue:
So we just run it in bash in the shell:
$ flutter pub get
$ flutter run
/home/username/projects/hanging-plotter/flutter/plotter_control/android/app/src/debug/AndroidManifest.xml Error:
uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [:flutter_blue]
Quick fix on the build gradle to correct the minimum sdk version
./android/app/build.gradle
android{
...
defaultConfig {
...
minSdkVersion 19
}
}
Copying main.dart and widgets.dart from the example project and rerunning...
E/flutter ( 5174): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(bluetooth_unavailable, the device does not have bluetooth, null)
right.
Installing on a device...
ESP-IDF BLE
Handily for us espressif has a gatt server walkthrough
First we enable the bluetooth stack in the handy menuconfig wrapper (shift+ctrl+p > ESP-IDF: Launch gui configuration tool)
There was an error finding cmake due to all of the nix files moving around after version upgrades, deleting the ./build directory and running build cleaned it up
Here is the esp-idf vscode plugins menuconfig GUI
Copying the example files into the esp32 project has a core panic:
0x4012d0d2: __assert_func at /builds/idf/crosstool-NG/.build/xtensa-esp32-elf/src/newlib/newlib/libc/stdlib/assert.c:58
0x40125745: s_prepare_reserved_regions at /nix/store/p6mcp8kcs20x7d64p9408d7nh3qk7i13-esp-idf/components/soc/src/memory_layout_utils.c:91 (discriminator 1)
0x401257c9: soc_get_available_memory_regions at /nix/store/p6mcp8kcs20x7d64p9408d7nh3qk7i13-esp-idf/components/soc/src/memory_layout_utils.c:114
0x400d416d: heap_caps_init at /nix/store/p6mcp8kcs20x7d64p9408d7nh3qk7i13-esp-idf/components/heap/heap_caps_init.c:67
0x40081491: call_start_cpu0 at /nix/store/p6mcp8kcs20x7d64p9408d7nh3qk7i13-esp-idf/components/esp32/cpu_start.c:266
Which points towards an issue with allocation. It has been merged in 4.2-dev, time to upgrade! Note: I had to modify the sha256 of the fetchFromGithub and fetchurl in order for nix to accept the version changes
$ idf.py build && idf.py flash && idf.py monitor
I (1279) GATTS_DEMO: REGISTER_APP_EVT, status 0, app_id 0
I (1369) GATTS_DEMO: CREATE_SERVICE_EVT, status 0, service_handle 40
I (1389) GATTS_DEMO: SERVICE_START_EVT, status 0, service_handle 40
I (1389) GATTS_DEMO: ADD_CHAR_EVT, status 0, attr_handle 42, service_handle 40
I (1394) GATTS_DEMO: the gatts demo char length = 3
I (1399) GATTS_DEMO: prf_char[0] =11
I (1404) GATTS_DEMO: prf_char[1] =22
I (1404) GATTS_DEMO: prf_char[2] =33
I (1409) GATTS_DEMO: REGISTER_APP_EVT, status 0, app_id 1
I (1419) GATTS_DEMO: ADD_DESCR_EVT, status 0, attr_handle 43, service_handle 40
I (1424) GATTS_DEMO: CREATE_SERVICE_EVT, status 0, service_handle 44
I (1434) GATTS_DEMO: SERVICE_START_EVT, status 0, service_handle 44
I (1439) GATTS_DEMO: ADD_CHAR_EVT, status 0, attr_handle 46, service_handle 44
I (1449) GATTS_DEMO: ADD_DESCR_EVT, status 0, attr_handle 47, service_handle 44
Two quick tweaks to finish this off. First I broke the bluetooth code into a separate file, but was having trouble including it.
this stack overflow post mentioned that you need extern C
to include c in cpp files:
main.cpp:
...
extern "C" {
#include "bluetooth.h"
}
void app_main() {
bluetooth_init()
...
}
Since the bluetooth stack is configured to run on core 0 I have the stepper loop bound to core 1 using xTaskCreatePinnedToCore
xTaskCreatePinnedToCore(&bounce, "bounce", 2048,NULL,5,NULL, 1);
Now the stepper is happily spinning away while the bluetooth stack remains responsive. Next up: controlling the stepper via an app