diff --git a/README.md b/README.md index 0cea476..a483312 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ UDP packets. If you find a good use for it, please let me know so I can add it h Frames can be protected by a checksum (~XOR, CRC16 or CRC32) and contain a unique ID field which can be used for chaining related messages. The highest bit -of the generated IDs is different for each peer to avoid collisions. +of the generated IDs is different in each peer to avoid collisions. Peers are functionally equivalent and can send messages to each other -(the names "master" and "slave" are used only for convenience). +(the names "master" and "slave" are used only for convenience and have special meaning +in the demos). The library lets you register listeners (callback functions) to wait for (1) any frame, (2) -a particular frame Type, or (3) a specific message ID. This lets you easily implement asynchronous -communication. +a particular frame Type, or (3) a specific message ID. This high-level API lets you +easily implement various async communication patterns. ## Frame structure @@ -50,24 +51,20 @@ DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message - Both peers must include the library with the same parameters (configured at the top of the header file) - Start by calling `TF_Init()` with `TF_MASTER` or `TF_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). + This function is used by `TF_Send()` and others 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). +- If you wish to use timeouts, periodically call `TF_Tick()`. The calling 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) and can also time-out ID listeners. - 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. +- Send a message using `TF_Send()`, `TF_Query()`, `TF_SendSimple()`, `TF_QuerySimple()`. + Query functions take a listener callback (function pointer)that 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 + with the msg boject you received, replacing the `data` pointer (and `len`) with response. - Manually reset the parser using `TF_ResetParser()` -### The concept of listeners +### Message listeners Listeners are callback functions that are called by TinyFrame when a message which they can handle is received. @@ -78,52 +75,14 @@ There are 3 listener types: - Type listeners - Generic listeners -They handle the message in this order, and if they decide not to handle it, they can return `false` -and let it be handled by some other listener, or discarded. - -### Implementing "synchronous query" - -Sometimes it's necessary to send a message and wait for a response to arrive. - -One (not too pretty) way to do this is using a global variable - pseudocode: - -```c -#define MSG_PING 42 - -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; -} -``` +Listeners return an enum constant based on what should be done next - remove the listener, +keep it, renew it's timeout, or let some other listener handle the message. + +### Examples + +You'll find various examples in the `demo/` folder. Each example has it's own Makefile, +read it to see what options are available. + +The demos are written for Linux, using sockets and `clone()` for background processing. +They try to simulate real TinyFrame behavior in an embedded system with asynchronous +Rx and Tx. If you can't run the demos, the source files are still good as examples. diff --git a/TinyFrame.c b/TinyFrame.c index dac526b..2a54faf 100644 --- a/TinyFrame.c +++ b/TinyFrame.c @@ -721,13 +721,14 @@ bool _TF_FN TF_SendSimple(TF_TYPE type, const uint8_t *data, TF_LEN len) } // send without listener and struct -bool _TF_FN TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout) +bool _TF_FN TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout, void *userdata) { TF_Msg msg; TF_ClearMsg(&msg); msg.type = type; msg.data = data; msg.len = len; + msg.userdata = userdata; return TF_Query(&msg, listener, timeout); } diff --git a/TinyFrame.h b/TinyFrame.h index c724a24..0ba0c77 100644 --- a/TinyFrame.h +++ b/TinyFrame.h @@ -209,7 +209,7 @@ bool TF_SendSimple(TF_TYPE type, const uint8_t *data, TF_LEN len); /** * Like TF_Query, but without the struct */ -bool TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout); +bool TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout, void *userdata); /** * Send a frame, and optionally attach an ID listener. diff --git a/demo/hello/master.c b/demo/hello/master.c index 22e5d58..04066a7 100644 --- a/demo/hello/master.c +++ b/demo/hello/master.c @@ -29,7 +29,7 @@ int main(void) TF_SendSimple(1, (pu8)"Ahoj", 5); TF_SendSimple(1, (pu8)"Hello", 6); - TF_QuerySimple(2, (pu8)"Query!", 6, testIdListener, 0); + TF_QuerySimple(2, (pu8)"Query!", 6, testIdListener, 0, NULL); demo_sleep(); } diff --git a/demo/hello/slave.c b/demo/hello/slave.c index 3493bd1..714f455 100644 --- a/demo/hello/slave.c +++ b/demo/hello/slave.c @@ -21,7 +21,7 @@ TF_Result replyListener(TF_Msg *msg) msg->len = (TF_LEN) strlen((const char *) msg->data); TF_Respond(msg); - // unsolicted reply - will not be handled + // unsolicited reply - will not be handled by the ID listener, which is already gone msg->data = (const uint8_t *) "SPAM"; msg->len = 5; TF_Respond(msg);