Current section
Files
Jump to
Current section
Files
kino_web_bluetooth
README.md
README.md
# KinoWebBluetooth
A [Livebook](https://livebook.dev) Smart Cell and Elixir API for
Bluetooth Low Energy devices, using the browser's
[Web Bluetooth API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API).
You pick a device in the Smart Cell. Then you can read, write and
subscribe to its characteristics, either in the cell or from Elixir code.
## Requirements
* Livebook v0.13 or later
* A Chromium based browser (Chrome, Edge) with Web Bluetooth enabled
## Installation
```elixir
Mix.install([
{:kino_web_bluetooth, "~> 0.1"}
])
```
## The Smart Cell
Add a **Web Bluetooth** Smart Cell. Then:
1. Enter a GATT service UUID. This can be a full UUID, a 16-bit alias
such as `0x180D`, or a standard name such as `heart_rate`.
2. Click **Connect** and pick a device in the browser dialog.
3. The cell lists the services and characteristics of the device. Use
**Read**, **Subscribe** and **Write** to work with them. Write values
are entered as hex (`01 ff`) or as text.
The cell saves your input in the notebook. When you evaluate it, the
device is bound to a variable (`device` by default).
## The API
Browsers only let a user pick a device, so you always connect in the
Smart Cell. Everything else also works from code:
```elixir
# Bound by the Smart Cell
device = KinoWebBluetooth.device("ble-...")
# Wait until the device is connected in the Smart Cell
:ok = KinoWebBluetooth.await_connected(device)
# Read
location = KinoWebBluetooth.characteristic!(device, "body_sensor_location")
{:ok, <<sensor_location>>} = KinoWebBluetooth.read(location)
# Write, synchronously or asynchronously
control_point = KinoWebBluetooth.characteristic!(device, "heart_rate_control_point")
:ok = KinoWebBluetooth.write(control_point, <<1>>)
:ok = KinoWebBluetooth.write_async(control_point, <<1>>)
# Subscribe to notifications
measurement = KinoWebBluetooth.characteristic!(device, "heart_rate_measurement")
:ok = KinoWebBluetooth.subscribe(measurement)
receive do
{:kino_web_bluetooth, :notification, ^measurement, <<_flags, bpm, _rest::binary>>} -> bpm
end
# ...or consume them as a stream
measurement
|> KinoWebBluetooth.stream()
|> Kino.listen(fn <<_flags, bpm, _rest::binary>> -> IO.puts("#{bpm} bpm") end)
```
See the `KinoWebBluetooth` module docs for all functions.
## How it works
Each Smart Cell starts a `KinoWebBluetooth.Device` process. When the
device connects, the Device process starts one
`KinoWebBluetooth.Characteristic` GenServer for each characteristic. That
GenServer holds the last value and the subscribers. It also runs GATT
operations one at a time, because browsers reject concurrent operations
on the same characteristic.
The state lives in Elixir. The browser keeps only what it has to: the
`BluetoothDevice` and its characteristic objects. The Smart Cell sends
operations to the browser tab that connected the device, and sends the
results back.
## Development
```sh
mix deps.get
mix test
```
### Spec
The requirements are in [spec/kino_web_bluetooth.spec.md](https://github.com/ErikMejerHansen/kino_web_bluetooth/blob/main/spec/kino_web_bluetooth.spec.md),
each with an ID such as `UI-4`. Tests declare the requirements they
verify with a tag:
```elixir
@tag spec: "UI-4"
test "shows when the device disconnects" do
```
Requirements that tests can't fully cover are reviewed by hand and
recorded in [spec/reviews.exs](https://github.com/ErikMejerHansen/kino_web_bluetooth/blob/main/spec/reviews.exs).
`mix spec` runs the tests and writes [spec/STATUS.md](https://github.com/ErikMejerHansen/kino_web_bluetooth/blob/main/spec/STATUS.md),
which lists each requirement as tested, reviewed, failing or open.
Commit it together with spec and code changes. CI fails when it is out
of date.
### Releasing
1. Bump `@version` in `mix.exs` and merge to `main`.
2. In GitHub, run **Actions → Publish to Hex** on `main` with that
version. Tick *Dry run* first to check the package without publishing.
The workflow runs all checks, publishes the package and docs to Hex,
and tags the release `vX.Y.Z`. It needs a `HEX_API_KEY` secret.
## License
MIT, see [LICENSE](https://github.com/ErikMejerHansen/kino_web_bluetooth/blob/main/LICENSE).