[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81919":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":10,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":10,"pushedAt":10,"updatedAt":30,"readmeContent":31,"aiSummary":32,"trendingCount":16,"starSnapshotCount":16,"syncStatus":33,"lastSyncTime":34,"discoverSource":35},81919,"filum","kavishka-dot\u002Ffilum","kavishka-dot","Pure-C federated learning library for MCU-class edge devices over LoRa. STM32 + SX1276.","",null,"C",28,5,3,1,0,39.33,false,"main",true,[22,23,24,25,26,27,28,29],"c99","edge-ai","embedded","federated-learning","iot","lora","machine-learning","stm32","2026-06-12 04:01:36","# Filum\n\n[![CI](https:\u002F\u002Fgithub.com\u002Fkavishka-dot\u002Ffilum\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fkavishka-dot\u002Ffilum\u002Factions\u002Fworkflows\u002Fci.yml)\n[![codecov](https:\u002F\u002Fcodecov.io\u002Fgh\u002Fkavishka-dot\u002Ffilum\u002Fbranch\u002Fmain\u002Fgraph\u002Fbadge.svg)](https:\u002F\u002Fcodecov.io\u002Fgh\u002Fkavishka-dot\u002Ffilum)\n[![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-blue.svg)](LICENSE)\n[![Version](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fversion-0.2.0-blue.svg)](CHANGELOG.md)\n![Language: C99](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flanguage-C99-brightgreen.svg)\n![Platform](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fplatform-STM32%20%7C%20Linux-lightgrey.svg)\n![No heap](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fheap-none-orange.svg)\n![No Python](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fpython-none-red.svg)\n\n**Federated learning for MCU-class edge devices over LoRa.**\n\nFilum is a pure C99 library for on-device federated learning on microcontrollers that communicate over LoRa radio. A **Shard** running on an STM32F411 trains on private local sensor data and transmits sparse gradient updates over LoRa. A **Herald** running on Linux or a Raspberry Pi coordinates training rounds, aggregates updates, and sends the improved global model back to the shards.\n\nNo Python runtime. No dynamic allocation. No heap usage. Designed to run within a 128 KB RAM budget.\n\n---\n\n## Contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Getting started](#getting-started)\n- [Build options](#build-options)\n- [Architecture](#architecture)\n- [API reference](#api-reference)\n- [Security](#security)\n- [Memory model](#memory-model)\n- [Wire protocol](#wire-protocol)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n- **Pure C99:** no C++, no RTOS dependency, no stdlib heap\n- **Zero dynamic allocation:** all buffers are compile-time static\n- **LoRa-native:** wire format designed around LoRa payload constraints, from SF7 to SF12\n- **Q8 sparse gradients:** top-k selection with Q8 quantization, giving about 5.5× compression compared with dense float updates\n- **Federated aggregation:** FedAvg, FedProx, and coordinate-wise median aggregation for Byzantine robustness\n- **Differential privacy:** per-shard Gaussian mechanism with an (ε, δ)-DP guarantee and budget tracking\n- **ECDH encryption:** Curve25519 key exchange with ChaCha20-Poly1305 authenticated encryption\n- **HAL abstraction:** shard logic stays hardware-independent through a struct of function pointers\n- **Configurable memory:** four presets ranging from 2 KB for STM32F103 to 1 MB for Herald Linux\n- **Tested:** 9 unit and integration tests, CI on every push, and Codecov coverage reporting\n\n---\n\n## Requirements\n\n### Host (Herald + tests)\n\n| Dependency | Version | Notes |\n|---|---|---|\n| GCC or Clang | ≥ 9 | C99 mode |\n| CMake | ≥ 3.20 | |\n| make or Ninja | any | |\n| Doxygen | optional | for `--target filum_docs` |\n\n### Shard (STM32 firmware)\n\n| Dependency | Version | Notes |\n|---|---|---|\n| arm-none-eabi-gcc | ≥ 10 | `sudo apt install gcc-arm-none-eabi` |\n| CMake | ≥ 3.20 | |\n| OpenOCD | any | for flashing |\n\n### Hardware (optional, all tests run without it)\n\n- STM32F411 Blackpill (or STM32F103 Blue Pill)\n- RFM95W (SX1276, 868 MHz EU \u002F 915 MHz US)\n- ST-Link V2 for flashing\n\n---\n\n## Getting started\n\n### 1. Clone and build\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fkavishka-dot\u002Ffilum.git\ncd filum\ncmake -B build -DFILUM_TARGET=host\ncmake --build build\n```\n\n### 2. Run the test suite\n\n```bash\ncd build && ctest --output-on-failure\n```\n\nExpected output:\n\n```\n1\u002F9 Test #1: test_quant ...........   Passed    0.00 sec\n2\u002F9 Test #2: test_sparse ..........   Passed    0.00 sec\n3\u002F9 Test #3: test_frame ...........   Passed    0.00 sec\n4\u002F9 Test #4: test_round ...........   Passed    2.00 sec\n5\u002F9 Test #5: test_aggregator ......   Passed    0.00 sec\n6\u002F9 Test #6: test_dp ..............   Passed    0.00 sec\n7\u002F9 Test #7: test_crypto ..........   Passed    0.00 sec\n8\u002F9 Test #8: test_median ..........   Passed    0.00 sec\n9\u002F9 Test #9: test_integration .....   Passed    0.10 sec\n\n100% tests passed, 0 tests failed out of 9\n```\n\n### 3. Run the end-to-end demo\n\n```bash\n.\u002Fbuild\u002Ffilum_demo\n```\n\nRuns a complete FL loop with Herald, Shard, and a synthetic dataset entirely in memory. No hardware is needed.\n\n```\n=======================================================\n  Filum  -  End-to-End Federated Learning Demo\n  1 Herald  |  1 Shard  |  Synthetic Data  |  No HW\n=======================================================\n\nRound  Shard acc   Global acc   Entries TX   Bytes saved   Time ms\n  1      75.0%       85.0%        14 entries    190 bytes     1 ms\n  2      97.5%       95.0%        14 entries    190 bytes     0 ms\n  3     100.0%      100.0%        14 entries    190 bytes     0 ms\n\nWire efficiency:\n  Dense upload   : 232 bytes  (58 params × 4B float)\n  Sparse upload  :  42 bytes  (14 entries × 3B Q8, top-25%)\n  Compression    : 5.5× smaller\n  LoRa packets   : 1 packet per round at SF7\n```\n\n### 4. Flash STM32\n\n```bash\n# Build STM32F411 firmware\ncmake -B build_stm32 -DFILUM_TARGET=STM32F411 -DFL_CONFIG_PRESET=SMALL\ncmake --build build_stm32\n\n# Flash via OpenOCD\nopenocd -f interface\u002Fstlink.cfg -f target\u002Fstm32f4x.cfg \\\n    -c \"program build_stm32\u002Ffilum_shard.bin 0x08000000 verify reset exit\"\n```\n\nBefore flashing, fill in the HAL stubs in `shard\u002Fhal\u002Fstm32\u002Fhal_lora.c` and implement `fl_user_data_cb()` in `examples\u002Fshard_stm32f4\u002Fmain.c`. See [`docs\u002Fporting.md`](docs\u002Fporting.md).\n\n### 5. Run the Herald\n\n```bash\n.\u002Fbuild\u002Ffilum_herald \\\n    --port \u002Fdev\u002FttyUSB0 \\\n    --baud 115200 \\\n    --params 192 \\\n    --window 7200 \\\n    --min 2 --max 20\n```\n\n---\n\n## Build options\n\n### Target\n\n```bash\ncmake -B build -DFILUM_TARGET=host          # Linux\u002FmacOS\u002FWSL\ncmake -B build -DFILUM_TARGET=STM32F411     # STM32F411 Blackpill\ncmake -B build -DFILUM_TARGET=STM32F103     # STM32F103 Blue Pill\n```\n\n### Memory presets\n\n| Preset | Max params | Approx RAM | Intended target |\n|---|---|---|---|\n| `TINY` | 64 | ~2 KB | STM32F103, 20 KB RAM |\n| `SMALL` | 256 | ~8 KB | STM32F411, constrained |\n| _(default)_ | 4096 | ~54 KB | STM32F411 full \u002F Linux |\n| `LARGE` | 16384 | ~1 MB | Herald Linux only |\n\n```bash\ncmake -B build -DFL_CONFIG_PRESET=SMALL\n\n# Or override individual limits\ncmake -B build -DFL_MODEL_MAX_PARAMS=256 -DFL_SPARSE_MAX_ENTRIES=64\n```\n\n### Other options\n\n| Option | Default | Description |\n|---|---|---|\n| `FILUM_ENABLE_TESTS` | `ON` | Build unit tests (host only) |\n| `FILUM_ENABLE_ASAN` | `OFF` | AddressSanitizer (GCC\u002FClang) |\n| `FILUM_LORA_SF` | `7` | LoRa spreading factor, from 7 to 12 |\n\n### Install\n\n```bash\ncmake --install build --prefix \u002Fusr\u002Flocal\n```\n\nInstalls headers to `include\u002Ffilum\u002F`, libraries to `lib\u002F`, and generates:\n- `lib\u002Fpkgconfig\u002Ffilum.pc`: use via `pkg-config --libs filum`\n- `lib\u002Fcmake\u002FFilum\u002FFilumConfig.cmake`: use via `find_package(Filum)`\n\n### Documentation\n\n```bash\nsudo apt install doxygen\ncmake --build build --target filum_docs\n# Output: build\u002Fdocs\u002Fhtml\u002Findex.html\n```\n\n---\n\n## Architecture\n\n```\nHerald (Linux, C)                      Shard (STM32F411, C)\n┌───────────────────────────┐         ┌────────────────────────────┐\n│  Event loop               │ LoRa    │  State machine             │\n│  Fragment pool            │ ◄─────► │  Local SGD                 │\n│  FedAvg \u002F Median \u002F FedProx│         │  Top-k sparse Q8 encoding  │\n│  Round scheduler          │         │  HAL (SX1276 SPI driver)   │\n│  DP sigma coordination    │         │  Gaussian DP noise         │\n│  ECDH key management      │         │  ChaCha20-Poly1305         │\n└───────────────────────────┘         └────────────────────────────┘\n             │                                      │\n      Serial\u002FUART to                          Deep sleep\n      LoRa gateway                         between rounds\n```\n\n### Round lifecycle\n\n```\nHerald                                  Shard\n  │                                       │\n  │──── FL_FRAME_BEACON ────────────────► │  Round announced; hyperparams + DP sigma\n  │                                       │  Local SGD training (local_epochs passes)\n  │                                       │  Gaussian DP noise added to delta\n  │                                       │  Top-k sparse encode → Q8 → fragment\n  │ ◄─── FL_FRAME_UPDATE (N packets) ─── │  Gradient fragments transmitted\n  │      Reassemble → aggregate           │\n  │──── FL_FRAME_ACK ──────────────────► │\n  │──── FL_FRAME_DELTA (M packets) ────► │  Aggregated global delta distributed\n  │──── FL_FRAME_ROUND_CLOSE ──────────► │  Shard deep-sleeps until next beacon\n```\n\n### Repository structure\n\n```\nfilum\u002F\n├── common\u002F                   Wire protocol, CRC, quantization, sparse encoding,\n│   ├── include\u002F              DP, crypto, error codes, version, config\n│   └── src\u002F\n├── shard\u002F                    Shard runtime (MCU side)\n│   ├── include\u002F              fl_shard.h, fl_train.h\n│   ├── src\u002F                  State machine, model, training\n│   └── hal\u002F\n│       ├── hal.h             HAL interface (struct of function pointers)\n│       ├── stm32\u002F            STM32F4 + SX1276 implementation\n│       └── host\u002F             Linux loopback HAL for testing\n├── herald\u002F                   Herald coordinator (Linux side)\n│   ├── include\u002F              fl_herald.h, fl_aggregator.h, fl_round.h, fl_fragment_pool.h\n│   ├── src\u002F\n│   └── transport\u002F            Serial\u002FUART LoRa gateway bridge\n├── examples\u002F\n│   ├── shard_stm32f4\u002F        Reference STM32 application\n│   ├── herald_linux\u002F         Linux daemon + pipe-based shard simulator\n│   └── demo\u002F                 Self-contained end-to-end demo (no hardware)\n├── tests\u002F                    9 unit and integration tests\n├── docs\u002F                     architecture.md, porting.md\n├── cmake\u002F                    FilumConfig.cmake.in, filum.pc.in\n├── .github\u002F                  CI workflow, issue templates, PR template\n├── CHANGELOG.md\n├── CONTRIBUTING.md\n└── SECURITY.md\n```\n\n---\n\n## API reference\n\nFull Doxygen-generated reference: `cmake --build build --target filum_docs`.\n\n### Umbrella header\n\n```c\n#include \u003Cfilum.h>   \u002F* all modules *\u002F\n\n\u002F* or include only what you need *\u002F\n#include \u003Cfl_shard.h>    \u002F* Shard runtime (MCU) *\u002F\n#include \u003Cfl_herald.h>   \u002F* Herald coordinator (Linux) *\u002F\n```\n\n### Error handling\n\nEvery public function returns `FLError`. Zero is success; negative values are errors.\n\n```c\nFLError err = fl_shard_init(&shard, &hal, SHARD_ID, &model);\nif (err != FL_OK) {\n    hal.log(\"init failed: %s\\n\", fl_strerror(err));\n}\n```\n\n| Code | Meaning |\n|---|---|\n| `FL_OK` | Success |\n| `FL_AGAIN` | No data available; try again |\n| `FL_TIMEOUT` | Operation timed out |\n| `FL_PENDING` | More fragments expected |\n| `FL_ERR_INVALID_ARG` | NULL or invalid argument |\n| `FL_ERR_BUFFER_TOO_SMALL` | Output buffer too small |\n| `FL_ERR_CRC` | Frame CRC mismatch; corrupt |\n| `FL_ERR_AUTH` | Poly1305 MAC failed; frame tampered |\n| `FL_ERR_TRANSPORT` | Serial write failed |\n\nFull list in [`common\u002Finclude\u002Ffl_error.h`](common\u002Finclude\u002Ffl_error.h).\n\n### Shard (MCU side)\n\n```c\nstatic FLModel model;\nstatic FLShard shard;\n\nstatic const FLLayerDesc layers[] = {\n    { .type=FL_LAYER_LINEAR, .activation=FL_ACT_RELU,\n      .in_features=8, .out_features=16, .param_count=8*16+16 },\n    { .type=FL_LAYER_LINEAR, .activation=FL_ACT_SIGMOID,\n      .in_features=16, .out_features=2, .param_count=16*2+2 },\n};\n\n\u002F* Provide training data one sample at a time *\u002F\nint fl_user_data_cb(void *ctx, FLSample *s) {\n    \u002F* fill s->input[], s->label[], s->input_len, s->label_len *\u002F\n    return 1;  \u002F* return 0 at end of epoch *\u002F\n}\n\nint main(void) {\n    fl_model_init(&model, layers, 2);\n    fl_model_init_random(&model, SHARD_ID);\n\n    fl_shard_init(&shard, &hal, SHARD_ID, &model);\n\n    \u002F* Optional: privacy and encryption *\u002F\n    fl_shard_enable_dp(&shard, 1.0f, 1e-5f, 1.0f);\n    fl_shard_enable_crypto(&shard, seed_32_bytes);\n\n    for (;;) {\n        fl_shard_tick(&shard);\n        fl_shard_sleep(&shard);\n    }\n}\n```\n\n### Herald (Linux side)\n\n```c\nstatic float    global_model[192];\nstatic FLHerald herald;\n\nFLHeraldConfig cfg = {\n    .serial_port       = \"\u002Fdev\u002FttyUSB0\",\n    .baud_rate         = 115200,\n    .model_param_count = 192,\n    .global_model      = global_model,\n    .aggregator        = FL_AGG_FEDAVG,   \u002F* or FL_AGG_MEDIAN, FL_AGG_FEDPROX *\u002F\n    .round_policy = {\n        .window_seconds      = 7200,\n        .inter_round_delay_s = 300,\n        .min_shards          = 2,\n        .max_shards          = 20,\n        .local_epochs        = 3,\n        .learning_rate       = 0.01f,\n    },\n};\n\nfl_herald_init(&herald, &cfg);\nfl_herald_run(&herald);   \u002F* blocking; call fl_herald_stop() from signal handler *\u002F\n```\n\n---\n\n## Security\n\nSecurity features are opt-in per shard and independent of each other.\n\n### Differential Privacy\n\n```c\nfl_shard_enable_dp(&shard,\n    1.0f,   \u002F* epsilon: privacy budget per round *\u002F\n    1e-5f,  \u002F* delta:   failure probability *\u002F\n    1.0f    \u002F* sensitivity: L2 clip norm *\u002F\n);\n\n\u002F* Query cumulative budget spent (basic composition) *\u002F\nfl_shard_privacy_report(&shard);\n```\n\nHerald coordinates noise levels by embedding `dp_sigma` in `FL_FRAME_BEACON`. Budget is tracked per shard using basic composition (`ε_total = T × ε_round`).\n\n### Encryption\n\n```c\n\u002F* seed: 32 bytes from MCU hardware RNG or XOR of UID registers *\u002F\nfl_shard_enable_crypto(&shard, seed);\n```\n\nShard sends `FL_FRAME_HANDSHAKE` on the next idle tick. Herald completes the X25519 exchange. All subsequent `FL_FRAME_UPDATE` payloads are encrypted (ChaCha20) and authenticated (Poly1305). Tampered frames return `FL_ERR_AUTH` and are discarded.\n\n> **Cryptography notice:** `fl_crypto.c` is pure C99 for MCU portability and has not been independently audited. For deployments where libsodium or mbedTLS is available, see [SECURITY.md](SECURITY.md).\n\n---\n\n## Memory model\n\n```\nComponent        Default RAM   TINY preset   SMALL preset\n─────────────────────────────────────────────────────────\nFLModel          32 KB         512 B         2 KB\nFLShard          22 KB         1.5 KB        6 KB\n─────────────────────────────────────────────────────────\nPer shard total  54 KB         2 KB          8 KB\nSTM32F411 RAM    128 KB        ✅            ✅\nSTM32F103 RAM    20 KB         ✅            ⚠️\n\nFLAggregator     1 MB          192 KB        512 KB\n(Herald only)\n```\n\nRAM budget macros:\n\n```c\n#include \u003Cfl_config.h>\n\u002F* FL_RAM_MODEL, FL_RAM_SHARD, FL_RAM_AGGREGATOR available at compile time *\u002F\n```\n\n---\n\n## Wire protocol\n\nEvery LoRa packet is a packed `FLFrame`:\n\n| Offset | Size | Field | Notes |\n|---|---|---|---|\n| 0 | 2 | `magic` | `0x464C` ('FL') |\n| 2 | 1 | `frame_type` | See table below |\n| 3 | 2 | `shard_id` | `0xFFFF` = Herald |\n| 5 | 1 | `round_id` | Wraps at 255 |\n| 6 | 1 | `frag_index` | 0-based |\n| 7 | 1 | `frag_total` | 1 = single-packet message |\n| 8 | 2 | `crc16` | CRC-16\u002FCCITT-FALSE over header + payload |\n| 10 | N | `payload` | Up to 214 bytes (SF7) |\n\n| Frame type | Direction | Description |\n|---|---|---|\n| `FL_FRAME_BEACON` | Herald → Shard | Opens a round; carries hyperparams + DP sigma |\n| `FL_FRAME_DELTA` | Herald → Shard | Aggregated global model delta |\n| `FL_FRAME_UPDATE` | Shard → Herald | Local gradient update |\n| `FL_FRAME_ACK` | Herald → Shard | Update fully received |\n| `FL_FRAME_ROUND_CLOSE` | Herald → Shard | Round closed; shard may sleep |\n| `FL_FRAME_HANDSHAKE` | Shard → Herald | ECDH public key |\n| `FL_FRAME_HANDSHAKE_ACK` | Herald → Shard | ECDH public key reply |\n\nCurrent wire protocol version: `FL_WIRE_VERSION 1`.\n\n---\n\n## HAL interface\n\nImplementing `FLHal` is the only requirement to port Filum to a new MCU.\n\n```c\ntypedef struct {\n    int      (*lora_send)(const uint8_t *buf, uint8_t len);\n    int      (*lora_recv)(uint8_t *buf, uint8_t *len, uint32_t timeout_ms);\n    int      (*lora_set_sf)(uint8_t sf);         \u002F* optional, may be NULL *\u002F\n    void     (*sleep_ms)(uint32_t ms);\n    void     (*deep_sleep_rtc)(uint32_t seconds);\n    uint32_t (*get_tick_ms)(void);\n    int      (*nvs_write)(uint32_t offset, const void *data, size_t len);\n    int      (*nvs_read)(uint32_t offset, void *data, size_t len);\n    void     (*log)(const char *fmt, ...);       \u002F* optional, may be NULL *\u002F\n} FLHal;\n```\n\nProvided implementations:\n- [`shard\u002Fhal\u002Fstm32\u002F`](shard\u002Fhal\u002Fstm32\u002F): STM32F4 with SX1276 via SPI1\n- [`shard\u002Fhal\u002Fhost\u002F`](shard\u002Fhal\u002Fhost\u002F): Linux loopback for host testing\n\nFor a complete porting walkthrough, see [`docs\u002Fporting.md`](docs\u002Fporting.md).\n\n---\n\n## Versioning\n\nFilum follows [Semantic Versioning 2.0.0](https:\u002F\u002Fsemver.org).\n\n| Component | Current |\n|---|---|\n| Library | `0.2.0` |\n| Wire protocol | `FL_WIRE_VERSION 1` |\n\n```c\n\u002F* Runtime check *\u002F\nif (!fl_version_compatible()) {\n    \u002F* headers and library MAJOR version differ *\u002F\n}\nprintf(\"Filum %s\\n\", fl_version_string());\n```\n\nShards and Heralds must use the same `FL_WIRE_VERSION` to interoperate. See [CHANGELOG.md](CHANGELOG.md) for migration notes between versions.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for build setup, code style, and the PR checklist.\n\nQuick summary:\n- All 9 tests must pass: `cd build && ctest --output-on-failure`\n- Every new public function needs Doxygen `@param` \u002F `@retval` comments\n- No dynamic allocation: use static buffers or return `FL_ERR_CAPACITY`\n- Update [CHANGELOG.md](CHANGELOG.md) under `[Unreleased]`\n\nTo report a security vulnerability, see [SECURITY.md](SECURITY.md). Do not open a public issue.\n\n---\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","Filum是一个为MCU级别的边缘设备通过LoRa进行联邦学习的纯C语言库。它主要使用C99标准编写，不依赖于任何动态内存分配或堆，适用于资源受限环境下的机器学习任务。项目支持Q8稀疏梯度更新、多种联邦聚合算法（如FedAvg和FedProx）以及差分隐私保护机制，确保了数据传输的安全性和效率。此外，Filum还集成了ECDH加密技术以增强通信安全。该库特别适合在物联网场景中部署，尤其是在需要利用本地传感器数据但又受限于计算能力和带宽的微控制器上，例如STM32系列搭配SX1276 LoRa模块的应用场合。",2,"2026-06-11 04:07:12","CREATED_QUERY"]