Packages

Stateless encoder/decoder for the LEGO® SPIKE™ Prime BLE protocol. Not affiliated with, sponsored or endorsed by The LEGO Group.

Current section

Files

Jump to
ex_spike README.md
Raw

README.md

# ExSPIKE

Stateless encoder and decoder for the LEGO® SPIKE™ Prime BLE protocol, written
in Elixir and based on the
[official protocol documentation](https://lego.github.io/spike-prime-docs/).

> [!IMPORTANT]
> ExSPIKE is an independent project. It is **not affiliated with**, sponsored,
> authorized or endorsed by The LEGO Group. LEGO® and SPIKE™ are trademarks of
> The LEGO Group.

ExSPIKE turns messages into bytes and bytes into messages. It holds no state
and starts no processes: you bring the BLE connection.

## Installation

```elixir
def deps do
  [{:ex_spike, "~> 0.1.0"}]
end
```

## Usage

### Sending messages

Build a message and encode it into a frame ready to write to the hub's RX
characteristic:

```elixir
frame = ExSPIKE.Messages.info_request() |> ExSPIKE.encode()
#=> <<0, 0, 2>>

# Messages can also be written as raw bitstrings:
frame = ExSPIKE.encode(<<0x1E, 0x00, 0x05>>)   # start program in slot 5
```

If a frame is larger than the hub's `max_packet_size`, split it:

```elixir
ExSPIKE.Frame.packets(frame, info.max_packet_size)
```

### Receiving messages

BLE notifications can hold part of a frame, or several frames. Pass the bytes
to `decode_stream/1` and keep the `rest` for the next notification:

```elixir
{results, rest} = ExSPIKE.decode_stream(rest <> notification)

for {:ok, message} <- results do
  case message do
    %ExSPIKE.Message.ConsoleNotification{text: text} -> IO.puts(text)
    %ExSPIKE.Message.DeviceNotification{messages: devices} -> IO.inspect(devices)
    _ -> :ok
  end
end
```

A single complete frame can be decoded with `ExSPIKE.decode/1`, and raw message
bytes with `ExSPIKE.Message.decode/1`.

### Uploading and running a program

```elixir
program = "print('Hello from Elixir')"

[
  ExSPIKE.Messages.clear_slot(0),
  ExSPIKE.Messages.start_file_upload("program.py", 0, ExSPIKE.CRC.crc32(program))
  | ExSPIKE.Messages.transfer_chunks(program, info.max_chunk_size)
] ++ [ExSPIKE.Messages.start_program(0)]
|> Enum.map(&ExSPIKE.encode/1)
```

Send each frame and wait for the hub's response before sending the next.

## Livebook

[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2FErikMejerHansen%2Fex_spike%2Fblob%2Fmain%2Fnotebooks%2Fspike_prime.livemd)

[notebooks/spike_prime.livemd](notebooks/spike_prime.livemd) connects to a
hub over Web Bluetooth with
[KinoWebBluetooth](https://github.com/ErikMejerHansen/kino_web_bluetooth),
asks it for its info, shows its console output and sensor readings, and
uploads and runs a program.

## Modules

| Module             | Purpose                                                 |
| ------------------ | ------------------------------------------------------- |
| `ExSPIKE`          | Encode/decode messages to/from frames                   |
| `ExSPIKE.Messages` | Functions that build the messages a client sends        |
| `ExSPIKE.Message`  | Message structs to/from raw bytes                       |
| `ExSPIKE.Device`   | Device messages inside a `DeviceNotification`           |
| `ExSPIKE.Frame`    | Framing: COBS, XOR, delimiters, stream splitting        |
| `ExSPIKE.CRC`      | CRC32 for file transfers                                |
| `ExSPIKE.Enums`    | Protocol enumerations as atoms                          |

## Development

```sh
mix test      # BDD style tests and doctests
mix spec      # tests, plus writes spec/STATUS.md
mix docs      # generate documentation
```

### Spec

The requirements are in [spec/ex_spike.spec.md](https://github.com/ErikMejerHansen/ex_spike/blob/main/spec/ex_spike.spec.md),
each with an ID such as `ARCH-4`. Tests declare the requirements they
verify with a tag:

```elixir
@tag spec: "ARCH-4"
test "when the application starts, then it starts no processes" do
```

Requirements that tests can't fully cover are reviewed by hand and
recorded in [spec/reviews.exs](https://github.com/ErikMejerHansen/ex_spike/blob/main/spec/reviews.exs).

`mix spec` runs the tests and writes [spec/STATUS.md](https://github.com/ErikMejerHansen/ex_spike/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 in the
`hex` environment (**Settings → Environments**), where you can also add
required reviewers.

## License

MIT, see [LICENSE](https://github.com/ErikMejerHansen/ex_spike/blob/main/LICENSE).