ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
physical.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file physical.h
6 * @brief Layer 1 (Physical) - link bring-up and live egress-interface reporting.
7 *
8 * The "physical" link is the 802.11 radio or a wired Ethernet PHY, brought up by
9 * the vendor backend selected with PC_VENDOR_* (board_drivers/physical/<vendor>/).
10 * Failover between interfaces is owned by the network stack itself (it reselects the
11 * default route when a link drops) - this layer adds no manager and no polling
12 * tick; it only *reports* which interface currently carries outbound traffic via
13 * pc_net_egress(), read on demand from the live default route so the answer is
14 * always current.
15 *
16 * @author Douglas Quigg (dstroy0)
17 * @date 2026
18 */
19
20#ifndef PROTOCORE_PHYSICAL_H
21#define PROTOCORE_PHYSICAL_H
22
23#include "board_drivers/board_profiles/pc_platform.h" // PC_VENDOR_* selector (picks the L1 backend)
25#include "protocore_config.h" // pc_iface
26#include <stddef.h>
27#include <stdint.h>
28
29// Does the selected vendor ship a physical (L1) backend? The real bring-up (radio / Ethernet PHY / lwIP
30// netif access) lives in a per-vendor subdir - physical/esp/ today; add physical/stm/, physical/rp/, ... as
31// they land, and OR their vendor here. When 0 (host/native, or a vendor whose PHY driver is not written yet),
32// physical.cpp supplies safe no-op stubs so the target still builds and runs headless - the L1 analogue of
33// crypto/ falling back to its portable software field path.
34#if PC_VENDOR_ESP
35#define PC_PHYSICAL_HAS_BACKEND 1
36#else
37#define PC_PHYSICAL_HAS_BACKEND 0
38#endif
39
40/**
41 * @brief Connect to a WiFi access point.
42 *
43 * Starts the station join and returns immediately; it does not block waiting for
44 * association. Poll wifi_ready() to check link status.
45 *
46 * @param ssid Network SSID (null-terminated).
47 * @param password WPA2 passphrase (null-terminated).
48 * @return Always returns true (the join is fire-and-forget).
49 */
50bool init_wifi_physical(const char *ssid, const char *password);
51
52/** @brief True if the WiFi station link is up (associated + an IP is assigned). */
53bool wifi_ready();
54
55/**
56 * @brief Bring the WiFi radio up in station mode WITHOUT associating to an AP.
57 *
58 * For raw-radio use that needs the PHY running but no IP link: ESP-NOW peer messaging and
59 * promiscuous capture. Pins the radio to @p channel (1..14) when non-zero; pass 0 to leave the
60 * channel to a capture layer that sets its own (services/radio/promisc). Returns immediately.
61 *
62 * @return true once the radio is started (always true on host builds).
63 */
64bool init_wifi_radio_physical(uint8_t channel);
65
66/**
67 * @brief Bring up a softAP, enabling AP+STA coexistence so a station link can run alongside it.
68 *
69 * @param ssid softAP SSID (null-terminated).
70 * @param password softAP passphrase (null-terminated; >= 8 chars for WPA2, "" for an open AP).
71 * @return true if the softAP started (false on host builds).
72 */
73bool init_wifi_ap_physical(const char *ssid, const char *password);
74
75/**
76 * @brief Bring up a wired Ethernet link (PC_ENABLE_ETHERNET).
77 *
78 * A thin wrapper over the Arduino ETH library (`ETH.begin()`); the RMII PHY pins / type /
79 * clock come from the standard `ETH_PHY_*` build flags for your board. Returns immediately
80 * (bring-up is asynchronous); poll eth_ready(). The egress reporting already classifies a
81 * wired route as pc_iface::PC_IFACE_ETH, so the server accepts on the link once it has an IP.
82 *
83 * @return true if ETH.begin() started the driver; false if Ethernet is disabled at build
84 * time or the driver failed to start (and always false on host builds).
85 */
86bool init_eth_physical(void);
87
88/** @brief True if the Ethernet link is up and an IP is assigned. */
89bool eth_ready(void);
90
91/**
92 * @brief Enable IPv6 (dual-stack) on the Wi-Fi interface (PC_ENABLE_IPV6).
93 *
94 * Turns on IPv6 for the netif so it acquires a SLAAC link-local address and, if the network
95 * advertises a prefix, a global address. Returns immediately (address configuration is
96 * asynchronous); poll pc_ipv6_ready(). The listeners already bind IPADDR_TYPE_ANY, so the server
97 * answers over IPv6 as soon as an address is up.
98 *
99 * @return true if IPv6 was enabled; false if disabled at build time or on host builds.
100 */
101bool init_ipv6_physical(void);
102
103/**
104 * @brief The interface's global (routable) IPv6 address, if it has one.
105 * @param[out] out receives the address (family pc_ip_family::PC_IP_V6) when true is returned.
106 * @return true if a valid global IPv6 address is assigned; false otherwise (incl. host builds).
107 */
108bool net_global_ipv6(pc_ip *out);
109
110/** @brief True once the interface has a global IPv6 address (see net_global_ipv6()). */
111bool pc_ipv6_ready(void);
112
113/**
114 * @brief Which interface currently carries outbound traffic.
115 *
116 * Reads the live lwIP default route, so it reflects the current state after any
117 * failover the stack performed - no polling, no cached state. Returns
118 * pc_iface::PC_IFACE_ETH / pc_iface::PC_IFACE_STA / pc_iface::PC_IFACE_AP, or pc_iface::PC_IFACE_ANY when no route is
119 * up (and on host builds).
120 */
122
123/** @brief IPv4 (network byte order) of the current egress interface, or 0 if none. */
124uint32_t pc_net_egress_ip(void);
125
126/** @brief softAP IPv4 (network byte order), or 0 if the softAP is not up (and on host builds). */
127uint32_t pc_net_ap_ip(void);
128
129/** @brief Station link RSSI in dBm, or 0 if not associated (and on host builds). */
130int8_t pc_net_rssi(void);
131
132/**
133 * @brief Copy the WiFi station interface MAC (6 bytes) into @p out.
134 *
135 * This is specifically the 802.11 STA address (what ESP-NOW and WiFi diagnostics want). It is only valid once
136 * the WiFi driver is up; on an Ethernet-only device (e.g. the P4 that never starts WiFi) it reads back as
137 * zeros. For "the MAC this device is actually using on the wire right now", regardless of link type, use
138 * pc_net_egress_mac().
139 *
140 * @return true on success; false if @p out is null or on a host build (out is left untouched).
141 */
142bool pc_net_mac(uint8_t out[6]);
143
144/**
145 * @brief Copy the MAC of the current egress interface (the live default-route netif) into @p out.
146 *
147 * Vendor- and link-neutral: returns the Ethernet PHY's MAC on a wired link, the WiFi STA MAC on a wireless
148 * one - whichever netif currently carries outbound traffic (the same interface pc_net_egress_ip() reports).
149 *
150 * @return true and fills @p out when a default interface with a 6-byte hwaddr exists; false otherwise (no
151 * egress up, @p out null, or a host build), leaving @p out untouched.
152 */
153bool pc_net_egress_mac(uint8_t out[6]);
154
155/**
156 * @brief Copy the associated SSID (null-terminated) into @p out.
157 * @return the SSID length in bytes, or 0 if not associated, @p cap is 0, or on a host build.
158 */
159size_t pc_net_ssid(char *out, size_t cap);
160
161/** @brief Station WiFi channel (1..14), or 0 if not associated (and on host builds). */
162uint8_t pc_net_channel(void);
163
164/**
165 * @brief Classify an egress IPv4 against the WiFi station / softAP IPs (pure helper,
166 * exposed for unit testing).
167 *
168 * A live egress IP equal to the station or softAP IP is that WiFi interface; any
169 * other live IP is a wired (Ethernet) route; 0 is no route.
170 *
171 * @param egress_ip Current default-route IPv4 (network order), 0 if none.
172 * @param sta_ip WiFi station IPv4 (network order), 0 if not connected.
173 * @param ap_ip softAP IPv4 (network order), 0 if the softAP is not up.
174 */
175pc_iface pc_net_classify_ip(uint32_t egress_ip, uint32_t sta_ip, uint32_t ap_ip);
176
177/* --------------------------------------------------------------------------------------------
178 * Radio control (L1 capability contract)
179 *
180 * Power save and monitor mode are properties of the radio, so they belong to the layer that owns
181 * the radio. Services used to reach for the vendor API directly, which put vendor flavor inside
182 * the core; the core's API is unaware of the vendor, and the flavoring happens at the edge, in
183 * board_drivers/physical/<vendor>/.
184 *
185 * Every entry point below returns false / does nothing when the selected vendor has no radio
186 * backend (PC_PHYSICAL_HAS_BACKEND == 0), so callers build and run headless on any target.
187 * ------------------------------------------------------------------------------------------ */
188
189/** @brief Radio power-save mode, in the library's own vocabulary. */
190enum class pc_phy_ps : uint8_t
191{
192 PC_PHY_PS_NONE = 0, ///< Radio always on: lowest latency, highest average draw.
193 PC_PHY_PS_MIN_MODEM = 1, ///< Wake on every DTIM beacon.
194 PC_PHY_PS_MAX_MODEM = 2, ///< Wake on a longer listen interval: lowest draw, highest latency.
195};
196
197/**
198 * @brief One received frame, delivered in neutral terms.
199 *
200 * Deliberately not the vendor's received-packet struct: handing that upward is what leaked the
201 * vendor into the monitor service to begin with. The FCS is already stripped.
202 *
203 * @param frame Frame bytes, valid only for the duration of the call.
204 * @param len Frame length in bytes, FCS excluded.
205 * @param rssi Received signal strength, dBm.
206 * @param channel Channel the frame arrived on.
207 */
208typedef void (*pc_phy_frame_fn)(const uint8_t *frame, uint16_t len, int8_t rssi, uint8_t channel);
209
210/** @brief Apply a power-save mode. @return false if there is no radio backend. */
211bool pc_phy_ps_set(pc_phy_ps mode);
212
213/** @brief Read the active power-save mode (PC_PHY_PS_NONE when unsupported). */
215
216/**
217 * @brief Cap transmit power.
218 * @param dbm Maximum transmit power in whole dBm; the backend converts to its own unit.
219 * @return false if there is no radio backend.
220 */
221bool pc_phy_tx_power_set(int8_t dbm);
222
223/** @brief Enter monitor mode on @p channel, delivering frames to @p cb. */
224bool pc_phy_monitor_begin(uint8_t channel, pc_phy_frame_fn cb);
225
226/** @brief Retune monitor mode to @p channel. */
227void pc_phy_monitor_set_channel(uint8_t channel);
228
229/** @brief Leave monitor mode. */
230void pc_phy_monitor_end(void);
231
232#endif
Layer 3 (Network) - a family-tagged IP address (IPv4 or IPv6) with RFC-faithful text parsing,...
The one vendor/die selector for the whole library.
bool pc_phy_ps_set(pc_phy_ps mode)
Apply a power-save mode.
Definition physical.cpp:104
void pc_phy_monitor_set_channel(uint8_t channel)
Retune monitor mode to channel.
Definition physical.cpp:120
bool pc_net_mac(uint8_t out[6])
Copy the WiFi station interface MAC (6 bytes) into out.
bool pc_ipv6_ready(void)
True once the interface has a global IPv6 address (see net_global_ipv6()).
Definition physical.cpp:73
void(* pc_phy_frame_fn)(const uint8_t *frame, uint16_t len, int8_t rssi, uint8_t channel)
One received frame, delivered in neutral terms.
Definition physical.h:208
pc_iface pc_net_classify_ip(uint32_t egress_ip, uint32_t sta_ip, uint32_t ap_ip)
Classify an egress IPv4 against the WiFi station / softAP IPs (pure helper, exposed for unit testing)...
Definition physical.cpp:20
uint32_t pc_net_egress_ip(void)
IPv4 (network byte order) of the current egress interface, or 0 if none.
Definition physical.cpp:77
pc_phy_ps pc_phy_ps_get(void)
Read the active power-save mode (PC_PHY_PS_NONE when unsupported).
Definition physical.cpp:108
bool eth_ready(void)
True if the Ethernet link is up and an IP is assigned.
Definition physical.cpp:61
bool init_wifi_radio_physical(uint8_t channel)
Bring the WiFi radio up in station mode WITHOUT associating to an AP.
Definition physical.cpp:49
bool pc_phy_monitor_begin(uint8_t channel, pc_phy_frame_fn cb)
Enter monitor mode on channel, delivering frames to cb.
Definition physical.cpp:116
size_t pc_net_ssid(char *out, size_t cap)
Copy the associated SSID (null-terminated) into out.
Definition physical.cpp:126
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
Definition physical.cpp:45
int8_t pc_net_rssi(void)
Station link RSSI in dBm, or 0 if not associated (and on host builds).
Definition physical.cpp:89
bool init_wifi_physical(const char *ssid, const char *password)
Connect to a WiFi access point.
Definition physical.cpp:41
void pc_phy_monitor_end(void)
Leave monitor mode.
Definition physical.cpp:123
uint32_t pc_net_ap_ip(void)
softAP IPv4 (network byte order), or 0 if the softAP is not up (and on host builds).
Definition physical.cpp:85
bool init_wifi_ap_physical(const char *ssid, const char *password)
Bring up a softAP, enabling AP+STA coexistence so a station link can run alongside it.
Definition physical.cpp:53
bool init_ipv6_physical(void)
Enable IPv6 (dual-stack) on the Wi-Fi interface (PC_ENABLE_IPV6).
Definition physical.cpp:65
pc_phy_ps
Radio power-save mode, in the library's own vocabulary.
Definition physical.h:191
@ PC_PHY_PS_MIN_MODEM
Wake on every DTIM beacon.
@ PC_PHY_PS_NONE
Radio always on: lowest latency, highest average draw.
@ PC_PHY_PS_MAX_MODEM
Wake on a longer listen interval: lowest draw, highest latency.
bool net_global_ipv6(pc_ip *out)
The interface's global (routable) IPv6 address, if it has one.
Definition physical.cpp:69
bool init_eth_physical(void)
Bring up a wired Ethernet link (PC_ENABLE_ETHERNET).
Definition physical.cpp:57
pc_iface pc_net_egress(void)
Which interface currently carries outbound traffic.
Definition physical.cpp:81
bool pc_phy_tx_power_set(int8_t dbm)
Cap transmit power.
Definition physical.cpp:112
uint8_t pc_net_channel(void)
Station WiFi channel (1..14), or 0 if not associated (and on host builds).
Definition physical.cpp:134
bool pc_net_egress_mac(uint8_t out[6])
Copy the MAC of the current egress interface (the live default-route netif) into out.
User-facing configuration for ProtoCore.
pc_iface
Network interface a connection arrived on (for per-route filtering).
A v4 or v6 address in network (big-endian) byte order.
Definition ip.h:52