Skip to content

petesramek/tiny-link

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

76 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

TinyLink πŸš€

TinyLink CI License: MIT

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.


🌟 Key Features

  • Strict Integrity: Uses the Fletcher-16 algorithmβ€”significantly more reliable than simple sum-checks for catching bit-flips and swapped bytes.

  • 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.

  • Event-Driven API: Support for Asynchronous Callbacks via onReceive(). Handle data the moment it arrives without cluttering your loop().

  • Zero-Copy & Zero-Heap: Optimized for 8-bit AVR. No dynamic memory allocation; data is processed directly in static buffers.

  • Multi-Platform: Native adapters for Arduino, Linux (POSIX), Windows (Win32), ESP-IDF, and STM32 HAL.


πŸ›  Installation

  1. Download this repository as a .zip.
  2. In Arduino IDE, go to Sketch > Include Library > Add .ZIP Library.
  3. For PlatformIO, simply drop the folder into your lib/ directory or reference it in platformio.ini.

πŸš€ Quick Start (Callback Style)

1. Define your Data

Both devices must share an identical struct with identical size that is ensured by __attribute__((packed)).

struct MyData {
  uint32_t uptime;
  float temperature;
} __attribute__((packed));

2. Implementation

TinyLink uses the tinylink namespace to prevent naming conflicts with other libraries.

2.1 Namespace

For Arduino/Simplified Sketches

Add using namespace tinylink; after your includes to use the library classes directly.

#include <TinyLink.h>
#include <adapters/TinyArduinoAdapter.h>

using namespace tinylink; // <--- Simplifies your code

TinyArduinoAdapter adapter(Serial);
TinyLink<MyData, TinyArduinoAdapter> link(adapter);

For Professional C++ / Large Projects

It is recommended to use the explicit namespace prefix to ensure absolute symbol safety.

#include <TinyLink.h>
#include <adapters/TinyArduinoAdapter.h>

tinylink::TinyPosixAdapter adapter("/dev/ttyUSB0", B9600);
tinylink::TinyLink<MyData, tinylink::TinyPosixAdapter> link(adapter);

2.2 Usage

Option A: Callback Style (Recommended)

Best for clean, event-driven code. The handler triggers automatically inside update().

void onReceive(const MyData& data) {
    Serial.println(data.temperature);
}

void setup() {
    link.onReceive(onReceive);
}

void loop() {
    link.update(); // Engine handles the callback
}

Option B: Polling Style

Best for manual control or integrating into existing linear logic.

void loop() {
    link.update(); // Keep the engine running

    if (link.available()) {
        const MyData& data = link.peek();
        Serial.println(data.temperature);
        
        link.flush(); // Clear flag for next packet
    }
}

3. Sending Data

Sending is identical for both styles:

#include <TinyLink.h>
#include <adapters/TinyArduinoAdapter.h>

TinyArduinoAdapter adapter(Serial);
TinyLink<MyData, TinyArduinoAdapter> link(adapter);

void loop() {
  if(link.connected()) {
    MyData msg = { millis(), 24.5f };
    link.send(TYPE_DATA, msg);
  }
}

πŸ“Š Choosing Your Pattern

Feature Polling Style (available/peek) Callback Style (onReceive)
Logic Flow Linear / Sequential Event-Driven / Asynchronous
Best For Simple sketches, step-by-step debugging. Complex projects, multi-tasking, clean loop().
Control User decides when to process data. Data is processed the moment it arrives.
Code Style Traditional Arduino if checks. Modern C++ "Listener" pattern.
Manual Cleanup Requires link.flush() after reading. Automatic; no flush() or available() needed.
RAM Usage Identical (Zero-copy const T&). Identical (2-byte Function Pointer).

Which one should I use?

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.

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.

πŸ“Š Performance & Telemetry

Monitor link health in real-time to diagnose wiring issues:

const TinyStats& stats = link.getStats();
Serial.print("Packets: "); Serial.println(stats.packets);
Serial.print("CRC Errors: "); Serial.println(stats.crcErrs);
Serial.print("Timeouts: "); Serial.println(stats.timeouts);

πŸ“‚ Project Structure

src/: Core protocol logic (TinyLink.h, TinyProtocol.h).

src/protocol/: Focused protocol headers (MessageType.h, Status.h, State.h, Stats.h, AckMessage.h, DebugMessage.h).

src/adapters/: Hardware-specific drivers (Arduino, Posix, Windows, etc.).

examples/: Ready-to-run duplex, callback, and health-monitoring demos.

test/: Native C++ unit tests using TinyTestAdapter.

πŸ”„ Migration Guide

MessageType::Req β†’ MessageType::Cmd (wire 'R' β†’ 'C') β€” Breaking Change

MessageType::Req (wire byte 'R') has been removed and replaced with MessageType::Cmd (wire byte 'C'). Legacy 'R' bytes are no longer accepted by the parser.

Action required: Update all peers to send 'C' for command frames.

// Outgoing command frames:
link.send(message_type_to_wire(MessageType::Cmd), msg);   // emits 'C'

// Parsing β€” only 'C' is accepted:
MessageType mt;
if (message_type_from_wire(link.type(), mt) && mt == MessageType::Cmd) {
    // handle command
}

MessageType::Ack added (wire 'A')

ACK/NACK frames now use MessageType::Ack ('A') carrying a TinyAck payload:

// TinyAck payload: 2 bytes β€” seq (uint8_t) + result (TinyStatus)
tinylink::TinyAck ack;
ack.seq    = link.seq();
ack.result = tinylink::TinyStatus::STATUS_OK;
link.send(message_type_to_wire(tinylink::MessageType::Ack), ack);

TinyStatus β€” consolidated ACK/error codes

TinyStatus now carries granular ACK codes (previously spread across a separate TinyResult type, which has been removed):

Value Code Meaning
0x00 STATUS_OK No error; operation succeeded
0x01 ERR_CRC Checksum or framing failure
0x02 ERR_TIMEOUT Inter-byte timeout
0x03 ERR_OVERFLOW Receive buffer overflow
0x04 ERR_BUSY Peer is busy; retry later
0x05 ERR_PROCESSING Peer is still processing a prior command
0xFF ERR_UNKNOWN Unspecified error

Use tinystatus_to_string() for human-readable diagnostics:

#include "protocol/Status.h"
const char* msg = tinylink::tinystatus_to_string(tinylink::TinyStatus::ERR_CRC);

DebugMessage β€” structured debug payload (MessageType::Debug / 'g')

#include "protocol/DebugMessage.h"
tinylink::DebugMessage dbg;
dbg.ts    = millis();
dbg.level = 1;
tinylink::debugmessage_set_text(dbg, "boot complete");
link.send(message_type_to_wire(tinylink::MessageType::Debug), dbg);

πŸ“œ License

This project is licensed under the MIT License.

Developed by Pete Sramek (2026)

About

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.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors