SDK for TESAIoT Dev Kit
API reference & tutorials (ModusToolbox)
Loading...
Searching...
No Matches
WiFi saved networks (OPTIGA store)

Functions

bool wifi_saved_probe_start (void)
 Sends the OPTIGA presence probe; strict start → ready → finish order.
bool wifi_saved_probe_ready (void)
 May be true immediately after probe_start() — handle the cached fast path.
void wifi_saved_probe_finish (void)
 Exactly once per probe, only after probe_ready(); sets the OPTIGA / RAM-fallback state.
bool wifi_saved_read_start (int index)
 One in-flight slot read at a time; a false return skips the slot.
bool wifi_saved_read_ready (void)
 Not ready means try again next tick.
bool wifi_saved_read_result (wifi_saved_entry_t *out)
 Fills a caller-owned entry; false means the slot is absent.
int wifi_saved_find (const char *ssid)
 Find a saved network by SSID.
bool wifi_saved_erase (int index)
 Erase a saved network from OPTIGA slot.
bool wifi_saved_add (const char *ssid, const char *password, uint8_t security)
 Add or update a network in saved list. If SSID already exists, updates in-place. If not, uses the first empty slot (or overwrites slot 0 if full).
int wifi_saved_load_all (wifi_saved_entry_t *entries)
 Load all saved networks into an array.
bool wifi_saved_load (int index, wifi_saved_entry_t *out)
 Load a saved network from OPTIGA slot.
bool wifi_saved_store (int index, const wifi_saved_entry_t *entry)
 Store a network entry to OPTIGA slot.
int wifi_saved_count (void)
 Count number of valid saved networks.

Detailed Description

Header: wifi_saved.h. Implementation: archived wifi_saved.c (libbento_cm55.a). Up to WIFI_SAVED_MAX (6) entries of 106 packed bytes each, one per OPTIGA Type 3 data object, with LRU eviction and a RAM fallback when OPTIGA is absent. Two access styles exist and the template uses both deliberately: the synchronous wifi_saved_load_all() (~1.5 s, used for post-mutation refresh) and the asynchronous probe + slot-read machinery (used for the initial page load so the screen never blocks). The template caller for everything in this topic is proj_cm55/modules/page-components/wifi_connect/demo/wifi_connect_native/wifi_connect_native.c, and every one of its callbacks re-checks s_ctx.ui == NULL first because the page can be destroyed while IPC is in flight.

Variant
mtb-mpy and mtb-only

Function Documentation

◆ wifi_saved_probe_start()

bool wifi_saved_probe_start ( void )

Sends the OPTIGA presence probe; strict start → ready → finish order.

Contract
Sends the OPTIGA presence probe and returns immediately. Strict order: probe_start() then poll probe_ready() then probe_finish(). The caller polls at 100 ms and deletes its own timer before advancing. LVGL timer / GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3b: Probe poll — fires every 100ms until OPTIGA probe completes */
static void wifi_native_saved_probe_poll_cb(lv_timer_t *timer)
{
if (!wifi_saved_probe_ready()) return;
lv_timer_delete(timer);
/* Start async slot loading — state machine: send → poll → next */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
}
/* Phase 3a: Start async OPTIGA probe */
static void wifi_native_deferred_load_saved_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) return;
/* Already probed (cached) — skip poll, go straight to slot loading */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
} else {
/* Probe IPC in flight — poll until ready */
lv_timer_create(wifi_native_saved_probe_poll_cb, 100, NULL);
}
}

◆ wifi_saved_probe_ready()

bool wifi_saved_probe_ready ( void )

May be true immediately after probe_start() — handle the cached fast path.

Contract
May be true immediately after probe_start() (cached result) — the caller must handle that fast path rather than unconditionally arming a poll timer, which is why the template has two call sites. GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3b: Probe poll — fires every 100ms until OPTIGA probe completes */
static void wifi_native_saved_probe_poll_cb(lv_timer_t *timer)
{
if (!wifi_saved_probe_ready()) return;
lv_timer_delete(timer);
/* Start async slot loading — state machine: send → poll → next */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
}
/* Phase 3a: Start async OPTIGA probe */
static void wifi_native_deferred_load_saved_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) return;
/* Already probed (cached) — skip poll, go straight to slot loading */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
} else {
/* Probe IPC in flight — poll until ready */
lv_timer_create(wifi_native_saved_probe_poll_cb, 100, NULL);
}
}

◆ wifi_saved_probe_finish()

void wifi_saved_probe_finish ( void )

Exactly once per probe, only after probe_ready(); sets the OPTIGA / RAM-fallback state.

