22 Commits
Author SHA1 Message Date
MightyPork aadbb0f89e add TF_VERSION define 2017-12-12 20:22:40 +01:00
MightyPork a27798188d Updated code with new improvements - mutex locks 2017-12-12 20:19:02 +01:00
MightyPork 970d6f7ced fix corrupt indentation 2017-11-21 23:17:12 +01:00
MightyPork a966345816 updated readme a little 2017-11-21 22:20:28 +01:00
MightyPork ed389d530b make Tx support multi-chunk payload (exceeding buffer size) 2017-11-21 22:17:06 +01:00
MightyPork 041467fcb0 Merge branch 'userdata' 2017-10-16 00:46:44 +02:00
MightyPork 95a7c89b98 improved api and updated readme 2017-10-16 00:46:30 +02:00
MightyPork 4ad1f09f73 great cleaning 2017-10-16 00:29:43 +02:00
MightyPork 87d1841fc8 demo tweaks ... 2017-10-15 23:30:25 +02:00
MightyPork 8415f15c99 demo works ... 2017-10-15 23:19:39 +02:00
MightyPork 46883a9baa kinda working demo 2017-10-15 22:40:53 +02:00
MightyPork e89ca3a6cb some cleaning 2017-10-15 18:32:21 +02:00
MightyPork 01c8ab7f90 oops checksums didnt work 2017-10-15 18:28:27 +02:00
MightyPork d1aec88f23 did some cleaning 2017-10-15 18:18:20 +02:00
MightyPork b89f458c19 fixed bugz 2017-10-13 11:14:20 +02:00
MightyPork b6532962a4 updated C code, added timeouts etc, not tested! 2017-10-12 16:25:05 +02:00
MightyPork c7f4e2c6f9 change api for easier use (only header file!!! WIP) 2017-10-12 12:06:34 +02:00
MightyPorkandGitHub cf4cc6807b Update README.md 2017-09-13 16:13:45 +02:00
MightyPorkandGitHub d1332408bd Update README.md 2017-09-13 16:12:48 +02:00
MightyPorkandGitHub 317f13c35a fix up some comments 2017-09-13 15:51:23 +02:00
MightyPorkandGitHub 1ca04066d2 syntax fixes for bad compilers 2017-04-18 13:22:37 +02:00
MightyPorkandGitHub 2b54631aba remove trailing comma to support bad compilers 2017-04-18 13:19:20 +02:00
23 changed files with 1605 additions and 520 deletions
+1
View File
@@ -27,6 +27,7 @@
*.i*86 *.i*86
*.x86_64 *.x86_64
*.hex *.hex
*.bin
# Debug files # Debug files
*.dSYM/ *.dSYM/
+16 -2
View File
@@ -4,8 +4,22 @@ project(tf)
set(CMAKE_CXX_STANDARD GNU89) set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES set(SOURCE_FILES
test.c demo/simple/test.c
demo/simple/TF_Config.h
demo/socket_demo/master.c
demo/socket_demo/slave.c
demo/socket_demo/TF_Config.h
demo/multipart_tx/test.c
demo/multipart_tx/TF_Config.h
demo/demo.c
demo/demo.h
TinyFrame.c TinyFrame.c
TinyFrame.h) TinyFrame.h
TF_Config.example.h
demo/utils.c
demo/utils.h
)
include_directories(demo/simple)
add_executable(tf ${SOURCE_FILES}) add_executable(tf ${SOURCE_FILES})
-10
View File
@@ -1,10 +0,0 @@
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
+43 -72
View File
@@ -2,22 +2,34 @@
TinyFrame is a simple library for building and parsing frames to be sent TinyFrame is a simple library for building and parsing frames to be sent
over a serial interface (e.g. UART, telnet etc.). The code is written over a serial interface (e.g. UART, telnet etc.). The code is written
in `--std=gnu89`. to build with `--std=gnu89` and later.
Frames are protected by a checksum (~XOR, CRC16 or CRC32) and contain TinyFrame is suitable for a wide range of applications, including inter-microcontroller
a unique ID field, which can be used for chaining messages. The highest value communication, as a protocol for FTDI-based PC applications or for messaging through
of the ID is different for each peer (TF_MASTER or TF_SLAVE) to avoid collisions. UDP packets. If you find a good use for it, please let me know so I can add it here!
All fields in the frame have configurable size (see the top of the header file). Frames can be protected by a checksum (~XOR, CRC16 or CRC32) and contain
By just changing a value, like `TF_LEN_BYTES`, the library seamlessly switches a unique ID field which can be used for chaining related messages. The highest bit
from `uint8_t` to `uint16_t` or `uint32_t` to support longer payloads. 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 and have special meaning
in the demos).
The library lets you bind listeners waiting for any frame, a particular frame Type, The library lets you register listeners (callback functions) to wait for (1) any frame, (2)
or a specific message ID. This lets you easily implement asynchronous a particular frame Type, or (3) a specific message ID. This high-level API lets you
communication. easily implement various async communication patterns.
## Frame structure ## Frame structure
All fields in the message frame have a configurable size (see the top of the header file).
By just changing a definition in the header, such as `TF_LEN_BYTES` (1, 2 or 4), the library
seamlessly switches between `uint8_t`, `uint16_t` and `uint32_t`. Choose field lengths that
best suit your application needs.
For example, you don't need 4 bytes (`uint32_t`) for the
length field if your payloads are 20 bytes long, using a 1-byte field (`uint8_t`) will save
3 bytes. This may be significant if you high throughput.
``` ```
,-----+----+-----+------+------------+- - - -+------------, ,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM | | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
@@ -35,27 +47,24 @@ DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message
## Usage Hints ## Usage Hints
- Both peers must include the library with the same parameters (config in the header file) - All TinyFrame functions, typedefs and macros start with the `TF_` prefix.
- Start by calling `TF_Init()` with MASTER or SLAVE as the argument - 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`. - 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. A frame can be sent in it's entirety, or in multiple parts, depending on its size.
- If you wish to use `TF_PARSER_TIMEOUT_TICKS`, periodically call `TF_Tick()`. The period - If you wish to use timeouts, periodically call `TF_Tick()`. The calling period determines
determines the length of 1 tick. This is used to time-out the parser in case it gets stuck 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). 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()`. - Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`.
- Send a message using `TF_Send()` or the other Send functions. - Send a message using `TF_Send()`, `TF_Query()`, `TF_SendSimple()`, `TF_QuerySimple()`.
If you provide a listener callback (function pointer) to the function, Query functions take a listener callback (function pointer)that will be added as
the listener will be added as an ID listener and wait for a response. an ID listener and wait for a response.
- To reply to a message (when your listener gets called), use `TF_Respond()` - To reply to a message (when your listener gets called), use `TF_Respond()`
with the same frame_id as in the received message. with the msg boject you received, replacing the `data` pointer (and `len`) with response.
- 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()` - 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 Listeners are callback functions that are called by TinyFrame when a message which
they can handle is received. they can handle is received.
@@ -66,52 +75,14 @@ There are 3 listener types:
- Type listeners - Type listeners
- Generic listeners - Generic listeners
They handle the message in this order, and if they decide not to handle it, they can return `false` Listeners return an enum constant based on what should be done next - remove the listener,
and let it be handled by some other listener, or discarded. keep it, renew it's timeout, or let some other listener handle the message.
### Implementing "synchronous query" ### Examples
Sometimes it's necessary to send a message and wait for a response to arrive. You'll find various examples in the `demo/` folder. Each example has it's own Makefile,
read it to see what options are available.
One (not too pretty) way to do this is using a global variable - pseudocode: The demos are written for Linux, some using sockets and `clone()` for background processing.
They try to simulate real TinyFrame behavior in an embedded system with asynchronous
```c Rx and Tx. If you can't run the demos, the source files are still good as examples.
#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;
}
```
+74
View File
@@ -0,0 +1,74 @@
//
// TinyFrame configuration file.
//
// Rename to TF_Config.h
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
//#include <esp8266.h> // when using with esphttpd
//----------------------------- 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 PEERS 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
// Checksum type
//#define TF_CKSUM_TYPE TF_CKSUM_NONE
//#define TF_CKSUM_TYPE TF_CKSUM_XOR
#define TF_CKSUM_TYPE TF_CKSUM_CRC16
//#define TF_CKSUM_TYPE TF_CKSUM_CRC32
// 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
//----------------------- PLATFORM COMPATIBILITY ----------------------------
// used for timeout tick counters - should be large enough for all used timeouts
typedef uint16_t TF_TICKS;
// used in loops iterating over listeners
typedef uint8_t TF_COUNT;
//----------------------------- PARAMETERS ----------------------------------
// Maximum received payload size (static buffer)
// Larger payloads will be rejected.
#define TF_MAX_PAYLOAD_RX 1024
// Size of the sending buffer. Larger payloads will be split to pieces and sent
// in multiple calls to the write function. This can be lowered to reduce RAM usage.
#define TF_SENDBUF_LEN 128
// --- Listener counts - determine sizes of the static slot tables ---
// Frame ID listeners (wait for response / multi-part message)
#define TF_MAX_ID_LST 10
// Frame Type listeners (wait for frame with a specific first payload byte)
#define TF_MAX_TYPE_LST 10
// Generic listeners (fallback if no other listener catches it)
#define TF_MAX_GEN_LST 5
// Timeout for receiving & parsing a frame
// ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10
//------------------------- End of user config ------------------------------
#endif //TF_CONFIG_H
+28
View File
@@ -0,0 +1,28 @@
#include "TinyFrame.h"
/**
* This is an example of integrating TinyFrame into the application.
*
* TF_WriteImpl() is required, the mutex functions are weak and can
* be removed if not used. They are called from all TF_Send/Respond functions.
*
* Also remember to periodically call TF_Tick() if you wish to use the
* listener timeout feature.
*/
void TF_WriteImpl(const uint8_t *buff, size_t len)
{
// send to UART
}
/** Claim the TX interface before composing and sending a frame */
void TF_ClaimTx(void)
{
// take mutex
}
/** Free the TX interface after composing and sending a frame */
void TF_ReleaseTx(void)
{
// release mutex
}
+385 -168
View File
@@ -10,6 +10,10 @@
#define _TF_FN #define _TF_FN
#endif #endif
// Helper macros
#define TF_MAX(a, b) ((a)>(b)?(a):(b))
#define TF_MIN(a, b) ((a)<(b)?(a):(b))
enum TFState { enum TFState {
TFState_SOF = 0, //!< Wait for SOF TFState_SOF = 0, //!< Wait for SOF
TFState_LEN, //!< Wait for Number Of Bytes TFState_LEN, //!< Wait for Number Of Bytes
@@ -17,21 +21,25 @@ enum TFState {
TFState_ID, //!< Wait for ID TFState_ID, //!< Wait for ID
TFState_TYPE, //!< Wait for message type TFState_TYPE, //!< Wait for message type
TFState_DATA, //!< Receive payload TFState_DATA, //!< Receive payload
TFState_DATA_CKSUM, //!< Wait for Checksum TFState_DATA_CKSUM //!< Wait for Checksum
}; };
typedef struct _IdListener_struct { typedef struct _IdListener_struct_ {
TF_ID id; TF_ID id;
TF_LISTENER fn; TF_Listener fn;
TF_TICKS timeout; // nr of ticks remaining to disable this listener
TF_TICKS timeout_max; // the original timeout is stored here
void *userdata;
void *userdata2;
} IdListener; } IdListener;
typedef struct _TypeListener_struct_ { typedef struct _TypeListener_struct_ {
TF_TYPE type; TF_TYPE type;
TF_LISTENER fn; TF_Listener fn;
} TypeListener; } TypeListener;
typedef struct _GenericListener_struct_ { typedef struct _GenericListener_struct_ {
TF_LISTENER fn; TF_Listener fn;
} GenericListener; } GenericListener;
/** /**
@@ -39,16 +47,16 @@ typedef struct _GenericListener_struct_ {
*/ */
static struct TinyFrameStruct { static struct TinyFrameStruct {
/* Own state */ /* Own state */
TF_PEER peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash) TF_Peer peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
TF_ID 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;
int parser_timeout_ticks; TF_TICKS parser_timeout_ticks;
TF_ID id; //!< Incoming packet ID TF_ID id; //!< Incoming packet ID
TF_LEN len; //!< Payload length TF_LEN len; //!< Payload length
uint8_t data[TF_MAX_PAYLOAD]; //!< Data byte buffer uint8_t data[TF_MAX_PAYLOAD_RX]; //!< Data byte buffer
size_t rxi; //!< Byte counter TF_LEN rxi; //!< Field size byte counter
TF_CKSUM cksum; //!< Checksum calculated of the data stream TF_CKSUM cksum; //!< Checksum calculated of the data stream
TF_CKSUM ref_cksum; //!< Reference checksum read from the message TF_CKSUM ref_cksum; //!< Reference checksum read from the message
TF_TYPE type; //!< Collected message type number TF_TYPE type; //!< Collected message type number
@@ -61,31 +69,34 @@ 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];
size_t count_id_lst; // Those counters are used to optimize look-up times.
size_t count_type_lst; // They point to the highest used slot number,
size_t count_generic_lst; // or close to it, depending on the removal order.
TF_COUNT count_id_lst;
TF_COUNT count_type_lst;
TF_COUNT count_generic_lst;
// Buffer for building frames // Buffer for building frames
uint8_t sendbuf[TF_MAX_PAYLOAD + TF_OVERHEAD_BYTES]; uint8_t sendbuf[TF_SENDBUF_LEN];
} tf; } tf;
//region Checksums //region Checksums
#if TF_CKSUM_TYPE == 0 #if TF_CKSUM_TYPE == TF_CKSUM_NONE
// NONE // NONE
#define CKSUM_RESET(cksum) #define CKSUM_RESET(cksum)
#define CKSUM_ADD(cksum, byte) #define CKSUM_ADD(cksum, byte)
#define CKSUM_FINALIZE(cksum) #define CKSUM_FINALIZE(cksum)
#elif TF_CKSUM_TYPE == 8 #elif TF_CKSUM_TYPE == TF_CKSUM_XOR
// ~XOR // ~XOR
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0) #define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0) #define CKSUM_ADD(cksum, byte) do { (cksum) ^= (byte); } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = ~cksum; } while(0) #define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~cksum; } while(0)
#elif TF_CKSUM_TYPE == 16 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC16
/** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */ /** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */
static const uint16_t crc16_table[256] = { static const uint16_t crc16_table[256] = {
@@ -128,11 +139,11 @@ static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte)
return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff]; return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff];
} }
#define CKSUM_RESET(cksum) do { cksum = 0; } while (0) #define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0)
#define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0) #define CKSUM_ADD(cksum, byte) do { (cksum) = crc16_byte((cksum), (byte)); } while(0)
#define CKSUM_FINALIZE(cksum) #define CKSUM_FINALIZE(cksum)
#elif TF_CKSUM_TYPE == 32 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC32
static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */ static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
@@ -185,16 +196,16 @@ static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte)
return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8)); return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8));
} }
#define CKSUM_RESET(cksum) do { cksum = (TF_CKSUM)0xFFFFFFFF; } while (0) #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_ADD(cksum, byte) do { (cksum) = crc32_byte(cksum, byte); } while(0)
#define CKSUM_FINALIZE(cksum) do { cksum = (TF_CKSUM)~cksum; } while(0) #define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~(cksum); } while(0)
#endif #endif
//endregion //endregion
void _TF_FN TF_Init(TF_PEER peer_bit) void _TF_FN TF_Init(TF_Peer peer_bit)
{ {
// Zero it out // Zero it out
memset(&tf, 0, sizeof(struct TinyFrameStruct)); memset(&tf, 0, sizeof(struct TinyFrameStruct));
@@ -204,15 +215,77 @@ void _TF_FN TF_Init(TF_PEER peer_bit)
//region Listeners //region Listeners
bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb) static void _TF_FN renew_id_listener(IdListener *lst)
{ {
size_t i; lst->timeout = lst->timeout_max;
}
/**
* Notify callback about ID listener demise & clean it
*
* @param lst - listener to clean
*/
static void _TF_FN cleanup_id_listener(TF_COUNT i, IdListener *lst)
{
TF_Msg msg;
if (lst->fn == NULL) return;
// Make user clean up their data - only if not NULL
if (lst->userdata != NULL) {
msg.userdata = lst->userdata;
msg.userdata2 = lst->userdata2;
msg.data = NULL; // this is a signal that the listener should clean up
lst->fn(&msg); // return value is ignored here - use TF_STAY or TF_CLOSE
}
lst->fn = NULL; // Discard listener
if (i == tf.count_id_lst - 1) {
tf.count_id_lst--;
}
}
/**
* Clean up Type listener
*
* @param lst - listener to clean
*/
static inline void _TF_FN cleanup_type_listener(TF_COUNT i, TypeListener *lst)
{
lst->fn = NULL; // Discard listener
if (i == tf.count_type_lst - 1) {
tf.count_type_lst--;
}
}
/**
* Clean up Generic listener
*
* @param lst - listener to clean
*/
static inline void _TF_FN cleanup_generic_listener(TF_COUNT i, GenericListener *lst)
{
lst->fn = NULL; // Discard listener
if (i == tf.count_generic_lst - 1) {
tf.count_generic_lst--;
}
}
bool _TF_FN TF_AddIdListener(TF_Msg *msg, TF_Listener cb, TF_TICKS timeout)
{
TF_COUNT i;
IdListener *lst;
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) { lst = &tf.id_listeners[i];
tf.id_listeners[i].fn = cb; // test for empty slot
tf.id_listeners[i].id = frame_id; if (lst->fn == NULL) {
lst->fn = cb;
lst->id = msg->frame_id;
lst->userdata = msg->userdata;
lst->userdata2 = msg->userdata2;
lst->timeout_max = lst->timeout = timeout;
if (i >= tf.count_id_lst) { if (i >= tf.count_id_lst) {
tf.count_id_lst = i + 1; tf.count_id_lst = (TF_COUNT) (i + 1);
} }
return true; return true;
} }
@@ -220,15 +293,18 @@ bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb)
return false; return false;
} }
bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb) bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_Listener cb)
{ {
size_t i; TF_COUNT i;
TypeListener *lst;
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) { lst = &tf.type_listeners[i];
tf.type_listeners[i].fn = cb; // test for empty slot
tf.type_listeners[i].type = frame_type; if (lst->fn == NULL) {
lst->fn = cb;
lst->type = frame_type;
if (i >= tf.count_type_lst) { if (i >= tf.count_type_lst) {
tf.count_type_lst = i + 1; tf.count_type_lst = (TF_COUNT) (i + 1);
} }
return true; return true;
} }
@@ -236,14 +312,17 @@ bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb)
return false; return false;
} }
bool _TF_FN TF_AddGenericListener(TF_LISTENER cb) bool _TF_FN TF_AddGenericListener(TF_Listener cb)
{ {
size_t i; TF_COUNT i;
GenericListener *lst;
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) { lst = &tf.generic_listeners[i];
tf.generic_listeners[i].fn = cb; // test for empty slot
if (lst->fn == NULL) {
lst->fn = cb;
if (i >= tf.count_generic_lst) { if (i >= tf.count_generic_lst) {
tf.count_generic_lst = i + 1; tf.count_generic_lst = (TF_COUNT) (i + 1);
} }
return true; return true;
} }
@@ -253,14 +332,13 @@ bool _TF_FN TF_AddGenericListener(TF_LISTENER cb)
bool _TF_FN TF_RemoveIdListener(TF_ID frame_id) bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
{ {
size_t i; TF_COUNT i;
IdListener *lst;
for (i = 0; i < tf.count_id_lst; i++) { for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn != NULL lst = &tf.id_listeners[i];
&& tf.id_listeners[i].id == frame_id) { // test if live & matching
tf.id_listeners[i].fn = NULL; if (lst->fn != NULL && lst->id == frame_id) {
if (i == tf.count_id_lst - 1) { cleanup_id_listener(i, lst);
tf.count_id_lst--;
}
return true; return true;
} }
} }
@@ -269,29 +347,28 @@ bool _TF_FN TF_RemoveIdListener(TF_ID frame_id)
bool _TF_FN TF_RemoveTypeListener(TF_TYPE type) bool _TF_FN TF_RemoveTypeListener(TF_TYPE type)
{ {
size_t i; TF_COUNT i;
TypeListener *lst;
for (i = 0; i < tf.count_type_lst; i++) { for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn != NULL lst = &tf.type_listeners[i];
&& tf.type_listeners[i].type == type) { // test if live & matching
tf.type_listeners[i].fn = NULL; if (lst->fn != NULL && lst->type == type) {
if (i == tf.count_type_lst - 1) { cleanup_type_listener(i, lst);
tf.count_type_lst--;
}
return true; return true;
} }
} }
return false; return false;
} }
bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb) bool _TF_FN TF_RemoveGenericListener(TF_Listener cb)
{ {
size_t i; TF_COUNT i;
GenericListener *lst;
for (i = 0; i < tf.count_generic_lst; i++) { for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn == cb) { lst = &tf.generic_listeners[i];
tf.generic_listeners[i].fn = NULL; // test if live & matching
if (i == tf.count_generic_lst - 1) { if (lst->fn == cb) {
tf.count_generic_lst--; cleanup_generic_listener(i, lst);
}
return true; return true;
} }
} }
@@ -299,9 +376,23 @@ bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb)
} }
/** Handle a message that was just collected & verified by the parser */ /** 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) static void _TF_FN TF_HandleReceivedMessage(void)
{ {
size_t i; TF_COUNT i;
IdListener *ilst;
TypeListener *tlst;
GenericListener *glst;
TF_Result res;
// Prepare message object
TF_Msg msg;
msg.frame_id = tf.id;
msg.is_response = false;
msg.type = tf.type;
msg.data = tf.data;
msg.len = tf.len;
//dumpFrameInfo(&msg);
// Any listener can consume the message (return true), // Any listener can consume the message (return true),
// or let someone else handle it. // or let someone else handle it.
@@ -311,17 +402,39 @@ static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_
// ID listeners first // ID listeners first
for (i = 0; i < tf.count_id_lst; i++) { for (i = 0; i < tf.count_id_lst; i++) {
if (tf.id_listeners[i].fn && (tf.id_listeners[i].id == frame_id)) { ilst = &tf.id_listeners[i];
if (tf.id_listeners[i].fn(frame_id, type, data, data_len)) { if (ilst->fn && ilst->id == msg.frame_id) {
msg.userdata = ilst->userdata; // pass userdata pointer to the callback
msg.userdata2 = ilst->userdata2;
res = ilst->fn(&msg);
ilst->userdata = msg.userdata; // put it back (may have changed the pointer or set to NULL)
ilst->userdata2 = msg.userdata2; // put it back (may have changed the pointer or set to NULL)
if (res != TF_NEXT) {
if (res == TF_CLOSE) {
cleanup_id_listener(i, ilst);
}
else if (res == TF_RENEW) {
renew_id_listener(ilst);
}
return; return;
} }
} }
} }
msg.userdata = NULL;
msg.userdata2 = NULL;
// clean up for the following listeners that don't use userdata
// Type listeners // Type listeners
for (i = 0; i < tf.count_type_lst; i++) { for (i = 0; i < tf.count_type_lst; i++) {
if (tf.type_listeners[i].fn && (tf.type_listeners[i].type == type)) { tlst = &tf.type_listeners[i];
if (tf.type_listeners[i].fn(frame_id, type, data, data_len)) { if (tlst->fn && tlst->type == msg.type) {
res = tlst->fn(&msg);
if (res != TF_NEXT) {
if (res == TF_CLOSE) {
cleanup_type_listener(i, tlst);
}
return; return;
} }
} }
@@ -329,8 +442,14 @@ static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_
// Generic listeners // Generic listeners
for (i = 0; i < tf.count_generic_lst; i++) { for (i = 0; i < tf.count_generic_lst; i++) {
if (tf.generic_listeners[i].fn) { glst = &tf.generic_listeners[i];
if (tf.generic_listeners[i].fn(frame_id, type, data, data_len)) { if (glst->fn) {
res = glst->fn(&msg);
if (res != TF_NEXT) {
if (res == TF_CLOSE) {
cleanup_generic_listener(i, glst);
}
return; return;
} }
} }
@@ -369,14 +488,14 @@ static void _TF_FN TF_ParsBeginFrame(void) {
void _TF_FN TF_AcceptChar(unsigned char c) void _TF_FN TF_AcceptChar(unsigned char c)
{ {
// Timeout - clear // Parser timeout - clear
if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) { if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
TF_ResetParser(); TF_ResetParser();
} }
tf.parser_timeout_ticks = 0; tf.parser_timeout_ticks = 0;
// DRY snippet - collect multi-byte number from the input stream // DRY snippet - collect multi-byte number from the input stream
#define COLLECT_NUMBER(dest, type) dest = ((dest) << 8) | c; \ #define COLLECT_NUMBER(dest, type) dest = (type)(((dest) << 8) | c); \
if (++tf.rxi == sizeof(type)) if (++tf.rxi == sizeof(type))
#if !TF_USE_SOF_BYTE #if !TF_USE_SOF_BYTE
@@ -413,7 +532,7 @@ void _TF_FN TF_AcceptChar(unsigned char c)
case TFState_TYPE: case TFState_TYPE:
CKSUM_ADD(tf.cksum, c); CKSUM_ADD(tf.cksum, c);
COLLECT_NUMBER(tf.type, TF_TYPE) { COLLECT_NUMBER(tf.type, TF_TYPE) {
#if TF_CKSUM_TYPE == 0 #if TF_CKSUM_TYPE == TF_CKSUM_NONE
tf.state = TFState_DATA; tf.state = TFState_DATA;
tf.rxi = 0; tf.rxi = 0;
#else #else
@@ -436,7 +555,7 @@ void _TF_FN TF_AcceptChar(unsigned char c)
} }
if (tf.len == 0) { if (tf.len == 0) {
TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0); TF_HandleReceivedMessage();
TF_ResetParser(); TF_ResetParser();
break; break;
} }
@@ -447,7 +566,7 @@ void _TF_FN TF_AcceptChar(unsigned char c)
CKSUM_RESET(tf.cksum); // Start collecting the payload CKSUM_RESET(tf.cksum); // Start collecting the payload
if (tf.len >= TF_MAX_PAYLOAD) { if (tf.len >= TF_MAX_PAYLOAD_RX) {
// ERROR - frame too long. Consume, but do not store. // ERROR - frame too long. Consume, but do not store.
tf.discard_data = true; tf.discard_data = true;
} }
@@ -463,9 +582,9 @@ void _TF_FN TF_AcceptChar(unsigned char c)
} }
if (tf.rxi == tf.len) { if (tf.rxi == tf.len) {
#if TF_CKSUM_TYPE == 0 #if TF_CKSUM_TYPE == TF_CKSUM_NONE
// All done // All done
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len); TF_HandleReceivedMessage();
TF_ResetParser(); TF_ResetParser();
#else #else
// Enter DATA_CKSUM state // Enter DATA_CKSUM state
@@ -481,14 +600,32 @@ void _TF_FN TF_AcceptChar(unsigned char c)
// Check the header checksum against the computed value // Check the header checksum against the computed value
CKSUM_FINALIZE(tf.cksum); CKSUM_FINALIZE(tf.cksum);
if (!tf.discard_data && tf.cksum == tf.ref_cksum) { if (!tf.discard_data && tf.cksum == tf.ref_cksum) {
TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len); TF_HandleReceivedMessage();
} }
TF_ResetParser(); TF_ResetParser();
} }
break; break;
} }
// we get here after finishing HEAD, if no data are to be received - handle and clear
if (tf.len == 0 && tf.state == TFState_DATA) {
TF_HandleReceivedMessage();
TF_ResetParser();
} }
}
// Helper macros for the Compose functions
// use variables: si - signed int, b - byte, outbuf - target buffer, pos - count of bytes in buffer
#define WRITENUM_BASE(type, num, xtra) \
for (si = sizeof(type)-1; si>=0; si--) { \
b = (uint8_t)((num) >> (si*8) & 0xFF); \
outbuff[pos++] = b; \
xtra; \
}
#define _NOOP()
#define WRITENUM(type, num) WRITENUM_BASE(type, num, _NOOP())
#define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
/** /**
* Compose a frame (used internally by TF_Send and TF_Respond). * Compose a frame (used internally by TF_Send and TF_Respond).
@@ -497,30 +634,25 @@ void _TF_FN TF_AcceptChar(unsigned char c)
* @param outbuff - buffer to store the result in * @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL * @param msgid - message ID is stored here, if not NULL
* @param type - message type * @param type - message type
* @param data - data buffer
* @param len - payload size in bytes * @param len - payload size in bytes
* @param explicit_id - ID to use in the frame (8-bit) * @param explicit_id - ID to use in the frame (8-bit)
* @param use_expl_id - whether to use the previous param * @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 * @return nr of bytes in outbuff used by the frame, 0 on failure
*/ */
static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, static inline size_t _TF_FN TF_ComposeHead(uint8_t *outbuff, TF_ID *id_ptr,
TF_TYPE type, TF_TYPE type, TF_LEN data_len,
const uint8_t *data, TF_LEN data_len,
TF_ID explicit_id, bool use_expl_id) TF_ID explicit_id, bool use_expl_id)
{ {
int i; int8_t si = 0; // signed small int
uint8_t b; uint8_t b = 0;
TF_ID id; TF_ID id = 0;
TF_CKSUM cksum; TF_CKSUM cksum = 0;
int pos = 0; size_t pos = 0; // can be needed to grow larger than TF_LEN
(void)cksum;
CKSUM_RESET(cksum); CKSUM_RESET(cksum);
// sanitize len
if (data_len > TF_MAX_PAYLOAD) {
return TF_ERROR;
}
// Gen ID // Gen ID
if (use_expl_id) { if (use_expl_id) {
id = explicit_id; id = explicit_id;
@@ -532,19 +664,7 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
} }
} }
if (id_ptr != NULL) if (id_ptr != NULL) *id_ptr = id;
*id_ptr = id;
// DRY helper for writing a multi-byte variable to the buffer
#define WRITENUM_BASE(type, num, xtra) \
for (i = sizeof(type)-1; i>=0; i--) { \
b = (uint8_t)(num >> (i*8) & 0xFF); \
outbuff[pos++] = b; \
xtra; \
}
#define WRITENUM(type, num) WRITENUM_BASE(type, num, )
#define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b))
// --- Start --- // --- Start ---
CKSUM_RESET(cksum); CKSUM_RESET(cksum);
@@ -558,98 +678,195 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr,
WRITENUM_CKSUM(TF_LEN, data_len); WRITENUM_CKSUM(TF_LEN, data_len);
WRITENUM_CKSUM(TF_TYPE, type); WRITENUM_CKSUM(TF_TYPE, type);
#if TF_CKSUM_TYPE != 0 #if TF_CKSUM_TYPE != TF_CKSUM_NONE
CKSUM_FINALIZE(cksum); CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum); WRITENUM(TF_CKSUM, cksum);
#endif #endif
// --- payload begin --- return pos;
if (data_len > 0) { }
CKSUM_RESET(cksum);
/**
* Compose a frame (used internally by TF_Send and TF_Respond).
* The frame can be sent using TF_WriteImpl(), or received by TF_Accept()
*
* @param outbuff - buffer to store the result in
* @param data - data buffer
* @param data_len - data buffer len
* @param cksum - checksum variable, used for all calls to TF_ComposeBody. Must be reset before first use! (CKSUM_RESET(cksum);)
* @return nr of bytes in outbuff used
*/
static size_t _TF_FN TF_ComposeBody(uint8_t *outbuff, const uint8_t *data, TF_LEN data_len, TF_CKSUM *cksum)
{
TF_LEN i = 0;
uint8_t b = 0;
size_t pos = 0;
// DATA
for (i = 0; i < data_len; i++) { for (i = 0; i < data_len; i++) {
b = data[i]; b = data[i];
outbuff[pos++] = b; outbuff[pos++] = b;
CKSUM_ADD(cksum, b); CKSUM_ADD(*cksum, b);
}
#if TF_CKSUM_TYPE != 0
CKSUM_FINALIZE(cksum);
WRITENUM(TF_CKSUM, cksum);
#endif
} }
return pos; return pos;
} }
bool _TF_FN TF_Send(TF_TYPE type, /**
const uint8_t *payload, TF_LEN payload_len, * Finalize a frame
TF_LISTENER listener, *
TF_ID *id_ptr) * @param outbuff - buffer to store the result in
* @param cksum - checksum variable used for the body
* @return nr of bytes in outbuff used
*/
static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
{ {
TF_ID msgid = 0; int8_t si = 0; // signed small int
int len; uint8_t b = 0;
len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false); size_t pos = 0;
if (len == TF_ERROR) return false;
if (listener) TF_AddIdListener(msgid, listener); #if TF_CKSUM_TYPE != TF_CKSUM_NONE
if (id_ptr) *id_ptr = msgid; CKSUM_FINALIZE(*cksum);
WRITENUM(TF_CKSUM, *cksum);
#endif
return pos;
}
// send with listener
static bool _TF_FN TF_SendFrame(TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{
size_t len = 0;
size_t remain = 0;
size_t sent = 0;
TF_CKSUM cksum = 0;
TF_ClaimTx();
len = TF_ComposeHead(tf.sendbuf,
&msg->frame_id,
msg->type,
msg->len,
msg->frame_id,
msg->is_response);
if (listener) TF_AddIdListener(msg, listener, timeout);
CKSUM_RESET(cksum);
remain = msg->len;
while (remain > 0) {
size_t chunk = TF_MIN(TF_SENDBUF_LEN - len, remain);
len += TF_ComposeBody(tf.sendbuf+len, msg->data+sent, (TF_LEN) chunk, &cksum);
remain -= chunk;
sent += chunk;
// Flush if the buffer is full and we have more to send
if (remain > 0 && len == TF_SENDBUF_LEN) {
TF_WriteImpl((const uint8_t *) tf.sendbuf, len);
len = 0;
}
}
// Flush if checksum wouldn't fit in the buffer
if (TF_SENDBUF_LEN - len < sizeof(TF_CKSUM)) {
TF_WriteImpl((const uint8_t *) tf.sendbuf, len);
len = 0;
}
// Add checksum, flush what remains to be sent
len += TF_ComposeTail(tf.sendbuf+len, &cksum);
TF_WriteImpl((const uint8_t *) tf.sendbuf, len);
TF_ReleaseTx();
TF_WriteImpl((const uint8_t *) tf.sendbuf, (TF_LEN)len);
return true; return true;
} }
// send without listener
bool _TF_FN TF_Send(TF_Msg *msg)
{
return TF_SendFrame(msg, NULL, 0);
}
// send without listener and struct
bool _TF_FN TF_SendSimple(TF_TYPE type, const uint8_t *data, TF_LEN len)
{
TF_Msg msg;
TF_ClearMsg(&msg);
msg.type = type;
msg.data = data;
msg.len = len;
return TF_Send(&msg);
}
// 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, void *userdata)
{
TF_Msg msg;
TF_ClearMsg(&msg);
msg.type = type;
msg.data = data;
msg.len = len;
msg.userdata = userdata;
return TF_SendFrame(&msg, listener, timeout);
}
// send with listener
bool _TF_FN TF_Query(TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{
return TF_SendFrame(msg, listener, timeout);
}
// Like TF_Send, but with explicit frame ID // Like TF_Send, but with explicit frame ID
bool _TF_FN TF_Respond(TF_TYPE type, bool _TF_FN TF_Respond(TF_Msg *msg)
const uint8_t *data, TF_LEN data_len,
TF_ID frame_id)
{ {
int len; msg->is_response = true;
len = TF_Compose(tf.sendbuf, NULL, type, data, data_len, frame_id, true); return TF_Send(msg);
if (len == TF_ERROR) return false; }
TF_WriteImpl(tf.sendbuf, (TF_LEN)len); bool _TF_FN TF_RenewIdListener(TF_ID id)
{
TF_COUNT i;
IdListener *lst;
for (i = 0; i < tf.count_id_lst; i++) {
lst = &tf.id_listeners[i];
// test if live & matching
if (lst->fn != NULL && lst->id == id) {
renew_id_listener(lst);
return true; return true;
} }
/**
* Like TF_Send(), but with no data
*/
bool _TF_FN TF_Send0(TF_TYPE type,
TF_LISTENER listener,
TF_ID *id_ptr)
{
return TF_Send(type, NULL, 0, listener, id_ptr);
} }
return false;
/**
* Like TF_Send(), but with just 1 data byte
*/
bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener,
TF_ID *id_ptr)
{
unsigned char b[] = {b1};
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 */ /** Timebase hook - for timeouts */
void _TF_FN TF_Tick(void) void _TF_FN TF_Tick(void)
{ {
TF_COUNT i = 0;
IdListener *lst;
// increment parser timeout (timeout is handled when receiving next byte)
if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) { if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) {
tf.parser_timeout_ticks++; tf.parser_timeout_ticks++;
} }
// decrement and expire ID listeners
for (i = 0; i < tf.count_id_lst; i++) {
lst = &tf.id_listeners[i];
if (!lst->fn || lst->timeout == 0) continue;
// count down...
if (--lst->timeout == 0) {
// Listener has expired
cleanup_id_listener(i, lst);
}
}
} }
void __attribute__((weak)) TF_ClaimTx(void)
{
// do nothing
}
void __attribute__((weak)) TF_ReleaseTx(void)
{
// do nothing
}
+170 -156
View File
@@ -1,66 +1,30 @@
#ifndef TinyFrameH #ifndef TinyFrameH
#define TinyFrameH #define TinyFrameH
/**
* TinyFrame protocol library
*
* (c) Ondřej Hruška 2017, MIT License
* no liability/warranty, free for any use, must retain this notice & license
*
* Upstream URL: https://github.com/MightyPork/TinyFrame
*/
#define TF_VERSION "1.2.0"
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc #include <stdint.h> // for uint8_t etc
#include <stdbool.h> // for bool #include <stdbool.h> // for bool
#include <stdlib.h> // for NULL #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)
#define TF_MAX_ID_LST 20
// Frame Type listeners (wait for frame with a specific first payload byte)
#define TF_MAX_TYPE_LST 20
// Generic listeners (fallback if no other listener catches it)
#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) // Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32)
#define TF_CKSUM_TYPE 16 #define TF_CKSUM_NONE 0
#define TF_CKSUM_XOR 8
#define TF_CKSUM_CRC16 16
#define TF_CKSUM_CRC32 32
// Use a SOF byte to mark the start of a frame #include <TF_Config.h>
#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 //region Resolve data types
@@ -97,13 +61,13 @@
#endif #endif
#if TF_CKSUM_TYPE == 8 || TF_CKSUM_TYPE == 0 #if TF_CKSUM_TYPE == TF_CKSUM_XOR || TF_CKSUM_TYPE == TF_CKSUM_NONE
// ~XOR (if 0, still use 1 byte - it won't be used) // ~XOR (if 0, still use 1 byte - it won't be used)
typedef uint8_t TF_CKSUM; typedef uint8_t TF_CKSUM;
#elif TF_CKSUM_TYPE == 16 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC16
// CRC16 // CRC16
typedef uint16_t TF_CKSUM; typedef uint16_t TF_CKSUM;
#elif TF_CKSUM_TYPE == 32 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC32
// CRC32 // CRC32
typedef uint32_t TF_CKSUM; typedef uint32_t TF_CKSUM;
#else #else
@@ -112,14 +76,19 @@
// Bytes added to TF_MAX_PAYLOAD for the send buffer size. // 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)) #define TF_OVERHEAD_BYTES \
(1*TF_USE_SOF_BYTE + \
sizeof(TF_ID) + \
sizeof(TF_LEN) + \
sizeof(TF_CKSUM) + \
sizeof(TF_TYPE) + \
sizeof(TF_CKSUM) \
)
//endregion //endregion
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#define TF_ERROR -1
// Type-dependent masks for bit manipulation in the ID field // 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_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)) #define TF_ID_PEERBIT (TF_ID)((TF_ID)1 << ((sizeof(TF_ID)*8) - 1))
@@ -129,34 +98,161 @@
/** Peer bit enum (used for init) */ /** Peer bit enum (used for init) */
typedef enum { typedef enum {
TF_SLAVE = 0, TF_SLAVE = 0,
TF_MASTER = 1, TF_MASTER,
} TF_PEER; } TF_Peer;
typedef enum {
TF_NEXT = 0, //!< Not handled, let other listeners handle it
TF_STAY = 1, //!< Handled, stay
TF_RENEW = 2, //!< Handled, stay, renew - useful only with listener timeout
TF_CLOSE = 3, //!< Handled, remove self
} TF_Result;
/** Data structure for sending / receiving messages */
typedef struct _TF_MSG_STRUCT_ {
TF_ID frame_id; //!< message ID
bool is_response; //!< internal flag, set when using the Respond function. frame_id is then kept unchanged.
TF_TYPE type; //!< received or sent message type
const uint8_t *data; //!< buffer of received data or data to send. NULL = listener timed out, free userdata!
TF_LEN len; //!< length of the buffer
void *userdata; //!< here's a place for custom data; this data will be stored with the listener
void *userdata2;
} TF_Msg;
/**
* Clear message struct
*/
static inline void TF_ClearMsg(TF_Msg *msg)
{
msg->frame_id = 0;
msg->is_response = false;
msg->type = 0;
msg->data = NULL;
msg->len = 0;
msg->userdata = NULL;
msg->userdata2 = NULL;
}
/** /**
* TinyFrame Type Listener callback * TinyFrame Type Listener callback
*
* @param frame_id - ID of the received frame * @param frame_id - ID of the received frame
* @param type - type field from the message
* @param data - byte buffer with the application data * @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 listener result
*/ */
typedef bool (*TF_LISTENER)(TF_ID frame_id, typedef TF_Result (*TF_Listener)(TF_Msg *msg);
TF_TYPE type,
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(TF_PEER peer_bit); void TF_Init(TF_Peer peer_bit);
/** /**
* Reset the frame parser state machine. * Reset the frame parser state machine.
* This does not affect registered listeners.
*/ */
void TF_ResetParser(void); void TF_ResetParser(void);
/**
* Register a frame type listener.
*
* @param msg - message (contains frame_id and userdata)
* @param cb - callback
* @param timeout - timeout in ticks to auto-remove the listener (0 = keep forever)
* @return slot index (for removing), or TF_ERROR (-1)
*/
bool TF_AddIdListener(TF_Msg *msg, TF_Listener cb, TF_TICKS timeout);
/**
* 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.
*
* @param frame_type - frame type to listen for
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
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.
*
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
bool TF_AddGenericListener(TF_Listener cb);
/**
* Remove a generic listener by function pointer
*
* @param cb - callback function to remove
*/
bool TF_RemoveGenericListener(TF_Listener cb);
/**
* Send a frame, no listener
*
* @param msg - message struct. ID is stored in the frame_id field
* @return success
*/
bool TF_Send(TF_Msg *msg);
/**
* Like TF_Send, but without the struct
*/
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, void *userdata);
/**
* Send a frame, and optionally attach an ID listener.
*
* @param msg - message struct. ID is stored in the frame_id field
* @param listener - listener waiting for the response (can be NULL)
* @param timeout - listener expiry time in ticks
* @return success
*/
bool TF_Query(TF_Msg *msg, TF_Listener listener, TF_TICKS timeout);
/**
* Send a response to a received message.
*
* @param msg - message struct. ID is read from frame_id. set ->renew to reset listener timeout
* @return success
*/
bool TF_Respond(TF_Msg *msg);
/**
* Renew ID listener timeout
*
* @param id - listener ID to renew
* @return true if listener was found and renewed
*/
bool TF_RenewIdListener(TF_ID id);
/** /**
* Accept incoming bytes & parse frames * Accept incoming bytes & parse frames
*
* @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
*/ */
@@ -164,108 +260,20 @@ 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(uint8_t c); void TF_AcceptChar(uint8_t c);
/**
* Register a frame type listener.
* @param frame_type - frame ID to listen for
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
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.
* @param frame_type - frame type to listen for
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
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.
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
bool TF_AddGenericListener(TF_LISTENER cb);
/**
* Remove a generic listener by function pointer
* @param cb - callback function to remove
*/
bool TF_RemoveGenericListener(TF_LISTENER cb);
/**
* Send a frame, and optionally attach an ID listener.
*
* @param type - message type
* @param data - data to send (can be NULL if 'data_len' is 0)
* @param data_len - nr of bytes to send
* @param listener - listener waiting for the response
* @param id_ptr - store the ID here, NULL to don't store.
* The ID may be used to unbind the listener after a timeout.
* @return success
*/
bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len,
TF_LISTENER listener,
TF_ID *id_ptr);
/**
* Like TF_Send(), but no data, just the type
*/
bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr);
/**
* Like TF_Send(), but with just 1 data byte
*/
bool TF_Send1(TF_TYPE type, uint8_t b1,
TF_LISTENER listener,
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.
*
* @param type - message type. If an ID listener is waiting for this response,
* then 'type' can be used to pass additional information.
* 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
*/
bool TF_Respond(TF_TYPE type,
const uint8_t *data, TF_LEN data_len,
TF_ID frame_id);
/** /**
* 'Write bytes' function that sends data to UART * 'Write bytes' function that sends data to UART
* *
* ! Implement this in your application code ! * ! Implement this in your application code !
*/ */
extern void TF_WriteImpl(const uint8_t *buff, TF_LEN len); extern void TF_WriteImpl(const uint8_t *buff, size_t len);
/** /**
* This function must be called periodically. * This function should be called periodically.
* *
* The time base is used to time-out partial frames in the parser and * The time base is used to time-out partial frames in the parser and
* automatically reset it. * automatically reset it.
@@ -274,4 +282,10 @@ extern void TF_WriteImpl(const uint8_t *buff, TF_LEN len);
*/ */
void TF_Tick(void); void TF_Tick(void);
/** Claim the TX interface before composing and sending a frame */
extern void TF_ClaimTx(void);
/** Free the TX interface after composing and sending a frame */
extern void TF_ReleaseTx(void);
#endif #endif
+219
View File
@@ -0,0 +1,219 @@
//
// Created by MightyPork on 2017/10/15.
//
#include "demo.h"
// those magic defines are needed so we can use clone()
#define _GNU_SOURCE
#define __USE_GNU
#include <sched.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
volatile int sockfd = -1;
volatile bool conn_disband = false;
/**
* Close socket
*/
void demo_disconn(void)
{
conn_disband = true;
if (sockfd >= 0) close(sockfd);
}
/**
* Demo WriteImpl - send stuff to our peer
*
* @param buff
* @param len
*/
void TF_WriteImpl(const uint8_t *buff, size_t len)
{
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
usleep(1000);
if (sockfd != -1) {
write(sockfd, buff, len);
} else {
printf("\nNo peer!\n");
}
}
/**
* Client bg thread
*
* @param unused
* @return unused
*/
static int demo_client(void* unused)
{
(void)unused;
ssize_t n = 0;
uint8_t recvBuff[1024];
struct sockaddr_in serv_addr;
printf("\n--- STARTING CLIENT! ---\n");
memset(recvBuff, '0', sizeof(recvBuff));
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Error : Could not create socket \n");
return false;
}
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\n inet_pton error occured\n");
return false;
}
if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
printf("\n Error : Connect Failed \n");
perror("PERROR ");
return false;
}
printf("\n Child Process \n");
while ((n = read(sockfd, recvBuff, sizeof(recvBuff) - 1)) > 0) {
printf("\033[36m--- RX %ld bytes ---\033[0m\n", n);
dumpFrame(recvBuff, (size_t) n);
TF_Accept(recvBuff, (size_t) n);
}
return 0;
}
/**
* Server bg thread
*
* @param unused
* @return unused
*/
static int demo_server(void* unused)
{
(void)unused;
ssize_t n;
int listenfd = 0;
uint8_t recvBuff[1024];
struct sockaddr_in serv_addr;
int option;
printf("\n--- STARTING SERVER! ---\n");
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
option = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&option, sizeof(option));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(PORT);
if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("Failed to bind");
return 1;
}
if (listen(listenfd, 10) < 0) {
perror("Failed to listen");
return 1;
}
while (1) {
printf("\nWaiting for client...\n");
sockfd = accept(listenfd, (struct sockaddr *) NULL, NULL);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&option, sizeof(option));
printf("\nClient connected\n");
conn_disband = false;
while ((n = read(sockfd, recvBuff, sizeof(recvBuff) - 1)) > 0 && !conn_disband) {
printf("\033[36m--- RX %ld bytes ---\033[0m\n", n);
dumpFrame(recvBuff, n);
TF_Accept(recvBuff, (size_t) n);
}
if (n < 0) {
printf("\n Read error \n");
}
printf("Closing socket\n");
close(sockfd);
sockfd = -1;
}
return 0;
}
/**
* Trap - clean up
*
* @param sig - signal that caused this
*/
static void signal_handler(int sig)
{
(void)sig;
printf("Shutting down...");
demo_disconn();
exit(sig); // pass the signal through - this is nonstandard behavior but useful for debugging
}
/**
* Sleaping Beauty's fave function
*/
void demo_sleep(void)
{
while(1) usleep(10);
}
/**
* Start the background thread
*
* Slave is started first and doesn't normally init transactions - but it could
*
* @param peer what peer we are
*/
void demo_init(TF_Peer peer)
{
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
int retc;
void *stack = malloc(8192);
if (stack == NULL) {
perror("Oh fuck");
signal_handler(9);
return;
}
printf("Starting %s...\n", peer == TF_MASTER ? "MASTER" : "SLAVE");
// CLONE_VM --- share heap
// CLONE_FILES --- share stdout and stderr
if (peer == TF_MASTER) {
retc = clone(&demo_client, (char *)stack+8192, CLONE_VM|CLONE_FILES, 0);
} else {
retc = clone(&demo_server, (char *)stack+8192, CLONE_VM|CLONE_FILES, 0);
}
if (retc == 0) {
perror("Clone fail");
signal_handler(9);
return;
}
printf("Thread started\n");
}
+23
View File
@@ -0,0 +1,23 @@
//
// Created by MightyPork on 2017/10/15.
//
#ifndef TF_DEMO_H
#define TF_DEMO_H
#include <stdbool.h>
#include "../TinyFrame.h"
#include "utils.h"
#define PORT 9798
/** Sleep and wait for ^C */
void demo_sleep(void);
/** Init server - DOES NOT init TinyFrame! */
void demo_init(TF_Peer peer);
/** Disconnect client from the server - can be called by a server-side callback */
void demo_disconn(void);
#endif //TF_DEMO_H
+11
View File
@@ -0,0 +1,11 @@
CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS)
run: test.bin
./test.bin
build: test.bin
test.bin: test.c $(CFILES)
gcc test.c $(CFLAGS) -o test.bin
+25
View File
@@ -0,0 +1,25 @@
//
// Created by MightyPork on 2017/10/15.
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
#define TF_CKSUM_TYPE TF_CKSUM_CRC16
#define TF_USE_SOF_BYTE 1
#define TF_SOF_BYTE 0x01
typedef uint16_t TF_TICKS;
typedef uint8_t TF_COUNT;
#define TF_MAX_PAYLOAD_RX 10240
#define TF_SENDBUF_LEN 64
#define TF_MAX_ID_LST 10
#define TF_MAX_TYPE_LST 10
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#endif //TF_CONFIG_H
+297
View File
@@ -0,0 +1,297 @@
#include <stdio.h>
#include <string.h>
#include "../../TinyFrame.h"
#include "../utils.h"
extern const char *romeo;
/**
* This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other)
*/
void TF_WriteImpl(const uint8_t *buff, size_t len)
{
printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
// Send it back as if we received it
TF_Accept(buff, len);
}
/** An example listener function */
TF_Result myListener(TF_Msg *msg)
{
dumpFrameInfo(msg);
if (strcmp(msg->data, romeo) == 0) {
printf("FILE TRANSFERRED OK!\r\n");
} else {
printf("FAIL!!!!\r\n");
}
return TF_STAY;
}
void main(void)
{
TF_Msg msg;
// Set up the TinyFrame library
TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a LOOONG message --------\n");
TF_ClearMsg(&msg);
msg.type = 0x22;
msg.data = (pu8)romeo;
msg.len = strlen(romeo);
TF_Send(&msg);
}
const char *romeo ="THE TRAGEDY OF ROMEO AND JULIET\n"
"\n"
"by William Shakespeare\n"
"\n"
"\n"
"\n"
"Dramatis Personae\n"
"\n"
" Chorus.\n"
"\n"
"\n"
" Escalus, Prince of Verona.\n"
"\n"
" Paris, a young Count, kinsman to the Prince.\n"
"\n"
" Montague, heads of two houses at variance with each other.\n"
"\n"
" Capulet, heads of two houses at variance with each other.\n"
"\n"
" An old Man, of the Capulet family.\n"
"\n"
" Romeo, son to Montague.\n"
"\n"
" Tybalt, nephew to Lady Capulet.\n"
"\n"
" Mercutio, kinsman to the Prince and friend to Romeo.\n"
"\n"
" Benvolio, nephew to Montague, and friend to Romeo\n"
"\n"
" Tybalt, nephew to Lady Capulet.\n"
"\n"
" Friar Laurence, Franciscan.\n"
"\n"
" Friar John, Franciscan.\n"
"\n"
" Balthasar, servant to Romeo.\n"
"\n"
" Abram, servant to Montague.\n"
"\n"
" Sampson, servant to Capulet.\n"
"\n"
" Gregory, servant to Capulet.\n"
"\n"
" Peter, servant to Juliet's nurse.\n"
"\n"
" An Apothecary.\n"
"\n"
" Three Musicians.\n"
"\n"
" An Officer.\n"
"\n"
"\n"
" Lady Montague, wife to Montague.\n"
"\n"
" Lady Capulet, wife to Capulet.\n"
"\n"
" Juliet, daughter to Capulet.\n"
"\n"
" Nurse to Juliet.\n"
"\n"
"\n"
" Citizens of Verona; Gentlemen and Gentlewomen of both houses;\n"
" Maskers, Torchbearers, Pages, Guards, Watchmen, Servants, and\n"
" Attendants.\n"
"\n"
" SCENE.--Verona; Mantua.\n"
"\n"
"\n"
"\n"
" THE PROLOGUE\n"
"\n"
" Enter Chorus.\n"
"\n"
"\n"
" Chor. Two households, both alike in dignity,\n"
" In fair Verona, where we lay our scene,\n"
" From ancient grudge break to new mutiny,\n"
" Where civil blood makes civil hands unclean.\n"
" From forth the fatal loins of these two foes\n"
" A pair of star-cross'd lovers take their life;\n"
" Whose misadventur'd piteous overthrows\n"
" Doth with their death bury their parents' strife.\n"
" The fearful passage of their death-mark'd love,\n"
" And the continuance of their parents' rage,\n"
" Which, but their children's end, naught could remove,\n"
" Is now the two hours' traffic of our stage;\n"
" The which if you with patient ears attend,\n"
" What here shall miss, our toil shall strive to mend.\n"
" [Exit.]\n"
"\n"
"\n"
"\n"
"\n"
"ACT I. Scene I.\n"
"Verona. A public place.\n"
"\n"
"Enter Sampson and Gregory (with swords and bucklers) of the house\n"
"of Capulet.\n"
"\n"
"\n"
" Samp. Gregory, on my word, we'll not carry coals.\n"
"\n"
" Greg. No, for then we should be colliers.\n"
"\n"
" Samp. I mean, an we be in choler, we'll draw.\n"
"\n"
" Greg. Ay, while you live, draw your neck out of collar.\n"
"\n"
" Samp. I strike quickly, being moved.\n"
"\n"
" Greg. But thou art not quickly moved to strike.\n"
"\n"
" Samp. A dog of the house of Montague moves me.\n"
"\n"
" Greg. To move is to stir, and to be valiant is to stand.\n"
" Therefore, if thou art moved, thou runn'st away.\n"
"\n"
" Samp. A dog of that house shall move me to stand. I will take\n"
" the wall of any man or maid of Montague's.\n"
"\n"
" Greg. That shows thee a weak slave; for the weakest goes to the\n"
" wall.\n"
"\n"
" Samp. 'Tis true; and therefore women, being the weaker vessels,\n"
" are ever thrust to the wall. Therefore I will push Montague's men\n"
" from the wall and thrust his maids to the wall.\n"
"\n"
" Greg. The quarrel is between our masters and us their men.\n"
"\n"
" Samp. 'Tis all one. I will show myself a tyrant. When I have\n"
" fought with the men, I will be cruel with the maids- I will cut off\n"
" their heads.\n"
"\n"
" Greg. The heads of the maids?\n"
"\n"
" Samp. Ay, the heads of the maids, or their maidenheads.\n"
" Take it in what sense thou wilt.\n"
"\n"
" Greg. They must take it in sense that feel it.\n"
"\n"
" Samp. Me they shall feel while I am able to stand; and 'tis known I\n"
" am a pretty piece of flesh.\n"
"\n"
" Greg. 'Tis well thou art not fish; if thou hadst, thou hadst\n"
" been poor-John. Draw thy tool! Here comes two of the house of\n"
" Montagues.\n"
"\n"
" Enter two other Servingmen [Abram and Balthasar].\n"
"\n"
"\n"
" Samp. My naked weapon is out. Quarrel! I will back thee.\n"
"\n"
" Greg. How? turn thy back and run?\n"
"\n"
" Samp. Fear me not.\n"
"\n"
" Greg. No, marry. I fear thee!\n"
"\n"
" Samp. Let us take the law of our sides; let them begin.\n"
"\n"
" Greg. I will frown as I pass by, and let them take it as they list.\n"
"\n"
" Samp. Nay, as they dare. I will bite my thumb at them; which is\n"
" disgrace to them, if they bear it.\n"
"\n"
" Abr. Do you bite your thumb at us, sir?\n"
"\n"
" Samp. I do bite my thumb, sir.\n"
"\n"
" Abr. Do you bite your thumb at us, sir?\n"
"\n"
" Samp. [aside to Gregory] Is the law of our side if I say ay?\n"
"\n"
" Greg. [aside to Sampson] No.\n"
"\n"
" Samp. No, sir, I do not bite my thumb at you, sir; but I bite my\n"
" thumb, sir.\n"
"\n"
" Greg. Do you quarrel, sir?\n"
"\n"
" Abr. Quarrel, sir? No, sir.\n"
"\n"
" Samp. But if you do, sir, am for you. I serve as good a man as\n"
" you.\n"
"\n"
" Abr. No better.\n"
"\n"
" Samp. Well, sir.\n"
"\n"
" Enter Benvolio.\n"
"\n"
"\n"
" Greg. [aside to Sampson] Say 'better.' Here comes one of my\n"
" master's kinsmen.\n"
"\n"
" Samp. Yes, better, sir.\n"
"\n"
" Abr. You lie.\n"
"\n"
" Samp. Draw, if you be men. Gregory, remember thy swashing blow.\n"
" They fight.\n"
"\n"
" Ben. Part, fools! [Beats down their swords.]\n"
" Put up your swords. You know not what you do.\n"
"\n"
" Enter Tybalt.\n"
"\n"
"\n"
" Tyb. What, art thou drawn among these heartless hinds?\n"
" Turn thee Benvolio! look upon thy death.\n"
"\n"
" Ben. I do but keep the peace. Put up thy sword,\n"
" Or manage it to part these men with me.\n"
"\n"
" Tyb. What, drawn, and talk of peace? I hate the word\n"
" As I hate hell, all Montagues, and thee.\n"
" Have at thee, coward! They fight.\n"
"\n"
" Enter an officer, and three or four Citizens with clubs or\n"
" partisans.\n"
"\n"
"\n"
" Officer. Clubs, bills, and partisans! Strike! beat them down!\n"
"\n"
" Citizens. Down with the Capulets! Down with the Montagues!\n"
"\n"
" Enter Old Capulet in his gown, and his Wife.\n"
"\n"
"\n"
" Cap. What noise is this? Give me my long sword, ho!\n"
"\n"
" Wife. A crutch, a crutch! Why call you for a sword?\n"
"\n"
" Cap. My sword, I say! Old Montague is come\n"
" And flourishes his blade in spite of me.\n"
"\n"
" Enter Old Montague and his Wife.\n"
"\n"
"\n"
" Mon. Thou villain Capulet!- Hold me not, let me go.\n"
"\n"
" M. Wife. Thou shalt not stir one foot to seek a foe.\n"
"\n"
" Enter Prince Escalus, with his Train.\n"
"\n"
"\n"
"END OF FILE\n";
+11
View File
@@ -0,0 +1,11 @@
CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS)
run: test.bin
./test.bin
build: test.bin
test.bin: test.c $(CFILES)
gcc test.c $(CFLAGS) -o test.bin
+25
View File
@@ -0,0 +1,25 @@
//
// Created by MightyPork on 2017/10/15.
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
#define TF_CKSUM_TYPE TF_CKSUM_CRC16
#define TF_USE_SOF_BYTE 1
#define TF_SOF_BYTE 0x01
typedef uint16_t TF_TICKS;
typedef uint8_t TF_COUNT;
#define TF_MAX_PAYLOAD_RX 1024
#define TF_SENDBUF_LEN 1024
#define TF_MAX_ID_LST 10
#define TF_MAX_TYPE_LST 10
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#endif //TF_CONFIG_H
+65
View File
@@ -0,0 +1,65 @@
#include <stdio.h>
#include <string.h>
#include "../../TinyFrame.h"
#include "../utils.h"
/**
* This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other)
*/
void TF_WriteImpl(const uint8_t *buff, size_t len)
{
printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
// Send it back as if we received it
TF_Accept(buff, len);
}
/** An example listener function */
TF_Result myListener(TF_Msg *msg)
{
dumpFrameInfo(msg);
return TF_STAY;
}
TF_Result testIdListener(TF_Msg *msg)
{
printf("OK - ID Listener triggered for msg!\n");
dumpFrameInfo(msg);
return TF_CLOSE;
}
void main(void)
{
TF_Msg msg;
const char *longstr = "Lorem ipsum dolor sit amet.";
// Set up the TinyFrame library
TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a message --------\n");
TF_ClearMsg(&msg);
msg.type = 0x22;
msg.data = (pu8)"Hello TinyFrame";
msg.len = 16;
TF_Send(&msg);
msg.type = 0x33;
msg.data = (pu8)longstr;
msg.len = (TF_LEN) (strlen(longstr)+1); // add the null byte
TF_Send(&msg);
msg.type = 0x44;
msg.data = (pu8)"Hello2";
msg.len = 7;
TF_Send(&msg);
msg.len = 0;
msg.type = 0x77;
TF_Query(&msg, testIdListener, 0);
}
+17
View File
@@ -0,0 +1,17 @@
CFILES=../demo.c ../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS)
build: master.bin slave.bin
master: master.bin
./master.bin
slave: slave.bin
./slave.bin
master.bin: master.c $(CFILES)
gcc master.c $(CFLAGS) -o master.bin
slave.bin: slave.c $(CFILES)
gcc slave.c $(CFLAGS) -o slave.bin
+25
View File
@@ -0,0 +1,25 @@
//
// Created by MightyPork on 2017/10/15.
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
#define TF_CKSUM_TYPE TF_CKSUM_CRC16
#define TF_USE_SOF_BYTE 1
#define TF_SOF_BYTE 0x01
typedef uint16_t TF_TICKS;
typedef uint8_t TF_COUNT;
#define TF_MAX_PAYLOAD_RX 1024
#define TF_SENDBUF_LEN 1024
#define TF_MAX_ID_LST 10
#define TF_MAX_TYPE_LST 10
#define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10
#endif //TF_CONFIG_H
+35
View File
@@ -0,0 +1,35 @@
//
// Created by MightyPork on 2017/10/15.
//
#include <stdio.h>
#include "../demo.h"
TF_Result testIdListener(TF_Msg *msg)
{
printf("testIdListener()\n");
dumpFrameInfo(msg);
return TF_CLOSE;
}
TF_Result testGenericListener(TF_Msg *msg)
{
printf("testGenericListener()\n");
dumpFrameInfo(msg);
return TF_STAY;
}
int main(void)
{
TF_Init(TF_MASTER);
TF_AddGenericListener(testGenericListener);
demo_init(TF_MASTER);
TF_SendSimple(1, (pu8)"Ahoj", 5);
TF_SendSimple(1, (pu8)"Hello", 6);
TF_QuerySimple(2, (pu8)"Query!", 6, testIdListener, 0, NULL);
demo_sleep();
}
+42
View File
@@ -0,0 +1,42 @@
//
// Created by MightyPork on 2017/10/15.
//
#include <stdio.h>
#include "../demo.h"
#include <memory.h>
TF_Result helloListener(TF_Msg *msg)
{
printf("helloListener()\n");
dumpFrameInfo(msg);
return TF_STAY;
}
TF_Result replyListener(TF_Msg *msg)
{
printf("replyListener()\n");
dumpFrameInfo(msg);
msg->data = (const uint8_t *) "response to query";
msg->len = (TF_LEN) strlen((const char *) msg->data);
TF_Respond(msg);
// 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);
// unrelated message
TF_SendSimple(77, (const uint8_t *) "NAZDAR", 7);
return TF_STAY;
}
int main(void)
{
TF_Init(TF_SLAVE);
TF_AddTypeListener(1, helloListener);
TF_AddTypeListener(2, replyListener);
demo_init(TF_SLAVE);
demo_sleep();
}
+32
View File
@@ -0,0 +1,32 @@
//
// Created by MightyPork on 2017/10/15.
//
#include "utils.h"
#include <stdio.h>
// helper func for testing
void dumpFrame(const uint8_t *buff, size_t len)
{
size_t i;
for(i = 0; i < len; i++) {
printf("%3u \033[94m%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\n");
}
void dumpFrameInfo(TF_Msg *msg)
{
printf("\033[33mFrame info\n"
" type: %02Xh\n"
" data: \"%.*s\"\n"
" len: %u\n"
" id: %Xh\033[0m\n\n",
msg->type, msg->len, msg->data, msg->len, msg->frame_id);
}
+26
View File
@@ -0,0 +1,26 @@
//
// Created by MightyPork on 2017/10/15.
//
#ifndef TF_UTILS_H
#define TF_UTILS_H
#include <stdio.h>
#include "../TinyFrame.h"
/** pointer to unsigned char */
typedef unsigned char* pu8;
/**
* Dump a binary frame as hex, dec and ASCII
*/
void dumpFrame(const uint8_t *buff, size_t len);
/**
* Dump message metadata (not the content)
*
* @param msg
*/
void dumpFrameInfo(TF_Msg *msg);
#endif //TF_UTILS_H
-77
View File
@@ -1,77 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "TinyFrame.h"
static void dumpFrame(const uint8_t *buff, TF_LEN 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 uint8_t *buff, TF_LEN len)
{
printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
// Send it back as if we received it
TF_Accept(buff, len);
}
/** An example listener function */
bool myListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{
printf("\033[33mRX frame\n"
" type: %02Xh\n"
" data: \"%.*s\"\n"
" len: %u\n"
" id: %Xh\033[0m\n", type, len, buff, len, frame_id);
return true;
}
bool testIdListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len)
{
printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id);
return true;
}
void main(void)
{
// Set up the TinyFrame library
TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(myListener);
printf("------ Simulate sending a message --------\n");
TF_Send(0x22, (unsigned char*)"Hello TinyFrame", 16, NULL, NULL);
const char *longstr = "Lorem ipsum dolor sit amet.";
TF_Send(0x33, (unsigned char*)longstr, (TF_LEN)(strlen(longstr)+1), NULL, NULL);
TF_Send(0x44, (unsigned char*)"Hello2", 7, NULL, NULL);
TF_Send0(0xF0, NULL, NULL);
TF_Send1(0xF1, 'Q', NULL, NULL);
TF_Send2(0xF2, 'A', 'Z', NULL, NULL);
TF_Send0(0x77, testIdListener, NULL);
}
// 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");
}