Updated & heavily improved

pull/9/head
Ondřej Hruška 8 years ago
parent c83cdf36e2
commit 61c82b614b
  1. 3
      .gitignore
  2. 2
      CMakeLists.txt
  3. 10
      Makefile
  4. 149
      README.md
  5. 347
      TinyFrame.c
  6. 145
      TinyFrame.h
  7. 97
      test.c

3
.gitignore vendored

@ -34,3 +34,6 @@
cmake-build-debug/ cmake-build-debug/
tf.bin tf.bin
.idea/

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.7)
project(tf) project(tf)
set(CMAKE_CXX_STANDARD GNU99) set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES set(SOURCE_FILES
test.c test.c

@ -0,0 +1,10 @@
build: tf.bin
run: tf.bin
./tf.bin
debug: tf.bin
gdb -q -ex run ./tf.bin
tf.bin: test.c TinyFrame.c TinyFrame.h
gcc -Os --std=gnu89 -Wall -Wno-main -Wno-unused -Wextra test.c TinyFrame.c -I. -o tf.bin

@ -1,56 +1,113 @@
# TinyFrame # TinyFrame
TinyFrame is a simple library for building and parsing frames TinyFrame is a simple library for building and parsing frames to be sent
(packets) to be sent over a serial interface (like UART). It's implemented over a serial interface (e.g. UART, telnet etc.). The code is written
to be compatible with C89 and platform agnostic. in `--std=gnu89`.
Frames are protected by a checksum and contain a "unique" ID, Frames are protected by a checksum (~XOR, CRC16 or CRC32) and contain
which can be used for chaining messages. Each peer uses a different a unique ID field, which can be used for chaining messages. The highest value
value for the first bit of all IDs it generates (the "master flag" of the ID is different for each peer (TF_MASTER or TF_SLAVE) to avoid collisions.
or "peer_bit") to ensure there are no clashes. Typically the master
(PC, main microcontroller) will use "1" and the surrogate (WiFi module,
USB-serial connected gadget, display driver...) uses "0".
The library lets you bind listeners waiting for any frame, or a All fields in the frame have configurable size (see the top of the header file).
particular ID. This allows for easy implementation of async communication. By just changing a value, like `TF_LEN_BYTES`, the library seamlessly switches
from `uint8_t` to `uint16_t` or `uint32_t` to support longer payloads.
## Frame structure The library lets you bind listeners waiting for any frame, a particular frame Type,
or a specific message ID. This lets you easily implement asynchronous
communication.
The frame makeup is inspired by that of SBMP (my other, more complicated ## Frame structure
and advanced UART protocol library).
``` ```
<SOF><ID><NOB><PAYLOAD><CKSUM> ,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
SOF ... start of frame, 0x01 | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
ID ... (master_flag | 7-bit counter) - the frame ID '-----+----+-----+------+------------+- - - -+------------'
NOB ... nr of payload bytes in the frame (1..256)
PAYLOAD ... NOB bytes of data, can contain any byte values 1..256 SOF ......... start of frame, 0x01
CKSUM ... checksum, implemented as XOR of all preceding bytes in the message ID ......... the frame ID (MSb is the peer bit)
LEN ......... nr of data bytes in the frame
TYPE ........ message type (used to run Type Listeners, pick any values you like)
HEAD_CKSUM .. header checksum
DATA ........ LEN bytes of data
DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message
``` ```
The frame ID (in SBMP called "session ID") can be used to chain multiple related ## Usage Hints
messages and maintain the context this way. For example, a response may copy
the frame ID of the request frame, which then triggers a callback bound by the - Both peers must include the library with the same parameters (config in the header file)
requesting peer. Such behavior is application specific and is thus left to the - Start by calling `TF_Init()` with MASTER or SLAVE as the argument
upper layers of the protocol. - Implement `TF_WriteImpl()` - declared at the bottom of the header file as `extern`.
This function is used by `TF_Send()` to write bytes to your UART (or other physical layer).
Presently, always a full frame is sent to this function.
## Usage hints - If you wish to use `TF_PARSER_TIMEOUT_TICKS`, periodically call `TF_Tick()`. The period
determines the length of 1 tick. This is used to time-out the parser in case it gets stuck
- Both sides of the protocol (slave and master) should include the same TinyFrame in a bad state (such as receiving a partial frame).
code. - Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`.
- Master inits the lib with `TF_Init(1);`, while slave uses `TF_Init(0);`. This is to avoid a message ID conflict. - Send a message using `TF_Send()` or the other Send functions.
- Both sides can add Generic and Type listeners (callbacks) using `TF_AddGenericListener(func)` and `TF_AddTypeListener(type, func)`. The listener is a function as showin in the example file test.c or declared in TinyFrame.h If you provide a listener callback (function pointer) to the function,
the listener will be added as an ID listener and wait for a response.
`bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { ... }` - To reply to a message (when your listener gets called), use `TF_Respond()`
with the same frame_id as in the received message.
The listener returns `true` if the message was consumed. If it returns `false`, it can be handled by some other listener (possibly a Generic Listener, if you added one) - Remove the ID listener using `TF_RemoveIdListener()` when it's no longer
- A message is sent using `TF_Send()`, and to use it, the `TF_WriteImpl()` stub must be implemented in the application code. See `test.c` for an example. needed. (Same for other listener types.) The slot count is limited.
- If the listener function returns `false`, some other listener will get
There are also helper functions `TF_Send1()` and `TF_Send2()` which send one or two bytes. a chance to handle it
- To reply, use `TF_Respond()` with ID same as in the received message (the listener gets this as it's argument). A listener provided as the last parameter to `TF_Send()` will be called after receiving the response. - Manually reset the parser using `TF_ResetParser()`
- To remove a listener, use `TF_RemoveListener()`. *Always remove your ID listeners after handling the response!* There's a limit to the number of listeners. ### The concept of listeners
- The function `TF_Accept()` is used to handle received chars. Call this in your UART Rx interrupt handler or a similar place. Listeners are callback functions that are called by TinyFrame when a message which
they can handle is received.
There are 3 listener types:
- ID listeners
- Type listeners
- Generic listeners
They handle the message in this order, and if they decide not to handle it, they can return `false`
and let it be handled by some other listener, or discarded.
### Implementing "synchronous query"
Sometimes it's necessary to send a message and wait for a response to arrive.
One (not too pretty) way to do this is using a global variable - pseudocode:
```c
#define MSG_PING 42
volatile bool onResponse_done = false;
/** ID listener */
bool onResponse(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len)
{
// ... Do something ...
// (eg. copy data to a global variable)
onResponse_done = true;
return true;
}
bool syncQuery(void)
{
TF_ID id;
// Send our request
TF_Send0(MSG_PING, onResponse, &id); // Send0 sends zero bytes of data, just TYPE
// Wait for the response
bool suc = true;
while (!onResponse_done) {
//delay
if (/*timeout*/) {
TF_RemoveIdListener(id); // free the listener slot
return false;
}
}
// ... Do something with the received data? ...
// (can be passed from the listener using a global variable)
return true;
}
```

@ -3,6 +3,13 @@
#include <string.h> #include <string.h>
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Compatibility with ESP8266 SDK
#ifdef ICACHE_FLASH_ATTR
#define _TF_FN ICACHE_FLASH_ATTR
#else
#define _TF_FN
#endif
enum TFState { enum TFState {
TFState_SOF = 0, //!< Wait for SOF TFState_SOF = 0, //!< Wait for SOF
TFState_LEN, //!< Wait for Number Of Bytes TFState_LEN, //!< Wait for Number Of Bytes
@ -14,12 +21,12 @@ enum TFState {
}; };
typedef struct _IdListener_struct { typedef struct _IdListener_struct {
unsigned int id; TF_ID id;
TF_LISTENER fn; TF_LISTENER fn;
} IdListener; } IdListener;
typedef struct _TypeListener_struct_ { typedef struct _TypeListener_struct_ {
unsigned char type; TF_TYPE type;
TF_LISTENER fn; TF_LISTENER fn;
} TypeListener; } TypeListener;
@ -45,6 +52,7 @@ static struct TinyFrameStruct {
TF_CKSUM cksum; //!< Checksum calculated of the data stream TF_CKSUM cksum; //!< Checksum calculated of the data stream
TF_CKSUM ref_cksum; //!< Reference checksum read from the message TF_CKSUM ref_cksum; //!< Reference checksum read from the message
TF_TYPE type; //!< Collected message type number TF_TYPE type; //!< Collected message type number
bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data.
/* --- Callbacks --- */ /* --- Callbacks --- */
@ -57,17 +65,30 @@ static struct TinyFrameStruct {
size_t count_type_lst; size_t count_type_lst;
size_t count_generic_lst; size_t count_generic_lst;
// Buffer for building frames
uint8_t sendbuf[TF_MAX_PAYLOAD + TF_OVERHEAD_BYTES]; uint8_t sendbuf[TF_MAX_PAYLOAD + TF_OVERHEAD_BYTES];
} tf; } tf;
//region Checksums
#if TF_CKSUM_TYPE == 0
// NONE
#define CKSUM_RESET(cksum)
#define CKSUM_ADD(cksum, byte)
#define CKSUM_FINALIZE(cksum)
//region Optional impls #elif TF_CKSUM_TYPE == 8
#if TF_USE_CRC16
// ---- CRC16 checksum impl ---- // ~XOR
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = ~cksum; } while(0)
#elif TF_CKSUM_TYPE == 16
/** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */ /** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */
const uint16_t crc16_table[256] = { static const uint16_t crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
@ -102,28 +123,78 @@ const uint16_t crc16_table[256] = {
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
}; };
static inline uint16_t crc16_byte(uint16_t crc, const uint8_t data) static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte)
{ {
return (crc >> 8) ^ crc16_table[(crc ^ data) & 0xff]; return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff];
} }
#endif #define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
//endregion Optional impls #define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0)
#define CKSUM_FINALIZE(cksum)
#elif TF_CKSUM_TYPE == 32
static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
// --- macros based on config --- static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte)
{
return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8));
}
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0) #define CKSUM_RESET(cksum) do { cksum = (TF_CKSUM)0xFFFFFFFF; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum = crc32_byte(cksum, byte); } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = (TF_CKSUM)~cksum; } while(0)
#if TF_USE_CRC16
// CRC16 checksum
#define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0)
#else
// XOR checksum
#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0)
#endif #endif
//endregion
void TF_Init(TF_PEER peer_bit)
void _TF_FN TF_Init(TF_PEER peer_bit)
{ {
// Zero it out // Zero it out
memset(&tf, 0, sizeof(struct TinyFrameStruct)); memset(&tf, 0, sizeof(struct TinyFrameStruct));
@ -133,7 +204,7 @@ void TF_Init(TF_PEER peer_bit)
//region Listeners //region Listeners
bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb) bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
{ {
size_t i; size_t i;
for (i = 0; i < TF_MAX_ID_LST; i++) { for (i = 0; i < TF_MAX_ID_LST; i++) {
@ -149,7 +220,7 @@ bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
return false; return false;
} }
bool TF_AddTypeListener(unsigned char frame_type, TF_LISTENER cb) bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb)
{ {
size_t i; size_t i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) { for (i = 0; i < TF_MAX_TYPE_LST; i++) {
@ -165,7 +236,7 @@ bool TF_AddTypeListener(unsigned char frame_type, TF_LISTENER cb)
return false; return false;
} }
bool TF_AddGenericListener(TF_LISTENER cb) bool _TF_FN TF_AddGenericListener(TF_LISTENER cb)
{ {
size_t i; size_t i;
for (i = 0; i < TF_MAX_GEN_LST; i++) { for (i = 0; i < TF_MAX_GEN_LST; i++) {
@ -180,7 +251,7 @@ bool TF_AddGenericListener(TF_LISTENER cb)
return false; return false;
} }
bool TF_RemoveIdListener(TF_ID frame_id) bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
{ {
size_t i; size_t i;
for (i = 0; i < tf.count_id_lst; i++) { for (i = 0; i < tf.count_id_lst; i++) {
@ -196,7 +267,7 @@ bool TF_RemoveIdListener(TF_ID frame_id)
return false; return false;
} }
bool TF_RemoveTypeListener(unsigned char type) bool _TF_FN TF_RemoveTypeListener(TF_TYPE type)
{ {
size_t i; size_t i;
for (i = 0; i < tf.count_type_lst; i++) { for (i = 0; i < tf.count_type_lst; i++) {
@ -212,7 +283,7 @@ bool TF_RemoveTypeListener(unsigned char type)
return false; return false;
} }
bool TF_RemoveGenericListener(TF_LISTENER cb) bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb)
{ {
size_t i; size_t i;
for (i = 0; i < tf.count_generic_lst; i++) { for (i = 0; i < tf.count_generic_lst; i++) {
@ -228,7 +299,7 @@ bool TF_RemoveGenericListener(TF_LISTENER cb)
} }
/** Handle a message that was just collected & verified by the parser */ /** Handle a message that was just collected & verified by the parser */
static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data) static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data, TF_LEN data_len)
{ {
size_t i; size_t i;
@ -240,8 +311,8 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// ID listeners first // ID listeners first
for (i = 0; i < tf.count_id_lst; i++) { for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn && tf.id_listeners[i].id == tf.id) { if (tf.id_listeners[i].fn && (tf.id_listeners[i].id == frame_id)) {
if (tf.id_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) { if (tf.id_listeners[i].fn(frame_id, type, data, data_len)) {
return; return;
} }
} }
@ -249,8 +320,8 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// Type listeners // Type listeners
for (i = 0; i < tf.count_type_lst; i++) { for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn && tf.type_listeners[i].type == tf.type) { if (tf.type_listeners[i].fn && (tf.type_listeners[i].type == type)) {
if (tf.type_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) { if (tf.type_listeners[i].fn(frame_id, type, data, data_len)) {
return; return;
} }
} }
@ -259,7 +330,7 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
// Generic listeners // Generic listeners
for (i = 0; i < tf.count_generic_lst; i++) { for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn) { if (tf.generic_listeners[i].fn) {
if (tf.generic_listeners[i].fn(tf.id, tf.type, tf.data, tf.len)) { if (tf.generic_listeners[i].fn(frame_id, type, data, data_len)) {
return; return;
} }
} }
@ -268,7 +339,7 @@ static void TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data
//endregion Listeners //endregion Listeners
void TF_Accept(const uint8_t *buffer, size_t count) void _TF_FN TF_Accept(const uint8_t *buffer, size_t count)
{ {
size_t i; size_t i;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
@ -276,16 +347,28 @@ void TF_Accept(const uint8_t *buffer, size_t count)
} }
} }
void TF_ResetParser(void) void _TF_FN TF_ResetParser(void)
{ {
tf.state = TFState_SOF; tf.state = TFState_SOF;
} }
void TF_AcceptChar(unsigned char c) /** SOF was received */
{ static void _TF_FN TF_ParsBeginFrame(void) {
// QUEUE IF PARSER LOCKED // Reset state vars
// FIRST PROCESS ALL QUEUED CKSUM_RESET(tf.cksum);
#if TF_USE_SOF_BYTE
CKSUM_ADD(tf.cksum, TF_SOF_BYTE);
#endif
tf.discard_data = false;
// Enter ID state
tf.state = TFState_ID;
tf.rxi = 0;
}
void _TF_FN TF_AcceptChar(unsigned char c)
{
// Timeout - clear // Timeout - clear
if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) { if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
TF_ResetParser(); TF_ResetParser();
@ -296,81 +379,109 @@ void TF_AcceptChar(unsigned char c)
#define COLLECT_NUMBER(dest, type) dest = ((dest) << 8) | c; \ #define COLLECT_NUMBER(dest, type) dest = ((dest) << 8) | c; \
if (++tf.rxi == sizeof(type)) if (++tf.rxi == sizeof(type))
#if !TF_USE_SOF_BYTE
if (tf.state == TFState_SOF) {
TF_ParsBeginFrame();
}
#endif
switch (tf.state) { switch (tf.state) {
case TFState_SOF: case TFState_SOF:
if (c == TF_SOF_BYTE) { if (c == TF_SOF_BYTE) {
TF_ParsBeginFrame();
}
break;
case TFState_ID:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.id, TF_ID) {
// Enter LEN state // Enter LEN state
tf.state = TFState_LEN; tf.state = TFState_LEN;
tf.len = 0;
tf.rxi = 0; tf.rxi = 0;
// Reset state vars
CKSUM_RESET(tf.cksum);
} }
break; break;
case TFState_LEN: case TFState_LEN:
CKSUM_ADD(tf.cksum, c); CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.len, TF_LEN) { COLLECT_NUMBER(tf.len, TF_LEN) {
// enter HEAD_CKSUM state // Enter TYPE state
tf.state = TFState_HEAD_CKSUM; tf.state = TFState_TYPE;
tf.ref_cksum = 0;
tf.rxi = 0; tf.rxi = 0;
} }
break; break;
case TFState_TYPE:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.type, TF_TYPE) {
#if TF_CKSUM_TYPE == 0
tf.state = TFState_DATA;
tf.rxi = 0;
#else
// enter HEAD_CKSUM state
tf.state = TFState_HEAD_CKSUM;
tf.rxi = 0;
tf.ref_cksum = 0;
#endif
}
break;
case TFState_HEAD_CKSUM: case TFState_HEAD_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM){ COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value // Check the header checksum against the computed value
CKSUM_FINALIZE(tf.cksum);
if (tf.cksum != tf.ref_cksum) { if (tf.cksum != tf.ref_cksum) {
TF_ResetParser(); TF_ResetParser();
break; break;
} }
// Enter ID state if (tf.len == 0) {
tf.state = TFState_ID; TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0);
tf.rxi = 0; TF_ResetParser();
break;
tf.cksum = 0; // Start collecting the payload }
}
break;
case TFState_ID:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.id, TF_ID) {
// Enter TYPE state
tf.state = TFState_TYPE;
tf.rxi = 0;
}
break;
case TFState_TYPE:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.type, TF_TYPE) {
// Enter DATA state // Enter DATA state
tf.state = TFState_DATA; tf.state = TFState_DATA;
tf.rxi = 0; tf.rxi = 0;
CKSUM_RESET(tf.cksum); // Start collecting the payload
if (tf.len >= TF_MAX_PAYLOAD) {
// ERROR - frame too long. Consume, but do not store.
tf.discard_data = true;
}
} }
break; break;
case TFState_DATA: case TFState_DATA:
CKSUM_ADD(tf.cksum, c); if (tf.discard_data) {
tf.data[tf.rxi++] = c; tf.rxi++;
} else {
CKSUM_ADD(tf.cksum, c);
tf.data[tf.rxi++] = c;
}
if (tf.rxi == tf.len) { if (tf.rxi == tf.len) {
// Enter DATA_CKSUM state #if TF_CKSUM_TYPE == 0
tf.state = TFState_DATA_CKSUM; // All done
tf.rxi = 0; TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
tf.ref_cksum = 0; TF_ResetParser();
#else
// Enter DATA_CKSUM state
tf.state = TFState_DATA_CKSUM;
tf.rxi = 0;
tf.ref_cksum = 0;
#endif
} }
break; break;
case TFState_DATA_CKSUM: case TFState_DATA_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) { COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value // Check the header checksum against the computed value
if (tf.cksum == tf.ref_cksum) { CKSUM_FINALIZE(tf.cksum);
// LOCK PARSER if (!tf.discard_data && tf.cksum == tf.ref_cksum) {
TF_HandleReceivedMessage(tf.id, tf.type, tf.data); TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
// UNLOCK PARSER
} }
TF_ResetParser(); TF_ResetParser();
@ -379,7 +490,20 @@ void TF_AcceptChar(unsigned char c)
} }
} }
int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, /**
* Compose a frame (used internally by TF_Send and TF_Respond).
* The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param type - message type
* @param data - data buffer
* @param len - payload size in bytes
* @param explicit_id - ID to use in the frame (8-bit)
* @param use_expl_id - whether to use the previous param
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
*/
static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
TF_TYPE type, TF_TYPE type,
const uint8_t *data, TF_LEN data_len, const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id) TF_ID explicit_id, bool use_expl_id)
@ -402,12 +526,15 @@ int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
id = explicit_id; id = explicit_id;
} }
else { else {
id = (TF_ID) ((tf.next_id++) & TF_ID_MASK); id = (TF_ID) (tf.next_id++ & TF_ID_MASK);
if (tf.peer_bit) { if (tf.peer_bit) {
id |= TF_ID_PEERBIT; id |= TF_ID_PEERBIT;
} }
} }
if (id_ptr != NULL)
*id_ptr = id;
// DRY helper for writing a multi-byte variable to the buffer // DRY helper for writing a multi-byte variable to the buffer
#define WRITENUM_BASE(type, num, xtra) \ #define WRITENUM_BASE(type, num, xtra) \
for (i = sizeof(type)-1; i>=0; i--) { \ for (i = sizeof(type)-1; i>=0; i--) { \
@ -420,36 +547,48 @@ int TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
#define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b)) #define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
// --- Start --- // --- Start ---
CKSUM_RESET(cksum);
outbuff[pos++] = TF_SOF_BYTE; #if TF_USE_SOF_BYTE
cksum = 0; outbuff[pos++] = TF_SOF_BYTE;
WRITENUM_CKSUM(TF_LEN, data_len); CKSUM_ADD(cksum, TF_SOF_BYTE);
WRITENUM(TF_CKSUM, cksum); #endif
// --- payload begin ---
cksum = 0;
WRITENUM_CKSUM(TF_ID, id); WRITENUM_CKSUM(TF_ID, id);
WRITENUM_CKSUM(TF_LEN, data_len);
WRITENUM_CKSUM(TF_TYPE, type); WRITENUM_CKSUM(TF_TYPE, type);
// DATA #if TF_CKSUM_TYPE != 0
for (i = 0; i < data_len; i++) { CKSUM_FINALIZE(cksum);
b = data[i]; WRITENUM(TF_CKSUM, cksum);
outbuff[pos++] = b; #endif
CKSUM_ADD(cksum, b);
}
WRITENUM(TF_CKSUM, cksum); // --- payload begin ---
if (data_len > 0) {
CKSUM_RESET(cksum);
// DATA
for (i = 0; i < data_len; i++) {
b = data[i];
outbuff[pos++] = b;
CKSUM_ADD(cksum, b);
}
if (id_ptr != NULL) #if TF_CKSUM_TYPE != 0
*id_ptr = id; CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
}
return pos; return pos;
} }
bool TF_Send(TF_TYPE type, const uint8_t *payload, TF_LEN payload_len, bool _TF_FN TF_Send(TF_TYPE type,
TF_LISTENER listener, TF_ID *id_ptr) const uint8_t *payload, TF_LEN payload_len,
TF_LISTENER listener,
TF_ID *id_ptr)
{ {
TF_ID msgid; TF_ID msgid = 0;
int len; int len;
len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false); len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false);
if (len == TF_ERROR) return false; if (len == TF_ERROR) return false;
@ -461,7 +600,8 @@ bool TF_Send(TF_TYPE type, const uint8_t *payload, TF_LEN payload_len,
return true; return true;
} }
bool TF_Respond(TF_TYPE type, // Like TF_Send, but with explicit frame ID
bool _TF_FN TF_Respond(TF_TYPE type,
const uint8_t *data, TF_LEN data_len, const uint8_t *data, TF_LEN data_len,
TF_ID frame_id) TF_ID frame_id)
{ {
@ -473,10 +613,20 @@ bool TF_Respond(TF_TYPE type,
return true; return true;
} }
/**
* Like TF_Send(), but with no data
*/
bool _TF_FN TF_Send0(TF_TYPE type,
TF_LISTENER listener,
TF_ID *id_ptr)
{
return TF_Send(type, NULL, 0, listener, id_ptr);
}
/** /**
* Like TF_Send(), but with just 1 data byte * Like TF_Send(), but with just 1 data byte
*/ */
bool TF_Send1(TF_TYPE type, uint8_t b1, bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener, TF_LISTENER listener,
TF_ID *id_ptr) TF_ID *id_ptr)
{ {
@ -487,7 +637,7 @@ bool TF_Send1(TF_TYPE type, uint8_t b1,
/** /**
* Like TF_Send(), but with just 2 data bytes * Like TF_Send(), but with just 2 data bytes
*/ */
bool TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2, bool _TF_FN TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
TF_LISTENER listener, TF_LISTENER listener,
TF_ID *id_ptr) TF_ID *id_ptr)
{ {
@ -495,7 +645,8 @@ bool TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
return TF_Send(type, b, 2, listener, id_ptr); return TF_Send(type, b, 2, listener, id_ptr);
} }
void TF_Tick(void) /** Timebase hook - for timeouts */
void _TF_FN TF_Tick(void)
{ {
if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) { if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) {
tf.parser_timeout_ticks++; tf.parser_timeout_ticks++;

@ -1,13 +1,22 @@
#ifndef TinyFrameH #ifndef TinyFrameH
#define TinyFrameH #define TinyFrameH
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc #include <stdint.h> // for uint8_t etc
#include <stdbool.h> // for bool #include <stdbool.h> // for bool
#include <stdlib.h> // for NULL #include <stdlib.h> // for NULL
//#include "messages.h" // for your message IDs (enum or defines)
//#include <esp8266.h> // when using with esphttpd
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// A static buffer of this size will be allocated
#define TF_MAX_PAYLOAD 2048 //----------------------------- PARAMETERS ----------------------------------
// Maximum send / receive payload size (static buffers size)
// Larger payloads will be rejected.
#define TF_MAX_PAYLOAD 1024
// --- Listener counts - determine sizes of the static slot tables ---
// Frame ID listeners (wait for response / multi-part message) // Frame ID listeners (wait for response / multi-part message)
#define TF_MAX_ID_LST 20 #define TF_MAX_ID_LST 20
@ -20,55 +29,109 @@
// ticks = number of calls to TF_Tick() // ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10 #define TF_PARSER_TIMEOUT_TICKS 10
#define TF_USE_CRC16 1 //----------------------------- FRAME FORMAT ---------------------------------
// The format can be adjusted to fit your particular application needs
// If the connection is reliable, you can disable the SOF byte and checksums.
// That can save up to 9 bytes of overhead.
// ,-----+----+-----+------+------------+- - - -+------------,
// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
// | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
// '-----+----+-----+------+------------+- - - -+------------'
// !!! BOTH SIDES MUST USE THE SAME SETTINGS !!!
// Adjust sizes as desired (1,2,4)
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
// Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32)
#define TF_CKSUM_TYPE 16
// Use a SOF byte to mark the start of a frame
#define TF_USE_SOF_BYTE 1
// Value of the SOF byte (if TF_USE_SOF_BYTE == 1)
#define TF_SOF_BYTE 0x01
//------------------------- End of user config ------------------------------
//----------------------------- USAGE HINTS ---------------------------------
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// ,-----+-----+-----------+----+------+- - - -+------------, //region Resolve data types
// | SOF | LEN | LEN_CKSUM | ID | TYPE | DATA | PLD_CKSUM |
// | 1 | 2? | 1? | 1? | 1? | ... | 1? | #if TF_LEN_BYTES == 1
// '-----+-----+-----------+----+------+- - - -+------------' typedef uint8_t TF_LEN;
// '----- payload -----' #elif TF_LEN_BYTES == 2
typedef uint16_t TF_LEN;
#elif TF_LEN_BYTES == 4
typedef uint32_t TF_LEN;
#else
#error Bad value of TF_LEN_BYTES, must be 1, 2 or 4
#endif
// The data section is designed thus so the higher levels of TinyFrame
// (message chaining, listeners etc) could be re-used for a different
// framing layer (e.g. sent in UDP packets)
// Fields marked '?' can have their size adjusted by changing the #if TF_TYPE_BYTES == 1
// typedefs below typedef uint8_t TF_TYPE;
#elif TF_TYPE_BYTES == 2
typedef uint16_t TF_TYPE;
#elif TF_TYPE_BYTES == 4
typedef uint32_t TF_TYPE;
#else
#error Bad value of TF_TYPE_BYTES, must be 1, 2 or 4
#endif
// Change those typedefs to adjust the field sizes. BOTH PEERS MUST HAVE THE SAME SIZES!
typedef uint8_t TF_ID; // Message ID. Effectively limits the nr of concurrent request-response sessions.
typedef uint8_t TF_TYPE; // Message type (sent together with the data payload, which can be empty)
typedef uint16_t TF_LEN; // Length of the data payload.
// TF_OVERHEAD_BYTES - added to TF_MAX_PAYLOAD for the send buffer size. #if TF_ID_BYTES == 1
// (TODO - buffer-less sending) typedef uint8_t TF_ID;
#if TF_USE_CRC16 #elif TF_ID_BYTES == 2
typedef uint16_t TF_CKSUM; typedef uint16_t TF_ID;
#define TF_OVERHEAD_BYTES 9 #elif TF_ID_BYTES == 4
typedef uint32_t TF_ID;
#else #else
#error Bad value of TF_ID_BYTES, must be 1, 2 or 4
#endif
#if TF_CKSUM_TYPE == 8 || TF_CKSUM_TYPE == 0
// ~XOR (if 0, still use 1 byte - it won't be used)
typedef uint8_t TF_CKSUM; typedef uint8_t TF_CKSUM;
#define TF_OVERHEAD_BYTES 7 #elif TF_CKSUM_TYPE == 16
// CRC16
typedef uint16_t TF_CKSUM;
#elif TF_CKSUM_TYPE == 32
// CRC32
typedef uint32_t TF_CKSUM;
#else
#error Bad value for TF_CKSUM_TYPE, must be 8, 16 or 32
#endif #endif
//---------------------------------------------------------------------------
#define TF_SOF_BYTE 0x01 // Bytes added to TF_MAX_PAYLOAD for the send buffer size.
#define TF_OVERHEAD_BYTES (1+sizeof(TF_ID)+sizeof(TF_LEN)+sizeof(TF_CKSUM)+sizeof(TF_TYPE)+sizeof(TF_CKSUM))
//endregion
//---------------------------------------------------------------------------
#define TF_ERROR -1 #define TF_ERROR -1
// Type-dependent masks for bit manipulation in the ID field // Type-dependent masks for bit manipulation in the ID field
#define TF_ID_MASK ((1 << (sizeof(TF_ID)*8 - 1)) - 1) #define TF_ID_MASK (TF_ID)(((TF_ID)1 << (sizeof(TF_ID)*8 - 1)) - 1)
#define TF_ID_PEERBIT (1 << (sizeof(TF_ID)*8) - 1) #define TF_ID_PEERBIT (TF_ID)((TF_ID)1 << ((sizeof(TF_ID)*8) - 1))
//---------------------------------------------------------------------------
/** Peer bit enum (used for init) */
typedef enum { typedef enum {
TF_SLAVE = 0, TF_SLAVE = 0,
TF_MASTER = 1, TF_MASTER = 1,
} TF_PEER; } TF_PEER;
//---------------------------------------------------------------------------
/** /**
* TinyFrame Type Listener callback * TinyFrame Type Listener callback
* @param frame_id - ID of the received frame * @param frame_id - ID of the received frame
@ -146,25 +209,6 @@ bool TF_AddGenericListener(TF_LISTENER cb);
*/ */
bool TF_RemoveGenericListener(TF_LISTENER cb); bool TF_RemoveGenericListener(TF_LISTENER cb);
/**
* Compose a frame (used internally by TF_Send and TF_Respond).
* The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param type - message type
* @param data - data buffer
* @param len - payload size in bytes
* @param explicit_id - ID to use in the frame (8-bit)
* @param use_expl_id - whether to use the previous param
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
*/
int TF_Compose(uint8_t *outbuff,
TF_ID *id_ptr,
TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id);
/** /**
* Send a frame, and optionally attach an ID listener. * Send a frame, and optionally attach an ID listener.
* *
@ -180,6 +224,11 @@ bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len,
TF_LISTENER listener, TF_LISTENER listener,
TF_ID *id_ptr); TF_ID *id_ptr);
/**
* Like TF_Send(), but no data, just the type
*/
bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr);
/** /**
* Like TF_Send(), but with just 1 data byte * Like TF_Send(), but with just 1 data byte
*/ */

@ -1,22 +1,9 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "TinyFrame.h" #include "TinyFrame.h"
// helper func for testing static void dumpFrame(const uint8_t *buff, TF_LEN len);
static void dumpFrame(const uint8_t *buff, TF_LEN len)
{
int i;
for(i = 0; i < len; i++) {
printf("%3u \033[34m%02X\033[0m", buff[i], buff[i]);
if (buff[i] >= 0x20 && buff[i] < 127) {
printf(" %c", buff[i]);
} else {
printf(" \033[31m.\033[0m", buff[i]);
}
printf("\n");
}
printf("--- end of frame ---\n");
}
/** /**
* This function should be defined in the application code. * This function should be defined in the application code.
@ -24,65 +11,67 @@ static void dumpFrame(const uint8_t *buff, TF_LEN len)
*/ */
void TF_WriteImpl(const uint8_t *buff, TF_LEN len) void TF_WriteImpl(const uint8_t *buff, TF_LEN len)
{ {
printf("\033[32;1mTF_WriteImpl - sending frame:\033[0m\n"); printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len); dumpFrame(buff, len);
// Send it back as if we received it
TF_Accept(buff, len);
} }
/** An example listener function */ /** An example listener function */
bool myListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len) bool myListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{ {
printf("\033[33mrx frame %s, len %d, id %d\033[0m\n", buff, len, frame_id); printf("\033[33mRX frame\n"
" type: %02Xh\n"
" data: \"%.*s\"\n"
" len: %u\n"
" id: %Xh\033[0m\n", type, len, buff, len, frame_id);
return true; return true;
} }
void main() bool testIdListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{ {
int i; printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id);
TF_ID msgid; return true;
int len; }
char buff[100];
void main(void)
{
// Set up the TinyFrame library // Set up the TinyFrame library
TF_Init(TF_MASTER); // 1 = master, 0 = slave TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a message --------\n"); printf("------ Simulate sending a message --------\n");
// Send a message TF_Send(0x22, (unsigned char*)"Hello TinyFrame", 16, NULL, NULL);
// args - payload, length (0 = strlen), listener, id_ptr (for storing the frame ID)
// (see the .h file for details)
TF_Respond(0xA5, (unsigned char*)"Hello TinyFrame", 16, 0x15);
// This builds the frame in an internal buffer and sends it to
// TF_WriteImpl()
const char *longstr = "Lorem ipsum dolor sit amet.";
TF_Send(0x33, (unsigned char*)longstr, (TF_LEN)(strlen(longstr)+1), NULL, NULL);
printf("------ Simulate receiving a message --------\n"); TF_Send(0x44, (unsigned char*)"Hello2", 7, NULL, NULL);
// Adding global listeners TF_Send0(0xF0, NULL, NULL);
// Listeners can listen to any frame (fallback listeners),
// or to a specific Frame Type (AddTypeListener). There are
// also ID listeners that can be bound automatically in TF_Send().
//
// Type listeners are matched by the first character of the payload,
// ID listeners by the message ID (which is the same in the response as in the request)
TF_AddGenericListener(myListener);
//TF_AddTypeListener(0xF1, myTypeListener);
//TF_AddIdListener(msgID, myIdListener);
// This lets us compose a frame (it's also used internally by TF_Send and TF_Respond) TF_Send1(0xF1, 'Q', NULL, NULL);
len = TF_Compose((unsigned char*)buff, // Buffer to write the frame to
&msgid, // Int to store the message ID in
0xA5,
(unsigned char*)"Hello TinyFrame", // Payload bytes
16, // Length - this will cut it at "ALPHA" (showing that it works)
// For string, we can use "0" to use strlen() internally
0x15, true); // Message ID - we could specify a particular ID if we were
// trying to build a response frame, which has the same ID
// as the request it responds to.
printf("The frame we'll receive is:\n"); TF_Send2(0xF2, 'A', 'Z', NULL, NULL);
dumpFrame((unsigned char*)buff, (TF_LEN)len);
TF_Send0(0x77, testIdListener, NULL);
}
// Accept the frame // helper func for testing
// You will normally call this method in the UART IRQ handler etc static void dumpFrame(const uint8_t *buff, TF_LEN len)
TF_Accept((unsigned char*)buff, (TF_LEN)len); {
int i;
for(i = 0; i < len; i++) {
printf("%3u \033[34m%02X\033[0m", buff[i], buff[i]);
if (buff[i] >= 0x20 && buff[i] < 127) {
printf(" %c", buff[i]);
} else {
printf(" \033[31m.\033[0m");
}
printf("\n");
}
printf("--- end of frame ---\n");
} }

Loading…
Cancel
Save