# courier
Batteries-included JSON messaging for ESP32. WiFi and user configuration, WebSocket, MQTT, reconnection — all handled.
Motivation: When you make something neat on your [M5Stick](https://shop.m5stack.com/products/m5stickc-plus2-esp32-mini-iot-development-kit?variant=44269818216705) you want the quickest path to messaging the back-end, and you want to carry it to places to show people and configure the Wi-Fi from your phone. Courier is how you do that.
Courier expects JSON messages with a `"type"` field. Messages are parsed with ArduinoJson and the `type` string is passed to `onMessage` callbacks alongside the parsed document. Use `onRawMessage` for non-JSON or custom framing.
```cpp
#include <Courier.h>
CourierConfig makeConfig() {
CourierConfig cfg;
cfg.host = "api.example.com";
cfg.port = 443;
cfg.path = "/ws";
return cfg;
}
Courier courier(makeConfig());
void setup() {
courier.onConnected([]() { courier.send("{\"type\":\"hello\"}"); });
courier.onMessage([](const char* type, JsonDocument& doc) {
Serial.printf("Got: %s\n", type);
});
courier.setup();
}
void loop() { courier.loop(); }
```
## What it does
- **WiFi** — captive portal config via WiFiManager, auto-reconnection
- **WebSocket** — built-in transport with TLS, ping/pong heartbeat
- **MQTT** — opt-in transport with subscribe/unsubscribe, topic-addressed publishing
- **Reconnection** — exponential backoff (5s-60s), health monitoring, automatic recovery
- **Time sync** — NTP primary (continuous drift correction) + HTTP Date header fallback
- **JSON routing** — messages parsed and dispatched by `type` field
- **Transport map** — named transports, broadcast or targeted sending
## Opinionated
Courier bundles a number of other great libraries:
- **WebSocket** — esp_websocket_client [Documentation](https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html)
- **MQTT** — esp_mqtt_client [Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/mqtt.html)
- **WiFi config** — WiFiManager [GitHub](https://github.com/tzapu/WiFiManager)
- **JSON** — ArduinoJson [Documentation](https://arduinojson.org/)
- **Time** — ezTime [GitHub](https://github.com/ropg/ezTime)
Use `onConfigure` hooks to access the full configuration surface of each bundled library.
## Install
**PlatformIO:**
```ini
lib_deps = inanimate/courier
```
**ESP-IDF Component:**
```yml
dependencies:
inanimate-tech/courier:
version: "^0.1.0"
```
## API
See [docs/api.md](docs/api.md) for the full API reference.
Quick overview:
```cpp
// State
courier.isConnected();
courier.getState(); // CourierState enum
// Sending — "To" suffix means you specify the transport
courier.send(payload); // default transport + topic
courier.sendTo("mqtt", payload); // named transport
courier.sendBinaryTo("ws", data, len); // named transport, binary
courier.publishTo("mqtt", "my/topic", payload); // named transport + topic
// Transports
courier.addTransport("mqtt", &mqttTransport);
courier.suspendTransports(); // free SRAM for OTA
courier.resumeTransports();
// Callbacks (multiple registrations supported, up to 4 per type)
courier.onMessage([](const char* type, JsonDocument& doc) { });
courier.onRawMessage([](const char* payload, size_t len) { });
courier.onConnected([]() { });
courier.onDisconnected([]() { });
courier.onError([](const char* category, const char* msg) { });
// Raw ESP-IDF config access
courier.builtinWS().onConfigure([](esp_websocket_client_config_t& cfg) { });
mqtt.onConfigure([](esp_mqtt_client_config_t& cfg) { });
courier.onConfigureWiFi([](WiFiManager& wm) { });
```
## Connectivity state machine
```
BOOTING -> WIFI_CONNECTING -> WIFI_CONNECTED -> TRANSPORTS_CONNECTING -> CONNECTED
^ |
RECONNECTING <-----------+
|
CONNECTION_FAILED
```
`onConnectionChange` fires at each state transition. `onError` fires alongside transitions caused by failures, providing a category and reason (e.g. `"WIFI"`, `"connection lost"`).
## Limitations
- **Single instance** — WiFiManager requires a static callback, so only one Courier instance per process
- **Single message slot** — transport callbacks queue one pending message at a time; messages arriving before the main loop drains are dropped
- **Arduino + ESP-IDF** — depends on Arduino framework for WiFiManager, ArduinoJson, ezTime
## License
MIT
idf.py add-dependency "inanimate/courier^0.2.0"