# DataNet Arduino SDK

Realtime pub/sub for ESP32 and ESP8266 devices. Connect to the [DataNet](https://datanet.art) platform, subscribe to channels, and publish JSON messages — with automatic JWT authentication, heartbeating, and exponential-backoff reconnection.

---

## Installation

This SDK is currently **repo-first** rather than distributed through Arduino
Library Manager. Install it from this repository:

1. Copy `packages/sdk-arduino` to a local folder named `DataNet`, or ZIP that folder yourself.
2. In the Arduino IDE use **Sketch → Include Library → Add .ZIP Library...**, or place the `DataNet` folder inside your Arduino libraries directory manually.
3. Install the dependencies below through **Library Manager**.

### Required dependencies

Install these via **Library Manager** before using DataNet:

| Library | Author | Version |
|---|---|---|
| ArduinoJson | Benoit Blanchon | 7.x |
| WebSockets | Markus Sattler (Links2004) | 2.4.x |

`HTTPClient` is bundled with the ESP32 and ESP8266 Arduino board packages — no separate install needed.

---

## Quick Start

```cpp
#include <WiFi.h>      // or <ESP8266WiFi.h>
#include <DataNet.h>

DataNet datanet("ak_YOUR_API_KEY");

void onMessage(const char* channel, JsonVariant data) {
    Serial.println(data.as<String>());
}

void setup() {
    Serial.begin(115200);

    WiFi.begin("SSID", "PASSWORD");
    while (WiFi.status() != WL_CONNECTED) delay(500);

    datanet.subscribe("my-project.my-channel", onMessage);
    datanet.connect();
}

void loop() {
    datanet.loop();                                          // required every iteration
    datanet.publishFloat("my-project.my-channel", "temp", 23.5f);
    delay(5000);
}
```

> **WiFi must be connected before calling `datanet.connect()`.**
> The SDK does not manage the WiFi connection for you.

---

## API Reference

### Constructor

```cpp
DataNet datanet(
    const char* apiKey,
    const char* apiUrl = "https://api.datanet.art",
    const char* wsHost = "ws.datanet.art",
    int         wsPort = 443
);
```

Override `apiUrl`, `wsHost`, and `wsPort` to point at a staging or local server.

---

### Methods

| Method | Returns | Description |
|---|---|---|
| `connect()` | `bool` | Fetch JWT via HTTPS, open WSS connection. Returns `true` on success. WiFi must already be connected. |
| `loop()` | `void` | **Must be called every `loop()` iteration.** Drives WebSocket events and heartbeat. |
| `connected()` | `bool` | `true` if the WebSocket is currently open. |
| `subscribe(channel, handler)` | `void` | Subscribe to a channel. `handler` is called on each incoming message. Max `DATANET_MAX_SUBS` (default 8) channels. |
| `unsubscribe(channel)` | `void` | Remove a channel subscription and send an `unsub` envelope. |
| `publish(channel, data)` | `bool` | Publish a `JsonVariant` as the `d` field. Returns `false` if not connected or serialization fails. |
| `publishFloat(channel, key, value)` | `bool` | Convenience: publish `{key: value}` as a float. |
| `publishString(channel, key, value)` | `bool` | Convenience: publish `{key: "value"}` as a string. |
| `on(event, handler)` | `void` | Register a lifecycle event handler. Supported events: `"connect"`, `"disconnect"`, `"error"`. |

---

### Handler signatures

```cpp
// Message handler
void myHandler(const char* channel, JsonVariant data);

// Event handler
void myEventHandler(const char* event, const char* info);
```

---

### Compile-time configuration

Override before `#include <DataNet.h>` or via `-D` compiler flags:

| Macro | Default | Description |
|---|---|---|
| `DATANET_MAX_SUBS` | `8` | Maximum simultaneous channel subscriptions |
| `DATANET_MAX_EVENT_HANDLERS` | `4` | Maximum handlers per event type |
| `DATANET_JWT_BUF_SIZE` | `512` | JWT character buffer size (bytes) |
| `DATANET_HEARTBEAT_INTERVAL_MS` | `30000` | Heartbeat send interval (ms) |
| `DATANET_RECONNECT_BASE_MS` | `1000` | Base reconnect backoff (ms) |
| `DATANET_RECONNECT_MAX_MS` | `60000` | Maximum reconnect backoff cap (ms) |
| `DATANET_JWT_REFRESH_AFTER_RECONNECTS` | `3` | Re-fetch JWT after this many reconnects |

Example:

```cpp
#define DATANET_MAX_SUBS 4        // save RAM on constrained devices
#define DATANET_JWT_BUF_SIZE 768  // increase if your JWT is longer than 512 bytes
#include <DataNet.h>
```

---

## Examples

### BasicPubSub

`File → Examples → DataNet → BasicPubSub`

Minimal subscribe + publish loop. Good starting point.

### TemperatureSensor

`File → Examples → DataNet → TemperatureSensor`

Simulated temperature/humidity sensor that publishes every 5 seconds and subscribes to a commands channel. Demonstrates event handlers, multi-field payloads, and proper `setup()`/`loop()` patterns.

---

## Protocol Notes

The SDK communicates using the DataNet WebSocket protocol:

- **Auth:** `POST https://api.datanet.art/auth/token` with `{"apiKey":"ak_..."}` → `{"token":"<jwt>"}`
- **WebSocket:** connect to `wss://ws.datanet.art/ws` with subprotocol header `Sec-WebSocket-Protocol: bearer, <jwt>`
- **Envelope format:** `{"op":"pub|sub|unsub|hb", "ch":"channel-name", "d":{...}}`
- **Heartbeat:** `{"op":"hb"}` sent every 30 seconds (configurable)

---

## Memory Tips for Constrained Devices

**ESP8266 has ~80 KB of heap.** Keep these points in mind:

- Reduce `DATANET_MAX_SUBS` if you only use a few channels.
- Reduce `DATANET_JWT_BUF_SIZE` to the actual length of your JWT (check your dashboard — typical JWTs are 300–450 bytes). Allowing 50 bytes of headroom is sufficient.
- Use `StaticJsonDocument` (stack-allocated) in your message handlers rather than `DynamicJsonDocument` (heap-allocated).
- Avoid subscribing to channels with large payloads; the incoming message document is sized at 1024 bytes by default. If you need larger payloads, modify `DataNet.cpp` (`_handleMessage` function).
- Call `WiFi.setOutputPower(10)` to reduce WiFi TX power if signal strength allows — this cuts current draw significantly on battery-powered nodes.
- The TLS/SSL handshake requires ~30 KB of heap momentarily. Ensure your sketch does not allocate large buffers before calling `connect()`.

### SSL certificate verification

By default, the SDK calls `setInsecure()` on the TLS client, which skips server certificate verification. This is acceptable for prototyping but **should be replaced with certificate pinning in production**:

**ESP32:**
```cpp
// In DataNet.cpp, replace setInsecure() with:
secureClient.setCACert(rootCACertificate); // PEM string stored in PROGMEM
```

**ESP8266:**
```cpp
// In DataNet.cpp, replace setInsecure() with:
secureClient.setFingerprint("AA BB CC ..."); // SHA-1 fingerprint of the server cert
```

---

## License

MIT — see [LICENSE](LICENSE) for details.
