SPSP connects low power IoT clients to MQTT1 or other protocols.
It's an extensible framework for publish-subscribe pattern.
Main goal is to create highly reliable platform for message delivery from and to IoT devices while taking into account protocol-specific, performance and power restrictions.
Implemenation is based on ESP-IDF2 framework, so all ESP323 devices (including subtypes) are supported and they are a key target of this project.
You can also run SPSP bridge node on Linux platform and OpenWrt4, so you can have an access point serving both regular WiFi clients and IoT devices.
This project currently requires C++ 17 or newer.
If you want to use SPSP in your ESP-IDF-based firmware, ESP-IDF2 v5.1 or newer is required. PlatformIO5 development is also supported.
If you want to use SPSP on Linux, you will need CMake and WiFi card with support for monitor mode and packet injection.
- Create ESP-IDF2 project.
- Create
idf_component.yml
file insidemain
project directory:dependencies: spsp: git: https://github.com/DavidB137/spsp.git
- Create
sdkconfig.defaults
file incide root project directory:SPSP uses exceptions, so they need to be enabled in the build process. It may be also necessary to enable size optimalization depending on the flash size.CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_CXX_EXCEPTIONS=y
- Rename
main.c
tomain.cpp
file insidemain
project directory and change it's content to:#include "spsp/spsp.hpp" extern "C" void app_main() { // ... }
- Install build tools. On Debian based systems:
sudo apt update sudo apt-get install make cmake build-essential git cmake ccache libffi-dev libssl-dev
- Create build directory:
mkdir linux/build
- Build the binaries:
cd linux/build cmake .. make
- Optionally install it (systemwide):
sudo make install
To run SPSP bridge, you will need a WiFi card with support for monitor mode and packet injection. To put it into monitor mode and enable the interface, use:
# Replace `IFACE` with your WiFi card's interface name.
sudo ip link set IFACE down
sudo iw IFACE set monitor none
sudo ip link set IFACE up
You will need to adjust the configuration. Example is provided in
linux/bridge_espnow_config.ini.example
file.
Finally, to run SPSP ESP-NOW bridge:
sudo spsp_bridge_espnow PATH_TO_YOUR_CONFIG.ini
See OpenWrt feed repository for SPSP: https://github.com/DavidB137/spsp-openwrt
Generally, Linux process applies. SPSP binaries, however, come packaged, so you will only need to setup network interface and adjust the configuration.
You can find usage examples in examples/
directory.
Code documentation is automatically generated by Doxygen6 from main branch and is available at: https://spsp.davidbenko.dev.
Currently, there are 2 node types: client and bridge.
Client is generic node type for low power sensors.
The client typically makes a measurement as quickly as possible (in order of 100s of milliseconds) and goes to deep sleep afterwards (for a longer time period). Always-on clients and any combinations are supported as well.
Upstream connectivity (to the bridge) is provided by local layer (for example ESP-NOW7), which allows for this extremly quick wake-up periods. The client somehow (protocol dependent) establishes connection to near-by bridge (think of it as router) processing all messages from client.
The client can publish data or subscribe to upstream data.
For debugging purposes, client also publishes signal strength when it receives
probe response to _report/rssi/{BRIDGE_ADDR}
topic
(+ prefix depending on far layer).
Bridge connects clients on local layer and far layer. You can think of bridge as a router or gateway.
All received publish messages are forwarded to upstream far layer (typically MQTT1). When subscribe request is received from a client, topic of the subscription gets stored in the database and registered on the far layer. Any number of clients can subscribe to the same topic and even the bridge itself can be one of them.
The bridge can also publish data, so it can function as both bridge and client at the same time.
Bridge reports (topics don't include far layer prefix):
- Signal strength to client nodes: Reported to
_report/rssi/{CLIENT_ADDR}
topic. - Probe request payload: Reported to
_report/probe_payload/{CLIENT_ADDR}
topic if it's not empty. By default it's empty on ESP-NOW clients, but you can set the value inSPSP::LocalLayers::ESPNOW::Config::probePayload
. This feature is targeted for firmware version reporting.
Reporting can be configured or completely disabled in bridge configuration.
Local layer protocols are used for local communication between nodes (clients and bridges).
There's just one local layer protocol: ESP-NOW. More will come later.
ESP-NOW local layer protocol is a wrapper around Espressif's ESP-NOW7. It's basically a WiFi without any state management (connecting to AP,...) whatsoever. Just a quick setup, sending of single packet, receiving of delivery confirmation – all over WiFi MAC.
It's built-in all ESP32 SOCs. You don't need any additional hardware – drastically saving cost, labour and enabling usage on third-party devices.
This implemenation includes built-in encryption using ChaCha20 with 256-bit password and 64-bit nonce. To allow multiple separate networks without much interference, 32-bit SSID must be set.
Each packet is checked for delivery status, but not resent when delivery fails! Internally, Espressif's implemenation does some sort of retransmission, but generally, it's not guaranteed that data will be delivered, so custom implementation of retransmission may be needed if required by your use-case (support is implemented).
Important
ESP-NOW is limited to 250 bytes of payload. SPSP's header is 20 bytes long,
so len(topic) + len(payload)
must be at most 230 bytes!
You have to take that into account when sending data.
In other words, make data you publish and/or subscribe to short.
Packet fragmentation is not currently implemented in SPSP.
Binary payload is supported.
Note
Espressif's ESP-NOW implementation limits number of paired devices to 20. SPSP, however, isn't limited by this count, because we dynamically add and remove paired device just before and after sending a single message (packet) to that device. In SPSP, this only limits number of devices concurently waiting for delivery confirmation. In theory, you could connect infinitely many clients to single bridge. Moreover, encryption is handled internally by SPSP, not by Espressif's ESP-NOW implementation.
Clients search for bridges to connect to using probes.
During this process, the client iterates through all WiFi channels allowed by
country restrictions (see WiFi::Station::setChannelRestrictions
) and
broadcasts probe requests to the broadcast address.
Bridges on the same SSID with the correct password will decrypt the request
and send back probe response. The client then chooses bridge with
the strongest signal.
Far layer protocols are used by bridges as concentrators. They collect all data published by clients and provide data from other sources.
There are two far layer protocols: MQTT and local broker. More will come later.
Wrapper for most well known publish-subscribe IoT protocol – MQTT1.
All standard authentication methods are supported:
- unauthenticated over TCP
- username and password
- TLS
- unauthenticated over WebSockets
- WebSockets secure
Internally ensures retransmission and reconnection to MQTT broker if needed.
Default topic structure for publishing is {PREFIX}/{ADDR}/{TOPIC}
where:
PREFIX
is configured topic prefix (spsp
by default)ADDR
is node's address as reasonably-formatted string (in case of ESP-NOW7 it's lowercase MAC address without separators)TOPIC
is topic received in message (e.g.temperature
)
Topics for subscribing are not prepended or modified in any way.
Basically local MQTT-like broker.
Functions just like MQTT, supports wildcards, but is hosted on the bridge itself. Handy if you only need communication between nearby IoT devices over single bridge (no IP connectivity required).
Default topic structure for publishing is {PREFIX}/{ADDR}/{TOPIC}
(see MQTT topic structure above).
Topics for subscribing are not prepended or modified in any way.
Message types are generic for current and any future protocols.
- Probe request (
PROBE_REQ
): Client attempts bridge discovery by broadcastingPROBE_REQ
messages on all available channels. - Probe response (
PROBE_RES
): Bridge response to receivedPROBE_REQ
from a client. Client listens forPROBE_RES
messages and chooses the one with the strongest signal. Address of that bridge is stored and all communication is routed to that address.
- Publish (
PUB
): Client sendsPUB
message to discovered bridge to publish payload to topic. Bridge forwards it to far layer (i.e. MQTT). - Subscribe request (
SUB_REQ
): Client sendsSUB_REQ
message to discovered bridge to subscribe to topic. This subscription is requested on far layer and valid for 15 minutes. The client can extend the lifetime by sending anotherSUB_REQ
(it's done automatically). - Subscribe data (
SUB_DATA
): When bridge receives data from far layer, it sendsSUB_DATA
messages to all clients that are subscribed to given topic. - Unsubscribe request (
UNSUB
): Client can (and should) notify the discovered bridge when it doesn't need subscription anymore by sending explicitUNSUB
message.
Many applications require current time information. Bridge nodes automatically sync time using SNTP and then provide it to clients.
- Time request (
TIME_REQ
): Client sendsTIME_REQ
whenClient::syncTime()
method is called. - Time response (
TIME_RES
): Bridge responds toTIME_REQ
by sendingTIME_RES
with current timestamp as payload. The timestamp has milliseconds precision, but link latency between bridge and client is not compensated.
Currently unused, but may be in the future.
- OK (
OK
): Generic OK message (typically for success responses). - Failure (
FAIL
): Generic failure message (typically for failure responses).
Footnotes
-
MQTT https://mqtt.org ↩ ↩2 ↩3
-
ESP-IDF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/ ↩ ↩2 ↩3
-
OpenWrt: https://openwrt.org ↩
-
PlatformIO: https://platformio.org ↩
-
Doxygen: https://www.doxygen.nl ↩
-
ESP-NOW: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html ↩ ↩2 ↩3