5 Commits
7 changed files with 946 additions and 450 deletions
+6
View File
@@ -31,3 +31,9 @@
# Debug files # Debug files
*.dSYM/ *.dSYM/
*.su *.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
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.
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 ## 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 SOF ......... start of frame, 0x01
ID ... (master_flag | 7-bit counter) - the frame ID ID ......... the frame ID (MSb is the peer bit)
NOB ... nr of payload bytes in the frame (1..256) LEN ......... nr of data bytes in the frame
PAYLOAD ... NOB bytes of data, can contain any byte values 1..256 TYPE ........ message type (used to run Type Listeners, pick any values you like)
CKSUM ... checksum, implemented as XOR of all preceding bytes in the message 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
requesting peer. Such behavior is application specific and is thus left to the
upper layers of the protocol.
- 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 Listeners are callback functions that are called by TinyFrame when a message which
code. they can handle is received.
- 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
`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) - ID listeners
- 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. - Type listeners
- Generic listeners
There are also helper functions `TF_Send1()` and `TF_Send2()` which send one or two bytes. They handle the message in this order, and if they decide not to handle it, they can return `false`
- 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. 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;
}
```
+507 -204
View File
@@ -3,31 +3,35 @@
#include <string.h> #include <string.h>
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
/* Note: payload length determines the Rx buffer size. Max 256 */ // Compatibility with ESP8266 SDK
#define TF_MAX_PAYLOAD 256 #ifdef ICACHE_FLASH_ATTR
#define TF_SOF_BYTE 0x01 #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_HEAD_CKSUM, //!< Wait for header Checksum
TFState_ID, //!< Wait for ID TFState_ID, //!< Wait for ID
TFState_NOB, //!< Wait for Number Of Bytes TFState_TYPE, //!< Wait for message type
TFState_PAYLOAD, //!< Receive payload TFState_DATA, //!< Receive payload
TFState_CKSUM //!< Wait for Checksum TFState_DATA_CKSUM //!< Wait for Checksum
}; };
typedef struct _IdListener_struct { typedef struct _IdListener_struct {
unsigned int id; TF_ID id;
TinyFrameListener fn; TF_LISTENER fn;
} IdListener; } IdListener;
typedef struct _TypeListener_struct_ { typedef struct _TypeListener_struct_ {
unsigned char type; TF_TYPE type;
TinyFrameListener fn; TF_LISTENER fn;
} TypeListener; } TypeListener;
typedef struct _GenericListener_struct_ { typedef struct _GenericListener_struct_ {
TinyFrameListener fn; TF_LISTENER fn;
} GenericListener; } GenericListener;
/** /**
@@ -35,16 +39,20 @@ typedef struct _GenericListener_struct_ {
*/ */
static struct TinyFrameStruct { static struct TinyFrameStruct {
/* Own state */ /* Own state */
bool peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash) TF_PEER peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
unsigned int next_id; //!< Next frame / frame chain ID TF_ID next_id; //!< Next frame / frame chain ID
/* Parser state */ /* Parser state */
enum TFState state; enum TFState state;
unsigned int id; //!< Incoming packet ID int parser_timeout_ticks;
unsigned int nob; //!< Payload length TF_ID id; //!< Incoming packet ID
unsigned char pldbuf[TF_MAX_PAYLOAD+1]; //!< Payload byte buffer TF_LEN len; //!< Payload length
unsigned int rxi; //!< Receive counter (for payload or other fields) uint8_t data[TF_MAX_PAYLOAD]; //!< Data byte buffer
unsigned int cksum; //!< Continually updated checksum 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 --- */ /* --- Callbacks --- */
@@ -53,301 +61,596 @@ static struct TinyFrameStruct {
TypeListener type_listeners[TF_MAX_TYPE_LST]; TypeListener type_listeners[TF_MAX_TYPE_LST];
GenericListener generic_listeners[TF_MAX_GEN_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; } 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)); memset(&tf, 0, sizeof(struct TinyFrameStruct));
tf.peer_bit = peer_bit; tf.peer_bit = peer_bit;
} }
void TF_ResetParser(void) //region Listeners
{
tf.state = TFState_SOF;
tf.cksum = 0;
}
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++) { for (i = 0; i < TF_MAX_ID_LST; i++) {
if (tf.id_listeners[i].fn == NULL) { if (tf.id_listeners[i].fn == NULL) {
tf.id_listeners[i].fn = cb; tf.id_listeners[i].fn = cb;
tf.id_listeners[i].id = frame_id; tf.id_listeners[i].id = frame_id;
return i; if (i >= tf.count_id_lst) {
tf.count_id_lst = i + 1;
} }
return true;
}
}
return false;
} }
return TF_ERROR; bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb)
}
int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb)
{ {
int i; size_t i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) { for (i = 0; i < TF_MAX_TYPE_LST; i++) {
if (tf.type_listeners[i].fn == NULL) { if (tf.type_listeners[i].fn == NULL) {
tf.type_listeners[i].fn = cb; tf.type_listeners[i].fn = cb;
tf.type_listeners[i].type = frame_type; 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 false;
} }
return TF_ERROR; bool _TF_FN TF_AddGenericListener(TF_LISTENER cb)
}
int TF_AddGenericListener(TinyFrameListener cb)
{ {
int i; size_t i;
for (i = 0; i < TF_MAX_GEN_LST; i++) { for (i = 0; i < TF_MAX_GEN_LST; i++) {
if (tf.generic_listeners[i].fn == NULL) { if (tf.generic_listeners[i].fn == NULL) {
tf.generic_listeners[i].fn = cb; 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 false;
} }
return TF_ERROR; bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
}
void TF_RemoveListener(unsigned int index)
{ {
// all listener arrays share common "address space" size_t i;
if (index < TF_MAX_ID_LST) { for (i = 0; i < tf.count_id_lst; i++) {
tf.id_listeners[index].fn = NULL; if (tf.id_listeners[i].fn != NULL
} && tf.id_listeners[i].id == frame_id) {
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) {
tf.id_listeners[i].fn = NULL; 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; size_t i;
for (i = 0; i < TF_MAX_TYPE_LST; i++) { for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn != NULL && tf.type_listeners[i].type == type) { if (tf.type_listeners[i].fn != NULL
&& tf.type_listeners[i].type == type) {
tf.type_listeners[i].fn = NULL; 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; size_t i;
for (i = 0; i < tf.count_generic_lst; 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++) {
if (tf.generic_listeners[i].fn == cb) { if (tf.generic_listeners[i].fn == cb) {
tf.generic_listeners[i].fn = NULL; tf.generic_listeners[i].fn = NULL;
if (i == tf.count_generic_lst - 1) {
tf.count_generic_lst--;
}
return true;
} }
} }
return false;
} }
void TF_Accept(const unsigned char *buffer, unsigned int count) /** 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)
{ {
unsigned int i; 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;
}
}
}
}
//endregion Listeners
void _TF_FN TF_Accept(const uint8_t *buffer, size_t count)
{
size_t i;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
TF_AcceptChar(buffer[i]); TF_AcceptChar(buffer[i]);
} }
} }
void TF_AcceptChar(unsigned char c) void _TF_FN TF_ResetParser(void)
{ {
int i; tf.state = TFState_SOF;
bool rv, brk; }
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: case TFState_SOF:
if (c == TF_SOF_BYTE) { if (c == TF_SOF_BYTE) {
tf.cksum = 0; TF_ParsBeginFrame();
tf.state = TFState_ID;
} }
break; break;
case TFState_ID: case TFState_ID:
tf.id = c; CKSUM_ADD(tf.cksum, c);
tf.state = TFState_NOB; COLLECT_NUMBER(tf.id, TF_ID) {
break; // Enter LEN state
tf.state = TFState_LEN;
case TFState_NOB:
tf.nob = c + 1; // using 0..255 as 1..256
tf.state = TFState_PAYLOAD;
tf.rxi = 0; tf.rxi = 0;
break;
case TFState_PAYLOAD:
tf.pldbuf[tf.rxi++] = c;
if (tf.rxi == tf.nob) {
tf.state = TFState_CKSUM;
} }
break; break;
case TFState_CKSUM: case TFState_LEN:
tf.state = TFState_SOF; 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) { case TFState_TYPE:
// Add 0 at the end of the data in the buffer (useful if it was a string) CKSUM_ADD(tf.cksum, c);
tf.pldbuf[tf.rxi] = '\0'; COLLECT_NUMBER(tf.type, TF_TYPE) {
brk = false; #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 case TFState_HEAD_CKSUM:
for (i = 0; i < TF_MAX_ID_LST; i++) { COLLECT_NUMBER(tf.ref_cksum, TF_CKSUM) {
if (tf.id_listeners[i].fn && tf.id_listeners[i].id == tf.id) { // Check the header checksum against the computed value
rv = tf.id_listeners[i].fn(tf.id, tf.pldbuf, tf.nob); CKSUM_FINALIZE(tf.cksum);
if (rv) {
brk = true; if (tf.cksum != tf.ref_cksum) {
TF_ResetParser();
break; break;
} }
}
}
if (!brk) { if (tf.len == 0) {
for (i = 0; i < TF_MAX_TYPE_LST; i++) { TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0);
if (tf.type_listeners[i].fn && TF_ResetParser();
tf.type_listeners[i].type == tf.pldbuf[0]) {
rv = tf.type_listeners[i].fn(tf.id, tf.pldbuf, tf.nob);
if (rv) {
brk = true;
break; break;
} }
}
}
}
if (!brk) { // Enter DATA state
for (i = 0; i < TF_MAX_GEN_LST; i++) { tf.state = TFState_DATA;
if (tf.generic_listeners[i].fn) { tf.rxi = 0;
rv = tf.generic_listeners[i].fn(tf.id, tf.pldbuf, tf.nob);
if (rv) { 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:
} if (tf.discard_data) {
} tf.rxi++;
} else { } else {
// Fail, return to base state CKSUM_ADD(tf.cksum, c);
tf.state = TFState_SOF; 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; break;
} }
// Update the checksum
tf.cksum ^= c;
} }
int TF_Compose(unsigned char *outbuff, unsigned int *msgid, /**
const unsigned char *payload, unsigned int payload_len, * Compose a frame (used internally by TF_Send and TF_Respond).
int explicit_id * The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
) { *
unsigned int i; * @param outbuff - buffer to store the result in
unsigned int id; * @param msgid - message ID is stored here, if not NULL
int xor; * @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 // sanitize len
if (payload_len > TF_MAX_PAYLOAD) return TF_ERROR; if (data_len > TF_MAX_PAYLOAD) {
if (payload_len == 0) payload_len = strlen(payload); return TF_ERROR;
}
// Gen ID // Gen ID
if (explicit_id == TF_NEXT_ID) { if (use_expl_id) {
id = tf.next_id++;
if (tf.peer_bit) {
id |= 0x80;
}
if (tf.next_id > 0x7F) {
tf.next_id = 0;
}
} else {
id = explicit_id; id = explicit_id;
} }
else {
outbuff[0] = TF_SOF_BYTE; id = (TF_ID) (tf.next_id++ & TF_ID_MASK);
outbuff[1] = id & 0xFF; if (tf.peer_bit) {
outbuff[2] = (payload_len - 1) & 0xFF; // use 0..255 as 1..256 id |= TF_ID_PEERBIT;
memcpy(outbuff+3, payload, payload_len); }
xor = 0;
for (i = 0; i < payload_len + 3; i++) {
xor ^= outbuff[i];
} }
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) \
return payload_len + 4; for (i = sizeof(type)-1; i>=0; i--) { \
b = (uint8_t)(num >> (i*8) & 0xFF); \
outbuff[pos++] = b; \
xtra; \
} }
int TF_Send(const unsigned char *payload, #define _NOOP()
unsigned int payload_len, #define WRITENUM(type, num) WRITENUM_BASE(type, num, _NOOP())
TinyFrameListener listener, #define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
unsigned int *id_ptr)
// --- 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;
}
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 len;
int lstid = TF_ERROR; len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false);
len = TF_Compose(tf.sendbuf, &msgid, payload, payload_len, TF_NEXT_ID); if (len == TF_ERROR) return false;
if (listener) lstid = TF_AddIdListener(msgid, listener);
TF_WriteImpl(tf.sendbuf, len);
if (listener) TF_AddIdListener(msgid, listener);
if (id_ptr) *id_ptr = msgid; 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, // Like TF_Send, but with explicit frame ID
unsigned int payload_len, bool _TF_FN TF_Respond(TF_TYPE type,
unsigned int frame_id) const uint8_t *data, TF_LEN data_len,
TF_ID frame_id)
{ {
int len; int len;
len = TF_Compose(tf.sendbuf, NULL, payload, payload_len, frame_id); len = TF_Compose(tf.sendbuf, NULL, type, data, data_len, frame_id, true);
TF_WriteImpl(tf.sendbuf, len); if (len == TF_ERROR) return false;
TF_WriteImpl(tf.sendbuf, (TF_LEN)len);
return true;
} }
/**
int TF_Send1(const unsigned char b0, * Like TF_Send(), but with no data
TinyFrameListener listener, */
unsigned int *id_ptr) bool _TF_FN TF_Send0(TF_TYPE type,
TF_LISTENER listener,
TF_ID *id_ptr)
{ {
unsigned char b[] = {b0}; return TF_Send(type, NULL, 0, listener, id_ptr);
return TF_Send(b, 1, listener, id_ptr);
} }
/**
int TF_Send2(const unsigned char b0, * Like TF_Send(), but with just 1 data byte
const unsigned char b1, */
TinyFrameListener listener, bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1,
unsigned int *id_ptr) TF_LISTENER listener,
TF_ID *id_ptr)
{ {
unsigned char b[] = {b0, b1}; unsigned char b[] = {b1};
return TF_Send(b, 2, listener, id_ptr); 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++;
}
}
+191 -81
View File
@@ -1,45 +1,154 @@
#ifndef TinyFrameH #ifndef TinyFrameH
#define TinyFrameH #define TinyFrameH
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <stdint.h> #include <stdint.h> // for uint8_t etc
#include <stdbool.h> #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) // Frame ID listeners (wait for response / multi-part message)
#ifndef TF_MAX_ID_LST #define TF_MAX_ID_LST 20
# define TF_MAX_ID_LST 64
#endif
// Frame Type listeners (wait for frame with a specific first payload byte) // Frame Type listeners (wait for frame with a specific first payload byte)
#ifndef TF_MAX_TYPE_LST #define TF_MAX_TYPE_LST 20
# define TF_MAX_TYPE_LST 16
#endif
// Generic listeners (fallback if no other listener catches it) // 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 #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 #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 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 * @param len - number of bytes in the buffer
* @return true if the frame was consumed * @return true if the frame was consumed
*/ */
typedef bool (*TinyFrameListener)(unsigned int frame_id, typedef bool (*TF_LISTENER)(TF_ID frame_id,
const unsigned char *buff, TF_TYPE type,
unsigned int len); const uint8_t *data, TF_LEN len);
/** /**
* Initialize the TinyFrame engine. * Initialize the TinyFrame engine.
* This can also be used to completely reset it (removing all listeners etc) * This can also be used to completely reset it (removing all listeners etc)
* @param peer_bit - peer bit to use for self * @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. * Reset the frame parser state machine.
@@ -51,13 +160,13 @@ void TF_ResetParser(void);
* @param buffer - byte buffer to process * @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer * @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 * Accept a single incoming byte
* @param c - a received char * @param c - a received char
*/ */
void TF_AcceptChar(unsigned char c); void TF_AcceptChar(uint8_t c);
/** /**
* Register a frame type listener. * Register a frame type listener.
@@ -65,7 +174,13 @@ void TF_AcceptChar(unsigned char c);
* @param cb - callback * @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1) * @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. * Register a frame type listener.
@@ -73,95 +188,90 @@ int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb);
* @param cb - callback * @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1) * @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. * Register a generic listener.
* @param cb - callback * @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1) * @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 * Remove a generic listener by function pointer
* @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
* @param cb - callback function to remove * @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 type - message type
* @param msgid - message ID is stored here, if not NULL * @param data - data to send (can be NULL if 'data_len' is 0)
* @param payload - data buffer * @param data_len - nr of bytes to send
* @param len - payload size in bytes, 0 to use strlen * @param listener - listener waiting for the response
* @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 id_ptr - store the ID here, NULL to don't store. * @param id_ptr - store the ID here, NULL to don't store.
* The ID may be used to unbind the listener after a timeout. * The ID may be used to unbind the listener after a timeout.
* @return listener ID if listener was attached, else -1 (FT_ERROR). * @return success
* If the listener arg was NULL, returned -1 does not indicate an error.
*/ */
int TF_Send(const unsigned char *payload, bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len,
unsigned int payload_len, TF_LISTENER listener,
TinyFrameListener listener, unsigned int *id_ptr); 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, bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr);
TinyFrameListener listener, unsigned int *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, bool TF_Send1(TF_TYPE type, uint8_t b1,
const unsigned char b1, TF_LISTENER listener,
TinyFrameListener listener, unsigned int *id_ptr); 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. * Send a response to a received message.
* *
* @param payload - data to send * @param type - message type. If an ID listener is waiting for this response,
* @param payload_len - nr of bytes to send * then 'type' can be used to pass additional information.
* @param frame_id - ID of the original frame * 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, bool TF_Respond(TF_TYPE type,
unsigned int payload_len, const uint8_t *data, TF_LEN data_len,
unsigned int frame_id); TF_ID frame_id);
/** /**
* Write implementation for TF, to be provided by user code. * 'Write bytes' function that sends data to UART
* This sends frames composed by TinyFrame 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 #endif
+47 -52
View File
@@ -1,82 +1,77 @@
#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 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");
}
/** /**
* This function should be defined in the application code. * This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other) * 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); 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(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); printf("\033[33mRX frame\n"
" type: %02Xh\n"
return false; // Do not unbind " 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; printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id);
int msgid; return true;
int len; }
char buff[100];
void main(void)
{
// Set up the TinyFrame library // 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"); 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_Send("Hello TinyFrame", 0, NULL, NULL);
// 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(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.
printf("The frame we'll receive is:\n"); TF_Send2(0xF2, 'A', 'Z', NULL, NULL);
dumpFrame(buff, len);
// Accept the frame TF_Send0(0x77, testIdListener, NULL);
// You will normally call this method in the UART IRQ handler etc }
TF_Accept(buff, len);
// 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");
} }