- C++ 98.1%
- Python 1%
- CMake 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| components | ||
| documentation | ||
| licenses | ||
| main | ||
| test | ||
| tools | ||
| .clang-tidy | ||
| .gitattributes | ||
| .gitignore | ||
| CMakeLists.txt | ||
| dependencies.lock | ||
| LICENSE | ||
| partitions.csv | ||
| README.md | ||
| sdkconfig.defaults | ||
| sdkconfig.defaults.debug | ||
| THIRD-PARTY-NOTICES.md | ||
| VERSION | ||
Lumi
Lumi is a device for sport activities, workouts, and games. You can read more about it on on my blog.
It is free and open source under the MIT license. See LICENSE and THIRD-PARTY-NOTICES.md. The sourc code was mostly generated using Claude Code.
Status
Working firmware, tested on real hardware with three pods.
Software
- Language: C++
- Framework: ESP-IDF
- OS: FreeRTOS
- GUI: LVGL
- Mesh technology: ESP-NOW
Hardware
The three buttons are capacitive. A capacitive button senses the touch of a finger and has no moving part.
| Part | GPIO | Description | Info |
|---|---|---|---|
| Wemos Lolin32 Lite | None | The microcontroller that runs the firmware. | Link |
| Button center | T3 (GPIO 15) | The large button in the center. | |
| Button left | T8 (GPIO 33) | The button on the left. | |
| Button right | T9 (GPIO 32) | The button on the right. | |
| Neopixel LED ring | 13 | A ring of 24 WS2812B RGB LEDs. The firmware sets the color of each LED. | |
| OLED SSD1306 | SDA 12, SCL 14 | A display, 128 pixels wide and 32 pixels high. The I2C address is 0x3C. | Link |
| TOF sensor VL53L0X | SDA 12, SCL 14 | A time-of-flight sensor. It measures the distance to your hand with light. The I2C address is 0x29. | Link |
| Piezo buzzer | 19 | It makes the sounds. |
Game modes
| Mode | What you do | Pods | |
|---|---|---|---|
| Reaction Time | One pod lights up at a time. Touch it. You get your average time, your best time, and your worst time. | 1+ | |
| Memory | The pods show a pattern. The pattern gets longer in each round. Do the pattern again. | 1+ | |
| Mine Sweeper | If a pod is green, touch it quickly. If a pod is red, do not touch it, because it is a mine. Each wave is faster than the wave before. | 1+ | |
| Interval | The buzzer sends you to one pod after another. Move your hand above the pod, or touch it, before the time runs out. The time gets shorter. | 1+ | |
| Reaction Multiplayer | Each player has a color. Touch the pods that show your color and collect points. The first player with all points wins. | 2+ |
How a game works
flowchart LR
A[Pick a game<br/>on any pod] --> B[This pod becomes<br/>the coordinator]
B --> C[It tells the other pods<br/>when to light up]
C --> D[The players react]
D --> E[All pods show<br/>the same result]
Repository layout
| Path | Contents |
|---|---|
components/lumi_core |
Games, mesh protocol, settings, menu logic. No hardware calls, so the unit tests run on a PC. |
components/lumi_hal |
Drivers: touch, LEDs, buzzer, display, distance sensor, storage, radio. |
components/lumi_ui |
Screens and the app state machine. |
main |
Firmware entry point. |
test |
Unit tests that run on a PC. |
documentation |
Requirements, game-mode diagrams, coding guidelines, CI setup. |
tools |
Version-header generator and the coverage report. |
Build and flash
You need ESP-IDF v6.0 and an ESP32 board.
idf.py set-target esp32
idf.py build
idf.py -p <PORT> flash
The release build writes no log output. To get log output on the serial port, build with the debug option:
idf.py fullclean
idf.py -DLUMI_DEBUG=ON build
idf.py -p <PORT> flash monitor
You must run idf.py fullclean first. ESP-IDF reads the sdkconfig.defaults files only when no sdkconfig file exists.
Run the tests
The game, mesh, and menu logic lives in components/lumi_core and has no hardware dependency, so it builds and runs on a normal PC.
cmake -S test -B test/build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build test/build
./test/build/lumi_core_tests
Line coverage, with a GCC build:
CC=gcc CXX=g++ cmake -S test -B test/build-coverage -G Ninja \
-DCMAKE_CXX_FLAGS="--coverage -O0" -DCMAKE_EXE_LINKER_FLAGS="--coverage"
cmake --build test/build-coverage
./test/build-coverage/lumi_core_tests
python3 tools/coverage_report.py test/build-coverage --min 80
Static analysis reads a compile_commands.json file. The build on the PC makes this file for lumi_core:
cmake -S test -B test/build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
clang-tidy -p test/build components/lumi_core/src/*.cpp
For lumi_hal and lumi_ui, point clang-tidy at the ESP-IDF build directory instead.
Documentation
- Requirements: what the device must do, with the IDs that the code refers to.
- Game modes: one diagram for each mode.
- Coding guidelines: the rules for the source code.
- Networking: how the pods send data to each other.