odesa-light/fds.md

229 lines
4.4 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Alright, lets do a **minimal, no-magic FDS example** that actually works for
**nRF52840 + SoftDevice + “user might pull power instantly”**.
Ill keep this focused on *one tiny config record*.
---
## What this example assumes
* You already have **SoftDevice enabled**
* You can handle **SoftDevice events** somewhere in your app
* You want to store ~56 bytes reliably
This is **pure SoftDevice FDS**, no Embassy storage abstractions.
---
## 1⃣ Define your config format
Keep it boring and explicit:
```rust
#[repr(C)]
#[derive(Clone, Copy)]
struct Config {
magic: u8,
version: u8,
data: [u8; 4],
}
const CONFIG_MAGIC: u8 = 0x42;
const CONFIG_VERSION: u8 = 1;
```
Total: **6 bytes** 👍
---
## 2⃣ FDS constants
These must be **non-zero** and stable forever:
```rust
const FDS_FILE_ID: u16 = 0x1111;
const FDS_RECORD_KEY: u16 = 0x2222;
```
---
## 3⃣ Initialize FDS
Call this **after SoftDevice is enabled**:
```rust
use nrf_softdevice::raw;
pub fn fds_init() {
unsafe {
let ret = raw::fds_init();
assert_eq!(ret, raw::NRF_SUCCESS);
}
}
```
⚠️ FDS is async — init finishes when you receive `FDS_EVT_INIT`.
---
## 4⃣ Handling FDS events (important!)
You **must** forward SoftDevice events to FDS:
```rust
pub fn on_sd_evt(evt: *const raw::ble_evt_t) {
unsafe {
raw::fds_sys_evt_handler(evt as u32);
}
}
```
And somewhere in your SD event handler:
```rust
match evt_id {
raw::FDS_EVT_INIT => {
// FDS ready
}
raw::FDS_EVT_WRITE => {
// Write completed safely
}
raw::FDS_EVT_UPDATE => {
// Update completed
}
_ => {}
}
```
You dont need to block — just observe completion.
---
## 5⃣ Read config from flash
```rust
pub fn load_config() -> Option<Config> {
unsafe {
let mut desc = core::mem::zeroed::<raw::fds_record_desc_t>();
let mut token = core::mem::zeroed::<raw::fds_find_token_t>();
let ret = raw::fds_record_find(
FDS_FILE_ID,
FDS_RECORD_KEY,
&mut desc,
&mut token,
);
if ret != raw::NRF_SUCCESS {
return None;
}
let mut record = core::mem::zeroed::<raw::fds_flash_record_t>();
raw::fds_record_open(&desc, &mut record);
let cfg = *(record.p_data as *const Config);
raw::fds_record_close(&desc);
if cfg.magic == CONFIG_MAGIC && cfg.version == CONFIG_VERSION {
Some(cfg)
} else {
None
}
}
}
```
On boot:
* If `None` → use defaults
* If `Some(cfg)` → youre golden
---
## 6⃣ Write / update config (power-loss safe)
This is the *core value* of FDS.
```rust
pub fn save_config(cfg: &Config) {
unsafe {
let record = raw::fds_record_t {
file_id: FDS_FILE_ID,
key: FDS_RECORD_KEY,
data: raw::fds_record_chunk_t {
p_data: cfg as *const _ as *const core::ffi::c_void,
length_words: (core::mem::size_of::<Config>() + 3) / 4,
},
};
let mut desc = core::mem::zeroed::<raw::fds_record_desc_t>();
let mut token = core::mem::zeroed::<raw::fds_find_token_t>();
let ret = raw::fds_record_find(
FDS_FILE_ID,
FDS_RECORD_KEY,
&mut desc,
&mut token,
);
if ret == raw::NRF_SUCCESS {
raw::fds_record_update(&mut desc, &record);
} else {
raw::fds_record_write(&mut desc, &record);
}
}
}
```
### Why this is safe 🔒
* Old record stays valid until new one is fully written
* Power loss = either old config or new config
* Never corrupted data
---
## 7⃣ BLE write flow (typical)
```text
BLE write received
→ validate data
→ build Config
→ save_config()
→ wait for FDS_EVT_UPDATE
→ ACK to user
```
If power dies *anywhere* → still safe.
---
## 8⃣ Flash usage (dont forget this!)
In your SoftDevice config:
* Enable **FDS**
* Reserve **at least 2 pages** (3 is safer)
FDS will garbage-collect automatically.
---
## TL;DR (real talk)
For your exact requirements:
* **Yes, this is the right solution**
* **Yes, its more code than 6 bytes deserve**
* **But its bulletproof**
If you want next:
* 🧵 Wrap this in an **Embassy async interface**
* 🧪 Add CRC / version migration
* 🔄 Reduce boilerplate further
* 🧠 Explain FDS GC & page sizing
Just say the word.