You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A networked micro-database running on a Raspberry Pi Pico 2W with a Waveshare Pico-ResTouch-LCD-3.5 display and 16GB SD card.
Serves a full SSR web UI with user auth, schema management, a query language with joins and aggregates, master-child forms, batch writes, and OTA firmware updates — all from a $6 microcontroller.
Data is organized into packs (like tables) containing cards (like rows). Each card is a binary blob with a 4-byte magic header (0xCA7D) followed by ordinal-tagged fields.
Pack
Purpose
Storage
0
Schema definitions
Flash
1
Users & auth
Flash
2
Days (reference)
SD
3
Countries (reference)
SD
4
Currencies (reference)
SD
5+
User-defined
SD
Schema card format (Pack 0)
Each pack's schema is stored as a card in Pack 0:
Ordinal
Field
Description
0
Pack name
Length-prefixed UTF-8
1
Field count
uint8
2
Field definitions
3 bytes each: ord, type, maxlen
3
Flags
bit 0: public-read, bits 1-3: cardinality bucket
4
Module
Groups packs into nav dropdowns
5
Field names
Null-separated ASCII
6
Children
Array of child pack ordinals (1:many)
Field types
Code
Name
Friendly
Size
0x01
uint8
Small number (0-255)
1
0x02
uint16
Number (0-65k)
2
0x03
uint32
Large number
4
0x04
int8
Small number (+/-)
1
0x05
int16
Number (+/-)
2
0x06
int32
Large number (+/-)
4
0x07
bool
Yes / No
1
0x08
ascii
Text
len-prefixed
0x09
utf8
Text
len-prefixed
0x0A
date
Date
len-prefixed
0x0B
time
Time
len-prefixed
0x0C
datetime
Date & Time
len-prefixed
0x10
array_u16
Number list
count-prefixed
0x11
blob
Binary data
raw bytes
0x12
lookup
Link
uint32 card ID
Relationships
Many→1 (lookup): A field of type 0x12 stores a card ID from another pack. The maxlen byte specifies the target pack ordinal.
1→Many (children): Schema ord 6 lists child pack ordinals. The child pack must have a lookup field pointing back to the parent.
Query language
S:field1,field2 — Select fields
S:* — Select all
S:pack.field — Select from joined pack
S:SUM|field — Aggregate (SUM, AVG, MIN, MAX, COUNT)
F:pack1,pack2 — From (comma-separated, first is primary)
W:field|op|value — Where (multiple = AND)
W:pack.field|op|value — Where on joined pack
Operators:==, !=, >, <, >=, <=, IN, NI
Response: Pipe-delimited rows, CRLF line endings, X-Pack and X-Count HTTP headers.
Query optimizer
Cardinality cache: 3-bit log10 buckets in SRAM, lazy-flushed to schema flags
Fanout stats: Tracks avg children per lookup value
Predicate reordering: Sorts WHERE clauses by estimated selectivity (most selective first)
Short-circuit AND: First failing predicate skips the row
Validates all entries + RBAC first, then writes all cards. Max 32 per batch.
OTA firmware update
Firmware uploads to SD card staging area (512KB, blocks 1-1024), then flashes to slot A on commit. No XIP contention — SD reads via SPI are independent of flash.
POST /update/begin → prepare SD staging
POST /update/chunk → write 1KB chunks to SD
POST /update/commit → SD → SRAM → flash, reboot
Core 1 consumer: drains request ring via multicore FIFO, dispatches APPEND/READ/KV_GET/KV_PUT/DELETE/RANGE/RECORD_COUNT ops to kv_flash. Runs background compaction when idle. OTA halt support (__wfe() spin).
src/wal_engine.h
6
Exports: wal_engine_run().
src/wal_dma.c
60
DMA copy helper using hardware DMA channel. wal_dma_copy() for fast bulk memory moves.
src/wal_dma.h
25
DMA init/copy/wait/busy API.
Storage — KV stores
File
Lines
Purpose
src/kv_flash.c
853
Flash-backed KV store for packs 0–1 (schemas + users). Page-based storage with V2 headers (sequence + CRC), mutation group commits, sorted SRAM index (binary search), compaction, deadlog recovery. Interrupt-safe flash writes.
src/kv_flash.h
101
Full KV API: kv_init/put/get/get_copy/delete/exists/range/stats/compact_step/wipe.
src/kv_sd.c
623
SD-backed KV store for packs 2+. Copy-on-write (COW) slot allocation with bitmap, 3-tier index (SRAM → Flash XIP → SD keylist), sorted merge range queries. Flash index at 0xC0000 with write-order hardening.
Unified routing layer. Inline functions route by pack ordinal: packs 0–1 → kv_flash, packs 2+ → kv_sd (when ready, else flash fallback). Used by web_server, udp_wal, query engine.
src/storage.c
603
Experimental packed/compressed storage engine with heatshrink. Superblock, block allocator, pack summaries, log writes. Currently compiled but not active in HTTP routes.
src/storage.h
93
Storage engine API with compression support.
src/field_index.c
203
Hash-based field-value index for O(1) lookups on SD. 4096-bucket hash table, chained entries in SD blocks. Used by storage.c.
src/field_index.h
48
Field index API: fidx_init/insert/search/search_prefix/remove_card.
Metadata & schema
File
Lines
Purpose
src/metadata_dict.c
231
Schema catalog: caches pack definitions from Pack 0, provides type/field/schema lookups. Reload from flash on boot.
src/metadata_dict.h
60
Type/field/schema structs, catalog API.
Auth & security
File
Lines
Purpose
src/user_auth.c
736
User management: session table (4 slots), login/logout, SHA-256 password hashing (salted), RBAC (readPacks/writePacks/deletePacks per user), admin detection, password change, user CRUD. Seeds default admin + reference data schemas on first boot.
Minimal mbedTLS: SHA-256 only (for password hashing + HKDF).
src/hs_config.h
10
Heatshrink compression: window=8, lookahead=4.
Hardware drivers
File
Lines
Purpose
drivers/lcd/ili9488.c
260
ILI9488 480×320 LCD driver over SPI1. Init sequence, buffered character rendering (one SPI transaction per glyph), pixel/rect/string drawing, backlight PWM.
drivers/lcd/ili9488.h
33
LCD API + color constants.
drivers/sd/sd_card.c
330
SD card SPI driver on SPI1. DMA-driven bulk transfers (512-byte blocks), bit-bang bus wakeup (required after LCD init), CMD0/CMD8/ACMD41 with timeout, single/multi block read/write at 50MHz. All read+write functions __no_inline_not_in_flash_func for OTA safety.