28 Commits
Author SHA1 Message Date
MightyPork b29315dce0 removed some confusing entries from the dummy cmakelists.txt file 2018-03-19 00:30:18 +01:00
jdavidbergerandMightyPork 9462a66956 Update README.md (#14)
Added blurb about TF_Accept
2018-03-18 23:29:40 +01:00
MightyPork 74d6451d50 words 2018-01-26 14:46:36 +01:00
MightyPork e3fb2c983a typo 2018-01-26 14:44:44 +01:00
MightyPork 16365dabc7 improved the readme 2018-01-26 14:44:02 +01:00
MightyPork 0692365b18 bump verison 2018-01-26 13:10:10 +01:00
MightyPork 5ba23e942f added example of using custom checksums 2018-01-26 11:44:10 +01:00
MightyPork 13c74ea6e6 Implemented multipart frame sending 2018-01-26 11:39:31 +01:00
MightyPork 0ed3fd91f7 Removed dead code and fixed demoes 2018-01-26 09:59:42 +01:00
MightyPork 137797cc7d Replace size_t with uint32_t, add default mutex impl, preparing internals for multipart Tx 2018-01-26 09:49:04 +01:00
MightyPork e24296598e formatting 2018-01-23 21:28:35 +01:00
MightyPork 0edd2e04fa added CRC8 1-wire type checksum 2018-01-23 21:23:58 +01:00
MightyPork 91e1c25c6d Added support for custom checksums 2018-01-23 21:02:47 +01:00
MightyPork b74bda7cf5 added logging support for better debug 2018-01-12 10:09:52 +01:00
MightyPorkandGitHub 530577958d Update README.md 2017-12-28 15:23:57 +01:00
MightyPorkandGitHub 2d121ddce9 Update README.md 2017-12-28 15:17:56 +01:00
MightyPork 1e6cdf22f0 remove lies from readme 2017-12-27 19:26:08 +01:00
MightyPork 148b8a0fe6 added better comment to config example 2017-12-27 19:22:43 +01:00
MightyPorkandGitHub 8205ab93b0 Update README.md 2017-12-27 17:25:19 +01:00
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
19 changed files with 1497 additions and 513 deletions
+3 -3
View File
@@ -1,16 +1,16 @@
cmake_minimum_required(VERSION 3.7) cmake_minimum_required(VERSION 3.7)
project(tf) project(tf)
# This is a dummy CMakeLists file for CLion, which still doesn't understand Makefiles.
# Do not try to build the library using this file, it's here only for static analysis.
set(CMAKE_CXX_STANDARD GNU89) set(CMAKE_CXX_STANDARD GNU89)
set(SOURCE_FILES set(SOURCE_FILES
demo/simple/test.c demo/simple/test.c
demo/simple/TF_Config.h
demo/socket_demo/master.c demo/socket_demo/master.c
demo/socket_demo/slave.c demo/socket_demo/slave.c
demo/socket_demo/TF_Config.h demo/socket_demo/TF_Config.h
demo/multipart_tx/test.c
demo/multipart_tx/TF_Config.h
demo/demo.c demo/demo.c
demo/demo.h demo/demo.h
TinyFrame.c TinyFrame.c
+129 -48
View File
@@ -1,88 +1,169 @@
# TinyFrame # TinyFrame
TinyFrame is a simple library for building and parsing frames to be sent TinyFrame is a simple library for building and parsing data 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, socket). The code is written to build with
to build with `--std=gnu89` and later. `--std=gnu99` and mostly compatible with `--std=gnu89`.
The library provides a high level interface for passing messages between the two peers.
Multi-message sessions, response listeners, checksums, timeouts are all handled by the library.
TinyFrame is suitable for a wide range of applications, including inter-microcontroller 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 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! UDP packets.
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) 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 a particular frame Type, or (3) a specific message ID. This high-level API is general
easily implement various async communication patterns. enough to implement most communication patterns.
TinyFrame is re-entrant and supports creating multiple instances with the limitation 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 that their structure (field sizes and checksum type) is the same. There is a support
for adding multi-threaded access to a shared instance using a mutex (via a callback stub). for adding multi-threaded access to a shared instance using a mutex.
## Frame structure TinyFrame also comes with (optional) helper functions for building and parsing message
payloads, those are provided in the `utils/` folder.
All fields in the message frame have a configurable size (see the top of the header file). ## Ports
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 TinyFrame has been ported to mutiple languages:
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. - The reference C implementation is in this repo
- Python port - [MightyPork/PonyFrame](https://github.com/MightyPork/PonyFrame)
- Rust port - [cpsdqs/tinyframe-rs](https://github.com/cpsdqs/tinyframe-rs)
- JavaScript port - [cpsdqs/tinyframe-js](https://github.com/cpsdqs/tinyframe-js)
Please note most of the ports are experimental and may exhibit various bugs or missing
features. Testers are welcome :)
## Functional overview
The basic functionality of TinyFrame is explained here. For particlars, such as the
API functions, it's recommended to read the doc comments in the header file.
### Structure of a frame
Each frame consists of a header and a payload. Both parts can be protected by a checksum,
ensuring a frame with a malformed header (e.g. with a corrupted length field) or a corrupted
payload is rejected.
The frame header contains a frame ID and a message type. Frame ID is incremented with each
new message. The highest bit of the ID field is fixed to 1 and 0 for the two peers,
avoiding a conflict.
Frame ID can be re-used in a response to tie the two messages together. Values of the
type field are user defined.
All fields in the frame have a configurable size. By changing a field in the config
file, such as `TF_LEN_BYTES` (1, 2 or 4), the library seamlessly switches between `uint8_t`,
`uint16_t` and `uint32_t` for all functions working with the field.
``` ```
,-----+----+-----+------+------------+- - - -+------------, ,-----+-----+-----+------+------------+- - - -+-------------,
| SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM | | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM |
| 1 | ? | ? | ? | ? | ... | ? | <- size (bytes) | 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes)
'-----+----+-----+------+------------+- - - -+------------' '-----+-----+-----+------+------------+- - - -+-------------'
SOF ......... start of frame, 0x01 SOF ......... start of frame, usually 0x01 (optional, configurable)
ID ......... the frame ID (MSb is the peer bit) ID ......... the frame ID (MSb is the peer bit)
LEN ......... nr of data bytes in the frame LEN ......... number of data bytes in the frame
TYPE ........ message type (used to run Type Listeners, pick any values you like) TYPE ........ message type (used to run Type Listeners, pick any values you like)
HEAD_CKSUM .. header checksum HEAD_CKSUM .. header checksum
DATA ........ LEN bytes of data DATA ........ LEN bytes of data
DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message DATA_CKSUM .. data checksum (left out if LEN is 0)
``` ```
### Message listeners
TinyFrame is based on the concept of message listeners. A listener is a callback function
waiting for a particular message Type or ID to be received.
There are 3 listener types, in the order of precedence:
- **ID listeners** - waiting for a response
- **Type listeners** - waiting for a message of the given Type field
- **Generic listeners** - fallback
ID listeners can be registered automatically when sending a message. All listeners can
also be registered and removed manually.
ID listeners are used to receive the response to a request. When registerign an ID
listener, it's possible to attach custom user data to it that will be made available to
the listener callback. This data (`void *`) can be any kind of application context
variable.
ID listeners can be assigned a timeout. When a listener expires, before it's removed,
the callback is fired with NULL payload data in order to let the user `free()` any
attached userdata. This happens only if the userdata is not NULL.
Listener callbacks return values of the `TF_Result` enum:
- `TF_CLOSE` - message accepted, remove the listener
- `TF_STAY` - message accepted, stay registered
- `TF_RENEW` - sameas `TF_STAY`, but the ID listener's timeout is renewed
- `TF_NEXT` - message NOT accepted, keep the listener and pass the message to the next
listener capable of handling it.
### Data buffers, multi-part frames
TinyFrame uses two data buffers: a small transmit buffer and a larger receive buffer.
The transmit buffer is used to prepare bytes to send, either all at once, or in a
circular fashion if the buffer is not large enough. The buffer must only contain the entire
frame header, so e.g. 32 bytes should be sufficient for short messages.
Using the `*_Multipart()` sending functions, it's further possible to split the frame
header and payload to multiple function calls, allowing the applciation to e.g. generate
the payload on-the-fly.
In contrast to the transmit buffer, the receive buffer must be large enough to contain
an entire frame. This is because the final checksum must be verified before the frame
is handled.
If frames larger than the possible receive buffer size are required (e.g. in embedded
systems with small RAM), it's recommended to implement a multi-message transport mechanism
at a higher level and send the data in chunks.
## Usage Hints ## Usage Hints
- All TinyFrame functions, typedefs and macros start with the `TF_` prefix. - 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) - Both peers must include the library with the same config parameters
- See `TF_Integration.example.c` and `TF_Config.example.c` for reference how to configure and integrate the library.
- DO NOT modify the library files, if possible. This makes it easy to upgrade.
- Start by calling `TF_Init()` with `TF_MASTER` or `TF_SLAVE` as the argument. This creates a handle. - 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 Use `TF_InitStatic()` to avoid the use of malloc().
using the `tf.userdata` / `tf.usertag` field. - 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`. - 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). 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. A frame can be sent in it's entirety, or in multiple parts, depending on its size.
- Use TF_AcceptChar(tf, byte) to give read data to TF. TF_Accept(tf, bytes, count) will accept mulitple bytes.
- If you wish to use timeouts, periodically call `TF_Tick()`. The calling period determines - 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 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. 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()`, `TF_Query()`, `TF_SendSimple()`, `TF_QuerySimple()`. - 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 Query functions take a listener callback (function pointer) that will be added as
an ID listener and wait for a response. an ID listener and wait for a response.
- Use the `*_Multipart()` variant of the above sending functions for payloads generated in
multiple function calls. The payload is sent afterwards by calling `TF_Multipart_Payload()`
and the frame is closed by `TF_Multipart_Close()`.
- If custom checksum implementation is needed, select `TF_CKSUM_CUSTOM8`, 16 or 32 and
implement the three checksum functions.
- 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 msg boject you received, replacing the `data` pointer (and `len`) with response. with the msg object you received, replacing the `data` pointer (and `len`) with a response.
- Manually reset the parser using `TF_ResetParser()` - At any time you can manually reset the message parser using `TF_ResetParser()`. It can also
be reset automatically after a timeout configured in the config file.
### Message listeners ### Gotchas to look out for
Listeners are callback functions that are called by TinyFrame when a message which - If any userdata is attached to an ID listener with a timeout, when the listener times out,
they can handle is received. it will be called with NULL `msg->data` to let the user free the userdata. Therefore
it's needed to check `msg->data` before proceeding to handle the message.
There are 3 listener types: - If a multi-part frame is being sent, the Tx part of the library is locked to prevent
concurrent access. The frame must be fully sent and closed before attempting to send
- ID listeners anything else.
- Type listeners - If multiple threads are used, don't forget to implement the mutex callbacks to avoid
- Generic listeners concurrent access to the Tx functions. The default implementation is not entirely thread
safe, as it can't rely on platform-specific resources like mutexes or atomic access.
Listeners return an enum constant based on what should be done next - remove the listener, Set `TF_USE_MUTEX` to `1` in the config file.
keep it, renew it's timeout, or let some other listener handle the message.
### Examples ### Examples
+15 -8
View File
@@ -8,6 +8,7 @@
#define TF_CONFIG_H #define TF_CONFIG_H
#include <stdint.h> #include <stdint.h>
#include <stdio.h> // used by the TF_Error() macro defined below
//#include <esp8266.h> // when using with esphttpd //#include <esp8266.h> // when using with esphttpd
//----------------------------- FRAME FORMAT --------------------------------- //----------------------------- FRAME FORMAT ---------------------------------
@@ -16,10 +17,10 @@
// If the connection is reliable, you can disable the SOF byte and checksums. // If the connection is reliable, you can disable the SOF byte and checksums.
// That can save up to 9 bytes of overhead. // That can save up to 9 bytes of overhead.
// ,-----+----+-----+------+------------+- - - -+------------, // ,-----+-----+-----+------+------------+- - - -+-------------,
// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM | // | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | DATA_CKSUM |
// | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes) // | 0-1 | 1-4 | 1-4 | 1-4 | 0-4 | ... | 0-4 | <- size (bytes)
// '-----+----+-----+------+------------+- - - -+------------' // '-----+-----+-----+------+------------+- - - -+-------------'
// !!! BOTH PEERS MUST USE THE SAME SETTINGS !!! // !!! BOTH PEERS MUST USE THE SAME SETTINGS !!!
@@ -28,11 +29,11 @@
#define TF_LEN_BYTES 2 #define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1 #define TF_TYPE_BYTES 1
// Checksum type // Checksum type. Options:
//#define TF_CKSUM_TYPE TF_CKSUM_NONE // TF_CKSUM_NONE, TF_CKSUM_XOR, TF_CKSUM_CRC8, TF_CKSUM_CRC16, TF_CKSUM_CRC32
//#define TF_CKSUM_TYPE TF_CKSUM_XOR // TF_CKSUM_CUSTOM8, TF_CKSUM_CUSTOM16, TF_CKSUM_CUSTOM32
// Custom checksums require you to implement checksum functions (see TinyFrame.h)
#define TF_CKSUM_TYPE TF_CKSUM_CRC16 #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 // Use a SOF byte to mark the start of a frame
#define TF_USE_SOF_BYTE 1 #define TF_USE_SOF_BYTE 1
@@ -69,6 +70,12 @@ typedef uint8_t TF_COUNT;
// ticks = number of calls to TF_Tick() // ticks = number of calls to TF_Tick()
#define TF_PARSER_TIMEOUT_TICKS 10 #define TF_PARSER_TIMEOUT_TICKS 10
// Whether to use mutex - requires you to implement TF_ClaimTx() and TF_ReleaseTx()
#define TF_USE_MUTEX 1
// Error reporting function. To disable debug, change to empty define
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
//------------------------- End of user config ------------------------------ //------------------------- End of user config ------------------------------
#endif //TF_CONFIG_H #endif //TF_CONFIG_H
+29 -2
View File
@@ -10,15 +10,20 @@
* listener timeout feature. * listener timeout feature.
*/ */
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len) void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{ {
// send to UART // send to UART
} }
// --------- Mutex callbacks ----------
// Needed only if TF_USE_MUTEX is 1 in the config file.
// DELETE if mutex is not used
/** Claim the TX interface before composing and sending a frame */ /** Claim the TX interface before composing and sending a frame */
void TF_ClaimTx(TinyFrame *tf) bool TF_ClaimTx(TinyFrame *tf)
{ {
// take mutex // take mutex
return true; // we succeeded
} }
/** Free the TX interface after composing and sending a frame */ /** Free the TX interface after composing and sending a frame */
@@ -26,3 +31,25 @@ void TF_ReleaseTx(TinyFrame *tf)
{ {
// release mutex // release mutex
} }
// --------- Custom checksums ---------
// This should be defined here only if a custom checksum type is used.
// DELETE those if you use one of the built-in checksum types
/** Initialize a checksum */
TF_CKSUM TF_CksumStart(void)
{
return 0;
}
/** Update a checksum with a byte */
TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{
return cksum ^ byte;
}
/** Finalize the checksum calculation */
TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{
return cksum;
}
+346 -136
View File
@@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "TinyFrame.h" #include "TinyFrame.h"
#include <malloc.h> #include <stdlib.h> // - for malloc() if dynamic constructor is used
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Compatibility with ESP8266 SDK // Compatibility with ESP8266 SDK
@@ -10,35 +10,94 @@
#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))
// TODO It would be nice to have per-instance configurable checksum types, but that would // Helper macros
// mandate configurable field sizes unless we use u32 everywhere (and possibly shorten #define TF_MIN(a, b) ((a)<(b)?(a):(b))
// it when encoding to the buffer). I don't really like this idea so much. -MP #define TF_TRY(func) do { if(!(func)) return false; } while (0)
// 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))
#if !TF_USE_MUTEX
// Not thread safe lock implementation, used if user did not provide a better one.
// This is less reliable than a real mutex, but will catch most bugs caused by
// inappropriate use fo the API.
/** Claim the TX interface before composing and sending a frame */
static bool TF_ClaimTx(TinyFrame *tf) {
if (tf->soft_lock) {
TF_Error("TF already locked for tx!");
return false;
}
tf->soft_lock = true;
return true;
}
/** Free the TX interface after composing and sending a frame */
static void TF_ReleaseTx(TinyFrame *tf)
{
tf->soft_lock = false;
}
#endif
//region Checksums //region Checksums
#if TF_CKSUM_TYPE == TF_CKSUM_NONE #if TF_CKSUM_TYPE == TF_CKSUM_NONE
// NONE static TF_CKSUM TF_CksumStart(void)
#define CKSUM_RESET(cksum) { return 0; }
#define CKSUM_ADD(cksum, byte)
#define CKSUM_FINALIZE(cksum) static TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{ return cksum; }
static TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{ return cksum; }
#elif TF_CKSUM_TYPE == TF_CKSUM_XOR #elif TF_CKSUM_TYPE == TF_CKSUM_XOR
// ~XOR static TF_CKSUM TF_CksumStart(void)
#define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0) { return 0; }
#define CKSUM_ADD(cksum, byte) do { (cksum) ^= (byte); } while(0)
#define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~cksum; } while(0) static TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{ return cksum ^ byte; }
static TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{ return (TF_CKSUM) ~cksum; }
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC8
static inline uint8_t crc8_bits(uint8_t data)
{
uint8_t crc = 0;
if(data & 1) crc ^= 0x5e;
if(data & 2) crc ^= 0xbc;
if(data & 4) crc ^= 0x61;
if(data & 8) crc ^= 0xc2;
if(data & 0x10) crc ^= 0x9d;
if(data & 0x20) crc ^= 0x23;
if(data & 0x40) crc ^= 0x46;
if(data & 0x80) crc ^= 0x8c;
return crc;
}
static TF_CKSUM TF_CksumStart(void)
{ return 0; }
static TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{ return crc8_bits(byte ^ cksum); }
static TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{ return cksum; }
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC16 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC16
// TODO try to replace with an algorithm // TODO try to replace with an algorithm
/** 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] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
@@ -71,21 +130,21 @@ static const uint16_t crc16_table[256] = {
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
}; };
static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte) static TF_CKSUM TF_CksumStart(void)
{ { return 0; }
return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff];
}
#define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0) static TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
#define CKSUM_ADD(cksum, byte) do { (cksum) = crc16_byte((cksum), (byte)); } while(0) { return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff]; }
#define CKSUM_FINALIZE(cksum)
static TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{ return cksum; }
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC32 #elif TF_CKSUM_TYPE == TF_CKSUM_CRC32
// TODO try to replace with an algorithm // TODO try to replace with an algorithm
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,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
@@ -129,25 +188,35 @@ static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
}; };
static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte) static TF_CKSUM TF_CksumStart(void)
{ { return (TF_CKSUM)0xFFFFFFFF; }
return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8));
}
#define CKSUM_RESET(cksum) do { (cksum) = (TF_CKSUM)0xFFFFFFFF; } while (0) static TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
#define CKSUM_ADD(cksum, byte) do { (cksum) = crc32_byte(cksum, byte); } while(0) { return crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8); }
#define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~(cksum); } while(0)
static TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{ return (TF_CKSUM) ~cksum; }
#endif #endif
#define CKSUM_RESET(cksum) do { (cksum) = TF_CksumStart(); } while (0)
#define CKSUM_ADD(cksum, byte) do { (cksum) = TF_CksumAdd((cksum), (byte)); } while (0)
#define CKSUM_FINALIZE(cksum) do { (cksum) = TF_CksumEnd((cksum)); } while (0)
//endregion //endregion
//region Init
/** Init with a user-allocated buffer */ /** Init with a user-allocated buffer */
void _TF_FN TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit) bool _TF_FN TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit)
{ {
if (tf == NULL) return; if (tf == NULL) {
TF_Error("TF_InitStatic() failed, tf is null.");
return false;
}
// Zero it out, keeping user config // Zero it out, keeping user config
uint32_t usertag = tf->usertag; uint32_t usertag = tf->usertag;
@@ -159,12 +228,18 @@ void _TF_FN TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit)
tf->userdata = userdata; tf->userdata = userdata;
tf->peer_bit = peer_bit; tf->peer_bit = peer_bit;
return true;
} }
/** Init with malloc */ /** Init with malloc */
TinyFrame * _TF_FN TF_Init(TF_Peer peer_bit) TinyFrame * _TF_FN TF_Init(TF_Peer peer_bit)
{ {
TinyFrame *tf = malloc(sizeof(TinyFrame)); TinyFrame *tf = malloc(sizeof(TinyFrame));
if (!tf) {
TF_Error("TF_Init() failed, out of memory.");
return NULL;
}
TF_InitStatic(tf, peer_bit); TF_InitStatic(tf, peer_bit);
return tf; return tf;
} }
@@ -176,6 +251,9 @@ void TF_DeInit(TinyFrame *tf)
free(tf); free(tf);
} }
//endregion Init
//region Listeners //region Listeners
/** Reset ID listener's timeout to the original value */ /** Reset ID listener's timeout to the original value */
@@ -191,7 +269,7 @@ static void _TF_FN cleanup_id_listener(TinyFrame *tf, TF_COUNT i, struct TF_IdLi
if (lst->fn == NULL) return; if (lst->fn == NULL) return;
// Make user clean up their data - only if not NULL // Make user clean up their data - only if not NULL
if (lst->userdata != NULL) { if (lst->userdata != NULL || lst->userdata2 != NULL) {
msg.userdata = lst->userdata; msg.userdata = lst->userdata;
msg.userdata2 = lst->userdata2; msg.userdata2 = lst->userdata2;
msg.data = NULL; // this is a signal that the listener should clean up msg.data = NULL; // this is a signal that the listener should clean up
@@ -243,6 +321,8 @@ bool _TF_FN TF_AddIdListener(TinyFrame *tf, TF_Msg *msg, TF_Listener cb, TF_TICK
return true; return true;
} }
} }
TF_Error("Failed to add ID listener");
return false; return false;
} }
@@ -263,6 +343,8 @@ bool _TF_FN TF_AddTypeListener(TinyFrame *tf, TF_TYPE frame_type, TF_Listener cb
return true; return true;
} }
} }
TF_Error("Failed to add type listener");
return false; return false;
} }
@@ -282,6 +364,8 @@ bool _TF_FN TF_AddGenericListener(TinyFrame *tf, TF_Listener cb)
return true; return true;
} }
} }
TF_Error("Failed to add generic listener");
return false; return false;
} }
@@ -298,6 +382,8 @@ bool _TF_FN TF_RemoveIdListener(TinyFrame *tf, TF_ID frame_id)
return true; return true;
} }
} }
TF_Error("ID listener %d to remove not found", (int)frame_id);
return false; return false;
} }
@@ -314,6 +400,8 @@ bool _TF_FN TF_RemoveTypeListener(TinyFrame *tf, TF_TYPE type)
return true; return true;
} }
} }
TF_Error("Type listener %d to remove not found", (int)type);
return false; return false;
} }
@@ -330,6 +418,8 @@ bool _TF_FN TF_RemoveGenericListener(TinyFrame *tf, TF_Listener cb)
return true; return true;
} }
} }
TF_Error("Generic listener to remove not found");
return false; return false;
} }
@@ -372,6 +462,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
if (res == TF_RENEW) { if (res == TF_RENEW) {
renew_id_listener(ilst); renew_id_listener(ilst);
} }
else if (res == TF_CLOSE) {
// Set userdata to NULL to avoid calling user for cleanup
ilst->userdata = NULL;
ilst->userdata2 = NULL;
cleanup_id_listener(tf, i, ilst);
}
return; return;
} }
} }
@@ -389,8 +485,12 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
res = tlst->fn(tf, &msg); res = tlst->fn(tf, &msg);
if (res != TF_NEXT) { if (res != TF_NEXT) {
// if it's TF_CLOSE, we assume user already cleaned up userdata // type listeners don't have userdata.
// TF_RENEW doesn't make sense here because type listeners don't expire // TF_RENEW doesn't make sense here because type listeners don't expire = same as TF_STAY
if (res == TF_CLOSE) {
cleanup_type_listener(tf, i, tlst);
}
return; return;
} }
} }
@@ -404,20 +504,51 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf)
res = glst->fn(tf, &msg); res = glst->fn(tf, &msg);
if (res != TF_NEXT) { if (res != TF_NEXT) {
// if it's TF_CLOSE, we assume user already cleaned up userdata // generic listeners don't have userdata.
// TF_RENEW doesn't make sense here because generic listeners don't expire // TF_RENEW doesn't make sense here because generic listeners don't expire = same as TF_STAY
// note: It's not expected that user will have multiple generic listeners, or
// ever actually remove them. They're most useful as default callbacks if no other listener
// handled the message.
if (res == TF_CLOSE) {
cleanup_generic_listener(tf, i, glst);
}
return; return;
} }
} }
} }
TF_Error("Unhandled message, type %d", (int)msg.type);
}
/** Externally renew an ID listener */
bool _TF_FN TF_RenewIdListener(TinyFrame *tf, TF_ID id)
{
TF_COUNT i;
struct TF_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;
}
}
TF_Error("Renew listener: not found (id %d)", (int)id);
return false;
} }
//endregion Listeners //endregion Listeners
//region Parser
/** Handle a received byte buffer */ /** Handle a received byte buffer */
void _TF_FN TF_Accept(TinyFrame *tf, const uint8_t *buffer, size_t count) void _TF_FN TF_Accept(TinyFrame *tf, const uint8_t *buffer, uint32_t count)
{ {
size_t i; uint32_t i;
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
TF_AcceptChar(tf, buffer[i]); TF_AcceptChar(tf, buffer[i]);
} }
@@ -450,7 +581,10 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
{ {
// Parser timeout - clear // Parser timeout - clear
if (tf->parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) { if (tf->parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) {
if (tf->state != TFState_SOF) {
TF_ResetParser(tf); TF_ResetParser(tf);
TF_Error("Parser timeout");
}
} }
tf->parser_timeout_ticks = 0; tf->parser_timeout_ticks = 0;
@@ -463,10 +597,11 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
#if !TF_USE_SOF_BYTE #if !TF_USE_SOF_BYTE
if (tf->state == TFState_SOF) { if (tf->state == TFState_SOF) {
TF_ParsBeginFrame(); pars_begin_frame(tf);
} }
#endif #endif
//@formatter:off
switch (tf->state) { switch (tf->state) {
case TFState_SOF: case TFState_SOF:
if (c == TF_SOF_BYTE) { if (c == TF_SOF_BYTE) {
@@ -495,15 +630,15 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, 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 == TF_CKSUM_NONE #if TF_CKSUM_TYPE == TF_CKSUM_NONE
tf->state = TFState_DATA; tf->state = TFState_DATA;
tf->rxi = 0; tf->rxi = 0;
#else #else
// enter HEAD_CKSUM state // enter HEAD_CKSUM state
tf->state = TFState_HEAD_CKSUM; tf->state = TFState_HEAD_CKSUM;
tf->rxi = 0; tf->rxi = 0;
tf->ref_cksum = 0; tf->ref_cksum = 0;
#endif #endif
} }
break; break;
@@ -513,11 +648,13 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
CKSUM_FINALIZE(tf->cksum); CKSUM_FINALIZE(tf->cksum);
if (tf->cksum != tf->ref_cksum) { if (tf->cksum != tf->ref_cksum) {
TF_Error("Rx head cksum mismatch");
TF_ResetParser(tf); TF_ResetParser(tf);
break; break;
} }
if (tf->len == 0) { if (tf->len == 0) {
// if the message has no body, we're done.
TF_HandleReceivedMessage(tf); TF_HandleReceivedMessage(tf);
TF_ResetParser(tf); TF_ResetParser(tf);
break; break;
@@ -529,7 +666,8 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, 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_RX) { if (tf->len > TF_MAX_PAYLOAD_RX) {
TF_Error("Rx payload too long: %d", (int)tf->len);
// 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;
} }
@@ -545,16 +683,16 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
} }
if (tf->rxi == tf->len) { if (tf->rxi == tf->len) {
#if TF_CKSUM_TYPE == TF_CKSUM_NONE #if TF_CKSUM_TYPE == TF_CKSUM_NONE
// All done // All done
TF_HandleReceivedMessage(); TF_HandleReceivedMessage(tf);
TF_ResetParser(); TF_ResetParser(tf);
#else #else
// Enter DATA_CKSUM state // Enter DATA_CKSUM state
tf->state = TFState_DATA_CKSUM; tf->state = TFState_DATA_CKSUM;
tf->rxi = 0; tf->rxi = 0;
tf->ref_cksum = 0; tf->ref_cksum = 0;
#endif #endif
} }
break; break;
@@ -562,25 +700,30 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
COLLECT_NUMBER(tf->ref_cksum, TF_CKSUM) { COLLECT_NUMBER(tf->ref_cksum, TF_CKSUM) {
// 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) {
if (tf->cksum == tf->ref_cksum) {
TF_HandleReceivedMessage(tf); TF_HandleReceivedMessage(tf);
} else {
TF_Error("Body cksum mismatch");
}
} }
TF_ResetParser(tf); TF_ResetParser(tf);
} }
break; break;
} }
//@formatter:on
// 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);
TF_ResetParser(tf);
}
} }
//endregion Parser
//region Compose and send
// Helper macros for the Compose functions // Helper macros for the Compose functions
// use variables: si - signed int, b - byte, outbuff - target buffer, pos - count of bytes in buffer // use variables: si - signed int, b - byte, outbuff - target buffer, pos - count of bytes in buffer
/** /**
* Write a number to the output buffer. * Write a number to the output buffer.
* *
@@ -624,15 +767,15 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c)
* @param msg - message written to the buffer * @param msg - message written to the buffer
* @return nr of bytes in outbuff used by the frame, 0 on failure * @return nr of bytes in outbuff used by the frame, 0 on failure
*/ */
static inline size_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF_Msg *msg) static inline uint32_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF_Msg *msg)
{ {
int8_t si = 0; // signed small int int8_t si = 0; // signed small int
uint8_t b = 0; uint8_t b = 0;
TF_ID id = 0; TF_ID id = 0;
TF_CKSUM cksum = 0; TF_CKSUM cksum = 0;
size_t pos = 0; // can be needed to grow larger than TF_LEN uint32_t pos = 0;
(void)cksum; (void)cksum; // suppress "unused" warning if checksums are disabled
CKSUM_RESET(cksum); CKSUM_RESET(cksum);
@@ -679,13 +822,13 @@ static inline size_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF_M
* @param cksum - checksum variable, used for all calls to TF_ComposeBody. Must be reset before first use! (CKSUM_RESET(cksum);) * @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 * @return nr of bytes in outbuff used
*/ */
static size_t _TF_FN TF_ComposeBody(uint8_t *outbuff, static inline uint32_t _TF_FN TF_ComposeBody(uint8_t *outbuff,
const uint8_t *data, TF_LEN data_len, const uint8_t *data, TF_LEN data_len,
TF_CKSUM *cksum) TF_CKSUM *cksum)
{ {
TF_LEN i = 0; TF_LEN i = 0;
uint8_t b = 0; uint8_t b = 0;
size_t pos = 0; uint32_t pos = 0;
for (i = 0; i < data_len; i++) { for (i = 0; i < data_len; i++) {
b = data[i]; b = data[i];
@@ -703,11 +846,11 @@ static size_t _TF_FN TF_ComposeBody(uint8_t *outbuff,
* @param cksum - checksum variable used for the body * @param cksum - checksum variable used for the body
* @return nr of bytes in outbuff used * @return nr of bytes in outbuff used
*/ */
static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum) static inline uint32_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
{ {
int8_t si = 0; // signed small int int8_t si = 0; // signed small int
uint8_t b = 0; uint8_t b = 0;
size_t pos = 0; uint32_t pos = 0;
#if TF_CKSUM_TYPE != TF_CKSUM_NONE #if TF_CKSUM_TYPE != TF_CKSUM_NONE
CKSUM_FINALIZE(*cksum); CKSUM_FINALIZE(*cksum);
@@ -716,6 +859,83 @@ static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
return pos; return pos;
} }
/**
* Begin building and sending a frame
*
* @param tf - instance
* @param msg - message to send
* @param listener - response listener or NULL
* @param timeout - listener timeout ticks, 0 = indefinite
* @return success (mutex claimed and listener added, if any)
*/
static bool _TF_FN TF_SendFrame_Begin(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{
TF_TRY(TF_ClaimTx(tf));
tf->tx_pos = (uint32_t) TF_ComposeHead(tf, tf->sendbuf, msg); // frame ID is incremented here if it's not a response
tf->tx_len = msg->len;
if (listener) {
TF_TRY(TF_AddIdListener(tf, msg, listener, timeout));
}
CKSUM_RESET(tf->tx_cksum);
return true;
}
/**
* Build and send a part (or all) of a frame body.
* Caution: this does not check the total length against the length specified in the frame head
*
* @param tf - instance
* @param buff - bytes to write
* @param length - count
*/
static void _TF_FN TF_SendFrame_Chunk(TinyFrame *tf, const uint8_t *buff, uint32_t length)
{
uint32_t remain;
uint32_t chunk;
uint32_t sent = 0;
remain = length;
while (remain > 0) {
// Write what can fit in the tx buffer
chunk = TF_MIN(TF_SENDBUF_LEN - tf->tx_pos, remain);
tf->tx_pos += TF_ComposeBody(tf->sendbuf+tf->tx_pos, buff+sent, (TF_LEN) chunk, &tf->tx_cksum);
remain -= chunk;
sent += chunk;
// Flush if the buffer is full
if (tf->tx_pos == TF_SENDBUF_LEN) {
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos);
tf->tx_pos = 0;
}
}
}
/**
* End a multi-part frame. This sends the checksum and releases mutex.
*
* @param tf - instance
*/
static void _TF_FN TF_SendFrame_End(TinyFrame *tf)
{
// Checksum only if message had a body
if (tf->tx_len > 0) {
// Flush if checksum wouldn't fit in the buffer
if (TF_SENDBUF_LEN - tf->tx_pos < sizeof(TF_CKSUM)) {
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos);
tf->tx_pos = 0;
}
// Add checksum, flush what remains to be sent
tf->tx_pos += TF_ComposeTail(tf->sendbuf + tf->tx_pos, &tf->tx_cksum);
}
TF_WriteImpl(tf, (const uint8_t *) tf->sendbuf, tf->tx_pos);
TF_ReleaseTx(tf);
}
/** /**
* Send a message * Send a message
* *
@@ -727,47 +947,22 @@ static size_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum)
*/ */
static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout) static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{ {
size_t len = 0; TF_TRY(TF_SendFrame_Begin(tf, msg, listener, timeout));
size_t remain = 0; if (msg->len == 0 || msg->data != NULL) {
size_t sent = 0; // Send the payload and checksum only if we're not starting a multi-part frame.
TF_CKSUM cksum = 0; // A multi-part frame is identified by passing NULL to the data field and setting the length.
// User then needs to call those functions manually
TF_ClaimTx(tf); TF_SendFrame_Chunk(tf, msg->data, msg->len);
TF_SendFrame_End(tf);
len = TF_ComposeHead(tf, tf->sendbuf, msg);
if (listener) TF_AddIdListener(tf, 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(tf, (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(tf, (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(tf, (const uint8_t *) tf->sendbuf, len);
TF_ReleaseTx(tf);
return true; return true;
} }
//endregion Compose and send
//region Sending API funcs
/** send without listener */ /** send without listener */
bool _TF_FN TF_Send(TinyFrame *tf, TF_Msg *msg) bool _TF_FN TF_Send(TinyFrame *tf, TF_Msg *msg)
{ {
@@ -809,26 +1004,56 @@ bool _TF_FN TF_Respond(TinyFrame *tf, TF_Msg *msg)
return TF_Send(tf, msg); return TF_Send(tf, msg);
} }
/** Externally renew an ID listener */ //endregion Sending API funcs
bool _TF_FN TF_RenewIdListener(TinyFrame *tf, TF_ID id)
//region Sending API funcs - multipart
bool _TF_FN TF_Send_Multipart(TinyFrame *tf, TF_Msg *msg)
{ {
TF_COUNT i; msg->data = NULL;
struct TF_IdListener_ *lst; return TF_Send(tf, msg);
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 false;
} }
bool _TF_FN TF_SendSimple_Multipart(TinyFrame *tf, TF_TYPE type, TF_LEN len)
{
return TF_SendSimple(tf, type, NULL, len);
}
bool _TF_FN TF_QuerySimple_Multipart(TinyFrame *tf, TF_TYPE type, TF_LEN len, TF_Listener listener, TF_TICKS timeout)
{
return TF_QuerySimple(tf, type, NULL, len, listener, timeout);
}
bool _TF_FN TF_Query_Multipart(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout)
{
msg->data = NULL;
return TF_Query(tf, msg, listener, timeout);
}
void _TF_FN TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg)
{
msg->data = NULL;
TF_Respond(tf, msg);
}
void _TF_FN TF_Multipart_Payload(TinyFrame *tf, const uint8_t *buff, uint32_t length)
{
TF_SendFrame_Chunk(tf, buff, length);
}
void _TF_FN TF_Multipart_Close(TinyFrame *tf)
{
TF_SendFrame_End(tf);
}
//endregion Sending API funcs - multipart
/** Timebase hook - for timeouts */ /** Timebase hook - for timeouts */
void _TF_FN TF_Tick(TinyFrame *tf) void _TF_FN TF_Tick(TinyFrame *tf)
{ {
TF_COUNT i = 0; TF_COUNT i;
struct TF_IdListener_ *lst; struct TF_IdListener_ *lst;
// increment parser timeout (timeout is handled when receiving next byte) // increment parser timeout (timeout is handled when receiving next byte)
@@ -842,24 +1067,9 @@ void _TF_FN TF_Tick(TinyFrame *tf)
if (!lst->fn || lst->timeout == 0) continue; if (!lst->fn || lst->timeout == 0) continue;
// count down... // count down...
if (--lst->timeout == 0) { if (--lst->timeout == 0) {
TF_Error("ID listener %d has expired", (int)lst->id);
// Listener has expired // Listener has expired
cleanup_id_listener(tf, i, lst); cleanup_id_listener(tf, i, lst);
} }
} }
} }
/** Default impl for claiming write mutex; can be specific to the instance */
void __attribute__((weak)) TF_ClaimTx(TinyFrame *tf)
{
(void) tf;
// do nothing
}
/** Default impl for releasing write mutex; can be specific to the instance */
void __attribute__((weak)) TF_ReleaseTx(TinyFrame *tf)
{
(void) tf;
// do nothing
}
+357 -215
View File
@@ -4,13 +4,13 @@
/** /**
* TinyFrame protocol library * TinyFrame protocol library
* *
* (c) Ondřej Hruška 2017, MIT License * (c) Ondřej Hruška 2017-2018, MIT License
* no liability/warranty, free for any use, must retain this notice & license * no liability/warranty, free for any use, must retain this notice & license
* *
* Upstream URL: https://github.com/MightyPork/TinyFrame * Upstream URL: https://github.com/MightyPork/TinyFrame
*/ */
#define TF_VERSION "2.0.1" #define TF_VERSION "2.3.0"
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <stdint.h> // for uint8_t etc #include <stdint.h> // for uint8_t etc
@@ -19,78 +19,77 @@
#include <string.h> // for memset() #include <string.h> // for memset()
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32) // Checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32)
#define TF_CKSUM_NONE 0 #define TF_CKSUM_NONE 0 // no checksums
#define TF_CKSUM_XOR 8 #define TF_CKSUM_XOR 8 // inverted xor of all payload bytes
#define TF_CKSUM_CRC16 16 #define TF_CKSUM_CRC8 9 // Dallas/Maxim CRC8 (1-wire)
#define TF_CKSUM_CRC32 32 #define TF_CKSUM_CRC16 16 // CRC16 with the polynomial 0x8005 (x^16 + x^15 + x^2 + 1)
#define TF_CKSUM_CRC32 32 // CRC32 with the polynomial 0xedb88320
#define TF_CKSUM_CUSTOM8 1 // Custom 8-bit checksum
#define TF_CKSUM_CUSTOM16 2 // Custom 16-bit checksum
#define TF_CKSUM_CUSTOM32 3 // Custom 32-bit checksum
#include "TF_Config.h" #include "TF_Config.h"
//region Resolve data types //region Resolve data types
#if TF_LEN_BYTES == 1 #if TF_LEN_BYTES == 1
typedef uint8_t TF_LEN; typedef uint8_t TF_LEN;
#elif TF_LEN_BYTES == 2 #elif TF_LEN_BYTES == 2
typedef uint16_t TF_LEN; typedef uint16_t TF_LEN;
#elif TF_LEN_BYTES == 4 #elif TF_LEN_BYTES == 4
typedef uint32_t TF_LEN; typedef uint32_t TF_LEN;
#else #else
#error Bad value of TF_LEN_BYTES, must be 1, 2 or 4 #error Bad value of TF_LEN_BYTES, must be 1, 2 or 4
#endif #endif
#if TF_TYPE_BYTES == 1 #if TF_TYPE_BYTES == 1
typedef uint8_t TF_TYPE; typedef uint8_t TF_TYPE;
#elif TF_TYPE_BYTES == 2 #elif TF_TYPE_BYTES == 2
typedef uint16_t TF_TYPE; typedef uint16_t TF_TYPE;
#elif TF_TYPE_BYTES == 4 #elif TF_TYPE_BYTES == 4
typedef uint32_t TF_TYPE; typedef uint32_t TF_TYPE;
#else #else
#error Bad value of TF_TYPE_BYTES, must be 1, 2 or 4 #error Bad value of TF_TYPE_BYTES, must be 1, 2 or 4
#endif #endif
#if TF_ID_BYTES == 1 #if TF_ID_BYTES == 1
typedef uint8_t TF_ID; typedef uint8_t TF_ID;
#elif TF_ID_BYTES == 2 #elif TF_ID_BYTES == 2
typedef uint16_t TF_ID; typedef uint16_t TF_ID;
#elif TF_ID_BYTES == 4 #elif TF_ID_BYTES == 4
typedef uint32_t TF_ID; typedef uint32_t TF_ID;
#else #else
#error Bad value of TF_ID_BYTES, must be 1, 2 or 4 #error Bad value of TF_ID_BYTES, must be 1, 2 or 4
#endif #endif
#if TF_CKSUM_TYPE == TF_CKSUM_XOR || TF_CKSUM_TYPE == TF_CKSUM_NONE #if (TF_CKSUM_TYPE == TF_CKSUM_XOR) || (TF_CKSUM_TYPE == TF_CKSUM_NONE) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM8) || (TF_CKSUM_TYPE == TF_CKSUM_CRC8)
// ~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 == TF_CKSUM_CRC16 #elif (TF_CKSUM_TYPE == TF_CKSUM_CRC16) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM16)
// CRC16 // CRC16
typedef uint16_t TF_CKSUM; typedef uint16_t TF_CKSUM;
#elif TF_CKSUM_TYPE == TF_CKSUM_CRC32 #elif (TF_CKSUM_TYPE == TF_CKSUM_CRC32) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM32)
// CRC32 // CRC32
typedef uint32_t TF_CKSUM; typedef uint32_t TF_CKSUM;
#else #else
#error Bad value for TF_CKSUM_TYPE, must be 8, 16 or 32 #error Bad value for TF_CKSUM_TYPE
#endif #endif
//endregion //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) */ /** Peer bit enum (used for init) */
typedef enum { typedef enum {
TF_SLAVE = 0, TF_SLAVE = 0,
TF_MASTER = 1, TF_MASTER = 1,
} TF_Peer; } TF_Peer;
/** Response from listeners */ /** Response from listeners */
typedef enum { typedef enum {
TF_NEXT = 0, //!< Not handled, let other listeners handle it TF_NEXT = 0, //!< Not handled, let other listeners handle it
@@ -99,43 +98,301 @@ typedef enum {
TF_CLOSE = 3, //!< Handled, remove self TF_CLOSE = 3, //!< Handled, remove self
} TF_Result; } TF_Result;
/** Data structure for sending / receiving messages */ /** Data structure for sending / receiving messages */
typedef struct _TF_MSG_STRUCT_ { typedef struct TF_Msg_ {
TF_ID frame_id; //!< message ID TF_ID frame_id; //!< message ID
bool is_response; //!< internal flag, set when using the Respond function. frame_id is then kept unchanged. 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 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 * Buffer of received data, or data to send.
*
* - If (data == NULL) in an ID listener, that means the listener timed out and
* the user should free any userdata and take other appropriate actions.
*
* - If (data == NULL) and length is not zero when sending a frame, that starts a multi-part frame.
* This call then must be followed by sending the payload and closing the frame.
*/
const uint8_t *data;
TF_LEN len; //!< length of the payload
/**
* Custom user data for the ID listener.
*
* This data will be stored in the listener slot and passed to the ID callback
* via those same fields on the received message.
*/
void *userdata;
void *userdata2; void *userdata2;
} TF_Msg; } TF_Msg;
/** /**
* Clear message struct * Clear message struct
*
* @param msg - message to clear in-place
*/ */
static inline void TF_ClearMsg(TF_Msg *msg) static inline void TF_ClearMsg(TF_Msg *msg)
{ {
memset(msg, 0, sizeof(TF_Msg)); memset(msg, 0, sizeof(TF_Msg));
} }
/** TinyFrame struct typedef */
typedef struct TinyFrame_ TinyFrame; typedef struct TinyFrame_ TinyFrame;
/** /**
* TinyFrame Type Listener callback * TinyFrame Type Listener callback
* *
* @param frame_id - ID of the received frame * @param tf - instance
* @param type - type field from the message * @param msg - the received message, userdata is populated inside the object
* @param data - byte buffer with the application data
* @param len - number of bytes in the buffer
* @return listener result * @return listener result
*/ */
typedef TF_Result (*TF_Listener)(TinyFrame *tf, TF_Msg *msg); typedef TF_Result (*TF_Listener)(TinyFrame *tf, TF_Msg *msg);
// -------------------------------------------------------------------
// region Internal // ---------------------------------- INIT ------------------------------
enum TFState_ { /**
* Initialize the TinyFrame engine.
* 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 tf - instance
* @param peer_bit - peer bit to use for self
* @return TF instance or NULL
*/
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 tf - instance
* @param peer_bit - peer bit to use for self
* @return success
*/
bool TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit);
/**
* De-init the dynamically allocated TF instance
*
* @param tf - instance
*/
void TF_DeInit(TinyFrame *tf);
// ---------------------------------- API CALLS --------------------------------------
/**
* Accept incoming bytes & parse frames
*
* @param tf - instance
* @param buffer - byte buffer to process
* @param count - nr of bytes in the buffer
*/
void TF_Accept(TinyFrame *tf, const uint8_t *buffer, uint32_t count);
/**
* Accept a single incoming byte
*
* @param tf - instance
* @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.
* It's also used to expire ID listeners if a timeout is set when registering them.
*
* A common place to call this from is the SysTick handler.
*
* @param tf - instance
*/
void TF_Tick(TinyFrame *tf);
/**
* Reset the frame parser state machine.
* This does not affect registered listeners.
*
* @param tf - instance
*/
void TF_ResetParser(TinyFrame *tf);
// ---------------------------- MESSAGE LISTENERS -------------------------------
/**
* Register a frame type listener.
*
* @param tf - instance
* @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(TinyFrame *tf, TF_Msg *msg, TF_Listener cb, TF_TICKS timeout);
/**
* Remove a listener by the message ID it's registered for
*
* @param tf - instance
* @param frame_id - the frame we're listening for
*/
bool TF_RemoveIdListener(TinyFrame *tf, TF_ID frame_id);
/**
* Register a frame type listener.
*
* @param tf - instance
* @param frame_type - frame type to listen for
* @param cb - callback
* @return slot index (for removing), or TF_ERROR (-1)
*/
bool TF_AddTypeListener(TinyFrame *tf, TF_TYPE frame_type, TF_Listener cb);
/**
* Remove a listener by type.
*
* @param tf - instance
* @param type - the type it's registered for
*/
bool TF_RemoveTypeListener(TinyFrame *tf, TF_TYPE type);
/**
* Register a generic listener.
*
* @param tf - instance
* @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 tf - instance
* @param cb - callback function to remove
*/
bool TF_RemoveGenericListener(TinyFrame *tf, TF_Listener cb);
/**
* Renew an ID listener timeout externally (as opposed to by returning TF_RENEW from the ID listener)
*
* @param tf - instance
* @param id - listener ID to renew
* @return true if listener was found and renewed
*/
bool TF_RenewIdListener(TinyFrame *tf, TF_ID id);
// ---------------------------- FRAME TX FUNCTIONS ------------------------------
/**
* Send a frame, no listener
*
* @param tf - instance
* @param msg - message struct. ID is stored in the frame_id field
* @return success
*/
bool TF_Send(TinyFrame *tf, TF_Msg *msg);
/**
* 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 tf - instance
* @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(TinyFrame *tf, TF_Msg *msg,
TF_Listener listener, TF_TICKS timeout);
/**
* Like TF_Query(), but without the struct
*/
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 tf - instance
* @param msg - message struct. ID is read from frame_id. set ->renew to reset listener timeout
* @return success
*/
bool TF_Respond(TinyFrame *tf, TF_Msg *msg);
// ------------------------ MULTIPART FRAME TX FUNCTIONS -----------------------------
// Those routines are used to send long frames without having all the data available
// at once (e.g. capturing it from a peripheral or reading from a large memory buffer)
/**
* TF_Send() with multipart payload.
* msg.data is ignored and set to NULL
*/
bool TF_Send_Multipart(TinyFrame *tf, TF_Msg *msg);
/**
* TF_SendSimple() with multipart payload.
*/
bool TF_SendSimple_Multipart(TinyFrame *tf, TF_TYPE type, TF_LEN len);
/**
* TF_QuerySimple() with multipart payload.
*/
bool TF_QuerySimple_Multipart(TinyFrame *tf, TF_TYPE type, TF_LEN len, TF_Listener listener, TF_TICKS timeout);
/**
* TF_Query() with multipart payload.
* msg.data is ignored and set to NULL
*/
bool TF_Query_Multipart(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout);
/**
* TF_Respond() with multipart payload.
* msg.data is ignored and set to NULL
*/
void TF_Respond_Multipart(TinyFrame *tf, TF_Msg *msg);
/**
* Send the payload for a started multipart frame. This can be called multiple times
* if needed, until the full length is transmitted.
*
* @param tf - instance
* @param buff - buffer to send bytes from
* @param length - number of bytes to send
*/
void TF_Multipart_Payload(TinyFrame *tf, const uint8_t *buff, uint32_t length);
/**
* Close the multipart message, generating chekcsum and releasing the Tx lock.
*
* @param tf - instance
*/
void TF_Multipart_Close(TinyFrame *tf);
// ---------------------------------- INTERNAL ----------------------------------
// This is publicly visible only to allow static init.
enum TF_State_ {
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
TFState_HEAD_CKSUM, //!< Wait for header Checksum TFState_HEAD_CKSUM, //!< Wait for header Checksum
@@ -149,7 +406,7 @@ struct TF_IdListener_ {
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; // nr of ticks remaining to disable this listener
TF_TICKS timeout_max; // the original timeout is stored here TF_TICKS timeout_max; // the original timeout is stored here (0 = no timeout)
void *userdata; void *userdata;
void *userdata2; void *userdata2;
}; };
@@ -178,7 +435,7 @@ struct TinyFrame_ {
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 TF_State_ state;
TF_TICKS 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
@@ -189,6 +446,18 @@ struct TinyFrame_ {
TF_TYPE type; //!< Collected message type number TF_TYPE type; //!< Collected message type number
bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data. bool discard_data; //!< Set if (len > TF_MAX_PAYLOAD) to read the frame, but ignore the data.
/* Tx state */
// Buffer for building frames
uint8_t sendbuf[TF_SENDBUF_LEN]; //!< Transmit temporary buffer
uint32_t tx_pos; //!< Next write position in the Tx buffer (used for multipart)
uint32_t tx_len; //!< Total expected Tx length
TF_CKSUM tx_cksum; //!< Transmit checksum accumulator
#if !TF_USE_MUTEX
bool soft_lock; //!< Tx lock flag used if the mutex feature is not enabled.
#endif
/* --- Callbacks --- */ /* --- Callbacks --- */
/* Transaction callbacks */ /* Transaction callbacks */
@@ -202,183 +471,56 @@ struct TinyFrame_ {
TF_COUNT count_id_lst; TF_COUNT count_id_lst;
TF_COUNT count_type_lst; TF_COUNT count_type_lst;
TF_COUNT count_generic_lst; TF_COUNT count_generic_lst;
// Buffer for building frames
uint8_t sendbuf[TF_SENDBUF_LEN];
}; };
// endregion
// ------------------------------------------------------------------- // ------------------------ TO BE IMPLEMENTED BY USER ------------------------
/**
* Initialize the TinyFrame engine.
* 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
*/
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(TinyFrame *tf);
/**
* 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(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)
*/
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
*/
bool TF_RemoveTypeListener(TinyFrame *tf, TF_TYPE type);
/**
* 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
*/
bool TF_RemoveGenericListener(TinyFrame *tf, 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(TinyFrame *tf, TF_Msg *msg);
/**
* 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 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(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout);
/**
* Like TF_Query, but without the struct
*/
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 msg - message struct. ID is read from frame_id. set ->renew to reset listener timeout
* @return success
*/
bool TF_Respond(TinyFrame *tf, TF_Msg *msg);
/**
* 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
*/
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 * 'Write bytes' function that sends data to UART
* *
* ! Implement this in your application code ! * ! Implement this in your application code !
*/ */
extern void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len); extern void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len);
/** Claim the TX interface before composing and sending a frame */ // Mutex functions
extern void TF_ClaimTx(TinyFrame *tf); #if TF_USE_MUTEX
/** Free the TX interface after composing and sending a frame */ /** Claim the TX interface before composing and sending a frame */
extern void TF_ReleaseTx(TinyFrame *tf); extern bool TF_ClaimTx(TinyFrame *tf);
/** Free the TX interface after composing and sending a frame */
extern void TF_ReleaseTx(TinyFrame *tf);
#endif
// Custom checksum functions
#if (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM8) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM16) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM32)
/**
* Initialize a checksum
*
* @return initial checksum value
*/
extern TF_CKSUM TF_CksumStart(void);
/**
* Update a checksum with a byte
*
* @param cksum - previous checksum value
* @param byte - byte to add
* @return updated checksum value
*/
extern TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte);
/**
* Finalize the checksum calculation
*
* @param cksum - previous checksum value
* @return final checksum value
*/
extern TF_CKSUM TF_CksumEnd(TF_CKSUM cksum);
#endif
#endif #endif
+1 -1
View File
@@ -39,7 +39,7 @@ void demo_disconn(void)
* @param buff * @param buff
* @param len * @param len
*/ */
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len) void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{ {
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len); dumpFrame(buff, len);
+3
View File
@@ -6,6 +6,7 @@
#define TF_CONFIG_H #define TF_CONFIG_H
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#define TF_ID_BYTES 1 #define TF_ID_BYTES 1
#define TF_LEN_BYTES 2 #define TF_LEN_BYTES 2
@@ -22,4 +23,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5 #define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10 #define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H #endif //TF_CONFIG_H
+21 -3
View File
@@ -5,18 +5,27 @@
TinyFrame *demo_tf; TinyFrame *demo_tf;
bool do_corrupt = false;
/** /**
* This function should be defined in the application code. * This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other) * It implements the lowest layer - sending bytes to UART (or other)
*/ */
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len) void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{ {
printf("--------------------\n"); printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
dumpFrame(buff, len);
uint8_t *xbuff = (uint8_t *)buff;
if (do_corrupt) {
printf("(corrupting to test checksum checking...)\n");
xbuff[8]++;
}
dumpFrame(xbuff, len);
// Send it back as if we received it // Send it back as if we received it
TF_Accept(tf, buff, len); TF_Accept(tf, xbuff, len);
} }
/** An example listener function */ /** An example listener function */
@@ -63,4 +72,13 @@ void main(void)
msg.len = 0; msg.len = 0;
msg.type = 0x77; msg.type = 0x77;
TF_Query(demo_tf, &msg, testIdListener, 0); TF_Query(demo_tf, &msg, testIdListener, 0);
printf("This should fail:\n");
// test checksums are tested
do_corrupt = true;
msg.type = 0x44;
msg.data = (pu8) "Hello2";
msg.len = 7;
TF_Send(demo_tf, &msg);
} }
@@ -1,12 +1,11 @@
CFILES=../utils.c ../../TinyFrame.c CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../.. INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wno-unused -Wextra $(CFILES) $(INCLDIRS) CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wno-unused -Wall -Wextra $(CFILES) $(INCLDIRS)
build: test.bin
run: test.bin run: test.bin
./test.bin ./test.bin
build: test.bin
test.bin: test.c $(CFILES) test.bin: test.c $(CFILES)
gcc test.c $(CFLAGS) -o test.bin gcc test.c $(CFLAGS) -o test.bin
+28
View File
@@ -0,0 +1,28 @@
//
// Created by MightyPork on 2018/01/26.
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
#include <stdio.h>
#define TF_ID_BYTES 1
#define TF_LEN_BYTES 2
#define TF_TYPE_BYTES 1
#define TF_CKSUM_TYPE TF_CKSUM_CUSTOM8
#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
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H
+88
View File
@@ -0,0 +1,88 @@
#include <stdio.h>
#include <string.h>
#include "../../TinyFrame.h"
#include "../utils.h"
TinyFrame *demo_tf;
bool do_corrupt = false;
/**
* 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, uint32_t len)
{
printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
uint8_t *xbuff = (uint8_t *)buff;
if (do_corrupt) {
printf("(corrupting to test checksum checking...)\n");
xbuff[8]++;
}
dumpFrame(xbuff, len);
// Send it back as if we received it
TF_Accept(tf, xbuff, 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);
printf("This should fail:\n");
// test checksums are tested
do_corrupt = true;
msg.type = 0x44;
msg.data = (pu8) "Hello2";
msg.len = 7;
TF_Send(demo_tf, &msg);
}
// a made up custom checksum - just to test it's used and works
TF_CKSUM TF_CksumStart(void)
{
return 0;
}
TF_CKSUM TF_CksumAdd(TF_CKSUM cksum, uint8_t byte)
{
return cksum ^ byte + 1;
}
TF_CKSUM TF_CksumEnd(TF_CKSUM cksum)
{
return ~cksum ^ 0xA5;
}
+12
View File
@@ -0,0 +1,12 @@
CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS)
build: test.bin
run: test.bin
./test.bin
test.bin: test.c $(CFILES)
gcc test.c $(CFLAGS) -o test.bin
@@ -6,6 +6,7 @@
#define TF_CONFIG_H #define TF_CONFIG_H
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#define TF_ID_BYTES 1 #define TF_ID_BYTES 1
#define TF_LEN_BYTES 2 #define TF_LEN_BYTES 2
@@ -22,4 +23,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5 #define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10 #define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H #endif //TF_CONFIG_H
@@ -11,7 +11,7 @@ extern const char *romeo;
* This function should be defined in the application code. * This function should be defined in the application code.
* It implements the lowest layer - sending bytes to UART (or other) * It implements the lowest layer - sending bytes to UART (or other)
*/ */
void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len) void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t len)
{ {
printf("--------------------\n"); printf("--------------------\n");
printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n");
@@ -24,6 +24,7 @@ void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, size_t len)
/** An example listener function */ /** An example listener function */
TF_Result myListener(TinyFrame *tf, TF_Msg *msg) TF_Result myListener(TinyFrame *tf, TF_Msg *msg)
{ {
(void)tf;
dumpFrameInfo(msg); dumpFrameInfo(msg);
if (strcmp((const char *) msg->data, romeo) == 0) { if (strcmp((const char *) msg->data, romeo) == 0) {
printf("FILE TRANSFERRED OK!\r\n"); printf("FILE TRANSFERRED OK!\r\n");
+12
View File
@@ -0,0 +1,12 @@
CFILES=../utils.c ../../TinyFrame.c
INCLDIRS=-I. -I.. -I../..
CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS)
build: test.bin
run: test.bin
./test.bin
test.bin: test.c $(CFILES)
gcc test.c $(CFLAGS) -o test.bin
+28
View File
@@ -0,0 +1,28 @@
//
// Created by MightyPork on 2018/01/26.
//
#ifndef TF_CONFIG_H
#define TF_CONFIG_H
#include <stdint.h>
#include <stdio.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
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H
+322
View File
@@ -0,0 +1,322 @@
#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, uint32_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)
{
(void)tf;
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");
// We prepare a message without .data but with a set .len
TF_ClearMsg(&msg);
msg.type = 0x22;
msg.len = (TF_LEN) strlen(romeo);
// Start the multipart frame
TF_Send_Multipart(demo_tf, &msg);
// Now we send the payload in as many pieces as we like.
// Careful - TF transmitter is locked until we close the multipart frame
uint32_t remain = strlen(romeo);
const uint8_t* toSend = (const uint8_t*)romeo;
while (remain > 0) {
uint32_t chunk = (remain>16) ? 16 : remain;
// Send a piece
TF_Multipart_Payload(demo_tf, toSend, chunk);
remain -= chunk;
toSend += chunk;
}
// Done, close
TF_Multipart_Close(demo_tf);
}
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";
+3
View File
@@ -6,6 +6,7 @@
#define TF_CONFIG_H #define TF_CONFIG_H
#include <stdint.h> #include <stdint.h>
#include <stdio.h>
#define TF_ID_BYTES 1 #define TF_ID_BYTES 1
#define TF_LEN_BYTES 2 #define TF_LEN_BYTES 2
@@ -22,4 +23,6 @@ typedef uint8_t TF_COUNT;
#define TF_MAX_GEN_LST 5 #define TF_MAX_GEN_LST 5
#define TF_PARSER_TIMEOUT_TICKS 10 #define TF_PARSER_TIMEOUT_TICKS 10
#define TF_Error(format, ...) printf("[TF] " format "\n", ##__VA_ARGS__)
#endif //TF_CONFIG_H #endif //TF_CONFIG_H