Skip to content

fedepaj/SCRAP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SCRAP: Serial Compact Reliable Asynchronous Protocol

SCRAP Protocol Logo

Version 1.0.0

Introduction

SCRAP is a lightweight, packet-based serial communication protocol designed for resource-constrained embedded systems. It provides reliable, structured data exchange over UART, RS-232, RS-485, or virtual serial ports.

Two fully interoperable implementations:

  • C (C89): Pure ANSI C, zero dependencies, no malloc. Runs on anything from bare-metal MCUs to Linux.
  • Python 3: Zero dependencies (native COBS and CRC). Ideal for PC-side tools and testing.

Features

  • Sliding Window (Go-Back-N): Configurable window size for higher throughput (default: Stop-and-Wait)
  • Virtual Channels: Multiplex independent data streams on a single link
  • Bus Topology: Optional addressing for RS-485 multi-node networks (SCRAP_ENABLE_ADDRESSING)
  • Heartbeat: Configurable periodic heartbeat with timeout detection
  • Statistics: Built-in packet counters (TX, RX, retransmissions, CRC errors, duplicates)
  • Error Callbacks: Get notified on CRC mismatches, decode failures, buffer overflows
  • COBS Framing: Robust packet delimiting with Consistent Overhead Byte Stuffing
  • CRC16-CCITT: Integrity check on every packet
  • Single-Header Option: Auto-generated dist/scrap.h for drop-in use (stb-style)
  • C89 Strict: Compiles with -std=c89 -Wall -Wextra -Werror -pedantic

Packet Structure

P2P mode (7-byte header):

seq(2) | flags(1) | channel(1) | type(1) | payload_len(2) | payload(N) | crc(2)

BUS mode (9-byte header, with SCRAP_ENABLE_ADDRESSING):

src(1) | dst(1) | seq(2) | flags(1) | channel(1) | type(1) | payload_len(2) | payload(N) | crc(2)

The raw packet is COBS-encoded and terminated with a 0x00 delimiter byte.

Quick Start (C)

Option A: Single-header (simplest)

Copy dist/scrap.h into your project. In one .c file:

#define SCRAP_IMPLEMENTATION
#include "scrap.h"

In all other files, just #include "scrap.h".

Option B: Multi-file

Add src/scrap.h and src/scrap.c to your build.

Minimal Example

#include "scrap.h"

#define MAX_PAYLOAD 128

static Scrap_Handle handle;
static uint8_t rx_buf[SCRAP_BUFFER_SIZE(MAX_PAYLOAD)];
static uint8_t tx_buf[SCRAP_BUFFER_SIZE(MAX_PAYLOAD)];

/* Implement these for your platform */
uint16_t my_write(const uint8_t* data, uint16_t len) { /* ... */ }
uint16_t my_timestamp_ms(void) { /* ... */ }

void on_data(Scrap_Handle* h, const uint8_t* payload, uint16_t len) {
    /* Handle received data */
}

int main(void) {
    Scrap_Config cfg;
    Scrap_Handle* h;

    memset(&cfg, 0, sizeof(cfg));
    cfg.rx_buffer      = rx_buf;
    cfg.rx_buffer_size = sizeof(rx_buf);
    cfg.tx_buffer      = tx_buf;
    cfg.tx_buffer_size = sizeof(tx_buf);
    cfg.max_payload_size = MAX_PAYLOAD;
    cfg.transport.transport_write = my_write;
    cfg.transport.get_timestamp_ms = my_timestamp_ms;
    cfg.callbacks.data_received = on_data;

    h = Scrap_Init(&handle, &cfg);

    /* Send data */
    Scrap_SendData(h, (const uint8_t*)"Hello", 5, 1 /* reliable */);

    /* Main loop */
    while (1) {
        uint8_t byte = uart_read();
        Scrap_ReceiveBytes(h, &byte, 1);
        Scrap_Process(h);
    }
}

Configuration

All configuration is done via #define before including scrap.h:

Define Default Description
SCRAP_MAX_PACKET_PAYLOAD 512 Maximum payload size in bytes
SCRAP_MAX_CHANNELS 1 Number of virtual channels
SCRAP_MAX_WINDOW_SIZE 1 Sliding window size (1 = Stop-and-Wait)
SCRAP_ENABLE_ADDRESSING (undef) Enable bus topology with src/dst addressing
SCRAP_U8 unsigned char 8-bit unsigned type override
SCRAP_U16 unsigned short 16-bit unsigned type override
SCRAP_U32 unsigned long 32-bit unsigned type override
SCRAP_LOG no-op Debug logging function
SCRAP_MEMCPY/MEMSET/MEMMOVE stdlib Memory function overrides

C API Reference

Core

Scrap_Handle* Scrap_Init(void* h_mem, const Scrap_Config* config);
void Scrap_ReceiveBytes(Scrap_Handle* h, const uint8_t* data, uint16_t len);
void Scrap_Process(Scrap_Handle* h);       /* Call periodically */
uint8_t Scrap_IsReadyToSend(Scrap_Handle* h);

Sending

uint16_t Scrap_SendData(h, payload, len, reliable);
uint16_t Scrap_SendCommand(h, cmd_id, params, params_len, reliable);
uint16_t Scrap_SendDataEx(h, channel, payload, len, reliable);
uint16_t Scrap_SendCommandEx(h, channel, cmd_id, params, params_len, reliable);
uint16_t Scrap_Ping(h);

Channels & Utilities

uint8_t Scrap_SetChannelCallbacks(h, channel, callbacks);
uint16_t Scrap_CRC16(data, len);
void* Scrap_GetUserData(h);
const Scrap_Stats* Scrap_GetStats(h);
void Scrap_ResetStats(h);
size_t Scrap_GetHandleSize(void);

Callbacks (set in Scrap_Config.callbacks)

void (*command_received)(h, cmd_id, params, params_len);
void (*data_received)(h, payload, len);
void (*send_success)(h, sequence_number);
void (*send_failed)(h, sequence_number);
void (*ping_response)(h, rtt_ms);
void (*error)(h, error_code);
void (*heartbeat_received)(h);
void (*heartbeat_timeout)(h);

Python Quick Start

from scrap.protocol import Scrap

# In-memory loopback example
wire = bytearray()
a = Scrap(lambda data: wire.extend(data) or len(data))
b = Scrap(lambda data: None)

b.on_data_received = lambda payload: print(f"Got: {payload}")
a.send_data(b"Hello!")
b.receive_bytes(bytes(wire))
# Output: Got: b'Hello!'

For serial communication:

import serial
ser = serial.Serial('/dev/ttyUSB0', 115200)
scrap = Scrap(ser.write, window_size=4)
scrap.on_data_received = lambda p: print(f"RX: {p}")
scrap.send_data(b"Hello!", reliable=True)

Building

make          # C89 check + unit tests + generate dist/scrap.h
make test     # Run C and Python tests only
make dist     # Generate dist/scrap.h only
make examples # Build file transfer examples
make clean    # Remove build artifacts

Project Structure

src/
  scrap.h          # Public API header
  scrap.c          # Implementation
dist/
  scrap.h          # Auto-generated single-header (stb-style)
pysrc/scrap/       # Python implementation (zero dependencies)
tests/
  test_scrap.c     # C unit tests (17 tests)
examples/
  arduino/         # Arduino sketch example
  file_transfer/   # C file transfer (host + device)
  python/          # Python loopback test
tools/
  amalgamate.py    # Generates dist/scrap.h from src/

License

MIT License. See LICENSE.md.

About

SCRAP is a lightweight, reliable, packet-based serial communication protocol designed for embedded systems.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors