38 Commits
Author SHA1 Message Date
MightyPork 88d6a5cd23 v bump 2017-12-27 17:15:43 +01:00
MightyPork c4b169cb80 fix call to undefined function if SOF byte is not used 2017-12-27 17:15:29 +01:00
MightyPorkandGitHub 03377b4b9f Update README.md 2017-12-27 00:37:52 +01:00
MightyPork 1a62d78443 fix wrongly disallowed rx of max possible size 2017-12-23 22:55:46 +01:00
MightyPork 1488abb517 bumped version 2017-12-23 16:24:33 +01:00
MightyPork 1f52a17fc2 Fix empty payload checksum being appended even if payload is empty. 2017-12-23 16:23:59 +01:00
MightyPork 7f6f21cdb7 fixed some indentation 2017-12-23 12:25:48 +01:00
MightyPork 38952f8405 version bump 2017-12-23 09:57:07 +01:00
MightyPork 68ea3bb3a4 Fix the ID listener removal bug 2017-12-23 09:56:33 +01:00
MightyPork ce06abfdbf fix for a compile bug & added TF_DeInit() 2017-12-14 11:40:22 +01:00
MightyPorkandGitHub db0ae30f41 Add re-entrancy support / multiple instances (#9)
* done with changes but now segfaulting

* fixed the bug

* fix some bad indents

* some cleanings

* updated readme
2017-12-14 00:28:06 +01:00
MightyPork 77f60e1876 removed no longer used TF_OVERHEAD_BYTES 2017-12-12 21:09:07 +01:00
MightyPork 978eabfb92 Added utilities 2017-12-12 20:54:47 +01:00
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
MightyPork 4f5c4d69f7 readme fix 2017-04-16 16:54:09 +02:00
MightyPork 61c82b614b Updated & heavily improved 2017-04-16 16:51:53 +02:00
MightyPork c83cdf36e2 beta code, needs testing. Adjustable field sizes, crc16 option, dedicated Type field 2017-04-16 01:55:09 +02:00
27 changed files with 2760 additions and 539 deletions
+7
View File
@@ -27,7 +27,14 @@
*.i*86
*.x86_64
*.hex
*.bin
# Debug files
*.dSYM/
*.su
cmake-build-debug/
tf.bin
.idea/
+25
View File
@@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.7)
project(tf)
set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES
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.h
TF_Config.example.h
demo/utils.c
demo/utils.h
)
include_directories(demo/simple)
add_executable(tf ${SOURCE_FILES})
+78 -36
View File
@@ -1,56 +1,98 @@
# TinyFrame
TinyFrame is a simple library for building and parsing frames
(packets) to be sent over a serial interface (like UART). It's implemented
to be compatible with C89 and platform agnostic.
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
to build with `--std=gnu89` and later.
Frames are protected by a checksum and contain a "unique" ID,
which can be used for chaining messages. Each peer uses a different
value for the first bit of all IDs it generates (the "master flag"
or "peer_bit") to ensure there are no clashes. Typically the master
(PC, main microcontroller) will use "1" and the surrogate (WiFi module,
USB-serial connected gadget, display driver...) uses "0".
TinyFrame is suitable for a wide range of applications, including inter-microcontroller
communication, as a protocol for FTDI-based PC applications or for messaging through
UDP packets. If you find a good use for it, please let me know so I can add it here!
The library lets you bind listeners waiting for any frame, or a
particular ID. This allows for easy implementation of async communication.
Frames can be protected by a checksum (~XOR, CRC16 or CRC32) and contain
a unique ID field which can be used for chaining related messages. The highest bit
of the generated IDs is different 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 register listeners (callback functions) to wait for (1) any frame, (2)
a particular frame Type, or (3) a specific message ID. This high-level API lets you
easily implement various async communication patterns.
TinyFrame is re-entrant and supports creating multiple instances with the limitation
that their structure (field sizes and checksum type) must be the same. There is a support
for adding multi-threaded access to a shared instance using a mutex (via a callback stub).
## Ports
A partial port to Python can be found at [MightyPork/PonyFrame](https://github.com/MightyPork/PonyFrame)
## Frame structure
The frame makeup is inspired by that of SBMP (my other, more complicated
and advanced UART protocol library).
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><NOB><PAYLOAD><CKSUM>
,-----+----+-----+------+------------+- - - -+------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM |
| 1 | ? | ? | ? | ? | ... | ? | <- size (bytes)
'-----+----+-----+------+------------+- - - -+------------'
SOF ... start of frame, 0x01
ID ... (master_flag | 7-bit counter) - the frame ID
NOB ... nr of payload bytes in the frame (1..256)
PAYLOAD ... NOB bytes of data, can contain any byte values 1..256
CKSUM ... checksum, implemented as XOR of all preceding bytes in the message
SOF ......... start of frame, 0x01
ID ......... the frame ID (MSb is the peer bit)
LEN ......... nr of data bytes in the frame
TYPE ........ message type (used to run Type Listeners, pick any values you like)
HEAD_CKSUM .. header checksum
DATA ........ LEN bytes of data
DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message
```
The frame ID (in SBMP called "session ID") can be used to chain multiple related
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
- All TinyFrame functions, typedefs and macros start with the `TF_` prefix.
- 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. This creates a handle.
Use `TF_InitStatic()` to avoid the use of malloc(). If multiple instances are used, you can tag them
using the `tf.userdata` / `tf.usertag` field.
- Implement `TF_WriteImpl()` - declared at the bottom of the header file as `extern`.
This function is used by `TF_Send()` and others to write bytes to your UART (or other physical layer).
A frame can be sent in it's entirety, or in multiple parts, depending on its size.
- If you wish to use timeouts, periodically call `TF_Tick()`. The calling period determines
the length of 1 tick. This is used to time-out the parser in case it gets stuck
in a bad state (such as receiving a partial frame) and can also time-out ID listeners.
- Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`.
- Send a message using `TF_Send()`, `TF_Query()`, `TF_SendSimple()`, `TF_QuerySimple()`.
Query functions take a listener callback (function pointer)that will be added as
an ID listener and wait for a response.
- To reply to a message (when your listener gets called), use `TF_Respond()`
with the msg boject you received, replacing the `data` pointer (and `len`) with response.
- Manually reset the parser using `TF_ResetParser()`
## Usage hints
### Message listeners
- 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
Listeners are callback functions that are called by TinyFrame when a message which
they can handle is received.
`bool myListener(unsigned int frame_id, const unsigned char *buff, unsigned int len) { ... }`
There are 3 listener types:
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.
- ID listeners
- Type listeners
- Generic listeners
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.
Listeners return an enum constant based on what should be done next - remove the listener,
keep it, renew it's timeout, or let some other listener handle the message.
- 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.
### Examples
- The function `TF_Accept()` is used to handle received chars. Call this in your UART Rx interrupt handler or a similar place.
You'll find various examples in the `demo/` folder. Each example has it's own Makefile,
read it to see what options are available.
The demos are written for Linux, some using sockets and `clone()` for background processing.
They try to simulate real TinyFrame behavior in an embedded system with asynchronous
Rx and Tx. If you can't run the demos, the source files are still good as examples.
+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(TinyFrame *tf, const uint8_t *buff, size_t len)
{
// send to UART
}
/** Claim the TX interface before composing and sending a frame */
void TF_ClaimTx(TinyFrame *tf)
{
// take mutex
}
/** Free the TX interface after composing and sending a frame */
void TF_ReleaseTx(TinyFrame *tf)
{
// release mutex
}
+791 -254
View File
File diff suppressed because it is too large Load Diff
+327 -110
View File
@@ -1,167 +1,384 @@
#ifndef TinyFrameH
#define TinyFrameH
//---------------------------------------------------------------------------
#include <stdint.h>
#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
#define TF_NEXT_ID -1
#define TF_ERROR -1
/**
* TinyFrame receive callback type.
* @param frame_id - ID of the received frame
* @param buff - byte buffer with the payload
* @param len - number of bytes in the buffer
* @return true if the frame was consumed
* 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
*/
typedef bool (*TinyFrameListener)(unsigned int frame_id,
const unsigned char *buff,
unsigned int len);
#define TF_VERSION "2.0.5"
//---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc
#include <stdbool.h> // for bool
#include <stddef.h> // for NULL
#include <string.h> // for memset()
//---------------------------------------------------------------------------
// Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32)
#define TF_CKSUM_NONE 0
#define TF_CKSUM_XOR 8
#define TF_CKSUM_CRC16 16
#define TF_CKSUM_CRC32 32
#include "TF_Config.h"
//region Resolve data types
#if TF_LEN_BYTES == 1
typedef uint8_t TF_LEN;
#elif TF_LEN_BYTES == 2
typedef uint16_t TF_LEN;
#elif TF_LEN_BYTES == 4
typedef uint32_t TF_LEN;
#else
#error Bad value of TF_LEN_BYTES, must be 1, 2 or 4
#endif
#if TF_TYPE_BYTES == 1
typedef uint8_t TF_TYPE;
#elif TF_TYPE_BYTES == 2
typedef uint16_t TF_TYPE;
#elif TF_TYPE_BYTES == 4
typedef uint32_t TF_TYPE;
#else
#error Bad value of TF_TYPE_BYTES, must be 1, 2 or 4
#endif
#if TF_ID_BYTES == 1
typedef uint8_t TF_ID;
#elif TF_ID_BYTES == 2
typedef uint16_t TF_ID;
#elif TF_ID_BYTES == 4
typedef uint32_t TF_ID;
#else
#error Bad value of TF_ID_BYTES, must be 1, 2 or 4
#endif
#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)
typedef uint8_t TF_CKSUM;
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC16
// CRC16
typedef uint16_t TF_CKSUM;
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC32
// CRC32
typedef uint32_t TF_CKSUM;
#else
#error Bad value for TF_CKSUM_TYPE, must be 8, 16 or 32
#endif
//endregion
//---------------------------------------------------------------------------
// 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_PEERBIT (TF_ID)((TF_ID)1 << ((sizeof(TF_ID)*8) - 1))
//---------------------------------------------------------------------------
/** Peer bit enum (used for init) */
typedef enum {
TF_SLAVE = 0,
TF_MASTER = 1,
} TF_Peer;
/** Response from listeners */
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)
{
memset(msg, 0, sizeof(TF_Msg));
}
typedef struct TinyFrame_ TinyFrame;
/**
* TinyFrame Type Listener callback
*
* @param frame_id - ID of the received frame
* @param type - type field from the message
* @param data - byte buffer with the application data
* @param len - number of bytes in the buffer
* @return listener result
*/
typedef TF_Result (*TF_Listener)(TinyFrame *tf, TF_Msg *msg);
// -------------------------------------------------------------------
// region Internal
enum TFState_ {
TFState_SOF = 0, //!< Wait for SOF
TFState_LEN, //!< Wait for Number Of Bytes
TFState_HEAD_CKSUM, //!< Wait for header Checksum
TFState_ID, //!< Wait for ID
TFState_TYPE, //!< Wait for message type
TFState_DATA, //!< Receive payload
TFState_DATA_CKSUM //!< Wait for Checksum
};
struct TF_IdListener_ {
TF_ID id;
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;
};
struct TF_TypeListener_ {
TF_TYPE type;
TF_Listener fn;
};
struct TF_GenericListener_ {
TF_Listener fn;
};
/**
* Frame parser internal state.
*/
struct TinyFrame_ {
/* Public user data */
void *userdata;
uint32_t usertag;
// --- the rest of the struct is internal, do not access directly ---
/* Own state */
TF_Peer peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash)
TF_ID next_id; //!< Next frame / frame chain ID
/* Parser state */
enum TFState_ state;
TF_TICKS parser_timeout_ticks;
TF_ID id; //!< Incoming packet ID
TF_LEN len; //!< Payload length
uint8_t data[TF_MAX_PAYLOAD_RX]; //!< Data byte buffer
TF_LEN rxi; //!< Field size byte counter
TF_CKSUM cksum; //!< Checksum calculated of the data stream
TF_CKSUM ref_cksum; //!< Reference checksum read from the message
TF_TYPE type; //!< Collected message type number
bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data.
/* --- Callbacks --- */
/* Transaction callbacks */
struct TF_IdListener_ id_listeners[TF_MAX_ID_LST];
struct TF_TypeListener_ type_listeners[TF_MAX_TYPE_LST];
struct TF_GenericListener_ generic_listeners[TF_MAX_GEN_LST];
// Those counters are used to optimize look-up times.
// They point to the highest used slot number,
// 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
uint8_t sendbuf[TF_SENDBUF_LEN];
};
// endregion
// -------------------------------------------------------------------
/**
* 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).
*
* The field .userdata (or .usertag) can be used to identify different instances
* in the TF_WriteImpl() function etc. Set this field after the init.
*
* This function is a wrapper around TF_InitStatic that calls malloc() to obtain
* the instance.
*
* @param peer_bit - peer bit to use for self
*/
void TF_Init(bool peer_bit);
TinyFrame *TF_Init(TF_Peer peer_bit);
/**
* Initialize the TinyFrame engine using a statically allocated instance struct.
*
* The .userdata / .usertag field is preserved when TF_InitStatic is called.
*
* @param peer_bit - peer bit to use for self
*/
void TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit);
/**
* De-init the dynamically allocated TF instance
*
* @param tf
*/
void TF_DeInit(TinyFrame *tf);
/**
* Reset the frame parser state machine.
* This does not affect registered listeners.
*/
void TF_ResetParser(void);
/**
* Accept incoming bytes & parse frames
* @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer
*/
void TF_Accept(const unsigned char *buffer, unsigned int count);
/**
* Accept a single incoming byte
* @param c - a received char
*/
void TF_AcceptChar(unsigned char c);
void TF_ResetParser(TinyFrame *tf);
/**
* Register a frame type listener.
* @param frame_type - frame ID to listen for
*
* @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)
*/
int TF_AddIdListener(unsigned int frame_id, TinyFrameListener cb);
bool TF_AddIdListener(TinyFrame *tf, 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(TinyFrame *tf, 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)
*/
int TF_AddTypeListener(unsigned char frame_type, TinyFrameListener cb);
/**
* Register a generic listener.
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
int TF_AddGenericListener(TinyFrameListener cb);
/**
* Remove any listener by the index received when registering it
* @param index - index in the callbacks table
*/
void TF_RemoveListener(unsigned int index);
/**
* Remove a listener by the message ID it's registered for
* @param frame_id - the frame we're listening for
*/
void TF_RemoveIdListener(unsigned int frame_id);
bool TF_AddTypeListener(TinyFrame *tf, TF_TYPE frame_type, TF_Listener cb);
/**
* Remove a listener by type.
*
* @param type - the type it's registered for
*/
void TF_RemoveTypeListener(unsigned char type);
bool TF_RemoveTypeListener(TinyFrame *tf, TF_TYPE type);
/**
* Remove a callback by the function pointer
* Register a generic listener.
*
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
bool TF_AddGenericListener(TinyFrame *tf, TF_Listener cb);
/**
* Remove a generic listener by function pointer
*
* @param cb - callback function to remove
*/
void TF_RemoveListenerFn(TinyFrameListener cb); // search all listener types
bool TF_RemoveGenericListener(TinyFrame *tf, TF_Listener cb);
/**
* Compose a frame
* Send a frame, no listener
*
* @param outbuff - buffer to store the result in
* @param msgid - message ID is stored here, if not NULL
* @param payload - data buffer
* @param len - payload size in bytes, 0 to use strlen
* @param explicit_id - ID to use, TF_NEXT_ID (-1) to chose next free
* @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure
* @param msg - message struct. ID is stored in the frame_id field
* @return success
*/
int TF_Compose(unsigned char *outbuff, unsigned int *msgid,
const unsigned char *payload, unsigned int payload_len,
int explicit_id);
bool TF_Send(TinyFrame *tf, TF_Msg *msg);
/**
* Send a frame, and optionally attach an ID listener for response.
* Like TF_Send, but without the struct
*/
bool TF_SendSimple(TinyFrame *tf, TF_TYPE type, const uint8_t *data, TF_LEN len);
/**
* Send a frame, and optionally attach an ID listener.
*
* @param payload - data to send
* @param payload_len - nr of bytes to send
* @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 listener ID if listener was attached, else -1 (FT_ERROR).
* If the listener arg was NULL, returned -1 does not indicate an error.
* @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
*/
int TF_Send(const unsigned char *payload,
unsigned int payload_len,
TinyFrameListener listener, unsigned int *id_ptr);
bool TF_Query(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout);
/**
* A shorthand for TF_Send() with just one byte as the payload
* Like TF_Query, but without the struct
*/
int TF_Send1(const unsigned char b0,
TinyFrameListener listener, unsigned int *id_ptr);
/**
* A shorthand for TF_Send() with just 2 bytes as the payload
*/
int TF_Send2(const unsigned char b0,
const unsigned char b1,
TinyFrameListener listener, unsigned int *id_ptr);
bool TF_QuerySimple(TinyFrame *tf, TF_TYPE type, const uint8_t *data, TF_LEN len,
TF_Listener listener, TF_TICKS timeout);
/**
* 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
* @param msg - message struct. ID is read from frame_id. set ->renew to reset listener timeout
* @return success
*/
void TF_Respond(const unsigned char *payload,
unsigned int payload_len,
unsigned int frame_id);
bool TF_Respond(TinyFrame *tf, TF_Msg *msg);
/**
* Write implementation for TF, to be provided by user code.
* This sends frames composed by TinyFrame to UART
* Renew an ID listener timeout externally (as opposed to by returning TF_RENEW from the ID listener)
*
* @param id - listener ID to renew
* @return true if listener was found and renewed
*/
extern void TF_WriteImpl(const unsigned char *buff, unsigned int len);
bool TF_RenewIdListener(TinyFrame *tf, TF_ID id);
/**
* Accept incoming bytes & parse frames
*
* @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer
*/
void TF_Accept(TinyFrame *tf, const uint8_t *buffer, size_t count);
/**
* Accept a single incoming byte
*
* @param c - a received char
*/
void TF_AcceptChar(TinyFrame *tf, uint8_t c);
/**
* This function should be called periodically.
*
* The time base is used to time-out partial frames in the parser and
* automatically reset it.
*
* (suggestion - call this in a SysTick handler)
*/
void TF_Tick(TinyFrame *tf);
// --- TO BE IMPLEMENTED BY USER ---
/**
* 'Write bytes' function that sends data to UART
*
* ! Implement this in your application code !
*/
extern void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len);
/** Claim the TX interface before composing and sending a frame */
extern void TF_ClaimTx(TinyFrame *tf);
/** Free the TX interface after composing and sending a frame */
extern void TF_ReleaseTx(TinyFrame *tf);
#endif
+226
View File
@@ -0,0 +1,226 @@
//
// 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>
#include <malloc.h>
#include <stdlib.h>
volatile int sockfd = -1;
volatile bool conn_disband = false;
TinyFrame *demo_tf;
/**
* 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(TinyFrame *tf, 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(demo_tf, 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(demo_tf, 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");
}
+25
View File
@@ -0,0 +1,25 @@
//
// 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
extern TinyFrame *demo_tf;
/** 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
+12
View File
@@ -0,0 +1,12 @@
CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wno-unused -Wextra $(CFILES) $(INCLDIRS)
build: test.bin
run: test.bin
./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
+300
View File
@@ -0,0 +1,300 @@
#include <stdio.h>
#include <string.h>
#include "../../TinyFrame.h"
#include "../utils.h"
TinyFrame *demo_tf;
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(TinyFrame *tf, 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(tf, buff, len);
}
/** An example listener function */
TF_Result myListener(TinyFrame *tf, TF_Msg *msg)
{
dumpFrameInfo(msg);
if (strcmp((const char *) 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
demo_tf = TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(demo_tf, myListener);
printf("------ Simulate sending a LOOONG message --------\n");
TF_ClearMsg(&msg);
msg.type = 0x22;
msg.data = (pu8) romeo;
msg.len = (TF_LEN) strlen(romeo);
TF_Send(demo_tf, &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 -Wno-unused -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
+66
View File
@@ -0,0 +1,66 @@
#include <stdio.h>
#include <string.h>
#include "../../TinyFrame.h"
#include "../utils.h"
TinyFrame *demo_tf;
/**
* This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other)
*/
void TF_WriteImpl(TinyFrame *tf, 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(tf, buff, len);
}
/** An example listener function */
TF_Result myListener(TinyFrame *tf, TF_Msg *msg)
{
dumpFrameInfo(msg);
return TF_STAY;
}
TF_Result testIdListener(TinyFrame *tf, 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
demo_tf = TF_Init(TF_MASTER); // 1 = master, 0 = slave
TF_AddGenericListener(demo_tf, myListener);
printf("------ Simulate sending a message --------\n");
TF_ClearMsg(&msg);
msg.type = 0x22;
msg.data = (pu8) "Hello TinyFrame";
msg.len = 16;
TF_Send(demo_tf, &msg);
msg.type = 0x33;
msg.data = (pu8) longstr;
msg.len = (TF_LEN) (strlen(longstr) + 1); // add the null byte
TF_Send(demo_tf, &msg);
msg.type = 0x44;
msg.data = (pu8) "Hello2";
msg.len = 7;
TF_Send(demo_tf, &msg);
msg.len = 0;
msg.type = 0x77;
TF_Query(demo_tf, &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 -Wno-unused -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(TinyFrame *tf, TF_Msg *msg)
{
printf("testIdListener()\n");
dumpFrameInfo(msg);
return TF_CLOSE;
}
TF_Result testGenericListener(TinyFrame *tf, TF_Msg *msg)
{
printf("testGenericListener()\n");
dumpFrameInfo(msg);
return TF_STAY;
}
int main(void)
{
demo_tf = TF_Init(TF_MASTER);
TF_AddGenericListener(demo_tf, testGenericListener);
demo_init(TF_MASTER);
TF_SendSimple(demo_tf, 1, (pu8) "Ahoj", 5);
TF_SendSimple(demo_tf, 1, (pu8) "Hello", 6);
TF_QuerySimple(demo_tf, 2, (pu8) "Query!", 6, testIdListener, 0);
demo_sleep();
}
+41
View File
@@ -0,0 +1,41 @@
//
// Created by MightyPork on 2017/10/15.
//
#include <stdio.h>
#include "../demo.h"
TF_Result helloListener(TinyFrame *tf, TF_Msg *msg)
{
printf("helloListener()\n");
dumpFrameInfo(msg);
return TF_STAY;
}
TF_Result replyListener(TinyFrame *tf, 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(tf, 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(tf, msg);
// unrelated message
TF_SendSimple(tf, 77, (const uint8_t *) "NAZDAR", 7);
return TF_STAY;
}
int main(void)
{
demo_tf = TF_Init(TF_SLAVE);
TF_AddTypeListener(demo_tf, 1, helloListener);
TF_AddTypeListener(demo_tf, 2, replyListener);
demo_init(TF_SLAVE);
demo_sleep();
}
+33
View File
@@ -0,0 +1,33 @@
//
// 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
-82
View File
@@ -1,82 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include "TinyFrame.h"
// 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);
}
/** 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);
return false; // Do not unbind
}
void main()
{
int i;
int msgid;
int len;
char buff[100];
// Set up the TinyFrame library
TF_Init(1); // 1 = master, 0 = slave
printf("------ Simulate sending a message --------\n");
// Send a message
// args - payload, length (0 = strlen), listener, id_ptr (for storing the frame ID)
// (see the .h file for details)
TF_Send("Hello TinyFrame", 0, NULL, 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);
}
+100
View File
@@ -0,0 +1,100 @@
#include <string.h>
#include "payload_builder.h"
#define pb_check_capacity(pb, needed) \
if ((pb)->current + (needed) > (pb)->end) { \
if ((pb)->full_handler == NULL || !(pb)->full_handler(pb, needed)) (pb)->ok = 0; \
}
/** Write from a buffer */
bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len)
{
pb_check_capacity(pb, len);
if (!pb->ok) return false;
memcpy(pb->current, buf, len);
pb->current += len;
return true;
}
/** Write s zero terminated string */
bool pb_string(PayloadBuilder *pb, const char *str)
{
uint32_t len = (uint32_t) strlen(str);
pb_check_capacity(pb, len+1);
if (!pb->ok) return false;
memcpy(pb->current, str, len+1);
pb->current += len+1;
return true;
}
/** Write uint8_t to the buffer */
bool pb_u8(PayloadBuilder *pb, uint8_t byte)
{
pb_check_capacity(pb, 1);
if (!pb->ok) return false;
*pb->current++ = byte;
return true;
}
/** Write uint16_t to the buffer. */
bool pb_u16(PayloadBuilder *pb, uint16_t word)
{
pb_check_capacity(pb, 2);
if (!pb->ok) return false;
if (pb->bigendian) {
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) (word & 0xFF);
} else {
*pb->current++ = (uint8_t) (word & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
}
return true;
}
/** Write uint32_t to the buffer. */
bool pb_u32(PayloadBuilder *pb, uint32_t word)
{
pb_check_capacity(pb, 4);
if (!pb->ok) return false;
if (pb->bigendian) {
*pb->current++ = (uint8_t) ((word >> 24) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 16) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) (word & 0xFF);
} else {
*pb->current++ = (uint8_t) (word & 0xFF);
*pb->current++ = (uint8_t) ((word >> 8) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 16) & 0xFF);
*pb->current++ = (uint8_t) ((word >> 24) & 0xFF);
}
return true;
}
/** Write int8_t to the buffer. */
bool pb_i8(PayloadBuilder *pb, int8_t byte)
{
return pb_u8(pb, ((union conv8){.i8 = byte}).u8);
}
/** Write int16_t to the buffer. */
bool pb_i16(PayloadBuilder *pb, int16_t word)
{
return pb_u16(pb, ((union conv16){.i16 = word}).u16);
}
/** Write int32_t to the buffer. */
bool pb_i32(PayloadBuilder *pb, int32_t word)
{
return pb_u32(pb, ((union conv32){.i32 = word}).u32);
}
/** Write 4-byte float to the buffer. */
bool pb_float(PayloadBuilder *pb, float f)
{
return pb_u32(pb, ((union conv32){.f32 = f}).u32);
}
+110
View File
@@ -0,0 +1,110 @@
#ifndef PAYLOAD_BUILDER_H
#define PAYLOAD_BUILDER_H
/**
* PayloadBuilder, part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2014-2017. MIT license.
*
* The builder supports big and little endian which is selected when
* initializing it or by accessing the bigendian struct field.
*
* This module helps you with building payloads (not only for TinyFrame)
*
* The builder performs bounds checking and calls the provided handler when
* the requested write wouldn't fit. Use the handler to realloc / flush the buffer
* or report an error.
*/
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include "type_coerce.h"
typedef struct PayloadBuilder_ PayloadBuilder;
/**
* Full buffer handler.
*
* 'needed' more bytes should be written but the end of the buffer was reached.
*
* Return true if the problem was solved (e.g. buffer was flushed and the
* 'current' pointer moved to the beginning).
*
* If false is returned, the 'ok' flag on the struct is set to false
* and all following writes are discarded.
*/
typedef bool (*pb_full_handler)(PayloadBuilder *pb, uint32_t needed);
struct PayloadBuilder_ {
uint8_t *start; //!< Pointer to the beginning of the buffer
uint8_t *current; //!< Pointer to the next byte to be read
uint8_t *end; //!< Pointer to the end of the buffer (start + length)
pb_full_handler full_handler; //!< Callback for buffer overrun
bool bigendian; //!< Flag to use big-endian parsing
bool ok; //!< Indicates that all reads were successful
};
// --- initializer helper macros ---
/** Start the builder. */
#define pb_start_e(buf, capacity, bigendian, full_handler) ((PayloadBuilder){buf, buf, (buf)+(capacity), full_handler, bigendian, 1})
/** Start the builder in big-endian mode */
#define pb_start_be(buf, capacity, full_handler) pb_start_e(buf, capacity, 1, full_handler)
/** Start the builder in little-endian mode */
#define pb_start_le(buf, capacity, full_handler) pb_start_e(buf, capacity, 0, full_handler)
/** Start the parser in little-endian mode (default) */
#define pb_start(buf, capacity, full_handler) pb_start_le(buf, capacity, full_handler)
// --- utilities ---
/** Get already used bytes count */
#define pb_length(pb) ((pb)->current - (pb)->start)
/** Reset the current pointer to start */
#define pb_rewind(pb) do { pb->current = pb->start; } while (0)
/** Write from a buffer */
bool pb_buf(PayloadBuilder *pb, const uint8_t *buf, uint32_t len);
/** Write a zero terminated string */
bool pb_string(PayloadBuilder *pb, const char *str);
/** Write uint8_t to the buffer */
bool pb_u8(PayloadBuilder *pb, uint8_t byte);
/** Write boolean to the buffer. */
static inline bool pb_bool(PayloadBuilder *pb, bool b)
{
return pb_u8(pb, (uint8_t) b);
}
/** Write uint16_t to the buffer. */
bool pb_u16(PayloadBuilder *pb, uint16_t word);
/** Write uint32_t to the buffer. */
bool pb_u32(PayloadBuilder *pb, uint32_t word);
/** Write int8_t to the buffer. */
bool pb_i8(PayloadBuilder *pb, int8_t byte);
/** Write char (int8_t) to the buffer. */
static inline bool pb_char(PayloadBuilder *pb, char c)
{
return pb_i8(pb, c);
}
/** Write int16_t to the buffer. */
bool pb_i16(PayloadBuilder *pb, int16_t word);
/** Write int32_t to the buffer. */
bool pb_i32(PayloadBuilder *pb, int32_t word);
/** Write 4-byte float to the buffer. */
bool pb_float(PayloadBuilder *pb, float f);
#endif // PAYLOAD_BUILDER_H
+121
View File
@@ -0,0 +1,121 @@
#include "payload_parser.h"
#define pp_check_capacity(pp, needed) \
if ((pp)->current + (needed) > (pp)->end) { \
if ((pp)->empty_handler == NULL || !(pp)->empty_handler(pp, needed)) {(pp)->ok = 0;} ; \
}
void pp_skip(PayloadParser *pp, uint32_t num)
{
pp->current += num;
}
uint8_t pp_u8(PayloadParser *pp)
{
pp_check_capacity(pp, 1);
if (!pp->ok) return 0;
return *pp->current++;
}
uint16_t pp_u16(PayloadParser *pp)
{
pp_check_capacity(pp, 2);
if (!pp->ok) return 0;
uint16_t x = 0;
if (pp->bigendian) {
x |= *pp->current++ << 8;
x |= *pp->current++;
} else {
x |= *pp->current++;
x |= *pp->current++ << 8;
}
return x;
}
uint32_t pp_u32(PayloadParser *pp)
{
pp_check_capacity(pp, 4);
if (!pp->ok) return 0;
uint32_t x = 0;
if (pp->bigendian) {
x |= (uint32_t) (*pp->current++ << 24);
x |= (uint32_t) (*pp->current++ << 16);
x |= (uint32_t) (*pp->current++ << 8);
x |= *pp->current++;
} else {
x |= *pp->current++;
x |= (uint32_t) (*pp->current++ << 8);
x |= (uint32_t) (*pp->current++ << 16);
x |= (uint32_t) (*pp->current++ << 24);
}
return x;
}
const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length)
{
int32_t len = (int) (pp->end - pp->current);
if (!pp->ok || len <= 0) {
if (length != NULL) *length = 0;
return NULL;
}
if (length != NULL) {
*length = (uint32_t) len;
}
return pp->current;
}
/** Read int8_t from the payload. */
int8_t pp_i8(PayloadParser *pp)
{
return ((union conv8) {.u8 = pp_u8(pp)}).i8;
}
/** Read int16_t from the payload. */
int16_t pp_i16(PayloadParser *pp)
{
return ((union conv16) {.u16 = pp_u16(pp)}).i16;
}
/** Read int32_t from the payload. */
int32_t pp_i32(PayloadParser *pp)
{
return ((union conv32) {.u32 = pp_u32(pp)}).i32;
}
/** Read 4-byte float from the payload. */
float pp_float(PayloadParser *pp)
{
return ((union conv32) {.u32 = pp_u32(pp)}).f32;
}
/** Read a zstring */
uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen)
{
pp_check_capacity(pp, 1);
uint32_t len = 0;
while (len < maxlen-1 && pp->current != pp->end) {
char c = *buffer++ = *pp->current++;
if (c == 0) break;
len++;
}
*buffer = 0;
return len;
}
/** Read a buffer */
uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen)
{
uint32_t len = 0;
while (len < maxlen && pp->current != pp->end) {
*buffer++ = *pp->current++;
len++;
}
return len;
}
+143
View File
@@ -0,0 +1,143 @@
#ifndef PAYLOAD_PARSER_H
#define PAYLOAD_PARSER_H
/**
* PayloadParser, part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2016-2017. MIT license.
*
* This module helps you with parsing payloads (not only from TinyFrame).
*
* The parser supports big and little-endian which is selected when
* initializing it or by accessing the bigendian struct field.
*
* The parser performs bounds checking and calls the provided handler when
* the requested read doesn't have enough data. Use the callback to take
* appropriate action, e.g. report an error.
*
* If the handler function is not defined, the pb->ok flag is set to false
* (use this to check for success), and further reads won't have any effect
* and always result in 0 or empty array.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "type_coerce.h"
typedef struct PayloadParser_ PayloadParser;
/**
* Empty buffer handler.
*
* 'needed' more bytes should be read but the end was reached.
*
* Return true if the problem was solved (e.g. new data loaded into
* the buffer and the 'current' pointer moved to the beginning).
*
* If false is returned, the 'ok' flag on the struct is set to false
* and all following reads will fail / return 0.
*/
typedef bool (*pp_empty_handler)(PayloadParser *pp, uint32_t needed);
struct PayloadParser_ {
uint8_t *start; //!< Pointer to the beginning of the buffer
uint8_t *current; //!< Pointer to the next byte to be read
uint8_t *end; //!< Pointer to the end of the buffer (start + length)
pp_empty_handler empty_handler; //!< Callback for buffer underrun
bool bigendian; //!< Flag to use big-endian parsing
bool ok; //!< Indicates that all reads were successful
};
// --- initializer helper macros ---
/** Start the parser. */
#define pp_start_e(buf, length, bigendian, empty_handler) ((PayloadParser){buf, buf, (buf)+(length), empty_handler, bigendian, 1})
/** Start the parser in big-endian mode */
#define pp_start_be(buf, length, empty_handler) pp_start_e(buf, length, 1, empty_handler)
/** Start the parser in little-endian mode */
#define pp_start_le(buf, length, empty_handler) pp_start_e(buf, length, 0, empty_handler)
/** Start the parser in little-endian mode (default) */
#define pp_start(buf, length, empty_handler) pp_start_le(buf, length, empty_handler)
// --- utilities ---
/** Get remaining length */
#define pp_length(pp) ((pp)->end - (pp)->current)
/** Reset the current pointer to start */
#define pp_rewind(pp) do { pp->current = pp->start; } while (0)
/**
* @brief Get the remainder of the buffer.
*
* Returns NULL and sets 'length' to 0 if there are no bytes left.
*
* @param pp
* @param length : here the buffer length will be stored. NULL to do not store.
* @return the remaining portion of the input buffer
*/
const uint8_t *pp_tail(PayloadParser *pp, uint32_t *length);
/** Read uint8_t from the payload. */
uint8_t pp_u8(PayloadParser *pp);
/** Read bool from the payload. */
static inline int8_t pp_bool(PayloadParser *pp)
{
return pp_u8(pp) != 0;
}
/** Skip bytes */
void pp_skip(PayloadParser *pp, uint32_t num);
/** Read uint16_t from the payload. */
uint16_t pp_u16(PayloadParser *pp);
/** Read uint32_t from the payload. */
uint32_t pp_u32(PayloadParser *pp);
/** Read int8_t from the payload. */
int8_t pp_i8(PayloadParser *pp);
/** Read char (int8_t) from the payload. */
static inline int8_t pp_char(PayloadParser *pp)
{
return pp_i8(pp);
}
/** Read int16_t from the payload. */
int16_t pp_i16(PayloadParser *pp);
/** Read int32_t from the payload. */
int32_t pp_i32(PayloadParser *pp);
/** Read 4-byte float from the payload. */
float pp_float(PayloadParser *pp);
/**
* Parse a zero-terminated string
*
* @param pp - parser
* @param buffer - target buffer
* @param maxlen - buffer size
* @return actual number of bytes, excluding terminator
*/
uint32_t pp_string(PayloadParser *pp, char *buffer, uint32_t maxlen);
/**
* Parse a buffer
*
* @param pp - parser
* @param buffer - target buffer
* @param maxlen - buffer size
* @return actual number of bytes, excluding terminator
*/
uint32_t pp_buf(PayloadParser *pp, uint8_t *buffer, uint32_t maxlen);
#endif // PAYLOAD_PARSER_H
+32
View File
@@ -0,0 +1,32 @@
#ifndef TYPE_COERCE_H
#define TYPE_COERCE_H
/**
* Structs for conversion between types,
* part of the TinyFrame utilities collection
*
* (c) Ondřej Hruška, 2016-2017. MIT license.
*
* This is a support header file for PayloadParser and PayloadBuilder.
*/
#include <stdint.h>
#include <stddef.h>
union conv8 {
uint8_t u8;
int8_t i8;
};
union conv16 {
uint16_t u16;
int16_t i16;
};
union conv32 {
uint32_t u32;
int32_t i32;
float f32;
};
#endif // TYPE_COERCE_H