UPDATE: THIS WAS WRITTEN BEFORE BlueHeron EXISTED
This is a braindump of my progress over the weekend to look into Bluetooth support on Nerves. While Bluetooth has a lot of specs, supporting a subset of BLE that makes it easy for Nerves devices to communicate with cell phones and back would be generally useful.
There are two options for BLE support on Nerves:
- Make a custom Nerves system that uses
bluez
- Use
Harald
bluez
is a fairly complete stack that doesn't integrate well with Nerves and is hard to debug when things go wrong.
Harald
is all Elixir and simple, but incomplete and missing examples for doing anything except scanning for
devices.
IMHO, it would be super useful if Nerves had a library that provided an API similar to Adafruit's Bluefruit, but using Elixir instead of AT commands. Even a subset of that library would be useful. I really hope to encourage someone to take steps toward such a library with this note.
The Bluetooth spec is massive and overwhelming. Here's what I think is necessary for getting started with an Elixir BLE stack. This is going to be entirely incomplete.
- Bluetooth modules usually connect to processors via a UART
- The Bluetooth Host-controller Interface spec (HCI) defines nearly all of the commands.
Harald
handles sending and receiving HCI messages. - There are some proprietary HCI commands that are documented by the module manufacturers. I don't think they're necessary to know except for initialization. And the RPi0W is currently set, so start there.
- The next layer up from HCI is L2CAP. The point of L2CAP is to be able to segment messages into smaller chunks and multiplex different kinds of data. BLE only uses a subset of L2CAP. The reset of L2CAP is for Bluetooth Classic.
- Then there's GAP which handles advertisements and connections. I'm pretty sure it doesn't need L2CAP.
- Finally there's GATT which lets you send data.
I'm not 100% sure on what the best order is, but I think that a reasonable goal would be to create a set of demos:
- BLE Beacon - This example would publish some information to the world. Basically a hello world style app that you could see on a cellphone (like in the LightBlue app)
- Counter monitor - This example would let you connect to it and it would share a counter
- HID keyboard (HID over GATT) - This example would advertise itself as a HID keyboard and after being paired, it would type
- UART - This example would be able to connect to Adafruit's BluefruitConnect app
Certainly, the more examples the better, so please add if there's something of interest to you.
Since the Bluetooth spec is large and overwhelming, the idea is to setup examples using the BlueKitchen stack, capture and study traces of it doing small things, and then replicating those in Elixir.
Currently Harald
is unmaintained and Very has put it on their "labs" github organization to
indicate that they're done with it. While it's possible that someone might put time into it
again, I recommend forking it to avoid any frustration. Then at some later time when there's been
progress, hopefully we can all get back together and rally around a common library.
My test environment is a Raspberry Pi Zero W connected to my Mac via a USB cable. I've also tried this on a Linux desktop and it worked too.
The RPi0 W has a Cypress CYW43438 module in it. Note that the RPi 3 A+ and B+ have a CYW43455 and Beaglebones with BT use TI Wilink modules. There's a device-specific part of the BLE module setup that means that you should expect differences between parts. For the Raspberry Pi/Cypress case, firmware can be found at github/LibreELEC/brcmfmac_sdio-firmware-rpi. You won't need to download firmware to do what I did, but keep it in mind since a proper solution neeeds to be aware of this.
Download the firmware and use fwup
to copy it to a MicroSD card.
This firmware boots the RPi Zero W and makes the UART going to the CYW43438 available over USB. The UART is set to 115200 baud. This is slower than it should be, but it worked for me, so I didn't bump it higher.
Plug in the RPi0W and check that it shows up as a tty:
$ ls /dev/tty.usbmodem14601
/dev/tty.usbmodem14601
Get the Harald source code, compile and run it:
$ iex -S mix
Erlang/OTP 22 [erts-10.7.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Harald.Transport.start_link(namespace: :bt, adapter: {Harald.Transport.UART, device: "/dev/tty.usbmodem14601", uart_opts: [speed: 115_200]})
{:ok, #PID<0.203.0>}
iex(2)> Harald.LE.scan(:bt)
%{
5106530278845 => %Harald.HCI.Event.LEMeta.AdvertisingReport.Device{
address: 5106530278845,
address_type: 1,
data: [
{"Manufacturer Specific Data",
<<6, 0, 1, 9, 32, 2, 76, 112, 87, 185, 24, 132, 221, 193, 5, 102, 252,
105, 140, 179, 230, 17, 29, 123, 175, 14, 147, 21, 139>>}
],
event_type: 3,
rss: 158
},
...
}
Clone btstack
A couple modifications need to be made:
$ cd btstack/port/posix-h4
Edit main.c
to turn off high speed mode and set the tty path:
$ git diff -w
diff --git a/port/posix-h4/main.c b/port/posix-h4/main.c
index 2b09bf1e6..be2a27f0e 100644
--- a/port/posix-h4/main.c
+++ b/port/posix-h4/main.c
@@ -204,7 +204,7 @@ static void local_version_information_handler(uint8_t * packet){
case BLUETOOTH_COMPANY_ID_BROADCOM_CORPORATION:
printf("Broadcom/Cypress - using BCM driver.\n");
hci_set_chipset(btstack_chipset_bcm_instance());
- use_fast_uart();
+ //use_fast_uart();
is_bcm = 1;
break;
case BLUETOOTH_COMPANY_ID_ST_MICROELECTRONICS:
@@ -246,7 +246,8 @@ int main(int argc, const char * argv[]){
// config.device_name = "/dev/tty.usbserial-A900K2WS"; // DFROBOT
// config.device_name = "/dev/tty.usbserial-A50285BI"; // BOOST-CC2564MODA New
// config.device_name = "/dev/tty.usbserial-A9OVNX5P"; // RedBear IoT pHAT breakout board
- config.device_name = "/dev/tty.usbserial-A900K0VK"; // CSR8811 breakout board
+ //config.device_name = "/dev/tty.usbserial-A900K0VK"; // CSR8811 breakout board
+ config.device_name = "/dev/tty.usbmodem14601"; // CSR8811 breakout board
// accept path from command line
if (argc >= 3 && strcmp(argv[1], "-u") == 0){
Then run make
.
There are lots of examples. Here's one:
$ ./gatt_counter
Packet Log: /tmp/hci_dump.pklg
H4 device: /dev/tty.usbmodem14601
BTstack counter 0001
Local version information:
- HCI Version 0x0007
- HCI Revision 0x0000
- LMP Version 0x0007
- LMP Subversion 0x2209
- Manufacturer 0x000f
Broadcom/Cypress - using BCM driver.
Local name: BCM43430A1
BTstack up and running at B8:27:EB:9A:47:33
Once it says BTstack up and running
, go to device that supports BLE. I used the LightBlue app on my phone.
You should see a device called "LE Counter" show up in the app. It might be "Unnamed" at first. Click on it.
Then click on the UUID 0x0000FF11-0000-1000-8000-00805F9B34FB.
Click "Listen for notifications". This starts printing out on the console:
BTstack up and running at B8:27:EB:9A:47:33
BTstack counter 0002
BTstack counter 0003
BTstack counter 0004
BTstack counter 0005
BTstack counter 0006
BTstack counter 0007
BTstack counter 0008
BTstack counter 0009
BTstack counter 0010
BTstack counter 0011
BTstack counter 0012
In the LightBlue app, you should see data. Look at the end at the strings 30303032
, etc.
That's ASCII for 0002
. I.e., the count comes over as ASCII data in this example.
Type CTRL-C to exit.
Using the BlueKitchen demos, run:
$ ./hog_keyboard
Now, go to a device that supports pairing with keyboards and search for HID Keyboard
. Pair
with the keyboard and then go back to the demo app and start typing.
The BlueKitchen stack logs HCI data to /tmp/hci_dump.pklg
. This file can be read
by Wireshark.
XCode also as program called PacketLogger
that can read this file, but I found
Wireshark to be easier.
The .pklg
file has real HCI packets interleaved comments which I thought was cool.
Assuming the file format is simple, this seems like something that would be good to
support to make it easier to compare traces from Elixir and BlueKitchen.
@robmckinnon Try adding
-L
to thecurl
commandline to follow the redirect from GitHub.