diff --git a/.gitignore b/.gitignore index 6dc0790..be999a8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ *.i*86 *.x86_64 *.hex +*.bin # Debug files *.dSYM/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fbd81f..b644369 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,20 @@ project(tf) set(CMAKE_CXX_STANDARD GNU89) set(SOURCE_FILES - test.c + demo/test/test.c + demo/test/TF_Config.h + demo/hello/master.c + demo/hello/slave.c + demo/hello/TF_Config.h + demo/demo.c + demo/demo.h TinyFrame.c - TinyFrame.h) + TinyFrame.h + TF_Config.example.h + demo/utils.c + demo/utils.h + ) + +include_directories(demo/test) add_executable(tf ${SOURCE_FILES}) diff --git a/Makefile b/Makefile deleted file mode 100644 index 0124af5..0000000 --- a/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -build: tf.bin - -run: tf.bin - ./tf.bin - -debug: tf.bin - gdb -q -ex run ./tf.bin - -tf.bin: test.c TinyFrame.c TinyFrame.h - gcc -Os --std=gnu89 -Wall -Wno-main -Wno-unused -Wextra test.c TinyFrame.c -I. -o tf.bin diff --git a/README.md b/README.md index 0cea476..a483312 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ UDP packets. If you find a good use for it, please let me know so I can add it h 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 for each peer to avoid collisions. +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). +(the names "master" and "slave" are used only for convenience and have special meaning +in the demos). The library lets you register listeners (callback functions) to wait for (1) any frame, (2) -a particular frame Type, or (3) a specific message ID. This lets you easily implement asynchronous -communication. +a particular frame Type, or (3) a specific message ID. This high-level API lets you +easily implement various async communication patterns. ## Frame structure @@ -50,24 +51,20 @@ DATA_CKSUM .. checksum, implemented as XOR of all preceding bytes in the message - Both peers must include the library with the same parameters (configured at the top of the header file) - Start by calling `TF_Init()` with `TF_MASTER` or `TF_SLAVE` as the argument - Implement `TF_WriteImpl()` - declared at the bottom of the header file as `extern`. - This function is used by `TF_Send()` to write bytes to your UART (or other physical layer). + This function is used by `TF_Send()` and others to write bytes to your UART (or other physical layer). Presently, always a full frame is sent to this function. -- If you wish to use `TF_PARSER_TIMEOUT_TICKS`, periodically call `TF_Tick()`. The period - determines the length of 1 tick. This is used to time-out the parser in case it gets stuck - in a bad state (such as receiving a partial frame). +- If you wish to use timeouts, periodically call `TF_Tick()`. The calling period determines + the length of 1 tick. This is used to time-out the parser in case it gets stuck + in a bad state (such as receiving a partial frame) and can also time-out ID listeners. - Bind Type or Generic listeners using `TF_AddTypeListener()` or `TF_AddGenericListener()`. -- Send a message using `TF_Send()` or the other Send functions. - If you provide a listener callback (function pointer) to the function, - the listener will be added as an ID listener and wait for a response. +- Send a message using `TF_Send()`, `TF_Query()`, `TF_SendSimple()`, `TF_QuerySimple()`. + Query functions take a listener callback (function pointer)that will be added as + an ID listener and wait for a response. - To reply to a message (when your listener gets called), use `TF_Respond()` - with the same frame_id as in the received message. -- Remove the ID listener using `TF_RemoveIdListener()` when it's no longer - needed. (Same for other listener types.) The slot count is limited. -- If the listener function returns `false`, some other listener will get - a chance to handle it + with the msg boject you received, replacing the `data` pointer (and `len`) with response. - Manually reset the parser using `TF_ResetParser()` -### The concept of listeners +### Message listeners Listeners are callback functions that are called by TinyFrame when a message which they can handle is received. @@ -78,52 +75,14 @@ There are 3 listener types: - Type listeners - Generic listeners -They handle the message in this order, and if they decide not to handle it, they can return `false` -and let it be handled by some other listener, or discarded. - -### Implementing "synchronous query" - -Sometimes it's necessary to send a message and wait for a response to arrive. - -One (not too pretty) way to do this is using a global variable - pseudocode: - -```c -#define MSG_PING 42 - -static volatile bool got_response = false; - -/** ID listener */ -static bool onResponse(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len) -{ - // ... Do something ... - // (eg. copy data to a global variable) - - got_response = true; - return true; -} - -bool syncQuery(void) -{ - TF_ID id; - // Send our request, and bind an ID listener - got_response = false; - TF_Send0(MSG_PING, onResponse, &id); // Send0 sends zero bytes of data, just TYPE - - // the ID is now in `id` so we can remove the listener after a timeout - - // Wait for the response - bool suc = true; - while (!got_response) { - //delay() - if (/*timeout*/) { - TF_RemoveIdListener(id); // free the listener slot - return false; - } - } - - // ... Do something with the received data? ... - // (can be passed from the listener using a global variable) - - return true; -} -``` +Listeners return an enum constant based on what should be done next - remove the listener, +keep it, renew it's timeout, or let some other listener handle the message. + +### Examples + +You'll find various examples in the `demo/` folder. Each example has it's own Makefile, +read it to see what options are available. + +The demos are written for Linux, using sockets and `clone()` for background processing. +They try to simulate real TinyFrame behavior in an embedded system with asynchronous +Rx and Tx. If you can't run the demos, the source files are still good as examples. diff --git a/TF_Config.example.h b/TF_Config.example.h new file mode 100644 index 0000000..edbd372 --- /dev/null +++ b/TF_Config.example.h @@ -0,0 +1,70 @@ +// +// Rename to TF_Config.h +// + +#ifndef TF_CONFIG_H +#define TF_CONFIG_H + +#include +//#include // when using with esphttpd + +//----------------------------- FRAME FORMAT --------------------------------- +// The format can be adjusted to fit your particular application needs + +// If the connection is reliable, you can disable the SOF byte and checksums. +// That can save up to 9 bytes of overhead. + +// ,-----+----+-----+------+------------+- - - -+------------, +// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM | +// | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes) +// '-----+----+-----+------+------------+- - - -+------------' + +// !!! BOTH SIDES MUST USE THE SAME SETTINGS !!! + +// Adjust sizes as desired (1,2,4) +#define TF_ID_BYTES 1 +#define TF_LEN_BYTES 2 +#define TF_TYPE_BYTES 1 + +// Checksum type +//#define TF_CKSUM_TYPE TF_CKSUM_NONE +//#define TF_CKSUM_TYPE TF_CKSUM_XOR +#define TF_CKSUM_TYPE TF_CKSUM_CRC16 +//#define TF_CKSUM_TYPE TF_CKSUM_CRC32 + +// Use a SOF byte to mark the start of a frame +#define TF_USE_SOF_BYTE 1 +// Value of the SOF byte (if TF_USE_SOF_BYTE == 1) +#define TF_SOF_BYTE 0x01 + +//----------------------- PLATFORM COMPATIBILITY ---------------------------- + +// used for timeout tick counters - should be large enough for all used timeouts +typedef uint16_t TF_TICKS; + +// used in loops iterating over listeners +typedef uint8_t TF_COUNT; + +//----------------------------- PARAMETERS ---------------------------------- + +// Maximum send / receive payload size (static buffers size) +// Larger payloads will be rejected. +#define TF_MAX_PAYLOAD_RX 1024 +#define TF_MAX_PAYLOAD_TX 1024 + +// --- Listener counts - determine sizes of the static slot tables --- + +// Frame ID listeners (wait for response / multi-part message) +#define TF_MAX_ID_LST 10 +// Frame Type listeners (wait for frame with a specific first payload byte) +#define TF_MAX_TYPE_LST 10 +// Generic listeners (fallback if no other listener catches it) +#define TF_MAX_GEN_LST 5 + +// Timeout for receiving & parsing a frame +// ticks = number of calls to TF_Tick() +#define TF_PARSER_TIMEOUT_TICKS 10 + +//------------------------- End of user config ------------------------------ + +#endif //TF_CONFIG_H diff --git a/TinyFrame.c b/TinyFrame.c index 9ea3d4c..2a54faf 100644 --- a/TinyFrame.c +++ b/TinyFrame.c @@ -1,6 +1,7 @@ //--------------------------------------------------------------------------- #include "TinyFrame.h" #include +//#include "demo/utils.h" //--------------------------------------------------------------------------- // Compatibility with ESP8266 SDK @@ -20,18 +21,21 @@ enum TFState { TFState_DATA_CKSUM //!< Wait for Checksum }; -typedef struct _IdListener_struct { +typedef struct _IdListener_struct_ { TF_ID id; - TF_LISTENER fn; + TF_Listener fn; + TF_TICKS timeout; // nr of ticks remaining to disable this listener + TF_TICKS timeout_max; // the original timeout is stored here + void *userdata; } IdListener; typedef struct _TypeListener_struct_ { TF_TYPE type; - TF_LISTENER fn; + TF_Listener fn; } TypeListener; typedef struct _GenericListener_struct_ { - TF_LISTENER fn; + TF_Listener fn; } GenericListener; /** @@ -39,16 +43,16 @@ typedef struct _GenericListener_struct_ { */ static struct TinyFrameStruct { /* Own state */ - TF_PEER peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash) + TF_Peer peer_bit; //!< Own peer bit (unqiue to avoid msg ID clash) TF_ID next_id; //!< Next frame / frame chain ID /* Parser state */ enum TFState state; - int parser_timeout_ticks; + TF_TICKS parser_timeout_ticks; TF_ID id; //!< Incoming packet ID TF_LEN len; //!< Payload length - uint8_t data[TF_MAX_PAYLOAD]; //!< Data byte buffer - size_t rxi; //!< Byte counter + 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 @@ -61,140 +65,143 @@ static struct TinyFrameStruct { TypeListener type_listeners[TF_MAX_TYPE_LST]; GenericListener generic_listeners[TF_MAX_GEN_LST]; - size_t count_id_lst; - size_t count_type_lst; - size_t count_generic_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_MAX_PAYLOAD + TF_OVERHEAD_BYTES]; + uint8_t sendbuf[TF_MAX_PAYLOAD_TX + TF_OVERHEAD_BYTES]; // TODO generate and send frames without a buffer } tf; //region Checksums -#if TF_CKSUM_TYPE == 0 - -// NONE -#define CKSUM_RESET(cksum) -#define CKSUM_ADD(cksum, byte) -#define CKSUM_FINALIZE(cksum) - -#elif TF_CKSUM_TYPE == 8 - -// ~XOR -#define CKSUM_RESET(cksum) do { cksum = 0; } while (0) -#define CKSUM_ADD(cksum, byte) do { cksum ^= byte; } while(0) -#define CKSUM_FINALIZE(cksum) do { cksum = (TF_CKSUM)~cksum; } while(0) - -#elif TF_CKSUM_TYPE == 16 - -/** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */ -static const uint16_t crc16_table[256] = { - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, - 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, - 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, - 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, - 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, - 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, - 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, - 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, - 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, - 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, - 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, - 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, - 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, - 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, - 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, - 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, - 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, - 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, - 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, - 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, - 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, - 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, - 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, - 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, - 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, - 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, - 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, - 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, - 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, - 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 -}; - -static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte) -{ - return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff]; -} - -#define CKSUM_RESET(cksum) do { cksum = 0; } while (0) -#define CKSUM_ADD(cksum, byte) do { cksum = crc16_byte(cksum, byte); } while(0) -#define CKSUM_FINALIZE(cksum) - -#elif TF_CKSUM_TYPE == 32 - -static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, - 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, - 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, - 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, - 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, - 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, - 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, - 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, - 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, - 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, - 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, - 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, - 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, - 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, - 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, - 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, - 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, - 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -}; +#if TF_CKSUM_TYPE == TF_CKSUM_NONE + + // NONE + #define CKSUM_RESET(cksum) + #define CKSUM_ADD(cksum, byte) + #define CKSUM_FINALIZE(cksum) + +#elif TF_CKSUM_TYPE == TF_CKSUM_XOR + + // ~XOR + #define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0) + #define CKSUM_ADD(cksum, byte) do { (cksum) ^= (byte); } while(0) + #define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~cksum; } while(0) + +#elif TF_CKSUM_TYPE == TF_CKSUM_CRC16 + + /** CRC table for the CRC-16. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) */ + static const uint16_t crc16_table[256] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 + }; + + static inline uint16_t crc16_byte(uint16_t cksum, const uint8_t byte) + { + return (cksum >> 8) ^ crc16_table[(cksum ^ byte) & 0xff]; + } -static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte) -{ - return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8)); -} + #define CKSUM_RESET(cksum) do { (cksum) = 0; } while (0) + #define CKSUM_ADD(cksum, byte) do { (cksum) = crc16_byte((cksum), (byte)); } while(0) + #define CKSUM_FINALIZE(cksum) + +#elif TF_CKSUM_TYPE == TF_CKSUM_CRC32 + + static const uint32_t crc32_table[] = { /* CRC polynomial 0xedb88320 */ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + static inline uint32_t crc32_byte(uint32_t cksum, const uint8_t byte) + { + return (crc32_table[((cksum) ^ ((uint8_t)byte)) & 0xff] ^ ((cksum) >> 8)); + } -#define CKSUM_RESET(cksum) do { cksum = (TF_CKSUM)0xFFFFFFFF; } while (0) -#define CKSUM_ADD(cksum, byte) do { cksum = crc32_byte(cksum, byte); } while(0) -#define CKSUM_FINALIZE(cksum) do { cksum = (TF_CKSUM)~cksum; } while(0) + #define CKSUM_RESET(cksum) do { (cksum) = (TF_CKSUM)0xFFFFFFFF; } while (0) + #define CKSUM_ADD(cksum, byte) do { (cksum) = crc32_byte(cksum, byte); } while(0) + #define CKSUM_FINALIZE(cksum) do { (cksum) = (TF_CKSUM)~(cksum); } while(0) #endif //endregion -void _TF_FN TF_Init(TF_PEER peer_bit) +void _TF_FN TF_Init(TF_Peer peer_bit) { // Zero it out memset(&tf, 0, sizeof(struct TinyFrameStruct)); @@ -204,15 +211,75 @@ void _TF_FN TF_Init(TF_PEER peer_bit) //region Listeners -bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb) +static void _TF_FN renew_id_listener(IdListener *lst) { - size_t i; + lst->timeout = lst->timeout_max; +} + +/** + * Notify callback about ID listener demise & clean it + * + * @param lst - listener to clean + */ +static void _TF_FN cleanup_id_listener(TF_COUNT i, IdListener *lst) +{ + TF_Msg msg; + if (lst->fn == NULL) return; + + // Make user clean up their data - only if not NULL + if (lst->userdata != NULL) { + msg.userdata = lst->userdata; + msg.data = NULL; // this is a signal that the listener should clean up + lst->fn(&msg); // return value is ignored here - use TF_STAY or TF_CLOSE + } + + lst->fn = NULL; // Discard listener + + if (i == tf.count_id_lst - 1) { + tf.count_id_lst--; + } +} + +/** + * Clean up Type listener + * + * @param lst - listener to clean + */ +static inline void _TF_FN cleanup_type_listener(TF_COUNT i, TypeListener *lst) +{ + lst->fn = NULL; // Discard listener + if (i == tf.count_type_lst - 1) { + tf.count_type_lst--; + } +} + +/** + * Clean up Generic listener + * + * @param lst - listener to clean + */ +static inline void _TF_FN cleanup_generic_listener(TF_COUNT i, GenericListener *lst) +{ + lst->fn = NULL; // Discard listener + if (i == tf.count_generic_lst - 1) { + tf.count_generic_lst--; + } +} + +bool _TF_FN TF_AddIdListener(TF_Msg *msg, TF_Listener cb, TF_TICKS timeout) +{ + TF_COUNT i; + IdListener *lst; for (i = 0; i < TF_MAX_ID_LST; i++) { - if (tf.id_listeners[i].fn == NULL) { - tf.id_listeners[i].fn = cb; - tf.id_listeners[i].id = frame_id; + lst = &tf.id_listeners[i]; + // test for empty slot + if (lst->fn == NULL) { + lst->fn = cb; + lst->id = msg->frame_id; + lst->userdata = msg->userdata; + lst->timeout_max = lst->timeout = timeout; if (i >= tf.count_id_lst) { - tf.count_id_lst = i + 1; + tf.count_id_lst = (TF_COUNT) (i + 1); } return true; } @@ -220,15 +287,18 @@ bool _TF_FN TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb) return false; } -bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb) +bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_Listener cb) { - size_t i; + TF_COUNT i; + TypeListener *lst; for (i = 0; i < TF_MAX_TYPE_LST; i++) { - if (tf.type_listeners[i].fn == NULL) { - tf.type_listeners[i].fn = cb; - tf.type_listeners[i].type = frame_type; + lst = &tf.type_listeners[i]; + // test for empty slot + if (lst->fn == NULL) { + lst->fn = cb; + lst->type = frame_type; if (i >= tf.count_type_lst) { - tf.count_type_lst = i + 1; + tf.count_type_lst = (TF_COUNT) (i + 1); } return true; } @@ -236,14 +306,17 @@ bool _TF_FN TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb) return false; } -bool _TF_FN TF_AddGenericListener(TF_LISTENER cb) +bool _TF_FN TF_AddGenericListener(TF_Listener cb) { - size_t i; + TF_COUNT i; + GenericListener *lst; for (i = 0; i < TF_MAX_GEN_LST; i++) { - if (tf.generic_listeners[i].fn == NULL) { - tf.generic_listeners[i].fn = cb; + lst = &tf.generic_listeners[i]; + // test for empty slot + if (lst->fn == NULL) { + lst->fn = cb; if (i >= tf.count_generic_lst) { - tf.count_generic_lst = i + 1; + tf.count_generic_lst = (TF_COUNT) (i + 1); } return true; } @@ -253,14 +326,13 @@ bool _TF_FN TF_AddGenericListener(TF_LISTENER cb) bool _TF_FN TF_RemoveIdListener(TF_ID frame_id) { - size_t i; + TF_COUNT i; + IdListener *lst; for (i = 0; i < tf.count_id_lst; i++) { - if (tf.id_listeners[i].fn != NULL - && tf.id_listeners[i].id == frame_id) { - tf.id_listeners[i].fn = NULL; - if (i == tf.count_id_lst - 1) { - tf.count_id_lst--; - } + lst = &tf.id_listeners[i]; + // test if live & matching + if (lst->fn != NULL && lst->id == frame_id) { + cleanup_id_listener(i, lst); return true; } } @@ -269,29 +341,28 @@ bool _TF_FN TF_RemoveIdListener(TF_ID frame_id) bool _TF_FN TF_RemoveTypeListener(TF_TYPE type) { - size_t i; + TF_COUNT i; + TypeListener *lst; for (i = 0; i < tf.count_type_lst; i++) { - if (tf.type_listeners[i].fn != NULL - && tf.type_listeners[i].type == type) { - tf.type_listeners[i].fn = NULL; - if (i == tf.count_type_lst - 1) { - tf.count_type_lst--; - } + lst = &tf.type_listeners[i]; + // test if live & matching + if (lst->fn != NULL && lst->type == type) { + cleanup_type_listener(i, lst); return true; } } return false; } -bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb) +bool _TF_FN TF_RemoveGenericListener(TF_Listener cb) { - size_t i; + TF_COUNT i; + GenericListener *lst; for (i = 0; i < tf.count_generic_lst; i++) { - if (tf.generic_listeners[i].fn == cb) { - tf.generic_listeners[i].fn = NULL; - if (i == tf.count_generic_lst - 1) { - tf.count_generic_lst--; - } + lst = &tf.generic_listeners[i]; + // test if live & matching + if (lst->fn == cb) { + cleanup_generic_listener(i, lst); return true; } } @@ -299,9 +370,23 @@ bool _TF_FN TF_RemoveGenericListener(TF_LISTENER cb) } /** Handle a message that was just collected & verified by the parser */ -static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_t *data, TF_LEN data_len) +static void _TF_FN TF_HandleReceivedMessage(void) { - size_t i; + TF_COUNT i; + IdListener *ilst; + TypeListener *tlst; + GenericListener *glst; + TF_Result res; + + // Prepare message object + TF_Msg msg; + msg.frame_id = tf.id; + msg.is_response = false; + msg.type = tf.type; + msg.data = tf.data; + msg.len = tf.len; + + //dumpFrameInfo(&msg); // Any listener can consume the message (return true), // or let someone else handle it. @@ -311,17 +396,36 @@ static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_ // ID listeners first for (i = 0; i < tf.count_id_lst; i++) { - if (tf.id_listeners[i].fn && (tf.id_listeners[i].id == frame_id)) { - if (tf.id_listeners[i].fn(frame_id, type, data, data_len)) { + ilst = &tf.id_listeners[i]; + if (ilst->fn && ilst->id == msg.frame_id) { + msg.userdata = ilst->userdata; // pass userdata pointer to the callback + res = ilst->fn(&msg); + ilst->userdata = msg.userdata; // put it back (may have changed the pointer or set to NULL) + + if (res != TF_NEXT) { + if (res == TF_CLOSE) { + cleanup_id_listener(i, ilst); + } + else if (res == TF_RENEW) { + renew_id_listener(ilst); + } return; } } } + msg.userdata = NULL; + // clean up for the following listeners that don't use userdata // Type listeners for (i = 0; i < tf.count_type_lst; i++) { - if (tf.type_listeners[i].fn && (tf.type_listeners[i].type == type)) { - if (tf.type_listeners[i].fn(frame_id, type, data, data_len)) { + tlst = &tf.type_listeners[i]; + if (tlst->fn && tlst->type == msg.type) { + res = tlst->fn(&msg); + + if (res != TF_NEXT) { + if (res == TF_CLOSE) { + cleanup_type_listener(i, tlst); + } return; } } @@ -329,8 +433,14 @@ static void _TF_FN TF_HandleReceivedMessage(TF_ID frame_id, TF_TYPE type, uint8_ // Generic listeners for (i = 0; i < tf.count_generic_lst; i++) { - if (tf.generic_listeners[i].fn) { - if (tf.generic_listeners[i].fn(frame_id, type, data, data_len)) { + glst = &tf.generic_listeners[i]; + if (glst->fn) { + res = glst->fn(&msg); + + if (res != TF_NEXT) { + if (res == TF_CLOSE) { + cleanup_generic_listener(i, glst); + } return; } } @@ -369,7 +479,7 @@ static void _TF_FN TF_ParsBeginFrame(void) { void _TF_FN TF_AcceptChar(unsigned char c) { - // Timeout - clear + // Parser timeout - clear if (tf.parser_timeout_ticks >= TF_PARSER_TIMEOUT_TICKS) { TF_ResetParser(); } @@ -413,7 +523,7 @@ void _TF_FN TF_AcceptChar(unsigned char c) case TFState_TYPE: CKSUM_ADD(tf.cksum, c); COLLECT_NUMBER(tf.type, TF_TYPE) { - #if TF_CKSUM_TYPE == 0 + #if TF_CKSUM_TYPE == TF_CKSUM_NONE tf.state = TFState_DATA; tf.rxi = 0; #else @@ -436,7 +546,7 @@ void _TF_FN TF_AcceptChar(unsigned char c) } if (tf.len == 0) { - TF_HandleReceivedMessage(tf.id, tf.type, NULL, 0); + TF_HandleReceivedMessage(); TF_ResetParser(); break; } @@ -447,7 +557,7 @@ void _TF_FN TF_AcceptChar(unsigned char c) CKSUM_RESET(tf.cksum); // Start collecting the payload - if (tf.len >= TF_MAX_PAYLOAD) { + if (tf.len >= TF_MAX_PAYLOAD_RX) { // ERROR - frame too long. Consume, but do not store. tf.discard_data = true; } @@ -463,9 +573,9 @@ void _TF_FN TF_AcceptChar(unsigned char c) } if (tf.rxi == tf.len) { - #if TF_CKSUM_TYPE == 0 + #if TF_CKSUM_TYPE == TF_CKSUM_NONE // All done - TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len); + TF_HandleReceivedMessage(); TF_ResetParser(); #else // Enter DATA_CKSUM state @@ -481,13 +591,19 @@ void _TF_FN TF_AcceptChar(unsigned char c) // Check the header checksum against the computed value CKSUM_FINALIZE(tf.cksum); if (!tf.discard_data && tf.cksum == tf.ref_cksum) { - TF_HandleReceivedMessage(tf.id, tf.type, tf.data, tf.len); + TF_HandleReceivedMessage(); } TF_ResetParser(); } break; } + + // we get here after finishing HEAD, if no data are to be received - handle and clear + if (tf.len == 0 && tf.state == TFState_DATA) { + TF_HandleReceivedMessage(); + TF_ResetParser(); + } } /** @@ -501,24 +617,27 @@ void _TF_FN TF_AcceptChar(unsigned char c) * @param len - payload size in bytes * @param explicit_id - ID to use in the frame (8-bit) * @param use_expl_id - whether to use the previous param - * @return nr of bytes in outbuff used by the frame, TF_ERROR (-1) on failure + * @return nr of bytes in outbuff used by the frame, 0 on failure */ -static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, +static inline size_t _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, TF_TYPE type, const uint8_t *data, TF_LEN data_len, TF_ID explicit_id, bool use_expl_id) { - int i; - uint8_t b; - TF_ID id; - TF_CKSUM cksum; - int pos = 0; + char si = 0; // signed small int + TF_LEN i = 0; + uint8_t b = 0; + TF_ID id = 0; + TF_CKSUM cksum = 0; + size_t pos = 0; // can be needed to grow larger than TF_LEN + + (void)cksum; CKSUM_RESET(cksum); // sanitize len - if (data_len > TF_MAX_PAYLOAD) { - return TF_ERROR; + if (data_len > TF_MAX_PAYLOAD_TX) { + return 0; } // Gen ID @@ -537,12 +656,12 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, // DRY helper for writing a multi-byte variable to the buffer #define WRITENUM_BASE(type, num, xtra) \ - for (i = sizeof(type)-1; i>=0; i--) { \ - b = (uint8_t)(num >> (i*8) & 0xFF); \ + for (si = sizeof(type)-1; si>=0; si--) { \ + b = (uint8_t)(num >> (si*8) & 0xFF); \ outbuff[pos++] = b; \ xtra; \ } - + #define _NOOP() #define WRITENUM(type, num) WRITENUM_BASE(type, num, _NOOP()) #define WRITENUM_CKSUM(type, num) WRITENUM_BASE(type, num, CKSUM_ADD(cksum, b)) @@ -559,7 +678,7 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, WRITENUM_CKSUM(TF_LEN, data_len); WRITENUM_CKSUM(TF_TYPE, type); - #if TF_CKSUM_TYPE != 0 + #if TF_CKSUM_TYPE != TF_CKSUM_NONE CKSUM_FINALIZE(cksum); WRITENUM(TF_CKSUM, cksum); #endif @@ -575,7 +694,7 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, CKSUM_ADD(cksum, b); } - #if TF_CKSUM_TYPE != 0 + #if TF_CKSUM_TYPE != TF_CKSUM_NONE CKSUM_FINALIZE(cksum); WRITENUM(TF_CKSUM, cksum); #endif @@ -584,73 +703,97 @@ static int _TF_FN TF_Compose(uint8_t *outbuff, TF_ID *id_ptr, return pos; } -bool _TF_FN TF_Send(TF_TYPE type, - const uint8_t *payload, TF_LEN payload_len, - TF_LISTENER listener, - TF_ID *id_ptr) +// send without listener +bool _TF_FN TF_Send(TF_Msg *msg) { - TF_ID msgid = 0; - int len; - len = TF_Compose(tf.sendbuf, &msgid, type, payload, payload_len, 0, false); - if (len == TF_ERROR) return false; - - if (listener) TF_AddIdListener(msgid, listener); - if (id_ptr) *id_ptr = msgid; - - TF_WriteImpl((const uint8_t *) tf.sendbuf, (TF_LEN)len); - return true; + return TF_Query(msg, NULL, 0); } -// Like TF_Send, but with explicit frame ID -bool _TF_FN TF_Respond(TF_TYPE type, - const uint8_t *data, TF_LEN data_len, - TF_ID frame_id) +// send without listener and struct +bool _TF_FN TF_SendSimple(TF_TYPE type, const uint8_t *data, TF_LEN len) { - int len; - len = TF_Compose(tf.sendbuf, NULL, type, data, data_len, frame_id, true); - if (len == TF_ERROR) return false; + TF_Msg msg; + TF_ClearMsg(&msg); + msg.type = type; + msg.data = data; + msg.len = len; + return TF_Send(&msg); +} - TF_WriteImpl(tf.sendbuf, (TF_LEN)len); - return true; +// send without listener and struct +bool _TF_FN TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout, void *userdata) +{ + TF_Msg msg; + TF_ClearMsg(&msg); + msg.type = type; + msg.data = data; + msg.len = len; + msg.userdata = userdata; + return TF_Query(&msg, listener, timeout); } -/** - * Like TF_Send(), but with no data - */ -bool _TF_FN TF_Send0(TF_TYPE type, - TF_LISTENER listener, - TF_ID *id_ptr) +// send with listener +bool _TF_FN TF_Query(TF_Msg *msg, TF_Listener listener, TF_TICKS timeout) { - return TF_Send(type, NULL, 0, listener, id_ptr); + size_t len; + len = TF_Compose(tf.sendbuf, + &msg->frame_id, + msg->type, + msg->data, + msg->len, + msg->frame_id, + msg->is_response); + + if (len == 0) return false; + + if (listener) TF_AddIdListener(msg, listener, timeout); + + TF_WriteImpl((const uint8_t *) tf.sendbuf, len); + return true; } -/** - * Like TF_Send(), but with just 1 data byte - */ -bool _TF_FN TF_Send1(TF_TYPE type, uint8_t b1, - TF_LISTENER listener, - TF_ID *id_ptr) +// Like TF_Send, but with explicit frame ID +bool _TF_FN TF_Respond(TF_Msg *msg) { - unsigned char b[] = {b1}; - return TF_Send(type, b, 1, listener, id_ptr); + msg->is_response = true; + return TF_Send(msg); } -/** - * Like TF_Send(), but with just 2 data bytes - */ -bool _TF_FN TF_Send2(TF_TYPE type, uint8_t b1, uint8_t b2, - TF_LISTENER listener, - TF_ID *id_ptr) +bool _TF_FN TF_RenewIdListener(TF_ID id) { - unsigned char b[] = {b1, b2}; - return TF_Send(type, b, 2, listener, id_ptr); + TF_COUNT i; + IdListener *lst; + for (i = 0; i < tf.count_id_lst; i++) { + lst = &tf.id_listeners[i]; + // test if live & matching + if (lst->fn != NULL && lst->id == id) { + renew_id_listener(lst); + return true; + } + } + return false; } /** Timebase hook - for timeouts */ void _TF_FN TF_Tick(void) { + TF_COUNT i = 0; + IdListener *lst; + + // increment parser timeout (timeout is handled when receiving next byte) if (tf.parser_timeout_ticks < TF_PARSER_TIMEOUT_TICKS) { tf.parser_timeout_ticks++; } + + // decrement and expire ID listeners + for (i = 0; i < tf.count_id_lst; i++) { + lst = &tf.id_listeners[i]; + if (!lst->fn || lst->timeout == 0) continue; + // count down... + if (--lst->timeout == 0) { + // Listener has expired + cleanup_id_listener(i, lst); + } + } } diff --git a/TinyFrame.h b/TinyFrame.h index 03672f6..0ba0c77 100644 --- a/TinyFrame.h +++ b/TinyFrame.h @@ -5,58 +5,15 @@ #include // for uint8_t etc #include // for bool #include // for NULL -//#include "messages.h" // for your message IDs (enum or defines) -//#include // when using with esphttpd //--------------------------------------------------------------------------- - -//----------------------------- PARAMETERS ---------------------------------- - -// Maximum send / receive payload size (static buffers size) -// Larger payloads will be rejected. -#define TF_MAX_PAYLOAD 1024 - -// --- Listener counts - determine sizes of the static slot tables --- - -// Frame ID listeners (wait for response / multi-part message) -#define TF_MAX_ID_LST 20 -// Frame Type listeners (wait for frame with a specific first payload byte) -#define TF_MAX_TYPE_LST 20 -// Generic listeners (fallback if no other listener catches it) -#define TF_MAX_GEN_LST 4 - -// Timeout for receiving & parsing a frame -// ticks = number of calls to TF_Tick() -#define TF_PARSER_TIMEOUT_TICKS 10 - -//----------------------------- FRAME FORMAT --------------------------------- -// The format can be adjusted to fit your particular application needs - -// If the connection is reliable, you can disable the SOF byte and checksums. -// That can save up to 9 bytes of overhead. - -// ,-----+----+-----+------+------------+- - - -+------------, -// | SOF | ID | LEN | TYPE | HEAD_CKSUM | DATA | PLD_CKSUM | -// | 1 | ? | ? | ? | ? | ... | ? | <- size (bytes) -// '-----+----+-----+------+------------+- - - -+------------' - -// !!! BOTH SIDES MUST USE THE SAME SETTINGS !!! - -// Adjust sizes as desired (1,2,4) -#define TF_ID_BYTES 1 -#define TF_LEN_BYTES 2 -#define TF_TYPE_BYTES 1 - // Select checksum type (0 = none, 8 = ~XOR, 16 = CRC16 0x8005, 32 = CRC32) -#define TF_CKSUM_TYPE 16 - -// Use a SOF byte to mark the start of a frame -#define TF_USE_SOF_BYTE 1 -// Value of the SOF byte (if TF_USE_SOF_BYTE == 1) -#define TF_SOF_BYTE 0x01 - -//------------------------- End of user config ------------------------------ +#define TF_CKSUM_NONE 0 +#define TF_CKSUM_XOR 8 +#define TF_CKSUM_CRC16 16 +#define TF_CKSUM_CRC32 32 +#include //region Resolve data types @@ -93,13 +50,13 @@ #endif -#if TF_CKSUM_TYPE == 8 || TF_CKSUM_TYPE == 0 +#if TF_CKSUM_TYPE == TF_CKSUM_XOR || TF_CKSUM_TYPE == TF_CKSUM_NONE // ~XOR (if 0, still use 1 byte - it won't be used) typedef uint8_t TF_CKSUM; -#elif TF_CKSUM_TYPE == 16 +#elif TF_CKSUM_TYPE == TF_CKSUM_CRC16 // CRC16 typedef uint16_t TF_CKSUM; -#elif TF_CKSUM_TYPE == 32 +#elif TF_CKSUM_TYPE == TF_CKSUM_CRC32 // CRC32 typedef uint32_t TF_CKSUM; #else @@ -108,15 +65,19 @@ // Bytes added to TF_MAX_PAYLOAD for the send buffer size. -#define TF_OVERHEAD_BYTES (1+sizeof(TF_ID)+sizeof(TF_LEN)+sizeof(TF_CKSUM)+sizeof(TF_TYPE)+sizeof(TF_CKSUM)) +#define TF_OVERHEAD_BYTES \ + (1*TF_USE_SOF_BYTE + \ + sizeof(TF_ID) + \ + sizeof(TF_LEN) + \ + sizeof(TF_CKSUM) + \ + sizeof(TF_TYPE) + \ + sizeof(TF_CKSUM) \ + ) //endregion //--------------------------------------------------------------------------- -// Return value indicating error state -#define TF_ERROR -1 - // 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)) @@ -126,138 +87,177 @@ /** Peer bit enum (used for init) */ typedef enum { TF_SLAVE = 0, - TF_MASTER = 1 -} TF_PEER; + TF_MASTER, +} TF_Peer; + +typedef enum { + TF_NEXT = 0, //!< Not handled, let other listeners handle it + TF_STAY = 1, //!< Handled, stay + TF_RENEW = 2, //!< Handled, stay, renew - useful only with listener timeout + TF_CLOSE = 3, //!< Handled, remove self +} TF_Result; + +/** Data structure for sending / receiving messages */ +typedef struct _TF_MSG_STRUCT_ { + TF_ID frame_id; //!< message ID + bool is_response; //!< internal flag, set when using the Respond function. frame_id is then kept unchanged. + TF_TYPE type; //!< received or sent message type + const uint8_t *data; //!< buffer of received data or data to send. NULL = listener timed out, free userdata! + TF_LEN len; //!< length of the buffer + void *userdata; //!< here's a place for custom data; this data will be stored with the listener +} TF_Msg; + +/** + * Clear message struct + */ +static inline void TF_ClearMsg(TF_Msg *msg) +{ + msg->frame_id = 0; + msg->is_response = false; + msg->type = 0; + msg->data = NULL; + msg->len = 0; + msg->userdata = NULL; +} /** * TinyFrame Type Listener callback + * * @param frame_id - ID of the received frame + * @param type - type field from the message * @param data - byte buffer with the application data * @param len - number of bytes in the buffer - * @return true if the frame was consumed + * @return listener result */ -typedef bool (*TF_LISTENER)(TF_ID frame_id, TF_TYPE type, const uint8_t *data, TF_LEN len); +typedef TF_Result (*TF_Listener)(TF_Msg *msg); /** * Initialize the TinyFrame engine. * This can also be used to completely reset it (removing all listeners etc) + * * @param peer_bit - peer bit to use for self */ -void TF_Init(TF_PEER peer_bit); +void TF_Init(TF_Peer peer_bit); /** * Reset the frame parser state machine. + * This does not affect registered listeners. */ void TF_ResetParser(void); -/** - * Accept incoming bytes & parse frames - * @param buffer - byte buffer to process - * @param count - nr of bytes in the buffer - */ -void TF_Accept(const uint8_t *buffer, size_t count); - -/** - * Accept a single incoming byte - * @param c - a received char - */ -void TF_AcceptChar(uint8_t c); - /** * Register a frame type listener. - * @param frame_type - frame ID to listen for + * + * @param msg - message (contains frame_id and userdata) * @param cb - callback + * @param timeout - timeout in ticks to auto-remove the listener (0 = keep forever) * @return slot index (for removing), or TF_ERROR (-1) */ -bool TF_AddIdListener(TF_ID frame_id, TF_LISTENER cb); +bool TF_AddIdListener(TF_Msg *msg, TF_Listener cb, TF_TICKS timeout); /** * Remove a listener by the message ID it's registered for + * * @param frame_id - the frame we're listening for */ bool TF_RemoveIdListener(TF_ID frame_id); /** * Register a frame type listener. + * * @param frame_type - frame type to listen for * @param cb - callback * @return slot index (for removing), or TF_ERROR (-1) */ -bool TF_AddTypeListener(TF_TYPE frame_type, TF_LISTENER cb); +bool TF_AddTypeListener(TF_TYPE frame_type, TF_Listener cb); /** * Remove a listener by type. + * * @param type - the type it's registered for */ bool TF_RemoveTypeListener(TF_TYPE type); /** * Register a generic listener. + * * @param cb - callback * @return slot index (for removing), or TF_ERROR (-1) */ -bool TF_AddGenericListener(TF_LISTENER cb); +bool TF_AddGenericListener(TF_Listener cb); /** * Remove a generic listener by function pointer + * * @param cb - callback function to remove */ -bool TF_RemoveGenericListener(TF_LISTENER cb); +bool TF_RemoveGenericListener(TF_Listener cb); /** - * Send a frame, and optionally attach an ID listener. + * Send a frame, no listener * - * @param type - message type - * @param data - data to send (can be NULL if 'data_len' is 0) - * @param data_len - nr of bytes to send - * @param listener - listener waiting for the response - * @param id_ptr - store the ID here, NULL to don't store. - * The ID may be used to unbind the listener after a timeout. + * @param msg - message struct. ID is stored in the frame_id field * @return success */ -bool TF_Send(TF_TYPE type, const uint8_t *data, TF_LEN data_len, - TF_LISTENER listener, - TF_ID *id_ptr); +bool TF_Send(TF_Msg *msg); /** - * Like TF_Send(), but no data, just the type + * Like TF_Send, but without the struct */ -bool TF_Send0(TF_TYPE type, TF_LISTENER listener, TF_ID *id_ptr); +bool TF_SendSimple(TF_TYPE type, const uint8_t *data, TF_LEN len); /** - * Like TF_Send(), but with just 1 data byte + * Like TF_Query, but without the struct */ -bool TF_Send1(TF_TYPE type, uint8_t b1, - TF_LISTENER listener, - TF_ID *id_ptr); +bool TF_QuerySimple(TF_TYPE type, const uint8_t *data, TF_LEN len, TF_Listener listener, TF_TICKS timeout, void *userdata); + /** - * Like TF_Send(), but with just 2 data bytes + * 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_Send2(TF_TYPE type, uint8_t b1, uint8_t b2, - TF_LISTENER listener, - TF_ID *id_ptr); +bool TF_Query(TF_Msg *msg, TF_Listener listener, TF_TICKS timeout); /** * Send a response to a received message. * - * @param type - message type. If an ID listener is waiting for this response, - * then 'type' can be used to pass additional information. - * Otherwise, 'type' can be used to handle the message using a TypeListener. - * @param data - data to send - * @param data_len - nr of bytes to send - * @param frame_id - ID of the response frame (re-use ID from the original message) + * @param msg - message struct. ID is read from frame_id. set ->renew to reset listener timeout * @return success */ -bool TF_Respond(TF_TYPE type, - const uint8_t *data, TF_LEN data_len, - TF_ID frame_id); +bool TF_Respond(TF_Msg *msg); + +/** + * Renew ID listener timeout + * + * @param id - listener ID to renew + * @return true if listener was found and renewed + */ +bool TF_RenewIdListener(TF_ID id); + +/** + * Accept incoming bytes & parse frames + * + * @param buffer - byte buffer to process + * @param count - nr of bytes in the buffer + */ +void TF_Accept(const uint8_t *buffer, size_t count); + +/** + * Accept a single incoming byte + * + * @param c - a received char + */ +void TF_AcceptChar(uint8_t c); /** * 'Write bytes' function that sends data to UART * * ! Implement this in your application code ! */ -extern void TF_WriteImpl(const uint8_t *buff, TF_LEN len); +extern void TF_WriteImpl(const uint8_t *buff, size_t len); /** * This function should be called periodically. diff --git a/demo/demo.c b/demo/demo.c new file mode 100644 index 0000000..4b234ff --- /dev/null +++ b/demo/demo.c @@ -0,0 +1,219 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#include "demo.h" + +// those magic defines are needed so we can use clone() +#define _GNU_SOURCE +#define __USE_GNU +#include + +#include +#include +#include +#include +#include +#include + +volatile int sockfd = -1; +volatile bool conn_disband = false; + +/** + * Close socket + */ +void demo_disconn(void) +{ + conn_disband = true; + if (sockfd >= 0) close(sockfd); +} + +/** + * Demo WriteImpl - send stuff to our peer + * + * @param buff + * @param len + */ +void TF_WriteImpl(const uint8_t *buff, size_t len) +{ + printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); + dumpFrame(buff, len); + usleep(1000); + + if (sockfd != -1) { + write(sockfd, buff, len); + } else { + printf("\nNo peer!\n"); + } +} + + +/** + * Client bg thread + * + * @param unused + * @return unused + */ +static int demo_client(void* unused) +{ + (void)unused; + + ssize_t n = 0; + uint8_t recvBuff[1024]; + struct sockaddr_in serv_addr; + + printf("\n--- STARTING CLIENT! ---\n"); + + memset(recvBuff, '0', sizeof(recvBuff)); + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + printf("\n Error : Could not create socket \n"); + return false; + } + + memset(&serv_addr, '0', sizeof(serv_addr)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { + printf("\n inet_pton error occured\n"); + return false; + } + + if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + printf("\n Error : Connect Failed \n"); + perror("PERROR "); + return false; + } + + printf("\n Child Process \n"); + + while ((n = read(sockfd, recvBuff, sizeof(recvBuff) - 1)) > 0) { + printf("\033[36m--- RX %ld bytes ---\033[0m\n", n); + dumpFrame(recvBuff, (size_t) n); + TF_Accept(recvBuff, (size_t) n); + } + return 0; +} + +/** + * Server bg thread + * + * @param unused + * @return unused + */ +static int demo_server(void* unused) +{ + (void)unused; + ssize_t n; + int listenfd = 0; + uint8_t recvBuff[1024]; + struct sockaddr_in serv_addr; + int option; + + printf("\n--- STARTING SERVER! ---\n"); + + listenfd = socket(AF_INET, SOCK_STREAM, 0); + memset(&serv_addr, '0', sizeof(serv_addr)); + + option = 1; + setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&option, sizeof(option)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); + serv_addr.sin_port = htons(PORT); + + if (bind(listenfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { + perror("Failed to bind"); + return 1; + } + + if (listen(listenfd, 10) < 0) { + perror("Failed to listen"); + return 1; + } + + while (1) { + printf("\nWaiting for client...\n"); + sockfd = accept(listenfd, (struct sockaddr *) NULL, NULL); + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&option, sizeof(option)); + printf("\nClient connected\n"); + conn_disband = false; + + while ((n = read(sockfd, recvBuff, sizeof(recvBuff) - 1)) > 0 && !conn_disband) { + printf("\033[36m--- RX %ld bytes ---\033[0m\n", n); + dumpFrame(recvBuff, n); + TF_Accept(recvBuff, (size_t) n); + } + + if (n < 0) { + printf("\n Read error \n"); + } + + printf("Closing socket\n"); + close(sockfd); + sockfd = -1; + } + return 0; +} + +/** + * Trap - clean up + * + * @param sig - signal that caused this + */ +static void signal_handler(int sig) +{ + (void)sig; + printf("Shutting down..."); + demo_disconn(); + + exit(sig); // pass the signal through - this is nonstandard behavior but useful for debugging +} + +/** + * Sleaping Beauty's fave function + */ +void demo_sleep(void) +{ + while(1) usleep(10); +} + +/** + * Start the background thread + * + * Slave is started first and doesn't normally init transactions - but it could + * + * @param peer what peer we are + */ +void demo_init(TF_Peer peer) +{ + signal(SIGTERM, signal_handler); + signal(SIGINT, signal_handler); + + int retc; + void *stack = malloc(8192); + if (stack == NULL) { + perror("Oh fuck"); + signal_handler(9); + return; + } + + printf("Starting %s...\n", peer == TF_MASTER ? "MASTER" : "SLAVE"); + + // CLONE_VM --- share heap + // CLONE_FILES --- share stdout and stderr + if (peer == TF_MASTER) { + retc = clone(&demo_client, (char *)stack+8192, CLONE_VM|CLONE_FILES, 0); + } else { + retc = clone(&demo_server, (char *)stack+8192, CLONE_VM|CLONE_FILES, 0); + } + + if (retc == 0) { + perror("Clone fail"); + signal_handler(9); + return; + } + + printf("Thread started\n"); +} diff --git a/demo/demo.h b/demo/demo.h new file mode 100644 index 0000000..4caa068 --- /dev/null +++ b/demo/demo.h @@ -0,0 +1,23 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#ifndef TF_DEMO_H +#define TF_DEMO_H + +#include +#include "../TinyFrame.h" +#include "utils.h" + +#define PORT 9798 + +/** Sleep and wait for ^C */ +void demo_sleep(void); + +/** Init server - DOES NOT init TinyFrame! */ +void demo_init(TF_Peer peer); + +/** Disconnect client from the server - can be called by a server-side callback */ +void demo_disconn(void); + +#endif //TF_DEMO_H diff --git a/demo/hello/Makefile b/demo/hello/Makefile new file mode 100644 index 0000000..6c3e627 --- /dev/null +++ b/demo/hello/Makefile @@ -0,0 +1,17 @@ +CFILES=../demo.c ../utils.c ../../TinyFrame.c +INCLDIRS=-I. -I.. -I../.. +CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS) + +build: master.bin slave.bin + +master: master.bin + ./master.bin + +slave: slave.bin + ./slave.bin + +master.bin: master.c $(CFILES) + gcc master.c $(CFLAGS) -o master.bin + +slave.bin: slave.c $(CFILES) + gcc slave.c $(CFLAGS) -o slave.bin diff --git a/demo/hello/TF_Config.h b/demo/hello/TF_Config.h new file mode 100644 index 0000000..306b833 --- /dev/null +++ b/demo/hello/TF_Config.h @@ -0,0 +1,25 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#ifndef TF_CONFIG_H +#define TF_CONFIG_H + +#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 1024 +#define TF_MAX_PAYLOAD_TX 1024 +#define TF_MAX_ID_LST 10 +#define TF_MAX_TYPE_LST 10 +#define TF_MAX_GEN_LST 5 +#define TF_PARSER_TIMEOUT_TICKS 10 + +#endif //TF_CONFIG_H diff --git a/demo/hello/master.c b/demo/hello/master.c new file mode 100644 index 0000000..04066a7 --- /dev/null +++ b/demo/hello/master.c @@ -0,0 +1,35 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#include +#include "../demo.h" + +TF_Result testIdListener(TF_Msg *msg) +{ + printf("testIdListener()\n"); + dumpFrameInfo(msg); + return TF_CLOSE; +} + +TF_Result testGenericListener(TF_Msg *msg) +{ + printf("testGenericListener()\n"); + dumpFrameInfo(msg); + return TF_STAY; +} + +int main(void) +{ + TF_Init(TF_MASTER); + TF_AddGenericListener(testGenericListener); + + demo_init(TF_MASTER); + + TF_SendSimple(1, (pu8)"Ahoj", 5); + TF_SendSimple(1, (pu8)"Hello", 6); + + TF_QuerySimple(2, (pu8)"Query!", 6, testIdListener, 0, NULL); + + demo_sleep(); +} diff --git a/demo/hello/slave.c b/demo/hello/slave.c new file mode 100644 index 0000000..714f455 --- /dev/null +++ b/demo/hello/slave.c @@ -0,0 +1,42 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#include +#include "../demo.h" +#include + +TF_Result helloListener(TF_Msg *msg) +{ + printf("helloListener()\n"); + dumpFrameInfo(msg); + return TF_STAY; +} + +TF_Result replyListener(TF_Msg *msg) +{ + printf("replyListener()\n"); + dumpFrameInfo(msg); + msg->data = (const uint8_t *) "response to query"; + msg->len = (TF_LEN) strlen((const char *) msg->data); + TF_Respond(msg); + + // unsolicited reply - will not be handled by the ID listener, which is already gone + msg->data = (const uint8_t *) "SPAM"; + msg->len = 5; + TF_Respond(msg); + + // unrelated message + TF_SendSimple(77, (const uint8_t *) "NAZDAR", 7); + return TF_STAY; +} + +int main(void) +{ + TF_Init(TF_SLAVE); + TF_AddTypeListener(1, helloListener); + TF_AddTypeListener(2, replyListener); + + demo_init(TF_SLAVE); + demo_sleep(); +} diff --git a/demo/test/Makefile b/demo/test/Makefile new file mode 100644 index 0000000..118c385 --- /dev/null +++ b/demo/test/Makefile @@ -0,0 +1,11 @@ +CFILES=../utils.c ../../TinyFrame.c +INCLDIRS=-I. -I.. -I../.. +CFLAGS=-O0 -ggdb --std=gnu99 -Wno-main -Wall -Wextra $(CFILES) $(INCLDIRS) + +run: test.bin + ./test.bin + +build: test.bin + +test.bin: test.c $(CFILES) + gcc test.c $(CFLAGS) -o test.bin diff --git a/demo/test/TF_Config.h b/demo/test/TF_Config.h new file mode 100644 index 0000000..306b833 --- /dev/null +++ b/demo/test/TF_Config.h @@ -0,0 +1,25 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#ifndef TF_CONFIG_H +#define TF_CONFIG_H + +#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 1024 +#define TF_MAX_PAYLOAD_TX 1024 +#define TF_MAX_ID_LST 10 +#define TF_MAX_TYPE_LST 10 +#define TF_MAX_GEN_LST 5 +#define TF_PARSER_TIMEOUT_TICKS 10 + +#endif //TF_CONFIG_H diff --git a/demo/test/test.c b/demo/test/test.c new file mode 100644 index 0000000..90c9371 --- /dev/null +++ b/demo/test/test.c @@ -0,0 +1,65 @@ +#include +#include +#include "../../TinyFrame.h" +#include "../utils.h" + + +/** + * This function should be defined in the application code. + * It implements the lowest layer - sending bytes to UART (or other) + */ +void TF_WriteImpl(const uint8_t *buff, size_t len) +{ + printf("--------------------\n"); + printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); + dumpFrame(buff, len); + + // Send it back as if we received it + TF_Accept(buff, len); +} + +/** An example listener function */ +TF_Result myListener(TF_Msg *msg) +{ + dumpFrameInfo(msg); + return TF_STAY; +} + +TF_Result testIdListener(TF_Msg *msg) +{ + printf("OK - ID Listener triggered for msg!\n"); + dumpFrameInfo(msg); + return TF_CLOSE; +} + +void main(void) +{ + TF_Msg msg; + const char *longstr = "Lorem ipsum dolor sit amet."; + + // Set up the TinyFrame library + TF_Init(TF_MASTER); // 1 = master, 0 = slave + TF_AddGenericListener(myListener); + + printf("------ Simulate sending a message --------\n"); + + TF_ClearMsg(&msg); + msg.type = 0x22; + msg.data = (pu8)"Hello TinyFrame"; + msg.len = 16; + TF_Send(&msg); + + msg.type = 0x33; + msg.data = (pu8)longstr; + msg.len = (TF_LEN) (strlen(longstr)+1); // add the null byte + TF_Send(&msg); + + msg.type = 0x44; + msg.data = (pu8)"Hello2"; + msg.len = 7; + TF_Send(&msg); + + msg.len = 0; + msg.type = 0x77; + TF_Query(&msg, testIdListener, 0); +} diff --git a/demo/utils.c b/demo/utils.c new file mode 100644 index 0000000..2454c2c --- /dev/null +++ b/demo/utils.c @@ -0,0 +1,32 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#include "utils.h" +#include + +// helper func for testing +void dumpFrame(const uint8_t *buff, size_t len) +{ + size_t i; + for(i = 0; i < len; i++) { + printf("%3u \033[94m%02X\033[0m", buff[i], buff[i]); + if (buff[i] >= 0x20 && buff[i] < 127) { + printf(" %c", buff[i]); + } else { + printf(" \033[31m.\033[0m"); + } + printf("\n"); + } + printf("--- end of frame ---\n\n"); +} + +void dumpFrameInfo(TF_Msg *msg) +{ + printf("\033[33mFrame info\n" + " type: %02Xh\n" + " data: \"%.*s\"\n" + " len: %u\n" + " id: %Xh\033[0m\n\n", + msg->type, msg->len, msg->data, msg->len, msg->frame_id); +} diff --git a/demo/utils.h b/demo/utils.h new file mode 100644 index 0000000..f45a807 --- /dev/null +++ b/demo/utils.h @@ -0,0 +1,26 @@ +// +// Created by MightyPork on 2017/10/15. +// + +#ifndef TF_UTILS_H +#define TF_UTILS_H + +#include +#include "../TinyFrame.h" + +/** pointer to unsigned char */ +typedef unsigned char* pu8; + +/** + * Dump a binary frame as hex, dec and ASCII + */ +void dumpFrame(const uint8_t *buff, size_t len); + +/** + * Dump message metadata (not the content) + * + * @param msg + */ +void dumpFrameInfo(TF_Msg *msg); + +#endif //TF_UTILS_H diff --git a/test.c b/test.c deleted file mode 100644 index e41dc4f..0000000 --- a/test.c +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include "TinyFrame.h" - -static void dumpFrame(const uint8_t *buff, TF_LEN len); - -/** - * This function should be defined in the application code. - * It implements the lowest layer - sending bytes to UART (or other) - */ -void TF_WriteImpl(const uint8_t *buff, TF_LEN len) -{ - printf("--------------------\n"); - printf("\033[32mTF_WriteImpl - sending frame:\033[0m\n"); - dumpFrame(buff, len); - - // Send it back as if we received it - TF_Accept(buff, len); -} - -/** An example listener function */ -bool myListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len) -{ - printf("\033[33mRX frame\n" - " type: %02Xh\n" - " data: \"%.*s\"\n" - " len: %u\n" - " id: %Xh\033[0m\n", type, len, buff, len, frame_id); - return true; -} - -bool testIdListener(TF_ID frame_id, TF_TYPE type, const uint8_t *buff, TF_LEN len) -{ - printf("OK - ID Listener triggered for msg (type %02X, id %Xh)!", type, frame_id); - return true; -} - -void main(void) -{ - // Set up the TinyFrame library - TF_Init(TF_MASTER); // 1 = master, 0 = slave - TF_AddGenericListener(myListener); - - printf("------ Simulate sending a message --------\n"); - - TF_Send(0x22, (unsigned char*)"Hello TinyFrame", 16, NULL, NULL); - - const char *longstr = "Lorem ipsum dolor sit amet."; - TF_Send(0x33, (unsigned char*)longstr, (TF_LEN)(strlen(longstr)+1), NULL, NULL); - - TF_Send(0x44, (unsigned char*)"Hello2", 7, NULL, NULL); - - TF_Send0(0xF0, NULL, NULL); - - TF_Send1(0xF1, 'Q', NULL, NULL); - - TF_Send2(0xF2, 'A', 'Z', NULL, NULL); - - TF_Send0(0x77, testIdListener, NULL); -} - -// helper func for testing -static void dumpFrame(const uint8_t *buff, TF_LEN len) -{ - int i; - for(i = 0; i < len; i++) { - printf("%3u \033[34m%02X\033[0m", buff[i], buff[i]); - if (buff[i] >= 0x20 && buff[i] < 127) { - printf(" %c", buff[i]); - } else { - printf(" \033[31m.\033[0m"); - } - printf("\n"); - } - printf("--- end of frame ---\n"); -}