From 13c74ea6e64c635601d8fc20ab7910f1479e40a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 26 Jan 2018 11:39:31 +0100 Subject: [PATCH] Implemented multipart frame sending --- TinyFrame.c | 129 ++++-- TinyFrame.h | 373 +++++++++++------- .../Makefile | 2 +- .../TF_Config.h | 0 .../test.c | 1 + demo/simple_multipart/Makefile | 12 + demo/simple_multipart/TF_Config.h | 28 ++ demo/simple_multipart/test.c | 322 +++++++++++++++ 8 files changed, 701 insertions(+), 166 deletions(-) rename demo/{multipart_tx => simple_long_payload}/Makefile (66%) rename demo/{multipart_tx => simple_long_payload}/TF_Config.h (100%) rename demo/{multipart_tx => simple_long_payload}/test.c (99%) create mode 100644 demo/simple_multipart/Makefile create mode 100644 demo/simple_multipart/TF_Config.h create mode 100644 demo/simple_multipart/test.c diff --git a/TinyFrame.c b/TinyFrame.c index 883ab8b..3eb49e4 100644 --- a/TinyFrame.c +++ b/TinyFrame.c @@ -10,13 +10,16 @@ #define _TF_FN #endif + // Helper macros #define TF_MIN(a, b) ((a)<(b)?(a):(b)) #define TF_TRY(func) do { if(!(func)) return false; } while (0) -// TODO It would be nice to have per-instance configurable checksum types, but that would -// mandate configurable field sizes unless we use u32 everywhere (and possibly shorten -// it when encoding to the buffer). I don't really like this idea so much. -MP + +// 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. @@ -513,8 +516,29 @@ static void _TF_FN TF_HandleReceivedMessage(TinyFrame *tf) 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 + +//region Parser + /** Handle a received byte buffer */ void _TF_FN TF_Accept(TinyFrame *tf, const uint8_t *buffer, uint32_t count) { @@ -685,9 +709,15 @@ void _TF_FN TF_AcceptChar(TinyFrame *tf, unsigned char c) //@formatter:on } +//endregion Parser + + +//region Compose and send + // Helper macros for the Compose functions // use variables: si - signed int, b - byte, outbuff - target buffer, pos - count of bytes in buffer + /** * Write a number to the output buffer. * @@ -737,9 +767,9 @@ static inline uint32_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF uint8_t b = 0; TF_ID id = 0; TF_CKSUM cksum = 0; - uint32_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); @@ -786,7 +816,7 @@ static inline uint32_t _TF_FN TF_ComposeHead(TinyFrame *tf, uint8_t *outbuff, TF * @param cksum - checksum variable, used for all calls to TF_ComposeBody. Must be reset before first use! (CKSUM_RESET(cksum);) * @return nr of bytes in outbuff used */ -static uint32_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, TF_CKSUM *cksum) { @@ -810,7 +840,7 @@ static uint32_t _TF_FN TF_ComposeBody(uint8_t *outbuff, * @param cksum - checksum variable used for the body * @return nr of bytes in outbuff used */ -static uint32_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 uint8_t b = 0; @@ -824,7 +854,7 @@ static uint32_t _TF_FN TF_ComposeTail(uint8_t *outbuff, TF_CKSUM *cksum) } /** - * Begin building a frame + * Begin building and sending a frame * * @param tf - instance * @param msg - message to send @@ -847,6 +877,14 @@ static bool _TF_FN TF_SendFrame_Begin(TinyFrame *tf, TF_Msg *msg, TF_Listener li 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; @@ -869,6 +907,11 @@ static void _TF_FN TF_SendFrame_Chunk(TinyFrame *tf, const uint8_t *buff, uint32 } } +/** + * 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 @@ -899,11 +942,21 @@ static void _TF_FN TF_SendFrame_End(TinyFrame *tf) static bool _TF_FN TF_SendFrame(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout) { TF_TRY(TF_SendFrame_Begin(tf, msg, listener, timeout)); - TF_SendFrame_Chunk(tf, msg->data, msg->len); - TF_SendFrame_End(tf); + if (msg->len == 0 || msg->data != NULL) { + // Send the payload and checksum only if we're not starting a multi-part frame. + // 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_SendFrame_Chunk(tf, msg->data, msg->len); + TF_SendFrame_End(tf); + } return true; } +//endregion Compose and send + + +//region Sending API funcs + /** send without listener */ bool _TF_FN TF_Send(TinyFrame *tf, TF_Msg *msg) { @@ -945,24 +998,52 @@ bool _TF_FN TF_Respond(TinyFrame *tf, TF_Msg *msg) return TF_Send(tf, msg); } -/** Externally renew an ID listener */ -bool _TF_FN TF_RenewIdListener(TinyFrame *tf, TF_ID id) +//endregion Sending API funcs + + +//region Sending API funcs - multipart + +bool _TF_FN TF_Send_Multipart(TinyFrame *tf, TF_Msg *msg) { - 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; - } - } + msg->data = NULL; + return TF_Send(tf, msg); +} - TF_Error("Renew listener: not found (id %d)", (int)id); - 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 */ void _TF_FN TF_Tick(TinyFrame *tf) { diff --git a/TinyFrame.h b/TinyFrame.h index ad6507d..8b378e1 100644 --- a/TinyFrame.h +++ b/TinyFrame.h @@ -34,67 +34,62 @@ //region Resolve data types #if TF_LEN_BYTES == 1 -typedef uint8_t TF_LEN; + typedef uint8_t TF_LEN; #elif TF_LEN_BYTES == 2 -typedef uint16_t TF_LEN; + typedef uint16_t TF_LEN; #elif TF_LEN_BYTES == 4 -typedef uint32_t TF_LEN; + typedef uint32_t TF_LEN; #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 #if TF_TYPE_BYTES == 1 -typedef uint8_t TF_TYPE; + typedef uint8_t TF_TYPE; #elif TF_TYPE_BYTES == 2 -typedef uint16_t TF_TYPE; + typedef uint16_t TF_TYPE; #elif TF_TYPE_BYTES == 4 -typedef uint32_t TF_TYPE; + typedef uint32_t TF_TYPE; #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 #if TF_ID_BYTES == 1 -typedef uint8_t TF_ID; + typedef uint8_t TF_ID; #elif TF_ID_BYTES == 2 -typedef uint16_t TF_ID; + typedef uint16_t TF_ID; #elif TF_ID_BYTES == 4 -typedef uint32_t TF_ID; + typedef uint32_t TF_ID; #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 #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) -typedef uint8_t TF_CKSUM; + // ~XOR (if 0, still use 1 byte - it won't be used) + typedef uint8_t TF_CKSUM; #elif (TF_CKSUM_TYPE == TF_CKSUM_CRC16) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM16) -// CRC16 -typedef uint16_t TF_CKSUM; + // CRC16 + typedef uint16_t TF_CKSUM; #elif (TF_CKSUM_TYPE == TF_CKSUM_CRC32) || (TF_CKSUM_TYPE == TF_CKSUM_CUSTOM32) -// CRC32 -typedef uint32_t TF_CKSUM; + // CRC32 + typedef uint32_t TF_CKSUM; #else -#error Bad value for TF_CKSUM_TYPE + #error Bad value for TF_CKSUM_TYPE #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 @@ -103,126 +98,59 @@ typedef enum { TF_CLOSE = 3, //!< Handled, remove self } TF_Result; + /** Data structure for sending / receiving messages */ typedef struct TF_Msg_ { 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 + + /** + * 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; } TF_Msg; /** * Clear message struct + * + * @param msg - message to clear in-place */ static inline void TF_ClearMsg(TF_Msg *msg) { memset(msg, 0, sizeof(TF_Msg)); } +/** TinyFrame struct typedef */ 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 + * @param tf - instance + * @param msg - the received message, userdata is populated inside the object * @return listener result */ typedef TF_Result (*TF_Listener)(TinyFrame *tf, TF_Msg *msg); -// ------------------------------------------------------------------- -// region Internal - -enum TF_State_ { - 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 TF_State_ 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. - - /* Tx state */ - uint32_t tx_pos; - uint32_t tx_len; - TF_CKSUM tx_cksum; - -#if !TF_USE_MUTEX - bool soft_lock; //!< Lock used if the mutex feature is not enabled. -#endif - - /* --- 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 - -// ------------------------------------------------------------------- +// ---------------------------------- INIT ------------------------------ /** * Initialize the TinyFrame engine. @@ -234,6 +162,7 @@ struct TinyFrame_ { * 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 */ @@ -245,6 +174,7 @@ TinyFrame *TF_Init(TF_Peer peer_bit); * * 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 */ @@ -253,19 +183,57 @@ bool TF_InitStatic(TinyFrame *tf, TF_Peer peer_bit); /** * De-init the dynamically allocated TF instance * - * @param tf + * @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) @@ -276,6 +244,7 @@ bool TF_AddIdListener(TinyFrame *tf, TF_Msg *msg, TF_Listener cb, TF_TICKS timeo /** * 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); @@ -283,6 +252,7 @@ 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) @@ -292,6 +262,7 @@ 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); @@ -299,6 +270,7 @@ 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) */ @@ -307,13 +279,27 @@ 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 */ @@ -327,6 +313,7 @@ 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 @@ -336,7 +323,7 @@ bool TF_Query(TinyFrame *tf, TF_Msg *msg, TF_Listener listener, TF_TICKS timeout); /** - * Like TF_Query, but without the struct + * Like TF_Query(), but without the struct */ bool TF_QuerySimple(TinyFrame *tf, TF_TYPE type, const uint8_t *data, TF_LEN len, @@ -345,45 +332,149 @@ bool TF_QuerySimple(TinyFrame *tf, TF_TYPE type, /** * 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) + /** - * 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 + * TF_Send() with multipart payload. + * msg.data is ignored and set to NULL */ -bool TF_RenewIdListener(TinyFrame *tf, TF_ID id); +bool TF_Send_Multipart(TinyFrame *tf, TF_Msg *msg); /** - * Accept incoming bytes & parse frames - * - * @param buffer - byte buffer to process - * @param count - nr of bytes in the buffer + * TF_SendSimple() with multipart payload. */ -void TF_Accept(TinyFrame *tf, const uint8_t *buffer, uint32_t count); +bool TF_SendSimple_Multipart(TinyFrame *tf, TF_TYPE type, TF_LEN len); /** - * Accept a single incoming byte + * 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 c - a received char + * @param tf - instance + * @param buff - buffer to send bytes from + * @param length - number of bytes to send */ -void TF_AcceptChar(TinyFrame *tf, uint8_t c); +void TF_Multipart_Payload(TinyFrame *tf, const uint8_t *buff, uint32_t length); /** - * 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. + * Close the multipart message, generating chekcsum and releasing the Tx lock. * - * A common place to call this from is the SysTick handler. + * @param tf - instance */ -void TF_Tick(TinyFrame *tf); +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_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 (0 = no timeout) + 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 TF_State_ 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. + + /* 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 --- */ + + /* 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; +}; + -// --- TO BE IMPLEMENTED BY USER --- +// ------------------------ TO BE IMPLEMENTED BY USER ------------------------ /** * 'Write bytes' function that sends data to UART diff --git a/demo/multipart_tx/Makefile b/demo/simple_long_payload/Makefile similarity index 66% rename from demo/multipart_tx/Makefile rename to demo/simple_long_payload/Makefile index a132e80..800e28c 100644 --- a/demo/multipart_tx/Makefile +++ b/demo/simple_long_payload/Makefile @@ -1,6 +1,6 @@ CFILES=../utils.c ../../TinyFrame.c INCLDIRS=-I. -I.. -I../.. -CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wno-unused -Wextra $(CFILES) $(INCLDIRS) +CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS) build: test.bin diff --git a/demo/multipart_tx/TF_Config.h b/demo/simple_long_payload/TF_Config.h similarity index 100% rename from demo/multipart_tx/TF_Config.h rename to demo/simple_long_payload/TF_Config.h diff --git a/demo/multipart_tx/test.c b/demo/simple_long_payload/test.c similarity index 99% rename from demo/multipart_tx/test.c rename to demo/simple_long_payload/test.c index b6140f3..2681992 100644 --- a/demo/multipart_tx/test.c +++ b/demo/simple_long_payload/test.c @@ -24,6 +24,7 @@ void TF_WriteImpl(TinyFrame *tf, const uint8_t *buff, uint32_t 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"); diff --git a/demo/simple_multipart/Makefile b/demo/simple_multipart/Makefile new file mode 100644 index 0000000..800e28c --- /dev/null +++ b/demo/simple_multipart/Makefile @@ -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 diff --git a/demo/simple_multipart/TF_Config.h b/demo/simple_multipart/TF_Config.h new file mode 100644 index 0000000..071223e --- /dev/null +++ b/demo/simple_multipart/TF_Config.h @@ -0,0 +1,28 @@ +// +// Created by MightyPork on 2018/01/26. +// + +#ifndef TF_CONFIG_H +#define TF_CONFIG_H + +#include +#include + +#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 diff --git a/demo/simple_multipart/test.c b/demo/simple_multipart/test.c new file mode 100644 index 0000000..681ca75 --- /dev/null +++ b/demo/simple_multipart/test.c @@ -0,0 +1,322 @@ +#include +#include +#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";