Skip to content

Commit b30dbbd

Browse files
Copilotpetesramek
andcommitted
feat: sendAck(), state-overwrite fix, and 3 ATtiny88↔ESP8266 bidirectional IoT examples
Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com> Agent-Logs-Url: https://github.com/petesramek/tiny-link/sessions/86755db5-0da8-4952-a821-d2b33def396a
1 parent 07c6e20 commit b30dbbd

17 files changed

Lines changed: 1129 additions & 10 deletions

File tree

README.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,53 @@ void serialEvent() {
202202
> the same type, the last-constructed one is the default ISR target. Call
203203
> `link.enableAutoUpdate()` on the desired instance to override.
204204
205-
See `examples/Auto_Update/` for all three modes with a full side-by-side comparison.
205+
For a complete two-device IoT scenario in all three modes, see the
206+
[`IoT_Sensor_Gateway_*`](examples/) family of examples below.
207+
208+
---
209+
210+
## 🌐 IoT Sensor Gateway — Full Bidirectional Examples
211+
212+
The `IoT_Sensor_Gateway_*` examples demonstrate a realistic ATtiny88 ↔ ESP8266
213+
system: the ATtiny88 reads a temperature sensor, the ESP8266 requests readings
214+
every 60 seconds and forwards them to a cloud endpoint.
215+
216+
### Scenario
217+
218+
```
219+
ATtiny88 ESP8266
220+
│◄──── Handshake ───────────────────►│ begin() on both sides
221+
│ both: WAIT_FOR_SYNC │
222+
│ │
223+
│◄═══ TYPE_CMD (request) ════════════│ every 60 s
224+
│═══ ACK ════════════════════════════►│ sendAck() in callback
225+
│═══ TYPE_DATA (temp + uptime) ═════►│
226+
│◄══ ACK ════════════════════════════ │ sendAck() in callback
227+
│ ESP: posts to cloud
228+
```
229+
230+
### `sendAck()` — Releasing the Peer from AWAITING_ACK
231+
232+
When `begin()` is called (handshake mode), every `sendData()` puts the sender
233+
into `AWAITING_ACK`. The receiver must call `sendAck()` to release it promptly:
234+
235+
```cpp
236+
void onReceive(const SensorMessage& data) {
237+
link.sendAck(); // ← release peer from AWAITING_ACK immediately
238+
link.sendData(TYPE_DATA, response); // reply — safe to call from callback
239+
}
240+
```
241+
242+
### Three Update Modes, Side by Side
243+
244+
| Example folder | update() driven by | ISR-safe callbacks? | HW interrupt? |
245+
|------------------------------------|----------------------|---------------------|---------------|
246+
| `IoT_Sensor_Gateway_Polling` | `loop()` | ✅ Yes | ❌ No |
247+
| `IoT_Sensor_Gateway_TimerISR` | Hardware timer ISR | ⚠ Deferred only | ✅ Yes |
248+
| `IoT_Sensor_Gateway_SerialEvent` | `serialEvent()` | ✅ Yes | ❌ No |
249+
250+
> **Mode 2 (Timer ISR) rule:** callbacks fire inside a hardware ISR — only set
251+
> `volatile` flags there. Call `sendAck()` and `sendData()` from `loop()`.
206252
207253
208254
## 📊 Performance & Telemetry

examples/Auto_Update/README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
# Auto Update — Three Ways to Drive TinyLink
22

3-
This example demonstrates **all three supported update modes** side by side so
4-
you can choose the one that best fits your hardware and application constraints.
5-
6-
TinyLink's engine must be called periodically to process incoming serial bytes,
7-
manage protocol timeouts, and fire callbacks. Previously, interrupt-driven
8-
updates required an explicit `enableAutoUpdate()` call. Since v0.4.x that step
9-
is **no longer required**: the constructor automatically registers the instance,
10-
so every mode works with zero extra setup.
3+
> **Looking for a complete two-device example?**
4+
> The [`IoT_Sensor_Gateway_Polling`](../IoT_Sensor_Gateway_Polling/README.md),
5+
> [`IoT_Sensor_Gateway_TimerISR`](../IoT_Sensor_Gateway_TimerISR/README.md), and
6+
> [`IoT_Sensor_Gateway_SerialEvent`](../IoT_Sensor_Gateway_SerialEvent/README.md)
7+
> examples show a full ATtiny88 ↔ ESP8266 bidirectional scenario — with
8+
> handshake, request, ACK, response, and cloud upload — in each of the three
9+
> update modes. This folder contains minimal single-device mode sketches for
10+
> quick reference.
1111
1212
---
1313

14+
# Auto Update — Mode Reference Sketches
15+
1416
## 🔍 Mode Comparison
1517

1618
| | Mode 1 — Main Loop | Mode 2 — Timer ISR | Mode 3 — serialEvent() |
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* IoT_Sensor_Gateway — Mode 1 (Polling / Main Loop)
3+
* Device: ATtiny88 — Temperature Sensor Node
4+
*
5+
* Responsibilities:
6+
* 1. Establish a TinyLink connection with the ESP8266 via begin().
7+
* 2. Wait for a TYPE_CMD request from the ESP8266.
8+
* 3. Acknowledge the request with sendAck() so the ESP8266 exits AWAITING_ACK.
9+
* 4. Read the temperature sensor and send back a TYPE_DATA response.
10+
* 5. Repeat every time the ESP8266 asks (nominally every 60 s).
11+
*
12+
* Update mode: link.update() called in loop() — works on every board,
13+
* no hardware interrupts or extra libraries required.
14+
*
15+
* Wiring (ATtiny88 @ 5 V ↔ ESP8266 @ 3.3 V):
16+
* ATtiny88 TX (PD1) → [1 kΩ]─┬─ ESP8266 RX
17+
* └─[2 kΩ]─ GND (voltage divider)
18+
* ATtiny88 RX (PD0) ──────────── ESP8266 TX (3.3 V safe on Tiny88 input)
19+
* GND ────────────────────────── GND
20+
*/
21+
22+
#include <TinyLink.h>
23+
#include <adapters/TinyArduinoAdapter.h>
24+
#include "SharedData.h"
25+
26+
using namespace tinylink;
27+
28+
TinyArduinoAdapter adapter(Serial);
29+
TinyLink<SensorMessage, TinyArduinoAdapter> link(adapter);
30+
31+
// ---------------------------------------------------------------------------
32+
// Sensor stub — replace with your real sensor library (e.g. DallasTemperature)
33+
// ---------------------------------------------------------------------------
34+
float readTemperature() {
35+
// Example: analogRead on ATtiny88 pin, scaled to °C
36+
return 21.5f; // stub value
37+
}
38+
39+
// ---------------------------------------------------------------------------
40+
// Callback: fires when a TYPE_CMD request arrives from the ESP8266
41+
// ---------------------------------------------------------------------------
42+
void onRequest(const SensorMessage& req) {
43+
// Step 1: ACK immediately — releases the ESP8266 from AWAITING_ACK.
44+
link.sendAck();
45+
46+
// Step 2: Read sensor and send the response back.
47+
SensorMessage resp;
48+
resp.requestId = req.requestId; // echo so ESP8266 can correlate
49+
resp.temperature = readTemperature();
50+
resp.uptime = static_cast<uint32_t>(millis());
51+
52+
link.sendData(TYPE_DATA, resp);
53+
// ATtiny88 now enters AWAITING_ACK; ESP8266 will call sendAck() when it
54+
// processes the data frame.
55+
}
56+
57+
// ---------------------------------------------------------------------------
58+
59+
void setup() {
60+
Serial.begin(9600);
61+
link.onDataReceived(onRequest);
62+
link.begin(); // Initiate TinyLink handshake with the ESP8266.
63+
// Both sides call begin(); whoever boots first retries
64+
// every 1 s until the other side answers.
65+
}
66+
67+
void loop() {
68+
link.update(); // Drive the TinyLink engine.
69+
// All application logic lives in the onRequest() callback.
70+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* IoT_Sensor_Gateway — Mode 1 (Polling / Main Loop)
3+
* Device: ESP8266 — IoT Gateway
4+
*
5+
* Responsibilities:
6+
* 1. Connect to Wi-Fi.
7+
* 2. Establish a TinyLink connection with the ATtiny88 via begin().
8+
* 3. Every 60 seconds, send a TYPE_CMD request to the ATtiny88.
9+
* 4. When the ATtiny88 sends back a TYPE_DATA response, acknowledge it
10+
* with sendAck() and forward the temperature reading to the cloud.
11+
*
12+
* Update mode: link.update() called in loop() — portable, zero extra setup.
13+
*
14+
* Wiring: see ATtiny88_Sensor.ino
15+
*/
16+
17+
#include <TinyLink.h>
18+
#include <adapters/TinyArduinoAdapter.h>
19+
#include <ESP8266WiFi.h>
20+
#include <ESP8266HTTPClient.h>
21+
#include "SharedData.h"
22+
23+
using namespace tinylink;
24+
25+
// ---------------------------------------------------------------------------
26+
// Configuration — update before uploading
27+
// ---------------------------------------------------------------------------
28+
const char* WIFI_SSID = "YourSSID";
29+
const char* WIFI_PASS = "YourPassword";
30+
const char* ENDPOINT_URL = "http://api.example.com/temperature";
31+
32+
const unsigned long REQUEST_INTERVAL_MS = 60000UL; // 60 s between requests
33+
34+
// ---------------------------------------------------------------------------
35+
36+
TinyArduinoAdapter adapter(Serial);
37+
TinyLink<SensorMessage, TinyArduinoAdapter> link(adapter);
38+
39+
static uint8_t requestId = 0;
40+
static unsigned long lastRequest = 0;
41+
42+
// ---------------------------------------------------------------------------
43+
// Send the temperature reading to the cloud endpoint
44+
// ---------------------------------------------------------------------------
45+
void postToCloud(const SensorMessage& data) {
46+
if (WiFi.status() != WL_CONNECTED) return;
47+
48+
WiFiClient client;
49+
HTTPClient http;
50+
http.begin(client, ENDPOINT_URL);
51+
http.addHeader("Content-Type", "application/json");
52+
53+
char body[96];
54+
snprintf(body, sizeof(body),
55+
"{\"id\":%u,\"temp\":%.2f,\"uptime\":%lu}",
56+
(unsigned)data.requestId,
57+
(double)data.temperature,
58+
(unsigned long)data.uptime);
59+
60+
int code = http.POST(body); // blocks until response or timeout
61+
http.end();
62+
(void)code; // log or handle error code as needed
63+
}
64+
65+
// ---------------------------------------------------------------------------
66+
// Callback: fires when a TYPE_DATA response arrives from the ATtiny88
67+
// ---------------------------------------------------------------------------
68+
void onSensorData(const SensorMessage& data) {
69+
// Step 1: ACK the response — releases the ATtiny88 from AWAITING_ACK.
70+
link.sendAck();
71+
72+
// Step 2: Forward the reading to the cloud.
73+
postToCloud(data);
74+
}
75+
76+
// ---------------------------------------------------------------------------
77+
78+
void setup() {
79+
Serial.begin(9600);
80+
81+
WiFi.begin(WIFI_SSID, WIFI_PASS);
82+
while (WiFi.status() != WL_CONNECTED) { delay(100); }
83+
84+
link.onDataReceived(onSensorData);
85+
link.begin(); // Initiate TinyLink handshake with the ATtiny88.
86+
}
87+
88+
void loop() {
89+
link.update(); // Drive the TinyLink engine.
90+
91+
// Every 60 s (once the link is up): request a sensor reading.
92+
if (link.connected() &&
93+
(millis() - lastRequest >= REQUEST_INTERVAL_MS)) {
94+
95+
SensorMessage req;
96+
req.requestId = requestId++;
97+
req.temperature = 0.0f; // not used in requests
98+
req.uptime = static_cast<uint32_t>(millis());
99+
100+
link.sendData(TYPE_CMD, req); // → ATtiny88 receives this as a Cmd
101+
lastRequest = millis();
102+
}
103+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# IoT Sensor Gateway — Mode 1: Main-Loop Polling
2+
3+
ATtiny88 temperature sensor node ↔ ESP8266 Wi-Fi gateway.
4+
This is the **simplest update mode**: `link.update()` called once per `loop()`.
5+
No hardware interrupts or extra libraries are required — works on every Arduino-compatible board.
6+
7+
---
8+
9+
## 🗺 Hardware Wiring
10+
11+
```
12+
ATtiny88 (5 V) Connection ESP8266 (3.3 V)
13+
────────────────── ─────────────────── ──────────────────
14+
TX (PD1) → [1 kΩ]──┬──────────── → RX
15+
└──[2 kΩ]── GND (voltage divider)
16+
RX (PD0) ──────────────────────── ← TX (3.3 V safe on Tiny88 input)
17+
GND ──────────────────────── GND (common)
18+
```
19+
20+
---
21+
22+
## 📡 Protocol Flow
23+
24+
```
25+
ATtiny88 ESP8266
26+
│ │
27+
│◄──── HS(v=0) ─────────────────────►│ begin() on both sides
28+
│───── HS(v=1) ────────────────────► │ (retries every 1 s until peer answers)
29+
│ both reach WAIT_FOR_SYNC │
30+
│ │
31+
│◄═══ TYPE_CMD (requestId=N) ════════│ every 60 s
32+
│ ESP: AWAITING_ACK
33+
│═══ ACK ════════════════════════════►│ sendAck() in onRequest()
34+
│ ESP: WAIT_FOR_SYNC
35+
│═══ TYPE_DATA (temp, uptime) ═══════►│ sendData() in onRequest()
36+
│ ATtiny88: AWAITING_ACK│
37+
│◄══ ACK ════════════════════════════ │ sendAck() in onSensorData()
38+
│ ATtiny88: WAIT_FOR_SYNC
39+
│ ESP posts to cloud
40+
│ │
41+
│ ... 60 seconds ... │
42+
```
43+
44+
---
45+
46+
## 🔑 Key Points
47+
48+
- **`begin()`** initiates the TinyLink handshake. Both sides call it; whoever
49+
boots first retries automatically every 1 second.
50+
- **`sendAck()`** releases the peer from `AWAITING_ACK` without waiting for the
51+
timeout. Call it first in every `onDataReceived` callback.
52+
- **`sendData()` from inside a callback** is safe and sets the sender into
53+
`AWAITING_ACK` correctly (the state is preserved, not overwritten).
54+
- **`link.type()`** returns `TYPE_CMD` or `TYPE_DATA` so a single callback can
55+
distinguish request from response if needed.
56+
57+
---
58+
59+
## 🔄 Update Mode: Main-Loop Polling
60+
61+
```cpp
62+
void loop() {
63+
link.update(); // ← the only required call
64+
}
65+
```
66+
67+
| Attribute | Value |
68+
|-----------------------|------------------------------------|
69+
| Hardware interrupt? | ❌ No |
70+
| Extra library? | ❌ No |
71+
| Works if loop blocks? | ❌ No — keep `loop()` responsive |
72+
| Min RAM overhead | 0 bytes |
73+
74+
> For a version that survives a blocking `loop()`, see
75+
> **IoT_Sensor_Gateway_TimerISR**.
76+
> For a zero-interrupt reactive alternative, see
77+
> **IoT_Sensor_Gateway_SerialEvent**.
78+
79+
---
80+
81+
## 🚀 How to Run
82+
83+
1. Edit `WIFI_SSID`, `WIFI_PASS`, and `ENDPOINT_URL` in `ESP8266_Gateway.ino`.
84+
2. Upload `ATtiny88_Sensor.ino` to the ATtiny88 (set clock to 8 MHz).
85+
3. Upload `ESP8266_Gateway.ino` to the ESP8266.
86+
4. Open the ESP8266 Serial Monitor (9600 baud) to observe the handshake and the
87+
60-second data cycles.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @file SharedData.h
3+
* @brief Common payload type used by both the ATtiny88 sensor node and the
4+
* ESP8266 gateway in all three IoT_Sensor_Gateway examples.
5+
*
6+
* Both devices instantiate:
7+
* TinyLink<SensorMessage, TinyArduinoAdapter> link(adapter);
8+
*
9+
* The TinyLink wire type (TYPE_CMD / TYPE_DATA) tells the receiver which
10+
* direction the frame is travelling:
11+
*
12+
* TYPE_CMD ('C') ESP8266 → ATtiny88 "please send a sensor reading"
13+
* TYPE_DATA ('D') ATtiny88 → ESP8266 sensor reading + uptime
14+
*/
15+
16+
#pragma once
17+
#include <stdint.h>
18+
#include "protocol/MessageType.h"
19+
20+
struct SensorMessage {
21+
uint8_t requestId; /**< Request counter. Response echoes this value. */
22+
float temperature; /**< Degrees Celsius. Set to 0.0 in request frames. */
23+
uint32_t uptime; /**< Sender's millis() at send time. 0 in requests. */
24+
} __attribute__((packed));
25+
26+
// Convenience constants — avoid scattering the cast everywhere.
27+
static const uint8_t TYPE_CMD =
28+
static_cast<uint8_t>(tinylink::MessageType::Cmd);
29+
static const uint8_t TYPE_DATA =
30+
static_cast<uint8_t>(tinylink::MessageType::Data);

0 commit comments

Comments
 (0)