5 Commits
7 changed files with 946 additions and 450 deletions
+6
View File
@@ -31,3 +31,9 @@
# Debug files
*.dSYM/
*.su
cmake-build-debug/
tf.bin
.idea/
+11
View File
@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.7)
project(tf)
set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES
test.c
TinyFrame.c
TinyFrame.h)
add_executable(tf ${SOURCE_FILES})
+10
View File
@@ -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
+98 -37
View File
@@ -1,56 +1,117 @@
# TinyFrame
TinyFrame is a simple library for building and parsing frames
(packets) to be sent over a serial interface (like UART). It's implemented
to be compatible with C89 and platform agnostic.
TinyFrame is a simple library for building and parsing frames to be sent
over a serial interface (e.g. UART, telnet etc.). The code is written
in `--std=gnu89`.
Frames are protected by a checksum and contain a "unique" ID,
which can be used for chaining messages. Each peer uses a different
value for the first bit of all IDs it generates (the "master flag"
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".
Frames are protected by a checksum (~XOR, CRC16 or CRC32) and contain
a unique ID field, which can be used for chaining messages. The highest value
of the ID is different for each peer (TF_MASTER or TF_SLAVE) to avoid collisions.
The library lets you bind listeners waiting for any frame, or a
particular ID. This allows for easy implementation of async communication.
All fields in the frame have configurable size (see the top of the header file).
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.
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.
## Frame structure
The frame makeup is inspired by that of SBMP (my other, more complicated
and advanced UART protocol library).
```
<SOF><ID><NOB><PAYLOAD><CKSUM>
,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
| 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
'-----+----+-----+------+------------+- - - -+------------'
SOF ... start of frame, 0x01
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
CKSUM ... checksum, implemented as XOR of all preceding bytes in the message
SOF ......... start of frame, 0x01
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
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
requesting peer. Such behavior is application specific and is thus left to the
upper layers of the protocol.
## Usage Hints
- Both peers must include the library with the same parameters (config in the header file)
- Start by calling `TF_Init()` with MASTER or SLAVE as the argument
- 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.
- 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
in a bad state (such as receiving a partial frame).
- Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`.
- Send a message using `TF_Send()` or the other Send functions.
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.
- To reply to a message (when your listener gets called), use `TF_Respond()`
with the same frame_id as in the received message.
- Remove the ID listener using `TF_RemoveIdListener()` when it's no longer
needed. (Same for other listener types.) The slot count is limited.
- If the listener function returns `false`, some other listener will get
a chance to handle it
- Manually reset the parser using `TF_ResetParser()`
## Usage hints
### The concept of listeners
- Both sides of the protocol (slave and master) should include the same TinyFrame
code.
- Master inits the lib with `TF_Init(1);`, while slave uses `TF_Init(0);`. This is to avoid a message ID conflict.
- 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
Listeners are callback functions that are called by TinyFrame when a message which
they can handle is received.
`bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { ... }`
There are 3 listener types:
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)
- 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.
- ID listeners
- Type listeners
- Generic listeners
There are also helper functions `TF_Send1()` and `TF_Send2()` which send one or two bytes.
- 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.
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.
- 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.
### Implementing "synchronous query"
- The function `TF_Accept()` is used to handle received chars. Call this in your UART Rx interrupt handler or a similar place.
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
static volatile bool got_response = false;
/** ID listener */
static 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)
got_response = true;
return true;
}
bool syncQuery(void)
{
TF_ID id;
// Send our request, and bind an ID listener
got_response = false;
TF_Send0(MSG_PING, onResponse, &id); // Send0 sends zero bytes of data, just TYPE
// the ID is now in `id` so we can remove the listener after a timeout
// Wait for the response
bool suc = true;
while (!got_response) {
//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;
}
```
+505 -202
View File
@@ -3,31 +3,35 @@
#include <string.h>
//---------------------------------------------------------------------------
/* Note: payload length determines the Rx buffer size. Max 256 */
#define TF_MAX_PAYLOAD 256
#define TF_SOF_BYTE 0x01
// Compatibility with ESP8266 SDK
#ifdef ICACHE_FLASH_ATTR
#define _TF_FN ICACHE_FLASH_ATTR
#else
#define _TF_FN
#endif
enum TFState {
TFState_SOF = 0, //!< Wait for SOF
TFState_LEN, //!< Wait for Number Of Bytes
TFState_HEAD_CKSUM, //!< Wait for header Checksum
TFState_ID, //!< Wait for ID
TFState_NOB, //!< Wait for Number Of Bytes
TFState_PAYLOAD, //!< Receive payload
TFState_CKSUM //!< Wait for Checksum
TFState_TYPE, //!< Wait for message type
TFState_DATA, //!< Receive payload
TFState_DATA_CKSUM //!< Wait for Checksum
};
typedef struct _IdListener_struct {
unsigned int id;
TinyFrameListener fn;
TF_ID id;
TF_LISTENER fn;
} IdListener;
typedef struct _TypeListener_struct_ {
unsigned char type;
TinyFrameListener fn;
TF_TYPE type;
TF_LISTENER fn;
} TypeListener;
typedef struct _GenericListener_struct_ {
TinyFrameListener fn;
TF_LISTENER fn;
} GenericListener;
/**
@@ -35,16 +39,20 @@ typedef struct _GenericListener_struct_ {
*/
static struct TinyFrameStruct {
/* Own state */
bool peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
unsigned int next_id; //!< Next frame / frame chain ID
TF_PEER peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
TF_ID next_id; //!< Next frame / frame chain ID
/* Parser state */
enum TFState state;
unsigned int id; //!< Incoming packet ID
unsigned int nob; //!< Payload length
unsigned char pldbuf[TF_MAX_PAYLOAD+1]; //!< Payload byte buffer
unsigned int rxi; //!< Receive counter (for payload or other fields)
unsigned int cksum; //!< Continually updated checksum
int parser_timeout_ticks;
TF_ID id; //!< Incoming packet ID
TF_LEN len; //!< Payload length
uint8_t data[TF_MAX_PAYLOAD]; //!< Data byte buffer
size_t rxi; //!< Byte counter
TF_CKSUM cksum; //!< Checksum calculated of the data stream
TF_CKSUM ref_cksum; //!< Reference checksum read from the message
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 --- */
@@ -53,301 +61,596 @@ static struct TinyFrameStruct {
TypeListener type_listeners[TF_MAX_TYPE_LST];
GenericListener generic_listeners[TF_MAX_GEN_LST];
char sendbuf[TF_MAX_PAYLOAD+1];
size_t count_id_lst;
size_t count_type_lst;
size_t count_generic_lst;
// Buffer for building frames
uint8_t sendbuf[TF_MAX_PAYLOAD + TF_OVERHEAD_BYTES];
} tf;
void TF_Init(bool peer_bit)
//region Checksums
#if TF_CKSUM_TYPE == 0
// NONE
#define CKSUM_RESET(cksum)
#define CKSUM_ADD(cksum, byte)
#define CKSUM_FINALIZE(cksum)
#elif TF_CKSUM_TYPE == 8
// ~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 = (TF_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) */
static const uint16_t crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte)
{
return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff];
}
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0)
#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
};
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 = (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)
#endif
//endregion
void _TF_FN TF_Init(TF_PEER peer_bit)
{
// Zero it out
memset(&tf, 0, sizeof(struct TinyFrameStruct));
tf.peer_bit = peer_bit;
}
void TF_ResetParser(void)
{
tf.state = TFState_SOF;
tf.cksum = 0;
}
//region Listeners
int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb)
bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
{
int i;
size_t i;
for (i = 0; i < TF_MAX_ID_LST; i++) {
if (tf.id_listeners[i].fn == NULL) {
tf.id_listeners[i].fn = cb;
tf.id_listeners[i].id = frame_id;
return i;
if (i >= tf.count_id_lst) {
tf.count_id_lst = i + 1;
}
return true;
}
}
return TF_ERROR;
return false;
}
int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb)
bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb)
{
int i;
size_t i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) {
if (tf.type_listeners[i].fn == NULL) {
tf.type_listeners[i].fn = cb;
tf.type_listeners[i].type = frame_type;
return TF_MAX_ID_LST + i;
if (i >= tf.count_type_lst) {
tf.count_type_lst = i + 1;
}
return true;
}
}
return TF_ERROR;
return false;
}
int TF_AddGenericListener(TinyFrameListener cb)
bool _TF_FN TF_AddGenericListener(TF_LISTENER cb)
{
int i;
size_t i;
for (i = 0; i < TF_MAX_GEN_LST; i++) {
if (tf.generic_listeners[i].fn == NULL) {
tf.generic_listeners[i].fn = cb;
return TF_MAX_ID_LST + TF_MAX_TYPE_LST + i;
if (i >= tf.count_generic_lst) {
tf.count_generic_lst = i + 1;
}
return true;
}
}
return TF_ERROR;
return false;
}
void TF_RemoveListener(unsigned int index)
bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
{
// all listener arrays share common "address space"
if (index < TF_MAX_ID_LST) {
tf.id_listeners[index].fn = NULL;
}
else if (index < TF_MAX_ID_LST + TF_MAX_TYPE_LST) {
tf.type_listeners[index - TF_MAX_ID_LST].fn = NULL;
}
else if (index < TF_MAX_ID_LST + TF_MAX_TYPE_LST + TF_MAX_GEN_LST) {
tf.generic_listeners[index - TF_MAX_ID_LST - TF_MAX_TYPE_LST].fn = NULL;
}
}
void TF_RemoveIdListener(unsigned int frame_id)
{
int i;
for (i = 0; i < TF_MAX_ID_LST; i++) {
if (tf.id_listeners[i].fn != NULL && tf.id_listeners[i].id == frame_id) {
size_t i;
for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn != NULL
&& tf.id_listeners[i].id == frame_id) {
tf.id_listeners[i].fn = NULL;
if (i == tf.count_id_lst - 1) {
tf.count_id_lst--;
}
return true;
}
}
return false;
}
void TF_RemoveTypeListener(unsigned char type)
bool _TF_FN TF_RemoveTypeListener(TF_TYPE type)
{
int i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) {
if (tf.type_listeners[i].fn != NULL && tf.type_listeners[i].type == type) {
size_t i;
for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn != NULL
&& tf.type_listeners[i].type == type) {
tf.type_listeners[i].fn = NULL;
if (i == tf.count_type_lst - 1) {
tf.count_type_lst--;
}
return true;
}
}
return false;
}
void TF_RemoveListenerFn(TinyFrameListener cb)
bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb)
{
int i;
for (i = 0; i < TF_MAX_ID_LST; i++) {
if (tf.id_listeners[i].fn == cb) {
tf.id_listeners[i].fn = NULL;
}
}
for (i = 0; i < TF_MAX_TYPE_LST; i++) {
if (tf.type_listeners[i].fn == cb) {
tf.type_listeners[i].fn = NULL;
}
}
for (i = 0; i < TF_MAX_GEN_LST; i++) {
size_t i;
for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn == cb) {
tf.generic_listeners[i].fn = NULL;
if (i == tf.count_generic_lst - 1) {
tf.count_generic_lst--;
}
return true;
}
}
return false;
}
/** Handle a message that was just collected & verified by the parser */
static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data, TF_LEN data_len)
{
size_t i;
// Any listener can consume the message (return true),
// or let someone else handle it.
// The loop upper bounds are the highest currently used slot index
// (or close to it, depending on the order of listener removals)
// ID listeners first
for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn && (tf.id_listeners[i].id == frame_id)) {
if (tf.id_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
}
// Type listeners
for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn && (tf.type_listeners[i].type == type)) {
if (tf.type_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
}
// Generic listeners
for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn) {
if (tf.generic_listeners[i].fn(frame_id, type, data, data_len)) {
return;
}
}
}
}
void TF_Accept(const unsigned char *buffer, unsigned int count)
{
unsigned int i;
//endregion Listeners
void _TF_FN TF_Accept(const uint8_t *buffer, size_t count)
{
size_t i;
for (i = 0; i < count; i++) {
TF_AcceptChar(buffer[i]);
}
}
void TF_AcceptChar(unsigned char c)
void _TF_FN TF_ResetParser(void)
{
int i;
bool rv, brk;
tf.state = TFState_SOF;
}
switch (tf.state)
{
/** SOF was received */
static void _TF_FN TF_ParsBeginFrame(void) {
// Reset state vars
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
if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
TF_ResetParser();
}
tf.parser_timeout_ticks = 0;
// DRY snippet - collect multi-byte number from the input stream
#define COLLECT_NUMBER(dest, type) dest = (type)(((dest) << 8) | c); \
if (++tf.rxi == sizeof(type))
#if !TF_USE_SOF_BYTE
if (tf.state == TFState_SOF) {
TF_ParsBeginFrame();
}
#endif
switch (tf.state) {
case TFState_SOF:
if (c == TF_SOF_BYTE) {
tf.cksum = 0;
tf.state = TFState_ID;
TF_ParsBeginFrame();
}
break;
case TFState_ID:
tf.id = c;
tf.state = TFState_NOB;
break;
case TFState_NOB:
tf.nob = c + 1; // using 0..255 as 1..256
tf.state = TFState_PAYLOAD;
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.id, TF_ID) {
// Enter LEN state
tf.state = TFState_LEN;
tf.rxi = 0;
break;
case TFState_PAYLOAD:
tf.pldbuf[tf.rxi++] = c;
if (tf.rxi == tf.nob) {
tf.state = TFState_CKSUM;
}
break;
case TFState_CKSUM:
tf.state = TFState_SOF;
case TFState_LEN:
CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.len, TF_LEN) {
// Enter TYPE state
tf.state = TFState_TYPE;
tf.rxi = 0;
}
break;
if (tf.cksum == (unsigned int)c) {
// Add 0 at the end of the data in the buffer (useful if it was a string)
tf.pldbuf[tf.rxi] = '\0';
brk = false;
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;
// Fire listeners
for (i = 0; i < TF_MAX_ID_LST; i++) {
if (tf.id_listeners[i].fn && tf.id_listeners[i].id == tf.id) {
rv = tf.id_listeners[i].fn(tf.id, tf.pldbuf, tf.nob);
if (rv) {
brk = true;
case TFState_HEAD_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value
CKSUM_FINALIZE(tf.cksum);
if (tf.cksum != tf.ref_cksum) {
TF_ResetParser();
break;
}
}
}
if (!brk) {
for (i = 0; i < TF_MAX_TYPE_LST; i++) {
if (tf.type_listeners[i].fn &&
tf.type_listeners[i].type == tf.pldbuf[0]) {
rv = tf.type_listeners[i].fn(tf.id, tf.pldbuf, tf.nob);
if (rv) {
brk = true;
if (tf.len == 0) {
TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0);
TF_ResetParser();
break;
}
}
}
}
if (!brk) {
for (i = 0; i < TF_MAX_GEN_LST; i++) {
if (tf.generic_listeners[i].fn) {
rv = tf.generic_listeners[i].fn(tf.id, tf.pldbuf, tf.nob);
if (rv) {
// Enter DATA state
tf.state = TFState_DATA;
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;
}
}
}
}
case TFState_DATA:
if (tf.discard_data) {
tf.rxi++;
} else {
// Fail, return to base state
tf.state = TFState_SOF;
CKSUM_ADD(tf.cksum, c);
tf.data[tf.rxi++] = c;
}
if (tf.rxi == tf.len) {
#if TF_CKSUM_TYPE == 0
// All done
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
TF_ResetParser();
#else
// Enter DATA_CKSUM state
tf.state = TFState_DATA_CKSUM;
tf.rxi = 0;
tf.ref_cksum = 0;
#endif
}
break;
case TFState_DATA_CKSUM:
COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
// Check the header checksum against the computed value
CKSUM_FINALIZE(tf.cksum);
if (!tf.discard_data && tf.cksum == tf.ref_cksum) {
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len);
}
TF_ResetParser();
}
break;
}
// Update the checksum
tf.cksum ^= c;
}
int TF_Compose(unsigned char *outbuff, unsigned int *msgid,
const unsigned char *payload, unsigned int payload_len,
int explicit_id
) {
unsigned int i;
unsigned int id;
int xor;
/**
* 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,
const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id)
{
int i;
uint8_t b;
TF_ID id;
TF_CKSUM cksum;
int pos = 0;
CKSUM_RESET(cksum);
// sanitize len
if (payload_len > TF_MAX_PAYLOAD) return TF_ERROR;
if (payload_len == 0) payload_len = strlen(payload);
if (data_len > TF_MAX_PAYLOAD) {
return TF_ERROR;
}
// Gen ID
if (explicit_id == TF_NEXT_ID) {
id = tf.next_id++;
if (tf.peer_bit) {
id |= 0x80;
}
if (tf.next_id > 0x7F) {
tf.next_id = 0;
}
} else {
if (use_expl_id) {
id = explicit_id;
}
outbuff[0] = TF_SOF_BYTE;
outbuff[1] = id & 0xFF;
outbuff[2] = (payload_len - 1) & 0xFF; // use 0..255 as 1..256
memcpy(outbuff+3, payload, payload_len);
xor = 0;
for (i = 0; i < payload_len + 3; i++) {
xor ^= outbuff[i];
else {
id = (TF_ID) (tf.next_id++ & TF_ID_MASK);
if (tf.peer_bit) {
id |= TF_ID_PEERBIT;
}
}
outbuff[payload_len + 3] = xor;
if (id_ptr != NULL)
*id_ptr = id;
if (msgid != NULL) *msgid = id;
// DRY helper for writing a multi-byte variable to the buffer
#define WRITENUM_BASE(type, num, xtra) \
for (i = sizeof(type)-1; i>=0; i--) { \
b = (uint8_t)(num >> (i*8) & 0xFF); \
outbuff[pos++] = b; \
xtra; \
}
return payload_len + 4;
#define _NOOP()
#define WRITENUM(type, num) WRITENUM_BASE(type, num, _NOOP())
#define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
// --- Start ---
CKSUM_RESET(cksum);
#if TF_USE_SOF_BYTE
outbuff[pos++] = TF_SOF_BYTE;
CKSUM_ADD(cksum, TF_SOF_BYTE);
#endif
WRITENUM_CKSUM(TF_ID, id);
WRITENUM_CKSUM(TF_LEN, data_len);
WRITENUM_CKSUM(TF_TYPE, type);
#if TF_CKSUM_TYPE != 0
CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
// --- 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 TF_CKSUM_TYPE != 0
CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
}
return pos;
}
int TF_Send(const unsigned char *payload,
unsigned int payload_len,
TinyFrameListener listener,
unsigned int *id_ptr)
bool _TF_FN TF_Send(TF_TYPE type,
const uint8_t *payload, TF_LEN payload_len,
TF_LISTENER listener,
TF_ID *id_ptr)
{
unsigned int msgid;
TF_ID msgid = 0;
int len;
int lstid = TF_ERROR;
len = TF_Compose(tf.sendbuf, &msgid, payload, payload_len, TF_NEXT_ID);
if (listener) lstid = TF_AddIdListener(msgid, listener);
TF_WriteImpl(tf.sendbuf, len);
len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false);
if (len == TF_ERROR) return false;
if (listener) TF_AddIdListener(msgid, listener);
if (id_ptr) *id_ptr = msgid;
return lstid;
TF_WriteImpl((const uint8_t *) tf.sendbuf, (TF_LEN)len);
return true;
}
void TF_Respond(const unsigned char *payload,
unsigned int payload_len,
unsigned int frame_id)
// Like TF_Send, but with explicit frame ID
bool _TF_FN TF_Respond(TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID frame_id)
{
int len;
len = TF_Compose(tf.sendbuf, NULL, payload, payload_len, frame_id);
TF_WriteImpl(tf.sendbuf, len);
len = TF_Compose(tf.sendbuf, NULL, type, data, data_len, frame_id, true);
if (len == TF_ERROR) return false;
TF_WriteImpl(tf.sendbuf, (TF_LEN)len);
return true;
}
int TF_Send1(const unsigned char b0,
TinyFrameListener listener,
unsigned int *id_ptr)
/**
* Like TF_Send(), but with no data
*/
bool _TF_FN TF_Send0(TF_TYPE type,
TF_LISTENER listener,
TF_ID *id_ptr)
{
unsigned char b[] = {b0};
return TF_Send(b, 1, listener, id_ptr);
return TF_Send(type, NULL, 0, listener, id_ptr);
}
int TF_Send2(const unsigned char b0,
const unsigned char b1,
TinyFrameListener listener,
unsigned int *id_ptr)
/**
* Like TF_Send(), but with just 1 data byte
*/
bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener,
TF_ID *id_ptr)
{
unsigned char b[] = {b0, b1};
return TF_Send(b, 2, listener, id_ptr);
unsigned char b[] = {b1};
return TF_Send(type, b, 1, listener, id_ptr);
}
/**
* Like TF_Send(), but with just 2 data bytes
*/
bool _TF_FN TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
TF_LISTENER listener,
TF_ID *id_ptr)
{
unsigned char b[] = {b1, b2};
return TF_Send(type, b, 2, listener, id_ptr);
}
/** Timebase hook - for timeouts */
void _TF_FN TF_Tick(void)
{
if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) {
tf.parser_timeout_ticks++;
}
}
+192 -82
View File
@@ -1,45 +1,154 @@
#ifndef TinyFrameH
#define TinyFrameH
//---------------------------------------------------------------------------
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h> // for uint8_t etc
#include <stdbool.h> // for bool
#include <stdlib.h> // for NULL
//#include "messages.h" // for your message IDs (enum or defines)
//#include <esp8266.h> // when using with esphttpd
//---------------------------------------------------------------------------
//----------------------------- 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)
#ifndef TF_MAX_ID_LST
# define TF_MAX_ID_LST 64
#endif
#define TF_MAX_ID_LST 20
// Frame Type listeners (wait for frame with a specific first payload byte)
#ifndef TF_MAX_TYPE_LST
# define TF_MAX_TYPE_LST 16
#endif
#define TF_MAX_TYPE_LST 20
// Generic listeners (fallback if no other listener catches it)
#ifndef TF_MAX_GEN_LST
# define TF_MAX_GEN_LST 4
#define TF_MAX_GEN_LST 4
// Timeout for receiving & parsing a frame
// ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10
//----------------------------- 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
#if TF_LEN_BYTES == 1
typedef uint8_t TF_LEN;
#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
#define TF_NEXT_ID -1
#if TF_TYPE_BYTES == 1
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
#if TF_ID_BYTES == 1
typedef uint8_t TF_ID;
#elif TF_ID_BYTES == 2
typedef uint16_t TF_ID;
#elif TF_ID_BYTES == 4
typedef uint32_t TF_ID;
#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;
#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
// 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
// Type-dependent masks for bit manipulation in the ID field
#define TF_ID_MASK (TF_ID)(((TF_ID)1 << (sizeof(TF_ID)*8 - 1)) - 1)
#define TF_ID_PEERBIT (TF_ID)((TF_ID)1 << ((sizeof(TF_ID)*8) - 1))
//---------------------------------------------------------------------------
/** Peer bit enum (used for init) */
typedef enum {
TF_SLAVE = 0,
TF_MASTER = 1
} TF_PEER;
/**
* TinyFrame receive callback type.
* TinyFrame Type Listener callback
* @param frame_id - ID of the received frame
* @param buff - byte buffer with the payload
* @param data - byte buffer with the application data
* @param len - number of bytes in the buffer
* @return true if the frame was consumed
*/
typedef bool (*TinyFrameListener)(unsigned int frame_id,
const unsigned char *buff,
unsigned int len);
typedef bool (*TF_LISTENER)(TF_ID frame_id,
TF_TYPE type,
const uint8_t *data, TF_LEN len);
/**
* Initialize the TinyFrame engine.
* This can also be used to completely reset it (removing all listeners etc)
* @param peer_bit - peer bit to use for self
*/
void TF_Init(bool peer_bit);
void TF_Init(TF_PEER peer_bit);
/**
* Reset the frame parser state machine.
@@ -51,13 +160,13 @@ void TF_ResetParser(void);
* @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer
*/
void TF_Accept(const unsigned char *buffer, unsigned int count);
void TF_Accept(const uint8_t *buffer, size_t count);
/**
* Accept a single incoming byte
* @param c - a received char
*/
void TF_AcceptChar(unsigned char c);
void TF_AcceptChar(uint8_t c);
/**
* Register a frame type listener.
@@ -65,7 +174,13 @@ void TF_AcceptChar(unsigned char c);
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb);
bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb);
/**
* Remove a listener by the message ID it's registered for
* @param frame_id - the frame we're listening for
*/
bool TF_RemoveIdListener(TF_ID frame_id);
/**
* Register a frame type listener.
@@ -73,95 +188,90 @@ int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb);
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb);
bool TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb);
/**
* Remove a listener by type.
* @param type - the type it's registered for
*/
bool TF_RemoveTypeListener(TF_TYPE type);
/**
* Register a generic listener.
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
int TF_AddGenericListener(TinyFrameListener cb);
bool TF_AddGenericListener(TF_LISTENER cb);
/**
* Remove any listener by the index received when registering it
* @param index - index in the callbacks table
*/
void TF_RemoveListener(unsigned int index);
/**
* Remove a listener by the message ID it's registered for
* @param frame_id - the frame we're listening for
*/
void TF_RemoveIdListener(unsigned int frame_id);
/**
* Remove a listener by type.
* @param type - the type it's registered for
*/
void TF_RemoveTypeListener(unsigned char type);
/**
* Remove a callback by the function pointer
* Remove a generic listener by function pointer
* @param cb - callback function to remove
*/
void TF_RemoveListenerFn(TinyFrameListener cb); // search all listener types
bool TF_RemoveGenericListener(TF_LISTENER cb);
/**
* Compose a frame
* Send a frame, and optionally attach an ID listener.
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param payload - data buffer
* @param len - payload size in bytes, 0 to use strlen
* @param explicit_id - ID to use, TF_NEXT_ID (-1) to chose next free
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
*/
int TF_Compose(unsigned char *outbuff, unsigned int *msgid,
const unsigned char *payload, unsigned int payload_len,
int explicit_id);
/**
* Send a frame, and optionally attach an ID listener for response.
*
* @param payload - data to send
* @param payload_len - nr of bytes to send
* @param type - message type
* @param data - data to send (can be NULL if 'data_len' is 0)
* @param data_len - nr of bytes to send
* @param listener - listener waiting for the response
* @param id_ptr - store the ID here, NULL to don't store.
* The ID may be used to unbind the listener after a timeout.
* @return listener ID if listener was attached, else -1 (FT_ERROR).
* If the listener arg was NULL, returned -1 does not indicate an error.
* @return success
*/
int TF_Send(const unsigned char *payload,
unsigned int payload_len,
TinyFrameListener listener, unsigned int *id_ptr);
bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len,
TF_LISTENER listener,
TF_ID *id_ptr);
/**
* A shorthand for TF_Send() with just one byte as the payload
* Like TF_Send(), but no data, just the type
*/
int TF_Send1(const unsigned char b0,
TinyFrameListener listener, unsigned int *id_ptr);
bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr);
/**
* A shorthand for TF_Send() with just 2 bytes as the payload
* Like TF_Send(), but with just 1 data byte
*/
int TF_Send2(const unsigned char b0,
const unsigned char b1,
TinyFrameListener listener, unsigned int *id_ptr);
bool TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener,
TF_ID *id_ptr);
/**
* Like TF_Send(), but with just 2 data bytes
*/
bool TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2,
TF_LISTENER listener,
TF_ID *id_ptr);
/**
* Send a response to a received message.
*
* @param payload - data to send
* @param payload_len - nr of bytes to send
* @param frame_id - ID of the original frame
* @param type - message type. If an ID listener is waiting for this response,
* then 'type' can be used to pass additional information.
* Otherwise, 'type' can be used to handle the message using a TypeListener.
* @param data - data to send
* @param data_len - nr of bytes to send
* @param frame_id - ID of the response frame (re-use ID from the original message)
* @return success
*/
void TF_Respond(const unsigned char *payload,
unsigned int payload_len,
unsigned int frame_id);
bool TF_Respond(TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID frame_id);
/**
* Write implementation for TF, to be provided by user code.
* This sends frames composed by TinyFrame to UART
* 'Write bytes' function that sends data to UART
*
* ! Implement this in your application code !
*/
extern void TF_WriteImpl(const unsigned char *buff, unsigned int len);
extern void TF_WriteImpl(const uint8_t *buff, TF_LEN len);
/**
* This function must be called periodically.
*
* The time base is used to time-out partial frames in the parser and
* automatically reset it.
*
* (suggestion - call this in a SysTick handler)
*/
void TF_Tick(void);
#endif
+47 -52
View File
@@ -1,82 +1,77 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "TinyFrame.h"
// helper func for testing
static void dumpFrame(const unsigned char *buff, unsigned int len)
{
int i;
for(i = 0; i < len; i++) {
printf("%3u %c\n", buff[i], buff[i]);
}
printf("--- end of frame ---\n");
}
static void dumpFrame(const uint8_t *buff, TF_LEN len);
/**
* This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other)
*/
void TF_WriteImpl(const unsigned char *buff, unsigned int 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);
// Send it back as if we received it
TF_Accept(buff, len);
}
/** An example listener function */
bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int 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);
return false; // Do not unbind
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;
}
void main()
bool testIdListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{
int i;
int msgid;
int len;
char buff[100];
printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id);
return true;
}
void main(void)
{
// Set up the TinyFrame library
TF_Init(1); // 1 = master, 0 = slave
TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a message --------\n");
// Send a message
// args - payload, length (0 = strlen), listener, id_ptr (for storing the frame ID)
// (see the .h file for details)
TF_Send("Hello TinyFrame", 0, NULL, NULL);
// This builds the frame in an internal buffer and sends it to
// TF_WriteImpl()
TF_Send(0x22, (unsigned char*)"Hello TinyFrame", 16, NULL, NULL);
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
// 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);
TF_Send0(0xF0, NULL, NULL);
// This lets us compose a frame (it's also used internally by TF_Send and TF_Respond)
len = TF_Compose(buff, // Buffer to write the frame to
&msgid, // Int to store the message ID in
"ALPHA BETA", // Payload bytes
5, // Length - this will cut it at "ALPHA" (showing that it works)
// For string, we can use "0" to use strlen() internally
TF_NEXT_ID); // 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.
TF_Send1(0xF1, 'Q', NULL, NULL);
printf("The frame we'll receive is:\n");
dumpFrame(buff, len);
TF_Send2(0xF2, 'A', 'Z', NULL, NULL);
// Accept the frame
// You will normally call this method in the UART IRQ handler etc
TF_Accept(buff, len);
TF_Send0(0x77, testIdListener, NULL);
}
// helper func for testing
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");
}
printf("\n");
}
printf("--- end of frame ---\n");
}