Contract
Called exactly once per probe and only after probe_ready() returned true; it sets the OPTIGA / RAM-fallback state the reads depend on. After it, wifi_saved_load() skips the probe. GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3b: Probe poll — fires every 100ms until OPTIGA probe completes */
static void wifi_native_saved_probe_poll_cb(lv_timer_t *timer)
{
if (!wifi_saved_probe_ready()) return;
lv_timer_delete(timer);
/* Start async slot loading — state machine: send → poll → next */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
}
/* Phase 3a: Start async OPTIGA probe */
static void wifi_native_deferred_load_saved_cb(lv_timer_t *timer)
{
lv_timer_delete(timer);
if (s_ctx.ui == NULL) return;
/* Already probed (cached) — skip poll, go straight to slot loading */
s_saved_count = 0;
s_saved_load_idx = 0;
s_slot_state = SLOT_SEND;
lv_timer_create(wifi_native_saved_slot_cb, 50, NULL);
} else {
/* Probe IPC in flight — poll until ready */
lv_timer_create(wifi_native_saved_probe_poll_cb, 100, NULL);
}
}

◆ wifi_saved_read_start()

bool wifi_saved_read_start ( int index)

One in-flight slot read at a time; a false return skips the slot.

Contract
One in-flight read at a time: an explicit SEND then POLL machine, one slot per 50 ms tick; never re-issue before read_ready(). A false return is a real error path — the template skips that slot and advances, it does not retry or abort. Only after the probe sequence completed (the slot callback is created only post-probe_finish()). GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3c: Async slot read — state machine: SEND (instant) → POLL (instant) */
static void wifi_native_saved_slot_cb(lv_timer_t *timer)
{
if (s_ctx.ui == NULL) { lv_timer_delete(timer); return; }
if (s_slot_state == SLOT_SEND) {
if (s_saved_load_idx >= WIFI_SAVED_MAX) {
lv_timer_delete(timer);
aic_wifi_set_saved_networks(s_ctx.ui, s_saved_buf, s_saved_count);
return;
}
/* Send IPC for this slot — returns instantly */
if (wifi_saved_read_start(s_saved_load_idx)) {
s_slot_state = SLOT_POLL;
} else {
/* Send failed — skip this slot */
s_saved_load_idx++;
}
return;
}
/* SLOT_POLL: check if IPC response is ready */
if (!wifi_saved_read_ready()) return; /* Not ready — try next tick */
memcpy(&s_saved_buf[s_saved_count], &tmp, sizeof(tmp));
s_saved_count++;
}
s_saved_load_idx++;
s_slot_state = SLOT_SEND; /* Next tick: send for next slot */
}

◆ wifi_saved_read_ready()

bool wifi_saved_read_ready ( void )

Not ready means try again next tick.

Contract
Not ready means try again next tick — return from the timer callback and leave the state machine in POLL. GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3c: Async slot read — state machine: SEND (instant) → POLL (instant) */
static void wifi_native_saved_slot_cb(lv_timer_t *timer)
{
if (s_ctx.ui == NULL) { lv_timer_delete(timer); return; }
if (s_slot_state == SLOT_SEND) {
if (s_saved_load_idx >= WIFI_SAVED_MAX) {
lv_timer_delete(timer);
aic_wifi_set_saved_networks(s_ctx.ui, s_saved_buf, s_saved_count);
return;
}
/* Send IPC for this slot — returns instantly */
if (wifi_saved_read_start(s_saved_load_idx)) {
s_slot_state = SLOT_POLL;
} else {
/* Send failed — skip this slot */
s_saved_load_idx++;
}
return;
}
/* SLOT_POLL: check if IPC response is ready */
if (!wifi_saved_read_ready()) return; /* Not ready — try next tick */
memcpy(&s_saved_buf[s_saved_count], &tmp, sizeof(tmp));
s_saved_count++;
}
s_saved_load_idx++;
s_slot_state = SLOT_SEND; /* Next tick: send for next slot */
}

◆ wifi_saved_read_result()

bool wifi_saved_read_result ( wifi_saved_entry_t * out)

Fills a caller-owned entry; false means the slot is absent.

