diff --git a/README.md b/README.md index 8651588..8718fe5 100644 --- a/README.md +++ b/README.md @@ -34,3 +34,23 @@ 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 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 + + `bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { ... }` + + 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. + + 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. + +- To remove a listener, use `TF_RemoveListener()`. *Always remove your ID listeners after handling the response!* There's a limit to the number of listeners. + +- The function `TF_Accept()` is used to handle received chars. Call this in your UART Rx interrupt handler or a similar place. diff --git a/TinyFrame.c b/TinyFrame.c index 5bd434d..56c1c87 100644 --- a/TinyFrame.c +++ b/TinyFrame.c @@ -5,7 +5,6 @@ /* Note: payload length determines the Rx buffer size. Max 256 */ #define TF_MAX_PAYLOAD 256 -#define TF_MAX_CALLBACKS 16 #define TF_SOF_BYTE 0x01 @@ -17,6 +16,19 @@ enum TFState { TFState_CKSUM //!< Wait for Checksum }; +typedef struct _IdListener_struct { + unsigned int id; + TinyFrameListener fn; +} IdListener; + +typedef struct _TypeListener_struct_ { + unsigned char type; + TinyFrameListener fn; +} TypeListener; + +typedef struct _GenericListener_struct_ { + TinyFrameListener fn; +} GenericListener; /** * Frame parser internal state @@ -34,9 +46,14 @@ static struct TinyFrameStruct { unsigned int rxi; //!< Receive counter (for payload or other fields) unsigned int cksum; //!< Continually updated checksum - /* Callbacks */ - int cb_ids[TF_MAX_CALLBACKS]; //!< Callback frame IDs, -1 = all - TinyFrameListener cb_funcs[TF_MAX_CALLBACKS]; //!< Callback funcs, 0 = free slot + /* --- Callbacks --- */ + + /* Transaction callbacks */ + IdListener id_listeners[TF_MAX_ID_LST]; + TypeListener type_listeners[TF_MAX_TYPE_LST]; + GenericListener generic_listeners[TF_MAX_GEN_LST]; + + char sendbuf[TF_MAX_PAYLOAD+1]; } tf; @@ -65,20 +82,20 @@ void TF_ResetParser(void) /** - * Register a receive callback. + * Register a frame type listener. * - * @param frame_id - ID of the frame to receive - * @param cb - callback func - * @return Callback slot index (for removing). TF_ERROR (-1) on failure + * @param frame_type - frame ID to listen for + * @param cb - callback + * @return slot index (for removing), or TF_ERROR (-1) */ -int TF_AddListener(int frame_id, TinyFrameListener cb) +int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb) { int i; - for (i = 0; i < TF_MAX_CALLBACKS; i++) { - if (tf.cb_funcs[i] == NULL) { - tf.cb_funcs[i] = cb; - tf.cb_ids[i] = frame_id; + 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; } } @@ -87,14 +104,87 @@ int TF_AddListener(int frame_id, TinyFrameListener cb) } +/** + * Register a frame type listener. + * + * @param frame_type - frame type to listen for + * @param cb - callback + * @return slot index (for removing), or TF_ERROR (-1) + */ +int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb) +{ + int 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; + } + } + + return TF_ERROR; +} + + +/** + * Register a generic listener. + * + * @param cb - callback + * @return slot index (for removing), or TF_ERROR (-1) + */ +int TF_AddGenericListener(TinyFrameListener cb) +{ + int 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; + } + } + + return TF_ERROR; +} + + /** * Remove a rx callback function by the index received when registering it * * @param index - index in the callbacks table */ -void TF_RemoveListener(int index) +void TF_RemoveListener(unsigned int index) +{ + // 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) { + tf.id_listeners[i].fn = NULL; + } + } +} + +void TF_RemoveTypeListener(unsigned char type) { - tf.cb_funcs[index] = NULL; + int i; + for (i = 0; i < TF_MAX_TYPE_LST; i++) { + if (tf.type_listeners[i].fn != NULL && tf.type_listeners[i].type == type) { + tf.type_listeners[i].fn = NULL; + } + } } @@ -107,14 +197,23 @@ void TF_RemoveListenerFn(TinyFrameListener cb) { int i; - for (i = 0; i < TF_MAX_CALLBACKS; i++) { - if (tf.cb_funcs[i] == cb) { - tf.cb_funcs[i] = NULL; - // no break, it can be here multiple times + 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; } } - return; + for (i = 0; i < TF_MAX_GEN_LST; i++) { + if (tf.generic_listeners[i].fn == cb) { + tf.generic_listeners[i].fn = NULL; + } + } } @@ -142,7 +241,7 @@ void TF_Accept(const unsigned char *buffer, unsigned int count) void TF_AcceptChar(unsigned char c) { int i; - bool rv; + bool rv, brk; switch (tf.state) { @@ -177,15 +276,40 @@ void TF_AcceptChar(unsigned char c) 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; // Fire listeners - for (i = 0; i < TF_MAX_CALLBACKS; i++) { - // Fire if used & matches - if (tf.cb_funcs[i] && (tf.cb_ids[i] == -1 || tf.cb_ids[i] == tf.id)) { - rv = tf.cb_funcs[i](tf.id, tf.pldbuf, tf.nob); + 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; + 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; + break; + } + } + } + } - // Unbind - if (rv) tf.cb_funcs[i] = NULL; + 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) { + break; + } + } } } } else { @@ -211,8 +335,8 @@ void TF_AcceptChar(unsigned char c) * @return nr of bytes in outbuff used by the frame, -1 on failure */ int TF_Compose(unsigned char *outbuff, unsigned int *msgid, - const unsigned char *payload, unsigned int payload_len, - int explicit_id + const unsigned char *payload, unsigned int payload_len, + int explicit_id ) { unsigned int i; unsigned int id; @@ -252,3 +376,58 @@ int TF_Compose(unsigned char *outbuff, unsigned int *msgid, return payload_len + 4; } + + +/** + * Send a frame, and optionally attach an ID listener for response. + * + * @param payload - data to send + * @param payload_len - nr of bytes to send + * @return listener ID if listener was attached, else -1 (FT_ERROR) + */ +int TF_Send(const unsigned char *payload, + unsigned int payload_len, + TinyFrameListener listener) +{ + unsigned int msgid; + 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); + return lstid; +} + + +/** + * 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 + */ +void TF_Respond(const unsigned char *payload, + unsigned int payload_len, + int frame_id) +{ + int len; + len = TF_Compose(tf.sendbuf, NULL, payload, payload_len, frame_id); + TF_WriteImpl(tf.sendbuf, len); +} + + +int TF_Send1(const unsigned char b0, + TinyFrameListener listener) +{ + unsigned char b[] = {b0}; + return TF_Send(b, 1, listener); +} + + +int TF_Send2(const unsigned char b0, + const unsigned char b1, + TinyFrameListener listener) +{ + unsigned char b[] = {b0, b1}; + return TF_Send(b, 2, listener); +} diff --git a/TinyFrame.h b/TinyFrame.h index 0c5dc4d..6f0120e 100644 --- a/TinyFrame.h +++ b/TinyFrame.h @@ -5,13 +5,28 @@ #include //--------------------------------------------------------------------------- +// Frame ID listeners (wait for response / multi-part message) +#ifndef TF_MAX_ID_LST +# define TF_MAX_ID_LST 64 +#endif + +// Frame Type listeners (wait for frame with a specific first payload byte) +#ifndef TF_MAX_TYPE_LST +# define TF_MAX_TYPE_LST 16 +#endif + +// Generic listeners (fallback if no other listener catches it) +#ifndef TF_MAX_GEN_LST +# define TF_MAX_GEN_LST 4 +#endif + /** * TinyFrame receive callback type. * - * @param frame_id - ID of the received byte (if response, same as the request) + * @param frame_id - ID of the received frame * @param buff - byte buffer with the payload * @param len - number of bytes in the buffer - * @return return true, if the callback should be removed. + * @return true if the frame was consumed */ typedef bool (*TinyFrameListener)( unsigned int frame_id, @@ -27,20 +42,42 @@ void TF_Init(bool peer_bit); void TF_Accept(const unsigned char *buffer, unsigned int count); void TF_AcceptChar(unsigned char c); -#define TF_ANY_ID -1 -int TF_AddListener(int frame_id, TinyFrameListener cb); // returns slot index -void TF_RemoveListener(int index); -void TF_RemoveListenerFn(TinyFrameListener cb); +// Add?Listener all return a unique listener ID +int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb); +int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb); +int TF_AddGenericListener(TinyFrameListener cb); -// returns "frame_id" +void TF_RemoveListener(unsigned int index); +void TF_RemoveIdListener(unsigned int frame_id); +void TF_RemoveTypeListener(unsigned char type); +void TF_RemoveListenerFn(TinyFrameListener cb); // search all listener types #define TF_NEXT_ID -1 #define TF_ERROR -1 // returns nr of bytes in the output buffer used, or TF_ERROR int TF_Compose(unsigned char *outbuff, unsigned int *msgid, - const unsigned char *payload, unsigned int payload_len, - int explicit_id -); + const unsigned char *payload, unsigned int payload_len, + int explicit_id); + +// returns listener ID (-1 if listener is NULL - does not indicate error) +int TF_Send(const unsigned char *payload, + unsigned int payload_len, + TinyFrameListener listener); + +int TF_Send1(const unsigned char b0, + TinyFrameListener listener); + +int TF_Send2(const unsigned char b0, + const unsigned char b1, + TinyFrameListener listener); + +// Respond with the same ID +void TF_Respond(const unsigned char *payload, + unsigned int payload_len, + int frame_id); + +/** Write implementation for TF, to be provided by user code */ +extern void TF_WriteImpl(const unsigned char *buff, unsigned int len); #endif diff --git a/test.c b/test.c index 201485f..01e2da9 100644 --- a/test.c +++ b/test.c @@ -2,9 +2,28 @@ #include #include "TinyFrame.h" -#define BUFLEN 200 +// 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"); +} + +/** + * 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) +{ + printf("\033[32;1mTF_WriteImpl - sending frame:\033[0m\n"); + dumpFrame(buff, len); +} -bool listen(unsigned int frame_id, const unsigned char *buff, unsigned int len) +/** An example listener function */ +bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { printf("\033[33mrx frame %s, len %d, id %d\033[0m\n", buff, len, frame_id); @@ -13,24 +32,50 @@ bool listen(unsigned int frame_id, const unsigned char *buff, unsigned int len) void main() { - TF_Init(1); - + int i; int msgid; - unsigned char buff[BUFLEN]; + int len; + char buff[100]; - TF_AddListener(TF_ANY_ID, listen); + // Set up the TinyFrame library + TF_Init(1); // 1 = master, 0 = slave - int len = TF_Compose(buff, &msgid, "Hello TinyFrame", 0, TF_NEXT_ID); - printf("Used = %d, id=%d\n",len, msgid); - for(int i=0;i