{"id":47635538,"url":"https://github.com/petesramek/tiny-link","last_synced_at":"2026-04-02T00:03:28.758Z","repository":{"id":343337814,"uuid":"1176815976","full_name":"petesramek/tiny-link","owner":"petesramek","description":"TinyLink is a lightweight, template-based serial protocol for reliable UART communication between microcontrollers. It features a robust state machine, checksum validation, and a hardware-agnostic design ideal for linking resource-constrained devices like the MH-Tiny88 and ESP-M3. Supports custom structs, error tracking, and non-blocking parsing. ","archived":false,"fork":false,"pushed_at":"2026-03-21T22:03:22.000Z","size":359,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-22T10:37:50.262Z","etag":null,"topics":["arduino","attiny","cpp","embedded","esp","header-only","iot","microcontroller","reliable-data-transfer","serial-communication","state-machine","uart-protocol"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/petesramek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-09T12:06:17.000Z","updated_at":"2026-03-21T13:19:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/petesramek/tiny-link","commit_stats":null,"previous_names":["petesramek/tiny-link"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/petesramek/tiny-link","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petesramek%2Ftiny-link","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petesramek%2Ftiny-link/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petesramek%2Ftiny-link/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petesramek%2Ftiny-link/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/petesramek","download_url":"https://codeload.github.com/petesramek/tiny-link/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/petesramek%2Ftiny-link/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31293160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T21:15:39.731Z","status":"ssl_error","status_checked_at":"2026-04-01T21:15:34.046Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arduino","attiny","cpp","embedded","esp","header-only","iot","microcontroller","reliable-data-transfer","serial-communication","state-machine","uart-protocol"],"created_at":"2026-04-02T00:03:27.327Z","updated_at":"2026-04-02T00:03:28.649Z","avatar_url":"https://github.com/petesramek.png","language":"C++","readme":"# TinyLink 🚀\n\n[![TinyLink CI](https://github.com/petesramek/tiny-link/actions/workflows/cpp-ci.yml/badge.svg)](https://github.com/petesramek/tiny-link/actions/workflows/cpp-ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n\u003e Disclaimer: It a vibe-coded project primarily used to learn how software for AVR is developed. At the same time its purpose is to understand how GitHub Copilot works and if there is any benefit using it. I discourage using the project in any scenario other than testing and validating your skills while developing DYI projects.\n\n**TinyLink** is a high-efficiency, template-based serial protocol for reliable, bidirectional UART communication. Optimized for memory-constrained devices like the **MH-Tiny88** (512B RAM) and **ESP-M3**, it provides a \"pro-level\" transport layer with zero-heap overhead.\n\n---\n\n## 🌟 Key Features\n\n- **Strict Integrity**: Uses the **Fletcher-16** algorithm—significantly more reliable than simple sum-checks for catching bit-flips and swapped bytes.\n\n- **COBS Framing**: Implements *Consistent Overhead Byte Stuffing*. By using `0x00` as a unique delimiter, the protocol never \"desyncs\" and is immune to payload data being mistaken for control characters.\n\n- **Event-Driven API**: Support for **Asynchronous Callbacks** via `onReceive()`. Handle data the moment it arrives without cluttering your `loop()`.\n\n- **Zero-Copy \u0026 Zero-Heap**: Optimized for 8-bit AVR. No dynamic memory allocation; data is processed directly in static buffers.\n\n- **Multi-Platform**: Native adapters for **Arduino**, **Linux (POSIX)**, **Windows (Win32)**, **ESP-IDF**, and **STM32 HAL**.\n\n---\n\n## 🛠 Installation\n\n1. Download this repository as a `.zip`.\n2. In Arduino IDE, go to **Sketch \u003e Include Library \u003e Add .ZIP Library**.\n3. For **PlatformIO**, simply drop the folder into your `lib/` directory or reference it in `platformio.ini`.\n\n---\n\n## 🚀 Quick Start (Callback Style)\n\n### 1. Define your Data\n\nBoth devices must share an identical `struct` with identical size that is ensured by `__attribute__((packed))`.\n\n```cpp\nstruct MyData {\n  uint32_t uptime;\n  float temperature;\n} __attribute__((packed));\n```\n\n### 2. Implementation\n\nTinyLink uses the tinylink namespace to prevent naming conflicts with other libraries.\n\n#### 2.1 Namespace\n\n**For Arduino/Simplified Sketches**\n\nAdd using namespace tinylink; after your includes to use the library classes directly.\n\n```cpp\n#include \u003cTinyLink.h\u003e\n#include \u003cadapters/TinyArduinoAdapter.h\u003e\n\nusing namespace tinylink; // \u003c--- Simplifies your code\n\nTinyArduinoAdapter adapter(Serial);\nTinyLink\u003cMyData, TinyArduinoAdapter\u003e link(adapter);\n```\n\n**For Professional C++ / Large Projects**\n\nIt is recommended to use the explicit namespace prefix to ensure absolute symbol safety.\n\n```cpp\n#include \u003cTinyLink.h\u003e\n#include \u003cadapters/TinyArduinoAdapter.h\u003e\n\ntinylink::TinyPosixAdapter adapter(\"/dev/ttyUSB0\", B9600);\ntinylink::TinyLink\u003cMyData, tinylink::TinyPosixAdapter\u003e link(adapter);\n```\n\n#### 2.2 Usage\n\n**Option A: Callback Style (Recommended)**\n\nBest for clean, event-driven code. The handler triggers automatically inside `update()`.\n\n```cpp\nvoid onReceive(const MyData\u0026 data) {\n    Serial.println(data.temperature);\n}\n\nvoid setup() {\n    link.onReceive(onReceive);\n}\n\nvoid loop() {\n    link.update(); // Engine handles the callback\n}\n```\n\n**Option B: Polling Style**\n\nBest for manual control or integrating into existing linear logic.\n\n```cpp\nvoid loop() {\n    link.update(); // Keep the engine running\n\n    if (link.available()) {\n        const MyData\u0026 data = link.peek();\n        Serial.println(data.temperature);\n        \n        link.flush(); // Clear flag for next packet\n    }\n}\n```\n\n### 3. Sending Data\n\nSending is identical for both styles:\n\n```cpp\n#include \u003cTinyLink.h\u003e\n#include \u003cadapters/TinyArduinoAdapter.h\u003e\n\nTinyArduinoAdapter adapter(Serial);\nTinyLink\u003cMyData, TinyArduinoAdapter\u003e link(adapter);\n\nvoid loop() {\n  if(link.connected()) {\n    MyData msg = { millis(), 24.5f };\n    link.send(TYPE_DATA, msg);\n  }\n}\n```\n\n## 📊 Choosing Your Pattern\n\n| Feature | Polling Style (available/peek) | Callback Style (onReceive) |\n| --- |  --- |  --- | \n| Logic Flow | Linear / Sequential | Event-Driven / Asynchronous |\n| Best For | Simple sketches, step-by-step debugging. | Complex projects, multi-tasking, clean loop(). |\n| Control | User decides when to process data. | Data is processed the moment it arrives. |\n| Code Style | Traditional Arduino if checks. | Modern C++ \"Listener\" pattern. |\n| Manual Cleanup | Requires link.flush() after reading. | Automatic; no flush() or available() needed. |\n| RAM Usage | Identical (Zero-copy const T\u0026). | Identical (2-byte Function Pointer). |\n\n**Which one should I use?**\n\n**Use Polling** if your `loop()` is already very busy with `delay()` calls or if you need to wait for a specific state before \"consuming\" the incoming serial data.\n\n**Use Callbacks** if you want to keep your communication logic completely separated from your application logic. This is highly recommended as it makes the `TinyLink` engine more reactive.\n\n\n## 🔄 Auto-Update Modes\n\nTinyLink's protocol engine must be called periodically to process incoming bytes,\nmanage timeouts, and fire callbacks.  There are **three supported ways** to drive\nit — choose the one that fits your hardware and application:\n\n| | Mode 1 — Main Loop | Mode 2 — Timer ISR | Mode 3 — serialEvent() |\n|---|---|---|---|\n| **How** | `link.update()` in `loop()` | `autoUpdateISR()` on a hardware timer | `autoUpdateISR()` from `serialEvent()` |\n| **Hardware interrupt?** | ❌ No | ✅ Timer required | ❌ No |\n| **Extra setup call?** | None | Timer init + `attachInterrupt` | None |\n| **`enableAutoUpdate()`?** | Not needed | Not needed | Not needed |\n| **Works if loop() blocks?** | ❌ No | ✅ Yes | ❌ No |\n| **Best for** | Any board, simplest code | Deterministic, busy loops | No-timer boards, reactive RX |\n\n### Mode 1 — Main Loop (default, works everywhere)\n\n```cpp\nvoid loop() {\n    link.update();  // ← only required call; works on every board\n}\n```\n\n### Mode 2 — Timer ISR (deterministic, interrupt-driven)\n\nThe constructor auto-registers the instance — no `enableAutoUpdate()` needed.\n\n```cpp\n#include \u003cTimerOne.h\u003e\n\nvoid setup() {\n    Timer1.initialize(1000);  // 1 ms\n    Timer1.attachInterrupt(TinyLink\u003cMyData, TinyArduinoAdapter\u003e::autoUpdateISR);\n    // No enableAutoUpdate() call required.\n}\n// loop() does not need to call update() at all.\n```\n\n### Mode 3 — serialEvent() (zero-setup, no timer needed)\n\nArduino calls `serialEvent()` automatically between `loop()` iterations when\nSerial bytes are waiting — no library or interrupt configuration required.\n\n```cpp\nvoid serialEvent() {\n    TinyLink\u003cMyData, TinyArduinoAdapter\u003e::autoUpdateISR();\n}\n// setup() and loop() have no TinyLink maintenance calls.\n```\n\n\u003e **Multi-instance note:** When you have two `TinyLink\u003cT,Adapter\u003e` objects of\n\u003e the same type, the last-constructed one is the default ISR target.  Call\n\u003e `link.enableAutoUpdate()` on the desired instance to override.\n\nFor a complete two-device IoT scenario in all three modes, see the\n[`IoT_Sensor_Gateway_*`](examples/) family of examples below.\n\n---\n\n## 🌐 IoT Sensor Gateway — Full Bidirectional Examples\n\nThe `IoT_Sensor_Gateway_*` examples demonstrate a realistic ATtiny88 ↔ ESP8266\nsystem: the ATtiny88 reads a temperature sensor, the ESP8266 requests readings\nevery 60 seconds and forwards them to a cloud endpoint.\n\n### Scenario\n\n```\nATtiny88                              ESP8266\n    │◄──── Handshake ───────────────────►│  begin() on both sides\n    │             both: WAIT_FOR_SYNC    │\n    │                                    │\n    │◄═══ TYPE_CMD (request) ════════════│  every 60 s\n    │═══ ACK ════════════════════════════►│  sendAck() in callback\n    │═══ TYPE_DATA (temp + uptime) ═════►│\n    │◄══ ACK ════════════════════════════ │  sendAck() in callback\n    │                         ESP: posts to cloud\n```\n\n### `sendAck()` — Releasing the Peer from AWAITING_ACK\n\nWhen `begin()` is called (handshake mode), every `sendData()` puts the sender\ninto `AWAITING_ACK`.  The receiver must call `sendAck()` to release it promptly:\n\n```cpp\nvoid onReceive(const SensorMessage\u0026 data) {\n    link.sendAck();          // ← release peer from AWAITING_ACK immediately\n    link.sendData(TYPE_DATA, response);  // reply — safe to call from callback\n}\n```\n\n### Three Update Modes, Side by Side\n\n| Example folder                     | update() driven by   | ISR-safe callbacks? | HW interrupt? |\n|------------------------------------|----------------------|---------------------|---------------|\n| `IoT_Sensor_Gateway_Polling`       | `loop()`             | ✅ Yes              | ❌ No         |\n| `IoT_Sensor_Gateway_TimerISR`      | Hardware timer ISR   | ⚠ Deferred only    | ✅ Yes        |\n| `IoT_Sensor_Gateway_SerialEvent`   | `serialEvent()`      | ✅ Yes              | ❌ No         |\n\n\u003e **Mode 2 (Timer ISR) rule:** callbacks fire inside a hardware ISR — only set\n\u003e `volatile` flags there.  Call `sendAck()` and `sendData()` from `loop()`.\n\n\n## 📊 Performance \u0026 Telemetry\n\nMonitor link health in real-time to diagnose wiring issues:\n\n```cpp\nconst TinyStats\u0026 stats = link.getStats();\nSerial.print(\"Packets: \"); Serial.println(stats.packets);\nSerial.print(\"CRC Errors: \"); Serial.println(stats.crcErrs);\nSerial.print(\"Timeouts: \"); Serial.println(stats.timeouts);\n```\n\n## 📂 Project Structure\n\n`src/`: Core protocol logic (`TinyLink.h`, `TinyProtocol.h`).\n\n`src/protocol/`: Focused protocol headers (`MessageType.h`, `Status.h`, `State.h`, `Stats.h`, `AckMessage.h`, `DebugMessage.h`).\n\n`src/adapters/`: Hardware-specific drivers (Arduino, Posix, Windows, etc.).\n\n`examples/`: Ready-to-run duplex, callback, and health-monitoring demos.\n\n`test/`: Native C++ unit tests using `TinyTestAdapter`.\n\n## 🔄 Migration Guide\n\n### `MessageType::Req` → `MessageType::Cmd` (wire `'R'` → `'C'`) — **Breaking Change**\n\n`MessageType::Req` (wire byte `'R'`) has been removed and replaced with `MessageType::Cmd`\n(wire byte `'C'`). Legacy `'R'` bytes are **no longer accepted** by the parser.\n\n**Action required**: Update all peers to send `'C'` for command frames.\n\n```cpp\n// Outgoing command frames:\nlink.send(message_type_to_wire(MessageType::Cmd), msg);   // emits 'C'\n\n// Parsing — only 'C' is accepted:\nMessageType mt;\nif (message_type_from_wire(link.type(), mt) \u0026\u0026 mt == MessageType::Cmd) {\n    // handle command\n}\n```\n\n### `MessageType::Ack` added (wire `'A'`)\n\nACK/NACK frames now use `MessageType::Ack` (`'A'`) carrying a `TinyAck` payload:\n\n```cpp\n// TinyAck payload: 2 bytes — seq (uint8_t) + result (TinyStatus)\ntinylink::TinyAck ack;\nack.seq    = link.seq();\nack.result = tinylink::TinyStatus::STATUS_OK;\nlink.send(message_type_to_wire(tinylink::MessageType::Ack), ack);\n```\n\n### `TinyStatus` — consolidated ACK/error codes\n\n`TinyStatus` now carries granular ACK codes (previously spread across a separate\n`TinyResult` type, which has been removed):\n\n| Value | Code | Meaning |\n|-------|------|---------|\n| `0x00` | `STATUS_OK` | No error; operation succeeded |\n| `0x01` | `ERR_CRC` | Checksum or framing failure |\n| `0x02` | `ERR_TIMEOUT` | Inter-byte timeout |\n| `0x03` | `ERR_OVERFLOW` | Receive buffer overflow |\n| `0x04` | `ERR_BUSY` | Peer is busy; retry later |\n| `0x05` | `ERR_PROCESSING` | Peer is still processing a prior command |\n| `0xFF` | `ERR_UNKNOWN` | Unspecified error |\n\nUse `tinystatus_to_string()` for human-readable diagnostics:\n\n```cpp\n#include \"protocol/Status.h\"\nconst char* msg = tinylink::tinystatus_to_string(tinylink::TinyStatus::ERR_CRC);\n```\n\n### `DebugMessage` — structured debug payload (`MessageType::Debug` / `'g'`)\n\n```cpp\n#include \"protocol/DebugMessage.h\"\ntinylink::DebugMessage dbg;\ndbg.ts    = millis();\ndbg.level = 1;\ntinylink::debugmessage_set_text(dbg, \"boot complete\");\nlink.send(message_type_to_wire(tinylink::MessageType::Debug), dbg);\n```\n\n## 📜 License\nThis project is licensed under the MIT License.\n\n**Developed by Pete Sramek (2026)**\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetesramek%2Ftiny-link","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpetesramek%2Ftiny-link","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpetesramek%2Ftiny-link/lists"}