DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
gateway.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 gateway.h
6 * @brief Radio / wireless gateway bridge (DETWS_ENABLE_GATEWAY) - the v5 southbound-to-
7 * northbound bridge.
8 *
9 * The generic gateway pattern that ties the hardware-ingest pipeline to the web stack. A
10 * southbound radio (LoRa / nRF24 / CC1101 / Zigbee / Z-Wave / ... reached over SPI / I2C /
11 * UART) is a **port**. When it receives a frame - the data-ready ISR reads it over DMA
12 * (services/dma), posts it onto the FORWARD lane (services/preempt_queue), and a per-radio
13 * codec extracts the source node address and payload - you call det_gw_uplink(). The
14 * gateway **envelopes** the frame (source address, port, RSSI, a sequence number) and
15 * **publishes it northbound** through the uplink callback, which you wire to MQTT / HTTP /
16 * WebSocket / UDP. A northbound command runs the other way through det_gw_downlink() to the
17 * port's transmit callback (the radio's SPI / UART write).
18 *
19 * The radio transmit and the northbound publish are **callbacks** - the seam a real radio
20 * driver and a real protocol binding plug into - so the bridge is fully host- and
21 * device-testable with no radio hardware (the tests / example supply capturing callbacks
22 * and feed simulated frames). This is the northbound half; the DMA + FORWARD lane carry the
23 * bytes, and each radio's frame format is its own codec.
24 *
25 * Per-port uplink rate cap (fail-closed), a routing-key helper (det_gw_topic() formats
26 * `<prefix>/<port>/<addr>`), and static tables (zero heap): DETWS_GW_MAX_PORTS ports.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef DETERMINISTICESPASYNCWEBSERVER_DET_GATEWAY_H
33#define DETERMINISTICESPASYNCWEBSERVER_DET_GATEWAY_H
34
35#include "ServerConfig.h"
36
37#if DETWS_ENABLE_GATEWAY
38
39#include <stddef.h>
40#include <stdint.h>
41
42/** @brief Southbound radio / bus kind a port bridges (informational + topic hint). */
43enum class det_gw_kind : uint8_t
44{
45 DET_GW_OTHER = 0,
46 DET_GW_LORA,
47 DET_GW_NRF24,
48 DET_GW_CC1101,
49 DET_GW_THREAD,
50 DET_GW_ZIGBEE,
51 DET_GW_ZWAVE,
52 DET_GW_ENOCEAN,
53 DET_GW_SIGFOX,
54 DET_GW_WISUN,
55 DET_GW_NFC,
56 DET_GW_BLE,
57};
58
59/**
60 * @brief A northbound message: a southbound frame enveloped with its routing metadata.
61 * @ref payload points at the caller's bytes and is valid only for the duration of
62 * the uplink callback - copy what you publish asynchronously.
63 */
64struct det_gw_msg
65{
66 const uint8_t *payload; ///< frame payload bytes
67 uint32_t seq; ///< per-gateway uplink sequence (wraps)
68 uint16_t len; ///< payload length
69 uint16_t src_addr; ///< source node address on the radio
70 int16_t rssi; ///< received signal strength (0 if the driver has none)
71 uint8_t port_id; ///< the port the frame arrived on
72 det_gw_kind kind; ///< det_gw_kind of that port
73};
74
75/**
76 * @brief Northbound publish: emit @p msg to MQTT / HTTP / WebSocket / UDP.
77 * @return true if the northbound stack accepted it; false drops (counted).
78 */
79typedef bool (*det_gw_uplink_fn)(const det_gw_msg *msg, void *ctx);
80
81/**
82 * @brief Southbound transmit (downlink): send @p payload to @p dst_addr on @p port_id.
83 * @return true if the radio accepted the frame; false drops (counted).
84 */
85typedef bool (*det_gw_tx_fn)(uint8_t port_id, uint16_t dst_addr, const uint8_t *payload, uint16_t len, void *ctx);
86
87/** @brief Southbound port (radio / bus) configuration passed to det_gw_add_port(). */
88struct det_gw_port_config
89{
90 uint8_t port_id; ///< caller-assigned id (used in topics and up/down-link calls).
91 det_gw_kind kind; ///< det_gw_kind.
92 det_gw_tx_fn tx; ///< downlink transmit (may be null for a receive-only port).
93 void *ctx; ///< opaque, forwarded to @ref tx.
94 uint16_t rate_cap; ///< max uplink frames/second from this port (0 = unlimited).
95};
96
97/** @brief Gateway counters (monotonic since the last det_gw_reset()). */
98struct det_gw_stats
99{
100 uint32_t up_in; ///< det_gw_uplink() calls
101 uint32_t up_published; ///< frames the uplink callback accepted
102 uint32_t up_dropped; ///< uplinks dropped (rate cap / no sink / refused / bad port)
103 uint32_t down_in; ///< det_gw_downlink() calls
104 uint32_t down_sent; ///< downlinks the port transmit accepted
105 uint32_t down_dropped; ///< downlinks dropped (bad port / no tx / refused)
106};
107
108/** @brief Clear all ports, the uplink sink, the topic prefix, and stats. */
109void det_gw_reset(void);
110
111/**
112 * @brief Register a southbound port.
113 * @return true; false if @p cfg is null, the id is already registered, or the table is
114 * full (DETWS_GW_MAX_PORTS).
115 */
116bool det_gw_add_port(const det_gw_port_config *cfg);
117
118/** @brief Install the northbound publish callback (required to publish anything). */
119void det_gw_set_uplink(det_gw_uplink_fn fn, void *ctx);
120
121/** @brief Set the topic prefix used by det_gw_topic() (caller-owned string; default "gw"). */
122void det_gw_set_topic_prefix(const char *prefix);
123
124/**
125 * @brief Bridge a received southbound frame northbound: envelope it and publish.
126 *
127 * Applies the port's uplink rate cap, then calls the uplink callback. Fail-closed: an
128 * unknown port, no installed sink, an exceeded cap, or a callback returning false drops
129 * the frame (counted), never blocks.
130 * @return true if published.
131 */
132bool det_gw_uplink(uint8_t port_id, uint16_t src_addr, const uint8_t *payload, uint16_t len, int16_t rssi);
133
134/**
135 * @brief Bridge a northbound command southbound: transmit it on @p port_id's radio.
136 * @return true if the port's transmit callback accepted it; false drops (counted).
137 */
138bool det_gw_downlink(uint8_t port_id, uint16_t dst_addr, const uint8_t *payload, uint16_t len);
139
140/**
141 * @brief Format a northbound routing key `<prefix>/<port>/<addr>` for @p msg into @p buf.
142 * @return the string length written (excluding the NUL), or 0 if @p buf is too small.
143 */
144uint16_t det_gw_topic(const det_gw_msg *msg, char *buf, uint16_t buflen);
145
146/** @brief Copy the current gateway counters into @p out. */
147void det_gw_get_stats(det_gw_stats *out);
148
149#if !defined(ARDUINO)
150/** @brief Host only: set the millisecond clock the rate cap uses (tests drive the window). */
151void det_gw_test_set_now(uint32_t ms);
152#endif
153
154#endif // DETWS_ENABLE_GATEWAY
155
156#endif // DETERMINISTICESPASYNCWEBSERVER_DET_GATEWAY_H
User-facing configuration for DeterministicESPAsyncWebServer.