SDK
AO_Net
The network of a Kode Dot: ping, HTTP, UDP and MQTT as jobs on two workers, the download loan, the screen upload and the Kode channel host.
What it is
Once a Kode Dot has joined a Wi-Fi network, AO_Net lets the console and your app reach other computers. It pings a host, fetches a web page over HTTP or HTTPS, sends a UDP datagram and keeps an MQTT client connected to a broker. It also streams a downloaded file to whoever installs it, uploads a picture of the screen to the cloud, and runs the Kode channel, the session through which the account owner sends console commands.
lwIP runs on the P4, so sockets, HTTP, MQTT and ICMP never use the C5's RPC channel. The C5 only carries frames. That is why AO_Net is not part of sdk/kaos/radio: a ping can run while a 38 s Wi-Fi scan runs, and neither waits for the other. A socket is memory in the P4, not a part of the board, so AO_Net owns no resource. It keeps one piece of state, the MQTT client.
Nothing in AO_Net blocks. A name lookup, the start of a TLS connection and a ping of four echoes all run as jobs on a worker task. The Kode channel is described on internals/kode-channel; this page covers only how AO_Net hosts it.
AO_Net also takes part in the board's rest (sdk/kaos/lowpower). Asleep, the channel and an MQTT client of yours stay connected, and AO_Net tells the rest when it next has work, so the board wakes for it.
How it is connected
flowchart LR req["Console, kode_mqtt_*()<br/>AO_Radio, AO_Display"] radio["AO_Radio"] subgraph p4["On the P4"] ao["AO_Net<br/>priority 2"] -- "kdrv_net_run()" --> w1["net_worker"] ao -- "stream jobs" --> w2["net_stream"] end req -- "NET_*, MQTT_*_REQ_SIG" --> ao radio -- "WIFI_STATE_SIG" --> ao w1 -- "lwIP, esp-mqtt, TLS" --> net["Network"] ao -- "MQTT_MSG_SIG,<br/>NET_*_SIG" --> subs["Subscribers"] class ao,radio ao class net ext class subs app
Priority and options
AO_Net runs at priority 2 (KODE_PRIO_NET), the lowest of any Active Object, below AO_Gui at 3. Network I/O is the slowest work in the system, so at this level it can only delay itself, and its workers can block on a socket as long as the network takes.
That is load-bearing: every kodeOS subscriber to MQTT_MSG_SIG outranks AO_Net, so a subscriber preempts it and handles each message before the next is published, and a chatty broker cannot fill a subscriber's queue.
Priority 1 (KODE_PRIO_USER_LOW) is the one level below, reserved for an app's own AO. An app that puts a subscriber there, or that blocks, needs a queue deep enough for its broker's bursts. The queue is 16 events (KQP_AO_QUEUE_LEN) and the stack 4096 bytes. kode_init() starts it after AO_Radio.
The Kconfig options below decide what is compiled. All default to on.
| Option | Compiles | Depends on |
|---|---|---|
KODEOS_NET | AO_Net | KODEOS_WIFI |
KODEOS_MQTT | The MQTT half | - |
KODEOS_CHANNEL | The Kode channel | KODEOS_MQTT, KODEOS_CONSOLE |
KODEOS_SHOT_HTTP | The screen upload | The channel, KODEOS_MIRROR |
KODE_LOWPOWER | AO_Net's part in the rest: the real-time waits, the radio wake and the mqtt check | - |
The channel's renewal time is KODEOS_CHANNEL_RENEW_S, 82800 s.
States
stateDiagram-v2 [*] --> Net_offline: subscribe WIFI_STATE_SIG, start workers, ask for the state Net_offline --> Net_online: WIFI_STATE_SIG got-ip, ip4 not 0 Net_online --> Net_offline: any other WIFI_STATE_SIG, stop MQTT
Net_root is the parent. It answers the queries in both states, because "not connected" is a valid answer: NET_INFO_REQ_SIG, MQTT_STATE_REQ_SIG and CHANNEL_STATE_REQ_SIG. It also handles worker outcomes, since a job submitted online can finish after the network has gone.
Start-up
The initial transition takes the job storage from PSRAM, starts net_worker and net_stream with kqp_worker_start() (12288 bytes of stack each, in PSRAM, so the TLS handshake costs no internal RAM) and installs the MQTT callback. If the storage or net_worker cannot be had, the network services stay down; without net_stream, transfers are refused. Then it asks for the Wi-Fi state, since AO_Radio may have had an address for a while.
Offline
Offline refuses every request with an answer. A caller that gets silence cannot tell "no network" from "still working". Most refusals log no network; <verb> needs an address - 'wifi_join' first and publish the terminator the verb closes on:
| Request | Refusal |
|---|---|
NET_PING_REQ_SIG | NET_PING_END_SIG with ok=false |
NET_HTTP_REQ_SIG | NET_HTTP_DONE_SIG with rc=KODE_DENIED |
NET_UDP_REQ_SIG | NET_UDP_DONE_SIG with rc=KODE_DENIED, sent=0 |
NET_TEST_REQ_SIG | TEST_RESULT_SIG named net, ok=false |
MQTT_UP_REQ_SIG | err = KDRV_MQTT_ERR_NO_NET, then MQTT_STATE_SIG |
MQTT_PUB/SUB/UNSUB_REQ_SIG | A log line, then MQTT_STATE_SIG, which closes the verb |
NET_STREAM_OPEN_REQ_SIG | NET_STREAM_OPEN_RSP_SIG to who with KODE_DENIED and medium = KDRV_MEDIUM_COUNT |
A stream read, return or close that arrives right after the network went is handled with no harm.
Going online and offline
Entering Net_online sets online, logs online as <ip>, starts the time client and starts the channel. Leaving it closes an open transfer, because a stuck sopen would refuse every later open with a transfer is already open for the rest of the boot. Any WIFI_STATE_SIG other than got-ip logs the network is gone, stops MQTT, ends the channel and goes offline. A got-ip with a non-zero address is the same network and is handled in place.
Jobs and the busy refusal
There are six job types (kdrv_net_job_t): PING, HTTP_GET, HTTP_POST, UDP_SEND, MQTT_START and MQTT_STOP. net_submit() copies a kdrv_net_req_t into storage in PSRAM and hands it to net_worker. me->busy is set at the submit and cleared only by the NET_JOB_SIG whose sequence number matches.
Every online request checks busy before it builds the job, and a refusal publishes its own terminator, so a busy refusal and a no-network refusal look the same to a waiting console.
AO_Net keeps one job in flight on each worker, so the download of a file never waits behind a long POST of the channel, and the POST never waits behind the download. Every job has a one-shot guard, NET_JOB_GUARD_SIG, of 60 s (KODE_NET_GUARD_MS). If it fires the job is still with the worker, busy stays up, and AO_Net answers whoever waits with KODE_TIMEOUT; the late done event answers nobody twice. A MQTT_START or MQTT_STOP waits for nobody.
The worker waits for a pool block when the pool is dry, so a dry pool delays a done event and never loses it.
Publishing answers
The driver keeps the results of the last job and a new job overwrites them, so AO_Net reads them between the done event and the next submit. A ping publishes one NET_PING_ENTRY_SIG per echo (seq, time_ms, ttl, ok) and then NET_PING_END_SIG (ip4, tx, rx, min_ms, avg_ms, max_ms, ok). AO_Net takes the terminator from the pool first, because net_ping and http_get disarm the console watchdog, so a lost terminator would leave the console busy for good; a short pool gives a short listing.
HTTP publishes up to 8 NET_HTTP_DATA_SIG records (KODE_NET_HTTP_CHUNKS) of 192 bytes each (KODE_NET_HTTP_CHUNK: 191 characters and a terminator), with every non-visible byte rewritten to ., then NET_HTTP_DONE_SIG (status, content_length, body_len, kept, truncated, rc).
The self-test sends one echo to the gateway, since a ping outside the building would test someone else's network, and passes on ok && rx > 0. Every field of a pooled event is written explicitly, because a pool block holds whatever the last event left in it.
MQTT
AO_Net tracks its own phase apart from the driver's state, because the driver knows whether a socket is connected and the phase knows whether anyone asked for one.
stateDiagram-v2 MQTT_IDLE --> MQTT_STARTING: MQTT_UP_REQ, job accepted MQTT_STARTING --> MQTT_LIVE: connected MQTT_STARTING --> MQTT_IDLE: start failed MQTT_STARTING --> MQTT_STOPPING: 15 s, down, or network gone MQTT_LIVE --> MQTT_STOPPING: down, or network gone MQTT_STOPPING --> MQTT_IDLE: MQTT_STOP_DONE
A stop that AO_Net could not submit leaves the phase alone, otherwise mqtt_down would answer "already down" over a running client. mqtt_up with a client existing answers the current state and no error. The connect deadline is NET_TMO_SIG, 15 s (KODE_MQTT_CONNECT_MS).
A publish, a subscribe and an unsubscribe do not go through the worker: they run on AO_Net's own thread, and a publish is written to the socket at once with esp_mqtt_client_publish() and never stored in the outbox, which takes one message per second and drops silently what is older than 30 s.
The error kdrv_mqtt_err_t is NONE, NO_NET, REFUSED, TIMEOUT or BUSY; BUSY is the refusal of mqtt_up and mqtt_down while the Kode channel holds the one client, printed as >err code=4 msg=busy.
Inbound messages
A broker on the internet sets the rate of this stream. esp-mqtt calls net_on_mqtt() on a task it owns, inside its API lock, and AO_Net can be blocked in a publish under the same lock. Nothing on that path may wait for AO_Net, so a message does not travel to AO_Net as an event.
- The ring. The driver copies the message into a ring of 48 messages in PSRAM (
KDRV_MQTT_RX_RING) and returns at once. The return is also what makes esp-mqtt acknowledge a QoS 1 message. A message that finds the ring full is dropped, with a warning and a count. - The notice. The driver calls
net_on_mqtt()when the ring goes from empty to holding a message, and again when a notice is owed: one was refused, or AO_Net asked for one withkdrv_net_mqtt_rx_owe(). The call carries no message. It posts the static eventNET_MQTT_RX_SIGwithQACTIVE_POST_Xand a margin of 4 free slots (NET_MQTT_MARGIN), so a flooding broker cannot fill the queue andQACTIVE_POSTnever asserts. The call returns whether the post was taken. - The drain. On
NET_MQTT_RX_SIG,net_rx_drain()takes messages withkdrv_net_mqtt_rx_take(), at most one ring's worth per step, into a scratch buffer in PSRAM, and handles each whole before it takes the next. When the ring still holds messages AO_Net posts the notice to itself. If that post is refused it callskdrv_net_mqtt_rx_owe(), and the next message to arrive sends the notice. A refused notice delays messages and does not lose them.chan_on_watch()also empties the ring every 15 s, but only while the client of the channel serves. - Where a message goes. The driver tags a message of the channel's client (
kdrv_mqtt_msg_t.channel) when it arrives. Such a message is a command: it joins the channel's queue and AO_Net never republishes it. The tag is used and not the topic, because the session that names the topic is wiped when the channel ends and the ring outlives it. Every other message is copied into anMqttMsgEvtand republished asMQTT_MSG_SIGfrom AO_Net's own thread, so only AO_Net publishes MQTT messages. A topic or payload longer than the event carries arrives shortened and says so:topic_cut,data_cut, andretainedfor a broker replay.
Link events (CONNECTED, DISCONNECTED, ERROR) are not in the ring. net_on_mqtt() copies each into NET_MQTT_LINK_SIG and posts it with a margin of 0, because losing one costs a command its answer or leaves the channel believing in a dead session. CONNECTED carries session_present, the broker's flag that it resumed the session.
Hosting the Kode channel
The channel is a sequence inside Net_online, not a state of its own: a phase cp says which answer AO_Net waits for, and one timer ctmo means a guard on the C5, a wait after a failure or the renewal. The setting chan (kset/chan, boolean, default on) is the switch.
AO_Net asks AO_Radio for the unit's certificate and a signature (ID_PART_SIG, ID_END_SIG, ID_SIGN_RSP_SIG), posts to https://api.kode.diy/kodes/challenge and /kodes/connect as HTTP_POST jobs, starts the MQTT client with the broker the answer names, and follows the in topic.
Waits after a failure back off from 5 s, doubling to 10 min, scaled to 75 to 125 % with khal_random(). The token lives 24 h and is renewed after 23 h.
The session asks for MQTT 5, no clean start, the age chan_keep as its expiry and QoS 1 on the in topic, so the broker keeps the commands sent while the board is away. On a connect AO_Net follows the topic again, publishes the presence again, logs channel: session resumed or channel: new session (nothing kept for this Kode) from the broker's session_present, and starts handing the queued commands to the console.
Replies leave through a 32 KB PSRAM ring (KCHAN_OUT_BYTES) in bursts of 8 lines (CHAN_OUT_BURST). While the channel is active, mqtt_up, mqtt_down, mqtt_pub, mqtt_sub and mqtt_unsub answer busy and touch nothing.
The setting chan_keep
chan_keep is AO_Net's second setting: how long the broker keeps the channel's session while the board is away. The words are 1h, 6h, 1d, 3d, 7d and never, the default is 1d, and the store is kset/chan_keep, a blob of one uint32_t in seconds (never is 0xFFFFFFFF, which a _Static_assert keeps equal to KDRV_MQTT_EXPIRY_NEVER, the MQTT 5 value for no expiry). AO_Net reads it once in its initial transition. A value that is none of the six, or a blob of another size, reads as the default. A set writes the blob, logs one notice and does nothing else: the next connect puts the age into the session, and a renewal is a connect, so a new age reaches the broker within 23 h and sooner after a drop. channel_info shows the age of the live session as keep=.
The queue of commands
The console takes one command at a time, and a Kode Dot that was away comes back to a burst. A command (a message tagged as the channel's) is checked first: a payload that is retained, cut, empty, 240 bytes or longer, or that holds a CR, an LF or a NUL is refused with >err code=3 msg=line (msg=retained for a retained one). A good one joins a ring of 32 lines in PSRAM (KODE_CHAN_QUEUE_MAX), and a line that finds it full is answered >err code=4 msg=busy at once.
chan_q_pump() gives the console the oldest line when none is out and the channel is up, so the answer can leave. It posts a ConsoleLineEvt with source NET and a sequence number, with a margin of 2 slots (CHAN_LINE_MARGIN). The line stays in the ring until it ends.
- The end. AO_Console calls
net_line_done()at the terminator of a line of source NET, with the sequence the line arrived with, and AO_Net takes it only when it matches the line that is out. It then removes the line and pumps. Any other end is ignored, which covers a late end of a line the bound gave up on. - A busy console. AO_Console does not refuse a line of source NET. It calls
net_line_busy()and AO_Net offers the line again after 100 ms (CHAN_LINE_RETRY_MS). The same retry follows a refused post. - The bound. The one-shot
lguard(NET_CHAN_LINE_SIG) runs for 30 s (KODE_CHAN_LINE_BOUND_MS) on the line that is out. When it fires, AO_Net gives up on the line, removes it and pumps. It is a guard that counts an expiry already queued after a stop, so the bound of a line that ended cannot pass for the bound of the next. - Channel told to go.
config key="chan" value=offand the leave for a restart or a jump empty the ring, stop the timer and discard what the driver still holds for the channel, with one notice (channel: halted; <n> command(s) that had not run are dropped). - Network lost. A lost address (
wifi_downincluded) and the OFF round of the rest leave the channel but keep the ring. The lines run in order after the next connect. - Reset. The ring is RAM. A command that resets the board takes the lines behind it. Delivery is at least once, so a command can run twice.
The channel's own states (CHAN_OFF, CHAN_NO_NET, CHAN_NO_IDENTITY, CHAN_JOINING, CHAN_UP, CHAN_WAITING, CHAN_REFUSED), its failure words, the fast mode, the renewal and the presence message are on internals/kode-channel. net_leave_request() makes AO_Net publish the offline presence, stop the client and answer AO_Boot exactly once with BOOT_NET_LEFT_SIG, so a deliberate restart does not wait for a keepalive.
The board's rest
With CONFIG_KODE_LOWPOWER, AO_Net joins the rounds of AO_LowPower as net (lowpower_join_radio(), because it takes part in radio wakes) and tells AO_Radio from its initial transition that it does (radio_lp_expect_net()). The full state machine is on sdk/kaos/lowpower. This is AO_Net's part.
| Case | What AO_Net does |
|---|---|
| ASLEEP | The Kode channel and an MQTT client of yours stay connected on the C5 link, which AO_Radio keeps up. A channel command that arrives is a console line like any other and wakes the board. A message of your client, republished as MQTT_MSG_SIG, wakes it too |
| Nothing new starts | A request that would start a job is refused with its own answer, as offline |
| Round for ASLEEP refused as busy | A job or transfer in flight (net job, net transfer), a screen upload (screen upload), a leave for a jump (net leaving), a join between steps (net joining, because its guards are QTimeEvts that would freeze), the wake time having no event to go in (net wake), or the check's dwell (check dwell) |
| OFF | Closes the channel and any MQTT session, tells AO_Radio with radio_net_left(), and only then answers ready. An OFF round that does not happen leaves the channel to join anew at the next AWAKE. A client of yours is not brought back |
The long waits are real time
A QTimeEvt does not count while ASLEEP, because the QP tick is stopped. A renewal due in 23 h would wait for 23 h of awake time, the token would lapse and the broker would drop the session. So the two long waits, the renewal (phase Up) and the back-off (phase Wait), carry a deadline in cdue_us, in khal_time_us(), which runs through light sleep. chan_wait() sets it and chan_due_arm() puts the wait where the board is:
- AWAKE.
ctmois armed for what is left of the deadline. - ASLEEP. Nothing is armed. AO_Net gives AO_LowPower the deadline with
lowpower_wake_at(), before it answers the round for ASLEEP and again whenever a wait begins in the rest. If the pool has no block to carry the note, the round is answered busy (net wake).
AO_LowPower wakes the board at the deadline with the cause timer, as a radio wake that does not light the glass. On LOWPOWER_STATE_SIG with state RADIO, or with AWAKE after a person used the board, chan_lp_resume() arms ctmo for the real time left and at least 2 s (CHAN_LP_SETTLE_MS), because AO_Radio takes the C5 link out of its rest on the same event. A deadline that passed in the rest acts 2 s after the wake and not a whole renewal later.
Nothing else starts in the rest. An address that arrives while ASLEEP sets lp_rejoin, and the join begins 2 s after the wake. A long timer that fires as the board goes to sleep gives the deadline again and does not begin the renewal. A deadline left registered after the channel ended costs one radio wake that finds nothing to do.
The radio wake
In a radio wake the tick runs and the lock is held, but the glass, the touch panel and every other owner stay at rest. net_lp_state() treats RADIO like AWAKE. AO_Net says it is finished with lowpower_radio_done(), once per radio wake, decided by net_lp_radio_check() at the start of the wake and at the end of each step of the channel. It is done when nothing is in the way of resting (no job, transfer, screen upload or leave) and the channel is not in use or waits for a time further off than 2 s. A renewal that is due, a back-off that has run out and a join between steps are work. A renewal that succeeds is done at once, and so is a try that fails and waits: a back-off starts at about 5 s. A broker that cannot be reached costs one radio wake per try while the back-off grows to 10 min, and the glass never lights. If the work takes longer than KODE_LOWPOWER_RADIO_MAX_MS (60 s), AO_LowPower asks AO_Net to rest anyway.
The check of the mqtt entry
AO_Scheduler has a built-in entry, mqtt, whose work is a look at the sessions in a radio wake. It is the net under the interrupt of the C5: if the interrupt that wakes the P4 for a message is ever lost, the message arrives at the next check. The scheduler tells AO_Net the next time with net_check_at(due_wall_s) (NET_CHECK_AT_SIG, Unix seconds in UTC, -1 for none), and AO_Net keeps one time at a time. AO_Net knows whether the C5 link is up from RADIO_STATE_SIG.
- Something must listen. The check counts only when the C5 link is up, Wi-Fi has an address, and the channel is in use or a client of yours holds the MQTT session. With nothing to listen to there is nothing to wake for, and a radio wake that finds the check due makes it at once.
- The time is part of the wake. Before it answers a round for ASLEEP, AO_Net gives
lowpower_wake_at()the earlier of the channel's deadline and the check's time. - The dwell. In a radio wake with the check due, or in a whole wake that finds it due, AO_Net arms
chk_tmo(NET_CHECK_TMO_SIG) for 3000 ms (KODE_NET_CHECK_DWELL_MS) from the beginning of the wake, so that what the C5 or the broker holds can flow in. While it runs, a round for ASLEEP is answered busy (check dwell) and the radio wake does not end. A round for OFF is not refused. At the timer the check is made. - The board has been awake. A check found due by a round, or by a repeated AWAKE state, is made at once with no dwell: the sessions were served while the time came.
- A check made. AO_Net forgets the time and tells the scheduler with
sched_check_done(), which answers with the next time.
The screen upload
screenshot dst=http token="…" uploads a picture of the screen. The channel carries lines of 240 bytes, which a picture does not fit, so MQTT carries the order and one HTTPS PUT carries the picture.
AO_Console checks the token and posts NET_SHOT_REQ_SIG. AO_Net holds the session's shots prefix and the JWT, asks AO_Display for a JPEG (display_shot_request(), with a 2 s deadline, KSHOT_DISPLAY_MS), copies it into the driver's buffer and releases the loan before it submits KDRV_NET_JOB_HTTP_PUT. A capture answers one NET_SHOT_RSP_SIG result.
An order with fps or secs is a stream: AO_Net answers at once, runs it alone, marks a frame due on each tick of a one-shot 1000 / fps ms timer, and starts it only when the last PUT is done and the worker is free, so a slow link lowers the rate and builds no backlog. Three network errors in a row end a stream (KSHOT_STREAM_ERR_MAX), a 429 drops that frame, and the channel always gets the worker first.
A stream ends with one of the words time, expired, refused, channel, network, replaced, display or stopped. The URL never comes from the order, only the token, whose characters cannot leave the last path segment.
Neither AO_Net nor kdrv_net logs the token, the link or the JWT, and kdrv_net holds the HTTP_CLIENT log tag at INFO during a PUT.
The download loan
A URL can stand in for a file name when an image is installed, the same conversation as sdk/kaos/storage. AO_Net has two 32 KB PSRAM buffers (NET_STREAM_BUFS, NET_STREAM_CHUNK) and lends them by index, 0 or 1, never by address. The reads are jobs on net_stream: the worker fills one buffer while the reader reads the other. A return frees the buffer and asks for the next chunk in the same step.
A close always wins: it forgets the transfer at once, answers a staged open with a refusal and owes the driver a close job, and a new open waits behind it so the driver never sees two transfers. Each job has a guard above the driver's own cap: 20 s plus 5 s of slack for an open or a read, 30 s for a close.
The callers are AO_AppMgr (install src="http…"), AO_Radio (a C5 image over HTTP) and AO_Gui (the files of Kody's pack). None has a deadline of its own, which is why the offline state answers an open and does not ignore it.
The clock
Entering Net_online starts the time client, not boot, because without a network it would retry and never reach a server. When a server answers, the callback sets the wall clock and logs one line, the moment where every timestamp in the session changes from milliseconds since boot to dates.
Specifications
Signals in
What AO_Net receives: requests, answers from other parts, and its own timers.
| Signal | Event | Direction | Meaning |
|---|---|---|---|
WIFI_STATE_SIG | WifiStateEvt | in, subscribed | Got-ip with a non-zero ip4 is online |
NET_PING_REQ_SIG | NetPingReqEvt | in, posted | net_ping; 1 to 10 echoes |
NET_HTTP_REQ_SIG | NetHttpReqEvt | in, posted | http_get. HTTP_POST is submitted only by the channel |
NET_UDP_REQ_SIG | NetUdpReqEvt | in, posted | udp_send; payload up to 64 bytes from an event |
NET_INFO_REQ_SIG, NET_TEST_REQ_SIG | none | in, posted | net_info; test component=net |
MQTT_UP_REQ_SIG | MqttUpEvt | in, posted | Start the client |
MQTT_DOWN_REQ_SIG, MQTT_STATE_REQ_SIG | none | in, posted | Stop; republish |
MQTT_PUB_REQ_SIG, MQTT_SUB_REQ_SIG, MQTT_UNSUB_REQ_SIG | MqttPubEvt, MqttTopicEvt | in, posted | Send, follow, unfollow |
CHANNEL_STATE_REQ_SIG | none | in, posted | channel_info; AO_Gui when the settings screen opens |
CONFIG_REQ_SIG | KodeConfigReqEvt | in, subscribed | The keys chan and chan_keep |
RADIO_STATE_SIG | RadioStateEvt | in, subscribed | Whether the C5 link is up, for the mqtt check |
LOWPOWER_ROUND_SIG, LOWPOWER_STATE_SIG | LowPowerRoundEvt, LowPowerStateEvt | in, subscribed, with KODE_LOWPOWER | A round to rest or to close the sessions, and the state the board went to |
NET_LINE_DONE_SIG | NetLineDoneEvt | in, posted by AO_Console | net_line_done(): the line of source NET ended, with the sequence of the line |
NET_LINE_BUSY_SIG | none | in, posted by AO_Console | net_line_busy(): the console was busy and did not take the line |
NET_CHECK_AT_SIG | none | in, posted by AO_Scheduler, with KODE_LOWPOWER | net_check_at(): the next time of the mqtt check |
ID_PART_SIG, ID_END_SIG, ID_SIGN_RSP_SIG | in, posted by AO_Radio | The channel's certificate and signature | |
NET_STREAM_OPEN/READ/NEXT/CLOSE_REQ_SIG | NetStreamOpenEvt for the open | in, posted | The download loan |
NET_SHOT_REQ_SIG, NET_SHOT_STOP_SIG | NetShotReqEvt, NetShotStopEvt | in, posted | screenshot dst=http and its stop |
DISPLAY_SHOT_RSP_SIG | DisplayShotRspEvt | in, posted by AO_Display | The JPEG, lent |
NET_LEAVE_REQ_SIG | none | in, posted | net_leave_request(), before a restart |
NET_JOB_SIG, NET_STREAM_JOB_SIG | KqpWorkerDoneEvt | in, from the workers | Job outcomes |
NET_JOB_GUARD_SIG, NET_STREAM_GUARD_SIG, NET_TMO_SIG, NET_CHAN_TMO_SIG, NET_CHAN_FAST_SIG, NET_CHAN_WATCH_SIG, NET_CHAN_LINE_SIG, NET_CHECK_TMO_SIG, NET_SHOT_TICK_SIG, NET_SHOT_WAIT_SIG | none | self, one-shot | Guards, the connect deadline, the channel's wait (real time for the renewal and the back-off), fast-mode hold (60 s), client look (15 s, which also empties the receive ring), the bound on the line the console runs (30 s) or its retry (100 ms), the dwell of the mqtt check (3 s), stream pace, display deadline |
NET_MQTT_LINK_SIG (NetMqttLinkEvt), NET_MQTT_RX_SIG (a static event), NET_CHAN_OUT_SIG | in, private | From esp-mqtt's task (a link event, or the notice that the receive ring holds messages) and from the ring's writer |
Signals out
What AO_Net publishes or posts back.
| Signal | Event | Direction | Meaning |
|---|---|---|---|
NET_PING_ENTRY_SIG, NET_PING_END_SIG | NetPingEntryEvt, NetPingEndEvt | out, published | The ping listing |
NET_HTTP_DATA_SIG, NET_HTTP_DONE_SIG | NetHttpDataEvt, NetHttpDoneEvt | out, published | The GET |
NET_UDP_DONE_SIG, NET_INFO_SIG | NetUdpDoneEvt, NetInfoEvt | out, published | NetInfoEvt: ip4, gw4, mask4, dns4 in network byte order, the MQTT state, online and rx_dropped, the messages the driver dropped since boot because its receive ring was full; answered in every state |
MQTT_STATE_SIG | MqttStateEvt | out, published | Where the client stands |
MQTT_MSG_SIG | MqttMsgEvt: topic, len, topic_cut, data_cut, retained, data | out, published | A message from the broker |
CHANNEL_STATE_SIG | ChannelStateEvt | out, published | State, reason, wait, fast mode, the upload counters, and the session (keep_s, the age the connect asked for, and session, what the broker said) |
CONSOLE_LINE_SIG | ConsoleLineEvt | out, posted to AO_Console | A command of the channel, source NET, one at a time from the queue, with a sequence number |
NET_SHOT_RSP_SIG | NetShotRspEvt | out, posted to the asker | Result of a capture or a stream |
NET_STREAM_OPEN_RSP_SIG, NET_STREAM_DATA_SIG | StorageOpenRspEvt, StorageDataEvt: idx, len, eof, status | out, posted to the reader | The loan |
BOOT_NET_LEFT_SIG | none | out, posted to AO_Boot | The channel has left; once per NET_LEAVE_REQ_SIG |
TEST_RESULT_SIG | named net | out, published | The verdict |
Calls, limits and verbs
| Thing | Value |
|---|---|
| Blocking calls | kode_net_lookup(host, &ip4), kode_net_ping(host, count, &stats) (count 1 to 10), kode_http_get(url, buf, cap, &result), kode_udp_send(host, port, data, len). Never from an AO; reentrant, each call opens its own socket. https:// is checked against the Mozilla bundle. A 404 returns KODE_OK and result.status says 404 |
| Posting calls | kode_mqtt_connect(), kode_mqtt_disconnect(), kode_mqtt_publish(), kode_mqtt_subscribe(), kode_mqtt_unsubscribe() and kode_mqtt_connected(). Safe anywhere. There is deliberately no polling accessor: subscribe an AO to MQTT_MSG_SIG |
| Request helpers | net_ping_request(), net_http_get_request(), net_udp_send_request(), net_info_request(), net_test_request(), mqtt_*_request(), net_leave_request(), net_shot_request(), net_shot_stop_request(), net_stream_* |
| Size limits | Host 64, URL 80, datagram 64, URI 96, user 33, password 65, topic 80, payload 240 (KODE_MQTT_PAYLOAD_MAX, one console line). Receive ring 48 messages (KDRV_MQTT_RX_RING), command queue 32 lines (KODE_CHAN_QUEUE_MAX). _Static_asserts check each against the driver's copy, including 8 x 191 = 1528 (KDRV_NET_BODY_MAX) |
| Guards | Job 60 s; connect 15 s; stream open or read 20 s + 5 s; stream close 30 s; screenshot display 2 s; a line out to the console 30 s |
| Stored settings | kset/chan, boolean, default on. kset/chan_keep, one uint32_t of seconds, default 86400 |
| Console | net_ping host= count=, net_info, http_get url=, udp_send host= port= data=, mqtt_up uri= user= pass=, mqtt_down, mqtt_pub, mqtt_sub, mqtt_unsub, mqtt_info, channel_info, config key="chan", config key="chan_keep", screenshot dst=http, test component=net. The ICMP verb is net_ping; plain ping is the console liveness check |
Gotchas
Example code
This fetches a page over HTTPS and logs the status and the size, once the Kode Dot is online. Join a network first (wifi_join on the console). The log level must be i: config key="log" value=i.
/*
* http_status.c - 2026-10-28 - from Kode with love
*
* Fetches https://api.kode.diy/ with AO_Net's blocking GET and logs the HTTP
* status and how many body bytes arrived. The Kode Dot must be online. With
* no network the log shows the failure code instead of a status.
*/
#include "kode.h"
/* Bring the system up, then GET one page every 10 s and report it. */
void app_main(void)
{
kode_init();
while (1) {
char body[128];
kode_http_result_t result;
kode_status_t status = kode_http_get("https://api.kode.diy/", body,
sizeof(body), &result);
if (status == KODE_OK) {
KLOG_I("http_status", "status %u, %u body bytes",
(unsigned)result.status, (unsigned)result.body_len);
} else {
KLOG_I("http_status", "no answer, status %d", (int)status);
}
kode_delay_ms(10000);
}
}Compiled against the SDK with ESP-IDF v6.1 on 2026-10-28. TBD: a run on a Kode Dot.
In the SDK
components/kaos/net:net_ao.c(the AO, MQTT, the loan, the uploads),net_channel.candnet_channel.h(the channel's pure logic).kode.h: thekode_net_*,kode_http_get,kode_udp_sendandkode_mqtt_*calls.components/kdrv/radio:kdrv_net.c, the sockets, esp-mqtt and TLS under the jobs.