Contract
Fills a caller-owned stack wifi_saved_entry_t; copy it out immediately. A false return is non-fatal — the slot is simply absent from the result and the index advances. GFX-task context.
Variant
mtb-mpy and mtb-only
/* Phase 3c: Async slot read — state machine: SEND (instant) → POLL (instant) */
static void wifi_native_saved_slot_cb(lv_timer_t *timer)
{
if (s_ctx.ui == NULL) { lv_timer_delete(timer); return; }
if (s_slot_state == SLOT_SEND) {
if (s_saved_load_idx >= WIFI_SAVED_MAX) {
lv_timer_delete(timer);
aic_wifi_set_saved_networks(s_ctx.ui, s_saved_buf, s_saved_count);
return;
}
/* Send IPC for this slot — returns instantly */
if (wifi_saved_read_start(s_saved_load_idx)) {
s_slot_state = SLOT_POLL;
} else {
/* Send failed — skip this slot */
s_saved_load_idx++;
}
return;
}
/* SLOT_POLL: check if IPC response is ready */
if (!wifi_saved_read_ready()) return; /* Not ready — try next tick */
memcpy(&s_saved_buf[s_saved_count], &tmp, sizeof(tmp));
s_saved_count++;
}
s_saved_load_idx++;
s_slot_state = SLOT_SEND; /* Next tick: send for next slot */
}

◆ wifi_saved_find()

int wifi_saved_find ( const char * ssid)

Find a saved network by SSID.

Resolves SSID to storage slot — UI list index is not the slot index.

Parameters
ssidSSID to search for
Returns
Slot index 0..5 if found, -1 if not found
Contract
UI list index is not the storage slot index — the single most important fact in this topic. Resolve SSID to slot with this before any erase; returns < 0 on a miss. Internally driven by wifi_saved_load(), so the storage layer (probe) must be up.
Variant
mtb-mpy and mtb-only
static void wifi_native_on_saved_delete(int saved_index)
{
/* Find the OPTIGA slot index for this entry */
if (!s_ctx.ui) return;
if (saved_index < 0 || saved_index >= s_ctx.ui->saved_count) return;
/* Find the matching slot by SSID (saved_index is UI order, need OPTIGA slot) */
const char *ssid = s_ctx.ui->saved_entries[saved_index].ssid;
int slot = wifi_saved_find(ssid);
if (slot >= 0) {
}
/* Refresh saved networks UI */
wifi_native_refresh_saved();
}

◆ wifi_saved_erase()

bool wifi_saved_erase ( int index)

Erase a saved network from OPTIGA slot.

Erases one slot, resolved via wifi_saved_find(); recovery is a full refresh.

Parameters
indexSlot index 0..5
Returns
true on success
Contract
Guarded by slot >= 0 from wifi_saved_find(). The bool return is deliberately ignored by the shipped caller: recovery is a full UI refresh through wifi_saved_load_all().
Variant
mtb-mpy and mtb-only
static void wifi_native_on_saved_delete(int saved_index)
{
/* Find the OPTIGA slot index for this entry */
if (!s_ctx.ui) return;
if (saved_index < 0 || saved_index >= s_ctx.ui->saved_count) return;
/* Find the matching slot by SSID (saved_index is UI order, need OPTIGA slot) */
const char *ssid = s_ctx.ui->saved_entries[saved_index].ssid;
int slot = wifi_saved_find(ssid);
if (slot >= 0) {
}
/* Refresh saved networks UI */
wifi_native_refresh_saved();
}

◆ wifi_saved_add()

bool wifi_saved_add ( const char * ssid,
const char * password,
uint8_t security )

Add or update a network in saved list. If SSID already exists, updates in-place. If not, uses the first empty slot (or overwrites slot 0 if full).

Saves credentials only after a confirmed successful association.

Parameters
ssidNetwork SSID
passwordNetwork password
securitySecurity type
Returns
true on success
Contract
Called only after a confirmed successful association — never on the credential-entry path, never speculatively. The connect is retried exactly once first (a WCM state-transition quirk after a scan). Callers do not pre-populate an entry: dedupe via find, zero-fill, NUL-termination at ssid[32] / password[64], flags = 0x01 (auto-connect) and last_used are all internal (wifi_saved.c:266-304), and the write goes through wifi_saved_store(). The return is not checked by the shipped caller; the follow-up refresh is the de-facto verification.
Variant
mtb-mpy and mtb-only
/* ...context: inside the deferred connect callback, after a confirmed association ... */
/* Auto-retry: cy_wcm_connect_ap() often fails on the first attempt
* after a scan due to internal WCM state transition. Retry once. */
bool ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
if (!ok) {
ok = wifi_manager_connect(s_ctx.pending_ssid, s_ctx.pending_password);
}
if (!ok) {
aic_wifi_set_state(s_ctx.ui, WIFI_STATE_ERROR);
aic_wifi_show_error(s_ctx.ui, WIFI_ERR_AUTH_FAILED, "Connect failed");
return;
}
/* Auto-save credentials on successful connect */
wifi_saved_add(s_ctx.pending_ssid, s_ctx.pending_password, s_ctx.pending_security);
wifi_native_refresh_saved();
wifi_native_scan_and_update();
}

