Loki is an embedded C project for a dragon-themed interactive device that runs on a single-board computer and talks to attached hardware such as a TFT display, SD card, flash memory, EEPROM, and a Flipper Zero over UART.
Loki is the master embedded systems framework for this ecosystem. It provides reusable HAL modules, device drivers, runtime orchestration, and Python-side integration points that downstream projects consume rather than re-implement.
The intended model is:
- Loki (master): source of truth for embedded framework capabilities
- FullStack (dependent): integrates Loki into broader application workflows
- BlackSmith (dependent): integrates Loki for project-specific system builds
Loki stays authoritative while dependent repositories reference Loki as a pinned Git submodule.
- FullStack and BlackSmith include Loki under a dedicated directory (commonly
loki/) - each dependent repo pins Loki to a specific commit for reproducible builds
- upgrades happen intentionally by moving the pinned Loki commit forward after validation
See /SUBMODULE_GUIDE.md for full setup and day-to-day workflow examples.
Current Loki repository layout:
Loki/
├── core/ # framework orchestration and runtime layers
├── drivers/ # hardware/peripheral drivers
├── hal/ # HAL modules (GPIO, SPI, I2C, UART, PWM)
├── plugins/ # Python plugin extensions
├── config/ # configuration helpers and generated/config assets
├── utils/ # utility helpers and scripts
├── README.md
├── SUBMODULE_GUIDE.md
├── BUILD.md
├── DEPLOYMENT.md
└── Makefile
Example dependent-project shape (outside this repo):
FullStack-or-BlackSmith/
├── loki/ # Git submodule -> Fomorianshifter/Loki
└── <project-specific files>
Loki dependency behavior is designed for compatibility and predictable integration:
- Version pinning: dependent repos pin Loki at a specific commit SHA (or approved tag)
- Compatibility checks: update Loki pins only after build/test validation in the dependent repo
- Coordinated updates: treat Loki version bumps as explicit changes with release notes and review
Use the compatibility guide for recommended pinning and rollout patterns.
- Submodule guide:
SUBMODULE_GUIDE.md - Integration guide:
INTEGRATION_GUIDE.md - Version compatibility:
VERSION_COMPATIBILITY.md - Build details:
BUILD.md - Deployment details:
DEPLOYMENT.md
This repo is most useful if you want to learn how to build a hardware-oriented C project with:
- a small hardware abstraction layer
- separate device drivers for SPI, I2C, UART, GPIO, and PWM devices
- structured logging instead of
printf - safer dynamic memory helpers
- retry logic for unreliable bus operations
- a simple top-level startup and shutdown flow
Main source files in the current root-level layout include:
main.c— program entry point, signal handling, and example hardware testssystem.c/system.h— system startup, subsystem initialization, and shutdownloki_life.c/loki_life.h— Loki life-cycle system (see below)log.c/log.h— leveled logging macros and logger implementationmemory.c/memory.h— tracked allocation helpers such asmalloc_safe()andfree_safe()retry.c/retry.h— retry strategies for transient hardware failuresgpio.c/gpio.h— GPIO abstractionspi.c/spi.h— SPI abstractioni2c.c/i2c.h— I2C abstractionuart.c/uart.h— UART abstractionpwm.c/pwm.h— PWM abstractiontft_driver.c/tft_driver.h— TFT display driversdcard_driver.c/sdcard_driver.h— SD card driverflash_driver.c/flash_driver.h— flash memory drivereeprom_driver.c/eeprom_driver.h— EEPROM driverflipper_uart.c/flipper_uart.h— Flipper Zero UART protocol supportboard_config.h— board-level settings such as frequencies and timingpinout.h— pin mappings used by the project
At a high level, main.c shows a teachable embedded application structure:
- print a startup banner
- initialize logging
- install signal handlers for graceful shutdown
- call
system_init() - call
loki_init()— birth Loki as an egg - run sample hardware tests
- run
demo_loki_lifecycle()— shows feeding, moods, and stage growth in one pass - enter a loop: each iteration calls
loki_tick()and routes Flipper commands to Loki actions - call
system_shutdown()before exit
That pattern is a good starting point for your own SBC or hardware-control application.
loki_life.c / loki_life.h implement the gameplay foundation for Loki's interactive behaviour.
Loki begins as an egg and advances through four stages based on accumulated growth points (gp):
| Stage | Threshold |
|---|---|
| Egg | starts here |
| Hatchling | 50 gp |
| Young | 200 gp |
| Adult | 500 gp |
Growth points are earned by feeding and interacting. The stage check runs automatically after every loki_feed(), loki_interact(), and loki_tick() call.
Three food qualities are available via loki_feed(state, food_type):
| Food | Hunger reduction | Growth pts | Happiness boost |
|---|---|---|---|
LOKI_FOOD_BASIC |
20 | 5 | 5 |
LOKI_FOOD_TASTY |
40 | 10 | 15 |
LOKI_FOOD_SPECIAL |
60 | 20 | 25 |
Feeding when Loki is not hungry halves all effects (overeating penalty).
Mood is derived from hunger, happiness, and energy using a priority ladder:
HUNGRY (hunger ≥ 70)
SLEEPY (energy ≤ 20)
GRUMPY (happiness ≤ 30 or hunger ≥ 50)
HAPPY (happiness ≥ 70)
PLAYFUL (happiness ≥ 50 and energy ≥ 60)
NEUTRAL (everything else)
When Loki is SLEEPY the tick function restores energy instead of draining it, modelling a natural sleep/wake cycle.
loki_get_animation_state(state) returns one of:
| State | When shown |
|---|---|
LOKI_ANIM_EGG_IDLE |
Default egg state |
LOKI_ANIM_EGG_WIGGLE |
Egg close to hatching |
LOKI_ANIM_HATCHLING_IDLE |
Hatchling stage, neutral mood |
LOKI_ANIM_EATING |
Shortly after any feeding |
LOKI_ANIM_SLEEPING |
Mood is SLEEPY |
LOKI_ANIM_HAPPY |
Mood is HAPPY |
LOKI_ANIM_GRUMPY |
Mood is GRUMPY or HUNGRY |
LOKI_ANIM_PLAYFUL |
Mood is PLAYFUL |
LOKI_ANIM_IDLE |
Default for young/adult |
void loki_init(loki_state_t *state);
void loki_tick(loki_state_t *state, uint32_t delta_seconds);
void loki_feed(loki_state_t *state, loki_food_t food);
void loki_interact(loki_state_t *state);
void loki_update_mood(loki_state_t *state);
void loki_check_progression(loki_state_t *state);
loki_anim_t loki_get_animation_state(const loki_state_t *state);
void loki_print_status(const loki_state_t *state);- More food types: add entries to
loki_food_tand handle them inloki_feed(). - Persistence: serialise
loki_state_tto EEPROM or SD card — every field is a plain integer. - Display rendering: read
loki_get_animation_state()in the render loop and switch on the returned enum to select sprite/frame sets. - Flipper commands: the main loop already maps Flipper command bytes
0x10–0x30to feed and interact actions.
The project separates responsibilities:
- low-level bus access lives in HAL-style modules like GPIO, SPI, I2C, UART, and PWM
- chip or peripheral behavior lives in driver files
- app behavior stays in
main.candsystem.c
This makes the code easier to debug and extend.
The logging system gives you levels such as:
LOG_CRITICAL()LOG_ERROR()LOG_WARN()LOG_INFO()LOG_DEBUG()
That is much better than scattering raw prints everywhere because it keeps diagnostics consistent.
The memory helpers encourage patterns like:
- allocate with
malloc_safe() - release with
free_safe() - report memory usage in debug workflows
That is a practical pattern for learning C without losing track of heap allocations.
Hardware communication can fail temporarily. The retry helpers show how to wrap operations like SPI, I2C, or EEPROM access with a reusable retry policy instead of duplicating error-handling code.
The signal handling in main.c demonstrates a clean exit path. That matters for embedded Linux software that may be stopped through SSH, a service manager, or a terminal.
Common commands documented in this repo are:
make
make DEBUG=1
make DEBUG=0
make test
make analyze
make docs
make cleanThe maintained native target is loki_core.so, a small shared library used by
loki.py. The interactive application is Python-based. Use Python 3.11 or
newer, then install the display dependency before starting the application:
python3 -m pip install -r requirements.txt
python3 main.pyOn Debian-based SBC images, sudo apt install python3-pil is an equivalent
system-package installation for the display renderer. Without Pillow, Loki
still starts its configuration UI and non-display plugins, but display and
dragon animation rendering fall back safely.
Use make test to run the Python test suite.
By default the configuration UI serves on loopback (http://127.0.0.1:8082),
which works on a fresh install with no extra network setup. To reach it from
another machine over USB, Loki uses the same USB-network addresses as a typical
Pwnagotchi setup: Loki is 10.0.0.2 and the connected computer is 10.0.0.1.
Set address = "10.0.0.2" under [ui.web] in config.toml, install the
included systemd-networkd profile on Loki once, then restart networking:
sudo install -D -m 644 network/loki-usb0.network /etc/systemd/network/10-loki-usb0.network
sudo systemctl enable --now systemd-networkd
sudo systemctl restart systemd-networkdSet the computer's USB Ethernet interface to the static address
10.0.0.1/24, connect to Loki over USB, and open http://10.0.0.2:8080.
On Linux, this can be configured with:
sudo ip address replace 10.0.0.1/24 dev <usb-interface>
sudo ip link set <usb-interface> upThe UI accepts connections only from that USB subnet. Edit values, save them,
then restart Loki for the updated configuration to take effect. If the
configured USB address is not assigned to a local interface yet (for example
usb0 is down), Loki logs a warning and serves the UI on loopback
(127.0.0.1) instead of failing to start. Configure the
WPA-SEC plugin key outside the repository:
export LOKI_WPA_SEC_API_KEY="your-key"
python3 main.pyFor a systemd-managed Loki process, persist the key in an override instead of
adding it to config.toml:
sudo systemctl edit loki
# Add: [Service]
# Add: Environment=LOKI_WPA_SEC_API_KEY=your-keySet [web_ui].enabled = false in config.toml to disable the editor. For a
strictly local-only UI instead, set [web_ui].host = "127.0.0.1".
Loki now includes a local actor-critic plugin at plugins/ai_brain.py that
runs a lightweight A2C loop without external ML dependencies.
- Enable Bettercap telemetry and the AI brain in
config.toml:[plugins.bettercap].enabled = true[plugins.ai_brain].enabled = true[plugins.ai_brain].learning = true(online updates)
- Start Loki normally with
python3 main.py. - Let it run for a while; policy/value weights are persisted to:
~/.local/share/loki/a2c_state.json
- For inference-only behavior, set:
[plugins.ai_brain].learning = false
- To make inference deterministic (no action sampling), set:
[plugins.ai_brain].deterministic = true
The AI brain consumes shared telemetry (AP/client counts and API health) from the Bettercap plugin and publishes its current action/probabilities through plugin state for other modules to consume.
Loki now uses a single master config.toml with a runtime-first structure inspired by Pwnagotchi:
[main],[main.auth],[main.network][main.plugins.*]including plugin loader settings[ui],[ui.web],[ui.display]
Build-time C macros are generated from [build.board] and [build.pinout] by:
python3 tools/gen_config.pyThe generated files are board_config.h, pinout.h, and config.h.
If you are learning from this repo, a strong reading order is:
README.mdmain.csystem.candsystem.hloki_life.handloki_life.c— the life-cycle systemlog.*,memory.*, andretry.*spi.*,i2c.*,uart.*,gpio.*, andpwm.*- the device drivers
BUILD.mdandDEPLOYMENT.md
The repository was originally documented around Raspberry Pi hardware, and much of the current checked-in code and documentation still reflects that. The code also includes Raspberry Pi and Flipper-related intent in various places, so treat board assumptions as something to verify before wiring real hardware.
This README now focuses only on the most teachable and durable information. For deeper platform setup, deployment details, troubleshooting, and build workflow notes, use the companion docs already in the repository such as:
BUILD.mdBUILD_WINDOWS.mdDEPLOYMENT.md— cross-compile and deploy to Orange Pi / Raspberry Pi, including setting up Loki as a persistent systemd serviceCONTRIBUTING.mdQUICK_REFERENCE.md
MIT License. See LICENSE.