Skip to content

davidmweber/soulstar

Repository files navigation

Soulstar

This is a wearable art piece that lets you know when your friends are close at events like festivals. It was directly inspired by the HiveMind proximity detector that was built by two close friends and tested at AfrikaBurn in 2018. Tech has moved on a bit since then so this is effectively V2. You can see the original project source code here courtesy of cpbotha.

Some disclaimers

I started the project using the Espressif IDF (a powerful tool indeed) but switched over to Rust for several reasons:

  • I like Rust.
  • The embedded ecosystem is surprisingly strong. I particularly like Embassy with its great support for ESP32 hardware and its async capabilities. In particular, the Embassy tasks and connector tools make dealing with asynchronous events safe and easy.
  • I really wanted to get a feel for how mature the ecosystem is with a view to another commercial product I am involved with.
  • Time is something I on my hands right now and am happy to struggle a bit, possibly even contribute back to the ecosystem.
  • I chose the #[no_std] (i.e. no Espressif IDF) option, mostly to feel out the ecosystem in a pure Rust world. This is an art piece after all.
  • Async has some significant advantages, particularly with stack management and efficient concurrency, so async interfaces are used wherever possible.
  • Finding examples like this is not easy, so I wanted to make this as complete and idiomatic as possible for others to get a head start.

Hardware Requirements

This project is based around the ESP32 family of embedded microprocessors with some basic requirements and a standard LS2812 LED strip.

  • ESP32C6 dev board. I chose the ESP32C6 because it has a v5.0 BlueTooth stack that includes BLE as well as an 802.15 stack which supports the ZigBee proximity detection used in HiveMind. I also prefer the RISC-V instruction set over Xtensa mostly because RISC-V is directly supported by LLVM and the Rust compiler. You can use pretty much any ESP32 processor that supports RMT and BLE. In particular, the ESP32-S3 and ESP32-H2 support DMA for the RMI interface used to drive the LED string, which is a better option for long LED strings.
  • WS2812 LED strip. These strips are ubiquitous and have great driver support. I used a ring LED strip for development.
  • Logic level shifter. The ESP32 devices are 3.3V while the led strips come in 5V or 12V so some logic level shifting is required. As cpbotha correctly reports, this gets fiddly and can be brittle in the field.

Software requirements

As this is a #[no_std] project, you do not need the Espressif IDF environment installed. You just need Rust and compilers for your targeted hardware. The tools you will need are:

  • A Rust development environment. Just install and move on.
  • A suitable toolchain. Install this for the RISC-V targets using:
    rustup target add riscv32imac-unknown-none-elf
    If you have an Xtensa device, you need to follow these instructions to install the required targets as they are not (yet) part of the supported targets in Rust..
  • probe_rs. This is used to flash and debug the device. Install using
    cargo install probe-rs-tools
  • Wokwi is a useful way to test without flashing a device all the time. There is a mostly working Wokwi setup in the repo. You will need to get an account and set up your IDE to use it. There are plugins for VSCode and Jetbrains. Wokwi is not helpful for BLE testing, so most testing was done on actual devices.

Building, configuration and running

Builds are mostly managed by cargo, but we use the awesome just tool to automate some of the builds. Running just --list will show all the available tasks.

The devices are custom flashed per user from the configuration file souls.toml. For this example:

[[device]]
id = "nefario"
bt_name = "Dr Nefario"
colour = [0xFF, 0x00, 0x00]

[[device]]
id = "strange"
bt_name = "Dr Strange"
colour = [0x00, 0xFF, 0x00]

[[device]]
id = "who"
bt_name = "Dr Who"
colour = [0x00, 0x00, 0xFF]

We have three souls that have an ID, bluetooth advertisement name and a desired colour. You configure the device by setting the SOUL_ID environment variables to one of the id's above which will generate src/soul_config.rs which hardcodes the details into the build. The easiest way to flash a device for a specific person is to use just:

just flash nefario # Will flash the device with a bluetooth name "Dr Nefario" and colour blue. 

The defaultSOUL_ID value is "nefario". This default is set here.

Useful links

Learnings

  • The Rust embedded ecosystem is potent but immature. That being said, it is actually really nice to work with and is rapidly evolving.
  • An event-driven embedded system is a winner. One stack with cooperative multitasking which pretty much eliminates race conditions with the only caveat is "don't block". I found this way easier to deal with than FreeRTOS tasks.
  • Make sure that you set suitable interval and window value for the BLE scanner, especially if you are advertising. In particular, the interval value must be greater than window else the stack just crashes at some point.
  • Sharing peripherals such as GPIO pins etc. can be a bit of a bear. The borrow checker is all over the peripheral struct, and I personally did not find it obvious how to deal with it.
  • Read the ESP32 docs carefully. I spent some time trying to get touch pins to work on the C6. It does not support this functionality…
  • AI (Gemini 2.5 at time of writing) saved about as much time as it wasted. I am no vibe coder, but I do use AI as a support, but it really struggles on roads less travelled.

About

A friend proximity detector for festivals

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors