A lightweight binary protocol for ESP32 communication over BLE or Serial UART.
Designed for embedded systems with:
- Minimal overhead (4 bytes per frame)
- CRC-8 error detection
- Up to 250 bytes payload
- Zero-copy callback architecture
- Transport-agnostic core
| Component | Description |
|---|---|
etherlink |
Core protocol (no dependencies) |
etherlink_ble |
BLE Nordic UART Service transport |
etherlink_uart |
Serial UART transport |
idf.py add-dependency "etherlink"
idf.py add-dependency "etherlink_ble" # Optional: for BLE
idf.py add-dependency "etherlink_uart" # Optional: for UARTIn your project's main/idf_component.yml:
dependencies:
etherlink:
path: "../etherlink/etherlink"
etherlink_ble:
path: "../etherlink/etherlink_ble"#include "etherlink.h"
#include "etherlink_ble.h"
// Protocol context
static el_ctx_t el_ctx;
// Message handler
static void on_message(uint8_t msg_id, const void *payload, uint8_t len) {
switch (msg_id) {
case 0x80: // Example command
// Handle command...
break;
}
}
void app_main(void) {
// Initialize protocol with BLE send callback
el_config_t el_config = {
.on_message = on_message,
.send_bytes = el_ble_send_raw,
};
el_init(&el_ctx, &el_config);
// Initialize BLE transport (auto-wires received data to protocol)
el_ble_config_t ble_config = {
.device_name = "MyDevice",
.protocol_ctx = &el_ctx,
};
el_ble_init(&ble_config);
// Send telemetry
while (1) {
if (el_ble_is_connected()) {
uint8_t data[] = {0x01, 0x02, 0x03};
el_send(&el_ctx, 0x10, data, sizeof(data));
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}#include "etherlink.h"
#include "etherlink_uart.h"
static el_ctx_t el_ctx;
static void on_message(uint8_t msg_id, const void *payload, uint8_t len) {
// Handle messages...
}
void app_main(void) {
// Initialize protocol
el_config_t el_config = {
.on_message = on_message,
.send_bytes = el_uart_send_raw,
};
el_init(&el_ctx, &el_config);
// Initialize UART transport
el_uart_config_t uart_config = {
.port = UART_NUM_1,
.baud_rate = 115200,
.tx_pin = 17,
.rx_pin = 16,
.protocol_ctx = &el_ctx,
};
el_uart_init(&uart_config);
}[SYNC] [MSG_ID] [LENGTH] [PAYLOAD...] [CRC8]
0xA5 1 byte 1 byte 0-250 bytes 1 byte
| Field | Size | Description |
|---|---|---|
| SYNC | 1 | Frame start marker (0xA5) |
| MSG_ID | 1 | Message type identifier |
| LENGTH | 1 | Payload length (0-250) |
| PAYLOAD | 0-250 | User data |
| CRC8 | 1 | CRC-8/CCITT over MSG_ID + LENGTH + PAYLOAD |
| Range | Usage |
|---|---|
| 0x00-0x0F | System messages (ping, pong, version, error) |
| 0x10-0x7F | Telemetry (device → host) |
| 0x80-0xFE | Commands (host → device) |
| 0xFF | Reserved |
#include "etherlink.h"
// Define packed struct for your message
EL_PACKED_STRUCT(my_sensor_t, {
uint32_t timestamp;
int16_t temperature; // 0.01°C units
int16_t humidity; // 0.01% units
});
// Send message
my_sensor_t sensor = {
.timestamp = get_time_ms(),
.temperature = 2350, // 23.50°C
.humidity = 6500, // 65.00%
};
EL_SEND(&el_ctx, 0x10, &sensor);
// Receive message
void on_message(uint8_t msg_id, const void *payload, uint8_t len) {
if (msg_id == 0x10) {
const my_sensor_t *data = EL_CAST(my_sensor_t, payload, len);
if (data) {
printf("Temp: %.2f°C\n", data->temperature / 100.0f);
}
}
}// Initialize context
bool el_init(el_ctx_t *ctx, const el_config_t *config);
// Reset parser state
void el_reset(el_ctx_t *ctx);
// Process received bytes
void el_process_byte(el_ctx_t *ctx, uint8_t byte);
void el_process_bytes(el_ctx_t *ctx, const uint8_t *data, size_t len);
// Send message
bool el_send(el_ctx_t *ctx, uint8_t msg_id, const void *payload, uint8_t len);
// CRC utilities
uint8_t el_crc8(const uint8_t *data, size_t len);esp_err_t el_ble_init(const el_ble_config_t *config);
void el_ble_send_raw(const uint8_t *data, size_t len);
esp_err_t el_ble_send(const uint8_t *data, size_t len);
bool el_ble_is_connected(void);
uint16_t el_ble_get_mtu(void);esp_err_t el_uart_init(const el_uart_config_t *config);
void el_uart_send_raw(const uint8_t *data, size_t len);
esp_err_t el_uart_send(const uint8_t *data, size_t len);
esp_err_t el_uart_deinit(void);MIT License - see LICENSE