Much improved functionality
This commit is contained in:
@@ -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.
|
||||
|
||||
+208
-29
@@ -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)
|
||||
{
|
||||
tf.cb_funcs[index] = NULL;
|
||||
// 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unbind
|
||||
if (rv) tf.cb_funcs[i] = NULL;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+47
-10
@@ -5,13 +5,28 @@
|
||||
#include <stdbool.h>
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// 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
|
||||
|
||||
@@ -2,9 +2,28 @@
|
||||
#include <stdlib.h>
|
||||
#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");
|
||||
}
|
||||
|
||||
bool listen(unsigned int frame_id, const unsigned char *buff, unsigned int 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)
|
||||
{
|
||||
printf("\033[32;1mTF_WriteImpl - sending frame:\033[0m\n");
|
||||
dumpFrame(buff, 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<len; i++) {
|
||||
printf("%3u %c\n", buff[i], buff[i]);
|
||||
}
|
||||
TF_Accept(buff, len);
|
||||
printf("------ Simulate sending a message --------\n");
|
||||
|
||||
len = TF_Compose(buff, &msgid, "PRASE PRASE", 5, TF_NEXT_ID);
|
||||
printf("Used = %d, id=%d\n",len, msgid);
|
||||
for(int i=0;i<len; i++) {
|
||||
printf("%3u %c\n", buff[i], buff[i]);
|
||||
}
|
||||
// Send a message, not expecting any reply
|
||||
// Returns Listener ID (if the listener is not NULL, like here)
|
||||
TF_Send("Hello TinyFrame", 0, NULL);
|
||||
// This builds the frame in an internal buffer and sends it to
|
||||
// TF_WriteImpl()
|
||||
|
||||
|
||||
printf("------ Simulate receiving a message --------\n");
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
|
||||
printf("The frame we'll receive is:\n");
|
||||
dumpFrame(buff, len);
|
||||
|
||||
// Accept the frame
|
||||
// You will normally call this method in the UART IRQ handler etc
|
||||
TF_Accept(buff, len);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user