Much improved functionality

pull/9/head
Ondřej Hruška 7 years ago
parent 788930eca4
commit 80cc7e71e7
  1. 20
      README.md
  2. 237
      TinyFrame.c
  3. 57
      TinyFrame.h
  4. 79
      test.c

@ -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.

@ -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);
}

@ -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");
}
/**
* 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<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);
}

Loading…
Cancel
Save