◆ wifi_saved_load_all()

int wifi_saved_load_all ( wifi_saved_entry_t * entries)

Load all saved networks into an array.

Synchronous full read (~1.5 s) into wifi_saved_entry_t[WIFI_SAVED_MAX].

Parameters
entriesOutput array (must have WIFI_SAVED_MAX elements)
Returns
Number of valid entries loaded (0..6)
Contract
The buffer must be wifi_saved_entry_t[WIFI_SAVED_MAX] — there is no capacity argument. Returns the populated count (compacted), not a status. This is the synchronous path (a wifi_saved_load() loop, ~1.5 s — see the comment at wifi_connect_native.c:136-143), used for post-mutation refresh; the initial page load uses the async probe + read machinery instead. Keep that split.
Variant
mtb-mpy and mtb-only
static void wifi_native_refresh_saved(void)
{
if (!s_ctx.ui) return;
int count = wifi_saved_load_all(entries);
aic_wifi_set_saved_networks(s_ctx.ui, entries, count);
}

◆ wifi_saved_load()

bool wifi_saved_load ( int index,
wifi_saved_entry_t * out )

Load a saved network from OPTIGA slot.

Reads one slot synchronously; internal — consumers use load_all()/find().

Parameters
indexSlot index 0..5
outOutput entry (filled on success)
Returns
true if a valid entry was read, false if empty/error
Contract
Reads one slot synchronously. Five callers, all internal to wifi_saved.c (:252, :271, :282, :312, :325). Consumers use wifi_saved_load_all() or wifi_saved_find(); the authored example says so and shows the one legitimate reason to reach for it.
Variant
mtb-mpy and mtb-only
Example (authored — no shipped call site)
static void bento_ex_wifi_saved_load(void)
{
/* Supported consumer path: whole-table load + SSID resolve. */
wifi_saved_entry_t table[WIFI_SAVED_MAX]; /* exact size — no cap arg */
int n = wifi_saved_load_all(table); /* populated count 0..6 */
int slot = wifi_saved_find("MyNetwork"); /* -1 on miss; slot != UI
* list index */
(void)n;
/* The primitive underneath (internal to wifi_saved.c in shipped
* firmware) — false means empty slot or read error, same as absent: */
if (slot >= 0) {
if (!wifi_saved_load(slot, &one)) {
return; /* slot empty since find */
}
(void)one;
}
}

◆ wifi_saved_store()

bool wifi_saved_store ( int index,
const wifi_saved_entry_t * entry )

Store a network entry to OPTIGA slot.

Writes one slot; internal to wifi_saved_add() — direct calls bypass dedupe.

Parameters
indexSlot index 0..5
entryEntry to write
Returns
true on success
Contract
Writes one slot. Its only caller is internal — wifi_saved.c:304, from inside wifi_saved_add() after that function has built the entry. Consumers use wifi_saved_add(); calling this directly bypasses dedupe, NUL-termination and the LRU timestamp.
Variant
mtb-mpy and mtb-only
Example (authored — no shipped call site)
static void bento_ex_wifi_saved_store(void)
{
/* RIGHT — after the connect succeeded (retried once, per the shipped
* UI path), let add() place the entry: */
if (!wifi_saved_add("MyNetwork", "correct-horse", 3u /* WPA2 */)) {
return; /* slot machinery refused */
}
/* The primitive underneath is store(index, entry) — internal in
* shipped firmware. Shown for completeness only; if you find
* yourself filling a wifi_saved_entry_t by hand, you are
* re-implementing add() without its dedupe/LRU. */
}

◆ wifi_saved_count()

int wifi_saved_count ( void )

Count number of valid saved networks.

Returns 0..6; zero callers workspace-wide.

Returns
Count 0..6
Contract
Returns 0..6. Zero callers workspace-wide — fourteen identical definitions across sibling kits and never a call. The shipped UI derives the count from wifi_saved_load_all()'s return instead. Authored.
Variant
mtb-mpy and mtb-only
Example (authored — no shipped call site)
static void bento_ex_wifi_saved_count(void)
{
/* Quick has-any-credentials decision (e.g. skip a "saved networks"
* section entirely when empty): */
if (wifi_saved_count() == 0) {
return; /* nothing saved — hide the list */
}
/* If you are about to render the entries, prefer one load_all() —
* its return value IS the count. */
}