Skip to content

File espos_wifi_sm.h

File List > espos_wifi > include > espos_wifi_sm.h

Go to the documentation of this file

/*
 * SPDX-FileCopyrightText: 2026 Dirk Wahrheit
 * SPDX-License-Identifier: Apache-2.0
 *
 * espos_wifi state machine — pure C, no esp_wifi calls. The driver side is
 * injected through espos_wifi_port_t so the machine runs unchanged on the
 * host under test. See docs/wifi.md for the state diagram.
 */
#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
#include "sdkconfig.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifdef CONFIG_ESPOS_WIFI_MAX_NETWORKS
#define ESPOS_WIFI_MAX_NETWORKS CONFIG_ESPOS_WIFI_MAX_NETWORKS
#else
#define ESPOS_WIFI_MAX_NETWORKS 4
#endif

typedef enum {
    ESPOS_WIFI_ST_DISABLED = 0,   /* sta_enabled == false */
    ESPOS_WIFI_ST_UNCONFIGURED,   /* no network has an SSID */
    ESPOS_WIFI_ST_CONNECTING,     /* connect issued, waiting for association */
    ESPOS_WIFI_ST_OBTAINING_IP,   /* associated, waiting for DHCP */
    ESPOS_WIFI_ST_CONNECTED,      /* has an IP */
    ESPOS_WIFI_ST_BACKOFF,        /* waiting before the next attempt */
} espos_wifi_state_t;

/* Our own reason codes live above the 802.11 / esp_wifi range. */
#define ESPOS_WIFI_REASON_NONE            0
#define ESPOS_WIFI_REASON_DHCP_TIMEOUT    1001
#define ESPOS_WIFI_REASON_CONNECT_TIMEOUT 1002
#define ESPOS_WIFI_REASON_LOST_IP         1003
#define ESPOS_WIFI_REASON_CONFIG_CHANGE   1004
#define ESPOS_WIFI_REASON_DISABLED        1005

typedef struct {
    char ssid[33];
    char psk[65];
    bool has_bssid;
    uint8_t bssid[6];
} espos_wifi_net_t;

typedef struct {
    bool sta_enabled;
    espos_wifi_net_t nets[ESPOS_WIFI_MAX_NETWORKS];
    size_t net_count;                /* nets[0..net_count) have an SSID, priority order */
    uint32_t backoff_max_ms;
    uint32_t dhcp_timeout_ms;
    uint32_t connect_timeout_ms;
    bool portal_enabled;
    uint32_t portal_after_ms;
} espos_wifi_cfg_t;

typedef enum {
    ESPOS_WIFI_EV_START,          /* cfg loaded; begin */
    ESPOS_WIFI_EV_STOP,           /* shut down (deinit) */
    ESPOS_WIFI_EV_CONFIG,         /* cfg replaced (arg = const espos_wifi_cfg_t *) */
    ESPOS_WIFI_EV_STA_CONNECTED,  /* associated (arg = const espos_wifi_link_t *) */
    ESPOS_WIFI_EV_STA_DISCONNECTED, /* arg = int reason (802.11/esp_wifi code) */
    ESPOS_WIFI_EV_GOT_IP,         /* arg = const espos_wifi_ip_t * */
    ESPOS_WIFI_EV_LOST_IP,
    ESPOS_WIFI_EV_TIMER,          /* the single SM timer expired */
    ESPOS_WIFI_EV_PORTAL_CLIENT,  /* arg = int station count on the portal AP */
    ESPOS_WIFI_EV_PORTAL_FAILED,  /* the port could not bring the AP up */
    ESPOS_WIFI_EV_PORTAL_RECONFIG, /* portal SSID/password changed */
} espos_wifi_event_t;

typedef struct {
    char ssid[33];
    uint8_t bssid[6];
    uint8_t channel;
    int8_t rssi;
} espos_wifi_link_t;

typedef struct {
    char ip[16];
    char netmask[16];
    char gateway[16];
} espos_wifi_ip_t;

/* Driver actions requested by the machine. All are called with the SM lock
 * held by the caller of espos_wifi_sm_event(); keep them short. */
typedef struct {
    esp_err_t (*connect)(void *ctx, const espos_wifi_net_t *net);
    esp_err_t (*disconnect)(void *ctx);
    esp_err_t (*portal_start)(void *ctx);
    esp_err_t (*portal_stop)(void *ctx);
    void (*arm_timer)(void *ctx, uint32_t ms);   /* one timer; re-arming replaces; must fire no earlier than now_ms()+ms */
    void (*cancel_timer)(void *ctx);
    uint32_t (*now_ms)(void *ctx);
    uint32_t (*random)(void *ctx);
    void (*status_changed)(void *ctx);           /* something observable changed */
} espos_wifi_port_t;

typedef struct {
    espos_wifi_state_t state;
    bool sta_enabled;
    int net_index;               /* network being tried / connected, -1 none */
    espos_wifi_link_t link;      /* valid in OBTAINING_IP / CONNECTED */
    espos_wifi_ip_t ip;          /* valid in CONNECTED */
    int reason;                  /* last failure reason (0 = none) */
    uint32_t backoff_until_ms;   /* valid in BACKOFF (port clock) */
    uint32_t attempt;            /* attempts since last success */
    uint32_t round;              /* full rounds over all networks since last success */
    uint32_t connected_since_ms; /* valid in CONNECTED */
    uint32_t disconnected_since_ms; /* when we last lost/lacked a connection */
    uint32_t connect_count;
    uint32_t disconnect_count;
    bool portal_active;
    int portal_clients;
} espos_wifi_sm_status_t;

typedef struct espos_wifi_sm {
    const espos_wifi_port_t *port;
    void *port_ctx;
    espos_wifi_cfg_t cfg;
    espos_wifi_sm_status_t st;
    bool started;
    bool timer_is_dhcp;          /* which timeout the armed timer represents */
    bool timer_is_portal;
    uint32_t portal_due_ms;      /* when the portal should come up (0 = not scheduled) */
    uint32_t state_due_ms;       /* deadline of the armed state timeout (0 = none) */
    uint32_t timer_due_ms;       /* deadline of the armed timer (0 = none); early fires are stale */
    /* An esp_wifi_connect() is outstanding. Distinct from state == CONNECTING,
     * which stays set between the attempts of a round: the portal must not be
     * raised inside an association (espOS #144) but must be raised in the gap
     * between two of them.
     *
     * Appended rather than inserted: a mid-struct field shifts every later
     * member's offset, so a caller built against the old header reads the wrong
     * ones. Appending changes only sizeof -- still an ABI break, hence the bump,
     * but the narrower kind. */
    bool connect_in_flight;
} espos_wifi_sm_t;

void espos_wifi_sm_init(espos_wifi_sm_t *sm, const espos_wifi_port_t *port, void *port_ctx,
                        const espos_wifi_cfg_t *cfg);
void espos_wifi_sm_event(espos_wifi_sm_t *sm, espos_wifi_event_t ev, const void *arg);
const espos_wifi_sm_status_t *espos_wifi_sm_status(const espos_wifi_sm_t *sm);
/* Remaining backoff in ms (0 if not in BACKOFF). */
uint32_t espos_wifi_sm_backoff_remaining_ms(const espos_wifi_sm_t *sm);

const char *espos_wifi_state_str(espos_wifi_state_t s);
/* Human explanation for a disconnect reason (esp_wifi codes and ours). */
const char *espos_wifi_reason_str(int reason);
/* Deprecated: espos_net_backoff_ms() (espos_net.h) is the same curve — 1 s ·
 * 2^round, capped, ±25 % jitter, floor 250 ms — for every retry loop in espOS.
 * Kept as a standalone copy so the state machine has no dependency and builds
 * on its own in test/host/espos_wifi_test; removed in 0.9. */
uint32_t espos_wifi_backoff_ms(uint32_t round, uint32_t cap_ms, uint32_t rnd);

#ifdef __cplusplus
}
#endif